├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py └── index.rst ├── samples ├── example1 │ ├── monkey.jpg │ └── slides.md ├── example2 │ ├── 0.md │ ├── 1.md │ ├── 2.md │ ├── 3_sub │ │ ├── 0.md │ │ └── monkey.jpg │ └── 4.md ├── example3 │ ├── slides.koi8_r.rst │ ├── slides.rst │ └── slides_h3.rst ├── example4 │ └── config.cfg ├── test.css └── test.js ├── setup.py └── src └── landslide ├── __init__.py ├── generator.py ├── macro.py ├── main.py ├── parser.py ├── rst.py ├── tests.py ├── themes ├── default │ ├── base.html │ ├── css │ │ ├── print.css │ │ └── screen.css │ └── js │ │ └── slides.js ├── light │ └── css │ │ └── screen.css └── tango │ └── css │ ├── background.png │ └── screen.css └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | .idea 4 | landslide/.idea 5 | presentation.html 6 | *.pdf 7 | samples/.DS_Store 8 | *.swp 9 | src/landslide.egg-info/ 10 | dist/ 11 | *.komodoproject 12 | .komodotools 13 | landslide.sublime-project 14 | docs/_build 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft src/landslide/themes 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Landslide generates a slideshow using the slides that power 4 | [the html5-slides presentation](http://apirocks.com/html5/html5.html). 5 | 6 | ![demo](http://adamzap.com/random/landslide.png) 7 | 8 | A sample slideshow is [here](http://adamzap.com/random/landslide.html). 9 | 10 | 11 | # Features 12 | 13 | - Write your slide contents easily using the [Markdown](http://daringfireball.net/projects/markdown/syntax) or [ReStructuredText](http://docutils.sourceforge.net/rst.html) syntaxes 14 | - [HTML5](http://dev.w3.org/html5/spec/), Web based, stand-alone document (embedded local images), fancy transitions 15 | - PDF export (using [PrinceXML](http://www.princexml.com/) if available) 16 | 17 | 18 | # Requirements 19 | 20 | `python` and the following modules: 21 | 22 | - `jinja2` 23 | - `pygments` for code blocks syntax coloration 24 | - `markdown` if you use Markdown syntax for your slide contents 25 | - `docutils` if you use ReStructuredText syntax for your slide contents 26 | 27 | ## Optional 28 | 29 | - `textile` for textile support 30 | 31 | # Installation 32 | 33 | The easiest way to install Landlside is probably using `pip`: 34 | 35 | $ pip install landslide 36 | 37 | Alternatively, you can use `easy_install`: 38 | 39 | $ easy_install landslide 40 | 41 | If you want to stay on the edge: 42 | 43 | $ git clone https://github.com/adamzap/landslide.git 44 | $ cd landslide 45 | $ python setup.py build 46 | $ sudo python setup.py install 47 | 48 | # Formatting 49 | 50 | ## Markdown 51 | 52 | - Your Markdown source files must be suffixed by `.md`, `.markdn`, `.mdown` or `.markdown` 53 | - To create a title slide, render a single `h1` element (eg. `# My Title`) 54 | - Separate your slides with a horizontal rule (`---` in markdown) except at the end of md files 55 | - Your other slides should have a heading that renders to an `h1` element 56 | - To highlight blocks of code, put !`{lang}` where `{lang}` is the pygment supported language identifier as the first indented line 57 | 58 | ## ReStructuredText 59 | 60 | - Your ReST source files must be suffixed by `.rst` or `.rest` (**`.txt` is not supported**) 61 | - Use headings for slide titles 62 | - Separate your slides using an horizontal rule (`----` in RST) except at the end of RST files 63 | 64 | ## Textile 65 | 66 | - Textile cannot generate
, so you must insert those manually to separate slides 67 | 68 | # Rendering 69 | 70 | - Run `landslide slides.md` or `landslide slides.rst` 71 | - Enjoy your newly generated `presentation.html` 72 | 73 | Or get it as a PDF document if PrinceXML is installed and available on your system: 74 | 75 | $ landslide README.md -d readme.pdf 76 | $ open readme.pdf 77 | 78 | # Viewing 79 | 80 | - Press `h` to toggle display of help 81 | - Press `left arrow` and `right arrow` to navigate 82 | - Press `t` to toggle a table of contents for your presentation. Slide titles are links 83 | - Press `ESC` to display the presentation overview (Exposé) 84 | - Press `n` to toggle slide number visibility 85 | - Press `b` to toggle screen blanking 86 | - Press `c` to toggle current slide context (previous and next slides) 87 | - Press `e` to make slides filling the whole available space within the document body 88 | - Press `S` to toggle display of link to the source file for each slide 89 | - Press '2' to toggle notes in your slides (specify with the .notes macro) 90 | - Press '3' to toggle pseudo-3D display (experimental) 91 | - Browser zooming is supported 92 | 93 | # Commandline Options 94 | 95 | Several options are available using the command line: 96 | 97 | -h, --help show this help message and exit 98 | -c, --copy-theme Copy theme directory into current presentation source 99 | directory 100 | -b, --debug Will display any exception trace to stdin 101 | -d FILE, --destination=FILE 102 | The path to the to the destination file: .html or .pdf 103 | extensions allowed (default: presentation.html) 104 | -e ENCODING, --encoding=ENCODING 105 | The encoding of your files (defaults to utf8) 106 | -i, --embed Embed stylesheet and javascript contents, 107 | base64-encoded images in presentation to make a 108 | standalone document 109 | -l LINENOS, --linenos=LINENOS 110 | How to output linenos in source code. Three options 111 | availables: no (no line numbers); inline (inside
112 |                           tag); table (lines numbers in another cell, copy-paste
113 |                           friendly)
114 |     -o, --direct-output    Prints the generated HTML code to stdin; won't work
115 |                           with PDF export
116 |     -q, --quiet           Won't write anything to stdin (silent mode)
117 |     -r, --relative        Make your presentation asset links relative to current
118 |                           pwd; This may be useful if you intend to publish your
119 |                           html presentation online.
120 |     -t THEME, --theme=THEME
121 |                           A theme name, or path to a landlside theme directory
122 |     -v, --verbose         Write informational messages to stdin (enabled by
123 |                           default)
124 |     -x EXTENSIONS, --extensions=EXTENSIONS
125 |                           Comma-separated list of extensions for Markdown
126 | 
127 | # Presentation Configuration
128 | 
129 | Landslide allows to configure your presentation using a `cfg` configuration file, therefore easing the aggregation of source directories and the reuse of them accross presentations. Landslide configuration files use the `cfg` syntax. If you know `ini` files, you get the picture. Below is a sample configuration file:
130 | 
131 |     [landslide]
132 |     theme  = /path/to/my/beautiful/theme
133 |     source = 0_my_first_slides.md
134 |              a_directory
135 |              another_directory
136 |              now_a_slide.markdown
137 |              another_one.rst
138 |     destination = myWonderfulPresentation.html
139 |     css =    my_first_stylesheet.css
140 |              my_other_stylesheet.css
141 |     js =     jquery.js
142 |              my_fancy_javascript.js
143 |     relative = True
144 |     linenos = inline
145 | 
146 | Don't forget to declare the `[landslide]` section. To generate the presentation as configured, just run:
147 | 
148 |     $ cd /path/to/my/presentation/sources
149 |     $ landslide config.cfg
150 | 
151 | # Macros
152 | 
153 | You can use macros to enhance your presentation:
154 | 
155 | ## Notes
156 | 
157 | Add notes to your slides using the `.notes:` keyword, eg.:
158 | 
159 |     # My Slide Title
160 | 
161 |     .notes: These are my notes, hidden by default
162 | 
163 |     My visible content goes here
164 | 
165 | You can toggle display of notes by pressing the `2` key.
166 | 
167 | Some other macros are also available by default: `.fx: foo bar` will add the `foo` and `bar` classes to the corresponding slide `
` element, easing styling of your presentation using CSS. 168 | 169 | # Presenter Notes 170 | 171 | You can also add presenter notes to each slide by following the slide content with a heading entitled "Presenter Notes". Press the 'p' key to open the presenter view. 172 | 173 | 174 | # Registering Macros 175 | 176 | Macros are used to transform the HTML contents of your slide. 177 | 178 | You can register your own macros by creating `landslide.macro.Macro` derived classes, implementing a `process(content, source=None)` method and returning a tuple containing the modified contents and some css classes you may be wanting to add to your slide `
` element. For example: 179 | 180 | !python 181 | import landslide 182 | 183 | class MyMacro(landslide.Macro): 184 | def process(self, content, source=None): 185 | return content + '

plop

', ['plopped_slide'] 186 | 187 | g = landslide.generator.Generator(source='toto.md') 188 | g.register_macro(MyMacro) 189 | print g.render() 190 | 191 | This will render any slide as below: 192 | 193 | !html 194 |
195 |

foo

196 |
197 |

my slide contents

198 |

plop>

199 |
200 |
201 | 202 | # Advanced Usage 203 | 204 | ## Setting Custom Destination File 205 | 206 | $ landslide slides.md -d ~/MyPresentations/KeynoteKiller.html 207 | 208 | ## Working with Directories 209 | 210 | $ landslide slides/ 211 | 212 | ## Working with Direct Output 213 | 214 | $ landslide slides.md -o | tidy 215 | 216 | ## Using an Alternate Landslide Theme 217 | 218 | $ landslide slides.md -t mytheme 219 | $ landslide slides.md -t /path/to/theme/dir 220 | 221 | ## Embedding Base-64-Encoded Images 222 | 223 | $ landslide slides.md -i 224 | 225 | ## Exporting to PDF 226 | 227 | $ landslide slides.md -d PowerpointIsDead.pdf 228 | 229 | # Theming 230 | 231 | A Landslide theme is a directory following this simple structure: 232 | 233 | mytheme/ 234 | |-- base.html 235 | |-- css 236 | | |-- print.css 237 | | `-- screen.css 238 | `-- js 239 | `-- slides.js 240 | 241 | If a theme does not provide HTML and JS files, those from the default theme will be used. CSS is not optional. 242 | 243 | Last, you can also copy the whole theme directory to your presentation one by passing the `--copy-theme` option to the `landslide` command: 244 | 245 | $ landslide slides.md -t /path/to/some/theme --copy-theme 246 | 247 | # User stylesheets and Javascripts 248 | 249 | If you don't want to bother making your own theme for tweaking up a bit your presentation style and/or add some interactivity using tiny bits of Javascript, you can include your own user css and js files to the generated presentation. 250 | 251 | This feature is only available if you use a landslide configuration file, by setting the `css` and/or `js` flags: 252 | 253 | [landslide] 254 | theme = /path/to/my/beautiful/theme 255 | source = slides.mdown 256 | css = custom.css 257 | js = jquery.js 258 | powerpoint.js 259 | 260 | These will link the ``custom.css`` stylesheet and both the ``jquery.js`` and ``powerpoint.js`` files within the ```` section of the presentation html file. 261 | 262 | **NOTE:** Paths to the css and js files must be relative to the directory you're running the ``landslide`` command from. 263 | 264 | 265 | # Publishing your Presentation Online 266 | 267 | If you intend to publish your HTML presentation online, you'll have to use the `--relative` option, as well as the `--copy-theme` one to have all asset links relative to the root of your presentation; 268 | 269 | $ landslide slides.md --relative --copy-theme 270 | 271 | That way, you'll just have to host the whole presentation directory to a webserver. Of course, no Python nor PHP nor anything else than a HTTP webserver (like Apache) is required to host a landslide presentation. 272 | 273 | Check out a [Landslide presentation customized this way](http://www.akei.com/presentations/2011-Djangocong/index.html). 274 | 275 | 276 | ## Theme Variables 277 | 278 | The `base.html` must be a [Jinja2 template file](http://jinja.pocoo.org/2/documentation/templates) where you can harness the following template variables: 279 | 280 | - `css`: the stylesheet contents, available via two keys, `print` and `screen`, both having: 281 | - a `path_url` key storing the url to the asset file path 282 | - a `contents` key storing the asset contents 283 | - `js`: the javascript contents, having: 284 | - a `path_url` key storing the url to the asset file path 285 | - a `contents` key storing the asset contents 286 | - `slides`: the slides list, each one having these properties: 287 | - `header`: the slide title 288 | - `content`: the slide contents 289 | - `number`: the slide number 290 | - `embed`: is the current document a standalone one? 291 | - `num_slides`: the number of slides in current presentation 292 | - `toc`: the Table of Contents, listing sections of the document. Each section has these properties available: 293 | - `title`: the section title 294 | - `number`: the slide number of the section 295 | - `sub`: subsections, if any 296 | 297 | # Styles Scope 298 | 299 | * To change HTML5 presentation styles, tweak the `css/screen.css` stylesheet bundled with the theme you are using 300 | * For PDF, modify the `css/print.css` 301 | 302 | # Authors 303 | 304 | ## Original Author and Development Lead 305 | 306 | - Adam Zapletal (adamzap@gmail.com) 307 | 308 | ## Co-Author 309 | 310 | - Nicolas Perriault (nperriault@gmail.com) 311 | 312 | ## Contributors 313 | 314 | See https://github.com/adamzap/landslide/contributors 315 | 316 | ## Base Template Authors and Contributors (html5-slides) 317 | 318 | - Marcin Wichary (mwichary@google.com) 319 | - Ernest Delgado (ernestd@google.com) 320 | - Alex Russell (slightlyoff@chromium.org) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Landslide.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Landslide.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Landslide" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Landslide" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Landslide documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 27 10:01:49 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('../src')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [ 29 | 'sphinx.ext.autodoc', 30 | 'sphinx.ext.todo', 31 | ] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | source_encoding = 'utf-8' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'Landslide' 47 | copyright = u'2011, Adam Zapletal, Nicolas Perriault' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '0.8' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '0.8.2' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all documents. 73 | #default_role = None 74 | 75 | # If true, '()' will be appended to :func: etc. cross-reference text. 76 | #add_function_parentheses = True 77 | 78 | # If true, the current module name will be prepended to all description 79 | # unit titles (such as .. function::). 80 | #add_module_names = True 81 | 82 | # If true, sectionauthor and moduleauthor directives will be shown in the 83 | # output. They are ignored by default. 84 | #show_authors = False 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | # A list of ignored prefixes for module index sorting. 90 | #modindex_common_prefix = [] 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_domain_indices = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | html_show_sourcelink = False 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'Landslidedoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | # The paper size ('letter' or 'a4'). 176 | #latex_paper_size = 'letter' 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #latex_font_size = '10pt' 180 | 181 | # Grouping the document tree into LaTeX files. List of tuples 182 | # (source start file, target name, title, author, documentclass [howto/manual]). 183 | latex_documents = [ 184 | ('index', 'Landslide.tex', u'Landslide Documentation', 185 | u'Adam Zapletal, Nicolas Perriault', 'manual'), 186 | ] 187 | 188 | # The name of an image file (relative to this directory) to place at the top of 189 | # the title page. 190 | #latex_logo = None 191 | 192 | # For "manual" documents, if this is true, then toplevel headings are parts, 193 | # not chapters. 194 | #latex_use_parts = False 195 | 196 | # If true, show page references after internal links. 197 | #latex_show_pagerefs = False 198 | 199 | # If true, show URL addresses after external links. 200 | #latex_show_urls = False 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #latex_preamble = '' 204 | 205 | # Documents to append as an appendix to all manuals. 206 | #latex_appendices = [] 207 | 208 | # If false, no module index is generated. 209 | #latex_domain_indices = True 210 | 211 | 212 | # -- Options for manual page output -------------------------------------------- 213 | 214 | # One entry per manual page. List of tuples 215 | # (source start file, name, description, authors, manual section). 216 | man_pages = [ 217 | ('index', 'landslide', u'Landslide Documentation', 218 | [u'Adam Zapletal, Nicolas Perriault'], 1) 219 | ] 220 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Landslide's documentation 2 | ========================= 3 | 4 | Landslide_ is a command-line based presentation generator relying on Web standards (html, javascript, css) and some Python_ libraries such as Jinja2_, Pygments_, docutils_ and Markdown_. 5 | 6 | Installation 7 | ------------ 8 | 9 | Landslide requires Python_ v2.5 minimum and these dependencies installed: 10 | 11 | * The Jinja2_ template engine 12 | * Pygments_ for code syntax highlighting 13 | 14 | One of the syntax handlers above: 15 | 16 | * The Markdown_ python library if you intend to write your slides contents using the Markdown_ syntax 17 | * or the docutils_ package if you rather prefer using reStructuredText_. 18 | 19 | The easiest way to install Landslide_ is using Pip_:: 20 | 21 | $ pip install landslide 22 | 23 | Alternatively, you can use easy_install_:: 24 | 25 | $ easy_install landslide 26 | 27 | If you rather want to stay `on the edge`_:: 28 | 29 | $ git clone https://github.com/n1k0/landslide.git 30 | $ cd landslide 31 | $ python setup.py build 32 | $ sudo python setup.py install 33 | 34 | Basic Usage 35 | ----------- 36 | 37 | Using the Markdown_ syntax 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | 40 | * Your Markdown source files must be suffixed by ``.md``, ``.markdn``, ``.mdown`` or ``.markdown`` 41 | * To create a title slide, render a single h1 element (eg. ``# My Title``) 42 | * Separate your slides with a horizontal rule (``---`` in markdown) except at the end of markdown files 43 | * Your other slides should have a heading that renders to an ``

`` or ``

`` element 44 | * To highlight blocks of code, put ``!{lang}`` where ``{lang}`` is the pygment supported language identifier as the first indented line 45 | 46 | Here's a sample presentation based on Markdown:: 47 | 48 | # My Presentation Heading 49 | --- 50 | ## My First Slide Title 51 | With some contents 52 | --- 53 | ## My Second Slide Title 54 | With some contents 55 | 56 | Using the reStructuredText_ syntax 57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | 59 | Bar 60 | 61 | API Documentation 62 | ----------------- 63 | 64 | Generator 65 | ~~~~~~~~~ 66 | 67 | .. automodule:: landslide.generator 68 | :members: 69 | 70 | Macros 71 | ~~~~~~ 72 | 73 | .. automodule:: landslide.macro 74 | :members: 75 | 76 | Parser 77 | ~~~~~~ 78 | 79 | .. automodule:: landslide.parser 80 | :members: 81 | 82 | Utils 83 | ~~~~~ 84 | 85 | .. automodule:: landslide.utils 86 | :members: 87 | 88 | Indices and tables 89 | ------------------ 90 | 91 | * :ref:`genindex` 92 | * :ref:`modindex` 93 | * :ref:`search` 94 | 95 | .. links 96 | 97 | .. _docutils: http://docutils.sourceforge.net/ 98 | .. _easy_install: http://packages.python.org/distribute/easy_install.html 99 | .. _Jinja2: http://jinja.pocoo.org/ 100 | .. _Landslide: https://github.com/n1k0/landslide 101 | .. _Markdown: http://daringfireball.net/projects/markdown/ 102 | .. _on the edge: https://github.com/n1k0/landslide/commits/master/ 103 | .. _Pip: http://pip.openplans.org/ 104 | .. _Pygments: http://pygments.org/ 105 | .. _Python: http://python.org/ 106 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html -------------------------------------------------------------------------------- /samples/example1/monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/landslide/bdf1807e1fce403d48ecfb6cc1d6c7f06c003392/samples/example1/monkey.jpg -------------------------------------------------------------------------------- /samples/example1/slides.md: -------------------------------------------------------------------------------- 1 | Landslide 2 | ========= 3 | 4 | --- 5 | 6 | Landslide 7 | --------- 8 | 9 | .notes: plop 10 | 11 | Generates a slideshow using the slides that power 12 | [the html5-slides presentation](http://apirocks.com/html5/html5.html). 13 | 14 | A `python` with the `jinja2`, `markdown`, and `pygments` modules is required. 15 | 16 | Markdown Formatting Instructions 17 | -------------------------------- 18 | 19 | - Separate your slides with a horizontal rule (--- in markdown) 20 | - Your first slide (title slide) should not have a heading, only `

`s 21 | - Your other slides should have a heading that renders to an h1 element 22 | - To highlight blocks of code, put !{{lang}} as the first indented line 23 | - See the included slides.md for an example 24 | 25 | Rendering Instructions 26 | ---------------------- 27 | 28 | - Put your markdown content in a file called `slides.md` 29 | - Run `python render.py` 30 | - Enjoy your newly generated `presentation.html` 31 | 32 | --- 33 | 34 | Slide #2 35 | ======== 36 | 37 | .fx: foo bar 38 | 39 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean magna tellus, 40 | fermentum nec venenatis nec, dapibus id metus. Phasellus nulla massa, consequat 41 | nec tempor et, elementum viverra est. Duis sed nisl in eros adipiscing tempor. 42 | 43 | Section #1 44 | ---------- 45 | 46 | Integer in dignissim ipsum. Integer pretium nulla at elit facilisis eu feugiat 47 | velit consectetur. 48 | 49 | Section #2 50 | ---------- 51 | 52 | Donec risus tortor, dictum sollicitudin ornare eu, egestas at purus. Cras 53 | consequat lacus vitae lectus faucibus et molestie nisl gravida. Donec tempor, 54 | tortor in varius vestibulum, mi odio laoreet magna, in hendrerit nibh neque eu 55 | eros. 56 | 57 | Unicode Characters Work 58 | ----------------------- 59 | 60 | © é 61 | 62 | --- 63 | 64 | Middle Title Slide 65 | ================== 66 | 67 | --- 68 | 69 | Slide #3 70 | ======== 71 | 72 | **Hello Gentlemen** 73 | 74 | - Mega Man 2 75 | - Mega Man 3 76 | - Spelunky 77 | - Dungeon Crawl Stone Soup 78 | - Etrian Odyssey 79 | 80 | *Are you prepared to see beyond the veil of reason?* - DeceasedCrab 81 | 82 | - Black Cascade 83 | - Two Hunters 84 | - Diadem of 12 Stars 85 | 86 | --- 87 | 88 | Slide #4 89 | ======== 90 | 91 | First code block: 92 | 93 | !python 94 | while True: 95 | print "Everything's gonna be allright" 96 | 97 | Second code block: 98 | 99 | !php 100 | 101 | 102 | Third code block: 103 | 104 | !xml 105 | 106 | 107 | Foligno Madonna, by Raphael 108 | This is Raphael's "Foligno" Madonna, painted in 109 | 15111512. 110 | 111 | 112 | 113 | --- 114 | 115 | Slide #5 116 | ======== 117 | 118 | Another code block: 119 | 120 | !java 121 | if (markdown && isEasy()) { 122 | return true; 123 | } 124 | 125 | --- 126 | 127 | Slide #6 128 | ======== 129 | 130 | An image: 131 | 132 | ![monkey](monkey.jpg) 133 | 134 | --- 135 | 136 | Slide #7 137 | ======== 138 | 139 | Landslide can generate QR codes: 140 | 141 | .qr: 450|http://github.com/adamzap/landslide 142 | 143 | --- 144 | 145 | This is a slide with no heading. It works too. 146 | 147 | --- 148 | 149 | Slide #9 150 | ======== 151 | 152 | This slide has presenter notes. 153 | 154 | Press `p` to open a new window for the presenter with its notes. 155 | 156 | # Presenter Notes 157 | 158 | Hello from presenter notes 159 | -------------------------------------------------------------------------------- /samples/example2/0.md: -------------------------------------------------------------------------------- 1 | Landslide 2 | ========= 3 | 4 | --- 5 | 6 | Landslide 7 | ========= 8 | 9 | Generates a slideshow using the slides that power 10 | [the html5-slides presentation](http://apirocks.com/html5/html5.html). 11 | 12 | A `python` with the `jinja2`, `markdown`, and `pygments` modules is required. 13 | 14 | Markdown Formatting Instructions 15 | -------------------------------- 16 | 17 | - Separate your slides with a horizontal rule (--- in markdown) 18 | - Your first slide (title slide) should not have a heading, only `

`s 19 | - Your other slides should have a heading that renders to an h1 element 20 | - To highlight blocks of code, put !{{lang}} as the first indented line 21 | - See the included slides.md for an example 22 | 23 | Rendering Instructions 24 | ---------------------- 25 | 26 | - Put your markdown content in a file called `slides.md` 27 | - Run `python render.py` 28 | - Enjoy your newly generated `presentation.html` 29 | 30 | --- 31 | 32 | Slide #2 33 | ======== 34 | 35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean magna tellus, 36 | fermentum nec venenatis nec, dapibus id metus. Phasellus nulla massa, consequat 37 | nec tempor et, elementum viverra est. Duis sed nisl in eros adipiscing tempor. 38 | 39 | Section #1 40 | ---------- 41 | 42 | Integer in dignissim ipsum. Integer pretium nulla at elit facilisis eu feugiat 43 | velit consectetur. 44 | 45 | Section #2 46 | ---------- 47 | 48 | Donec risus tortor, dictum sollicitudin ornare eu, egestas at purus. Cras 49 | consequat lacus vitae lectus faucibus et molestie nisl gravida. Donec tempor, 50 | tortor in varius vestibulum, mi odio laoreet magna, in hendrerit nibh neque eu 51 | eros. 52 | 53 | Unicode Characters Work 54 | ----------------------- 55 | 56 | © é 57 | -------------------------------------------------------------------------------- /samples/example2/1.md: -------------------------------------------------------------------------------- 1 | Middle slide 2 | ============ 3 | -------------------------------------------------------------------------------- /samples/example2/2.md: -------------------------------------------------------------------------------- 1 | Slide #3, With a Somewhat Long Title 2 | ==================================== 3 | 4 | **Hello Gentlemen** 5 | 6 | - Mega Man 2 7 | - Mega Man 3 8 | - Spelunky 9 | - Dungeon Crawl Stone Soup 10 | - Etrian Odyssey 11 | 12 | *Are you prepared to see beyond the veil of reason?* - DeceasedCrab 13 | 14 | - Black Cascade 15 | - Two Hunters 16 | - Diadem of 12 Stars 17 | 18 | --- 19 | 20 | Slide #4 21 | ======== 22 | 23 | render.py 24 | --------- 25 | 26 | First code block: 27 | 28 | !python 29 | import jinja2 30 | import markdown 31 | 32 | with open('presentation.html', 'w') as outfile: 33 | slides_src = markdown.markdown(open('slides.md').read()).split('


\n') 34 | 35 | slides = [] 36 | 37 | for slide_src in slides_src: 38 | header, content = slide_src.split('\n', 1) 39 | slides.append({'header': header, 'content': content}) 40 | 41 | template = jinja2.Template(open('base.html').read()) 42 | 43 | outfile.write(template.render({'slides': slides})) 44 | 45 | Second code block: 46 | 47 | !php 48 | exec('python render.py --help'); 49 | -------------------------------------------------------------------------------- /samples/example2/3_sub/0.md: -------------------------------------------------------------------------------- 1 | A slide in a subdirectory 2 | ========================= 3 | 4 | It also works. 5 | 6 | An image: 7 | 8 | ![monkey](monkey.jpg) 9 | -------------------------------------------------------------------------------- /samples/example2/3_sub/monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/landslide/bdf1807e1fce403d48ecfb6cc1d6c7f06c003392/samples/example2/3_sub/monkey.jpg -------------------------------------------------------------------------------- /samples/example2/4.md: -------------------------------------------------------------------------------- 1 | Using `.markdown` extension 2 | =========================== 3 | 4 | It works as well. 5 | 6 | -------------------------------------------------------------------------------- /samples/example3/slides.koi8_r.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/landslide/bdf1807e1fce403d48ecfb6cc1d6c7f06c003392/samples/example3/slides.koi8_r.rst -------------------------------------------------------------------------------- /samples/example3/slides.rst: -------------------------------------------------------------------------------- 1 | Slides in ReStructuredText 2 | ========================== 3 | 4 | ---- 5 | 6 | Here we Go 7 | ---------- 8 | 9 | This is foo 10 | 11 | This is bar 12 | 13 | This is ünicô∂e 14 | 15 | - This 16 | - Is 17 | - A 18 | - List 19 | 20 | ---- 21 | 22 | Middle Title Slide 23 | ================== 24 | 25 | ---- 26 | 27 | Here we Go Again 28 | ---------------- 29 | 30 | This is foo again 31 | 32 | This is bargain 33 | 34 | ---- 35 | 36 | RST Features 37 | ------------ 38 | 39 | *italics* 40 | 41 | **bold** 42 | 43 | ``monospace`` 44 | 45 | http://docutils.sf.net/ 46 | 47 | 1. one 48 | 2. two 49 | 50 | ---- 51 | 52 | Some code now 53 | ------------- 54 | 55 | Let me give you this snippet: 56 | 57 | .. sourcecode:: python 58 | 59 | def foo(): 60 | "just a test" 61 | print bar 62 | 63 | Then this one, a more ReSTful way (haha, nerd joke spotted) using the ``sourcecode`` directive: 64 | 65 | .. sourcecode:: python 66 | 67 | def bar(): 68 | """pretty cool""" 69 | print baz 70 | 71 | 72 | Then this other one with the ``code-block`` directive: 73 | 74 | .. code-block:: python 75 | 76 | def batman(): 77 | "foobar" 78 | return robin 79 | -------------------------------------------------------------------------------- /samples/example3/slides_h3.rst: -------------------------------------------------------------------------------- 1 | --------- 2 | Heading 1 3 | --------- 4 | 5 | bla bla 6 | 7 | ---- 8 | 9 | Heading 2 10 | --------- 11 | 12 | bla bla 13 | 14 | ---- 15 | 16 | Heading 3 17 | ~~~~~~~~~ 18 | 19 | Oups !! 20 | 21 | ---- 22 | 23 | Heading 3 24 | ~~~~~~~~~ 25 | 26 | oups ! 27 | 28 | ---- 29 | 30 | 31 | Heading 2 32 | --------- 33 | 34 | bla 35 | 36 | ---- 37 | 38 | ----------- 39 | Heading 1.2 40 | ----------- 41 | 42 | bla -------------------------------------------------------------------------------- /samples/example4/config.cfg: -------------------------------------------------------------------------------- 1 | [landslide] 2 | theme = default 3 | source = ../example1 4 | ../example2 5 | ../example3 -------------------------------------------------------------------------------- /samples/test.css: -------------------------------------------------------------------------------- 1 | * {color: red;} -------------------------------------------------------------------------------- /samples/test.js: -------------------------------------------------------------------------------- 1 | alert('foo'); -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='landslide', 5 | version='1.0.1', 6 | description='Lightweight markup language-based html5 slideshow generator', 7 | packages=find_packages('src'), 8 | package_dir = {'': 'src'}, 9 | include_package_data=True, 10 | zip_safe=False, 11 | author='Adam Zapletal', 12 | author_email='adamzap@gmail.com', 13 | url='http://github.com/adamzap/landslide', 14 | license='Apache 2.0', 15 | platforms=['any'], 16 | keywords=[ 17 | 'markdown', 18 | 'slideshow', 19 | 'presentation', 20 | 'rst', 21 | 'restructuredtext', 22 | 'textile' 23 | ], 24 | install_requires=['Jinja2', 'Markdown', 'Pygments', 'docutils'], 25 | classifiers=[ 26 | 'Programming Language :: Python', 27 | 'Programming Language :: Python :: 2.5', 28 | 'Programming Language :: Python :: 2.6', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Development Status :: 4 - Beta', 31 | 'Operating System :: OS Independent', 32 | 'License :: OSI Approved :: Apache Software License', 33 | 'Topic :: Multimedia :: Graphics :: Presentation', 34 | 'Topic :: Text Processing :: Markup' 35 | ], 36 | long_description="""\ 37 | Landslide takes your Markdown, RST, or Textile file(s) and generates a 38 | slideshow like `this `_. 39 | 40 | Read the `README `_ 41 | for formatting instructions and more information. 42 | """, 43 | entry_points={ 44 | "console_scripts": [ 45 | "landslide = landslide.main:main", 46 | ] 47 | }, 48 | ) 49 | -------------------------------------------------------------------------------- /src/landslide/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/landslide/bdf1807e1fce403d48ecfb6cc1d6c7f06c003392/src/landslide/__init__.py -------------------------------------------------------------------------------- /src/landslide/generator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2010 Adam Zapletal 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import re 19 | import codecs 20 | import inspect 21 | import jinja2 22 | import shutil 23 | import tempfile 24 | import utils 25 | import ConfigParser 26 | 27 | from subprocess import Popen 28 | 29 | import macro as macro_module 30 | from parser import Parser 31 | 32 | 33 | BASE_DIR = os.path.dirname(__file__) 34 | THEMES_DIR = os.path.join(BASE_DIR, 'themes') 35 | TOC_MAX_LEVEL = 2 36 | VALID_LINENOS = ('no', 'inline', 'table') 37 | 38 | 39 | class Generator(object): 40 | """The Generator class takes and processes presentation source as a file, a 41 | folder or a configuration file and provides methods to render them as a 42 | presentation. 43 | """ 44 | DEFAULT_DESTINATION = 'presentation.html' 45 | default_macros = [ 46 | macro_module.CodeHighlightingMacro, 47 | macro_module.EmbedImagesMacro, 48 | macro_module.FixImagePathsMacro, 49 | macro_module.FxMacro, 50 | macro_module.NotesMacro, 51 | macro_module.QRMacro, 52 | ] 53 | user_css = [] 54 | user_js = [] 55 | 56 | def __init__(self, source, **kwargs): 57 | """ Configures this generator. Available ``args`` are: 58 | - ``source``: source file or directory path 59 | Available ``kwargs`` are: 60 | - ``copy_theme``: copy theme directory and files into presentation 61 | one 62 | - ``destination_file``: path to html or PDF destination file 63 | - ``direct``: enables direct rendering presentation to stdout 64 | - ``debug``: enables debug mode 65 | - ``embed``: generates a standalone document, with embedded assets 66 | - ``encoding``: the encoding to use for this presentation 67 | - ``extensions``: Comma separated list of markdown extensions 68 | - ``logger``: a logger lambda to use for logging 69 | - ``presenter_notes``: enable presenter notes 70 | - ``relative``: enable relative asset urls 71 | - ``theme``: path to the theme to use for this presentation 72 | - ``verbose``: enables verbose output 73 | """ 74 | self.copy_theme = kwargs.get('copy_theme', False) 75 | self.debug = kwargs.get('debug', False) 76 | self.destination_file = kwargs.get('destination_file', 77 | 'presentation.html') 78 | self.direct = kwargs.get('direct', False) 79 | self.embed = kwargs.get('embed', False) 80 | self.encoding = kwargs.get('encoding', 'utf8') 81 | self.extensions = kwargs.get('extensions', None) 82 | self.logger = kwargs.get('logger', None) 83 | self.presenter_notes = kwargs.get('presenter_notes', True) 84 | self.relative = kwargs.get('relative', False) 85 | self.theme = kwargs.get('theme', 'default') 86 | self.verbose = kwargs.get('verbose', False) 87 | self.linenos = self.linenos_check(kwargs.get('linenos')) 88 | self.num_slides = 0 89 | self.__toc = [] 90 | 91 | # macros registering 92 | self.macros = [] 93 | self.register_macro(*self.default_macros) 94 | 95 | if self.direct: 96 | # Only output html in direct output mode, not log messages 97 | self.verbose = False 98 | 99 | if not source or not os.path.exists(source): 100 | raise IOError(u"Source file/directory %s does not exist" 101 | % source) 102 | 103 | self.source_base_dir = os.path.split(os.path.abspath(source))[0] 104 | if source.endswith('.cfg'): 105 | config = self.parse_config(source) 106 | self.source = config.get('source') 107 | if not self.source: 108 | raise IOError('unable to fetch a valid source from config') 109 | self.destination_file = config.get('destination', 110 | self.DEFAULT_DESTINATION) 111 | self.embed = config.get('embed', False) 112 | self.relative = config.get('relative', False) 113 | self.theme = config.get('theme', 'default') 114 | self.add_user_css(config.get('css', [])) 115 | self.add_user_js(config.get('js', [])) 116 | self.linenos = self.linenos_check(config.get('linenos')) 117 | else: 118 | self.source = source 119 | 120 | if (os.path.exists(self.destination_file) 121 | and not os.path.isfile(self.destination_file)): 122 | raise IOError(u"Destination %s exists and is not a file" 123 | % self.destination_file) 124 | 125 | if self.destination_file.endswith('.html'): 126 | self.file_type = 'html' 127 | elif self.destination_file.endswith('.pdf'): 128 | self.file_type = 'pdf' 129 | self.embed = True 130 | else: 131 | raise IOError(u"This program can only write html or pdf files. " 132 | "Please use one of these file extensions in the " 133 | "destination") 134 | 135 | self.theme_dir = self.find_theme_dir(self.theme, self.copy_theme) 136 | self.template_file = self.get_template_file() 137 | 138 | def add_user_css(self, css_list): 139 | """ Adds supplementary user css files to the presentation. The 140 | ``css_list`` arg can be either a ``list`` or a ``basestring`` 141 | instance. 142 | """ 143 | if isinstance(css_list, basestring): 144 | css_list = [css_list] 145 | for css_path in css_list: 146 | if css_path and not css_path in self.user_css: 147 | if not os.path.exists(css_path): 148 | raise IOError('%s user css file not found' % (css_path,)) 149 | self.user_css.append({ 150 | 'path_url': utils.get_path_url(css_path, self.relative), 151 | 'contents': open(css_path).read(), 152 | }) 153 | 154 | def add_user_js(self, js_list): 155 | """ Adds supplementary user javascript files to the presentation. The 156 | ``js_list`` arg can be either a ``list`` or a ``basestring`` 157 | instance. 158 | """ 159 | if isinstance(js_list, basestring): 160 | js_list = [js_list] 161 | for js_path in js_list: 162 | if js_path and not js_path in self.user_js: 163 | if not os.path.exists(js_path): 164 | raise IOError('%s user js file not found' % (js_path,)) 165 | self.user_js.append({ 166 | 'path_url': utils.get_path_url(js_path, self.relative), 167 | 'contents': open(js_path).read(), 168 | }) 169 | 170 | def add_toc_entry(self, title, level, slide_number): 171 | """ Adds a new entry to current presentation Table of Contents. 172 | """ 173 | self.__toc.append({'title': title, 'number': slide_number, 174 | 'level': level}) 175 | 176 | @property 177 | def toc(self): 178 | """ Smart getter for Table of Content list. 179 | """ 180 | toc = [] 181 | stack = [toc] 182 | for entry in self.__toc: 183 | entry['sub'] = [] 184 | while entry['level'] < len(stack): 185 | stack.pop() 186 | while entry['level'] > len(stack): 187 | stack.append(stack[-1][-1]['sub']) 188 | stack[-1].append(entry) 189 | return toc 190 | 191 | def execute(self): 192 | """ Execute this generator regarding its current configuration. 193 | """ 194 | if self.direct: 195 | if self.file_type == 'pdf': 196 | raise IOError(u"Direct output mode is not available for PDF " 197 | "export") 198 | else: 199 | print self.render().encode(self.encoding) 200 | else: 201 | self.write() 202 | self.log(u"Generated file: %s" % self.destination_file) 203 | 204 | def get_template_file(self): 205 | """ Retrieves Jinja2 template file path. 206 | """ 207 | if os.path.exists(os.path.join(self.theme_dir, 'base.html')): 208 | return os.path.join(self.theme_dir, 'base.html') 209 | default_dir = os.path.join(THEMES_DIR, 'default') 210 | if not os.path.exists(os.path.join(default_dir, 'base.html')): 211 | raise IOError(u"Cannot find base.html in default theme") 212 | return os.path.join(default_dir, 'base.html') 213 | 214 | def fetch_contents(self, source): 215 | """ Recursively fetches Markdown contents from a single file or 216 | directory containing itself Markdown files. 217 | """ 218 | slides = [] 219 | 220 | if type(source) is list: 221 | for entry in source: 222 | slides.extend(self.fetch_contents(entry)) 223 | elif os.path.isdir(source): 224 | self.log(u"Entering %s" % source) 225 | entries = os.listdir(source) 226 | entries.sort() 227 | for entry in entries: 228 | slides.extend(self.fetch_contents(os.path.join(source, entry))) 229 | else: 230 | try: 231 | parser = Parser(os.path.splitext(source)[1], self.encoding, 232 | self.extensions) 233 | except NotImplementedError: 234 | return slides 235 | 236 | self.log(u"Adding %s (%s)" % (source, parser.format)) 237 | 238 | try: 239 | file = codecs.open(source, encoding=self.encoding) 240 | file_contents = file.read() 241 | except UnicodeDecodeError: 242 | self.log(u"Unable to decode source %s: skipping" % source, 243 | 'warning') 244 | else: 245 | inner_slides = re.split(r'', parser.parse(file_contents)) 246 | for inner_slide in inner_slides: 247 | slides.append(self.get_slide_vars(inner_slide, source)) 248 | 249 | if not slides: 250 | self.log(u"Exiting %s: no contents found" % source, 'notice') 251 | 252 | return slides 253 | 254 | def find_theme_dir(self, theme, copy_theme=False): 255 | """ Finds them dir path from its name. 256 | """ 257 | if os.path.exists(theme): 258 | self.theme_dir = theme 259 | elif os.path.exists(os.path.join(THEMES_DIR, theme)): 260 | self.theme_dir = os.path.join(THEMES_DIR, theme) 261 | else: 262 | raise IOError(u"Theme %s not found or invalid" % theme) 263 | target_theme_dir = os.path.join(os.getcwd(), 'theme') 264 | if copy_theme or os.path.exists(target_theme_dir): 265 | self.log(u'Copying %s theme directory to %s' 266 | % (theme, target_theme_dir)) 267 | if not os.path.exists(target_theme_dir): 268 | try: 269 | shutil.copytree(self.theme_dir, target_theme_dir) 270 | except Exception, e: 271 | self.log(u"Skipped copy of theme folder: %s" % e) 272 | pass 273 | self.theme_dir = target_theme_dir 274 | return self.theme_dir 275 | 276 | def get_css(self): 277 | """ Fetches and returns stylesheet file path or contents, for both 278 | print and screen contexts, depending if we want a standalone 279 | presentation or not. 280 | """ 281 | css = {} 282 | 283 | print_css = os.path.join(self.theme_dir, 'css', 'print.css') 284 | if not os.path.exists(print_css): 285 | # Fall back to default theme 286 | print_css = os.path.join(THEMES_DIR, 'default', 'css', 'print.css') 287 | 288 | if not os.path.exists(print_css): 289 | raise IOError(u"Cannot find css/print.css in default theme") 290 | 291 | css['print'] = { 292 | 'path_url': utils.get_path_url(print_css, self.relative), 293 | 'contents': open(print_css).read(), 294 | } 295 | 296 | screen_css = os.path.join(self.theme_dir, 'css', 'screen.css') 297 | if (os.path.exists(screen_css)): 298 | css['screen'] = { 299 | 'path_url': utils.get_path_url(screen_css, self.relative), 300 | 'contents': open(screen_css).read(), 301 | } 302 | else: 303 | self.log(u"No screen stylesheet provided in current theme", 304 | 'warning') 305 | 306 | return css 307 | 308 | def get_js(self): 309 | """ Fetches and returns javascript file path or contents, depending if 310 | we want a standalone presentation or not. 311 | """ 312 | js_file = os.path.join(self.theme_dir, 'js', 'slides.js') 313 | 314 | if not os.path.exists(js_file): 315 | js_file = os.path.join(THEMES_DIR, 'default', 'js', 'slides.js') 316 | 317 | if not os.path.exists(js_file): 318 | raise IOError(u"Cannot find slides.js in default theme") 319 | 320 | return { 321 | 'path_url': utils.get_path_url(js_file, self.relative), 322 | 'contents': open(js_file).read(), 323 | } 324 | 325 | def get_slide_vars(self, slide_src, source=None): 326 | """ Computes a single slide template vars from its html source code. 327 | Also extracts slide informations for the table of contents. 328 | """ 329 | find = re.search(r'((.+?))\s?(.+)?', slide_src, 330 | re.DOTALL | re.UNICODE) 331 | presenter_notes = None 332 | 333 | if not find: 334 | header = level = title = None 335 | content = slide_src.strip() 336 | else: 337 | header = find.group(1) 338 | level = int(find.group(2)) 339 | title = find.group(3) 340 | content = find.group(4).strip() if find.group(4) else find.group(4) 341 | 342 | slide_classes = [] 343 | 344 | if header: 345 | header, _ = self.process_macros(header, source) 346 | 347 | if content: 348 | content, slide_classes = self.process_macros(content, source) 349 | 350 | find = re.search(r']*>presenter notes', content, 351 | re.DOTALL | re.UNICODE | re.IGNORECASE) 352 | 353 | if find: 354 | if self.presenter_notes: 355 | presenter_notes = content[find.end():].strip() 356 | content = content[:find.start()] 357 | 358 | source_dict = {} 359 | 360 | if source: 361 | source_dict = {'rel_path': source, 362 | 'abs_path': os.path.abspath(source)} 363 | 364 | if header or content: 365 | return {'header': header, 'title': title, 'level': level, 366 | 'content': content, 'classes': slide_classes, 367 | 'source': source_dict, 'presenter_notes': presenter_notes} 368 | 369 | def get_template_vars(self, slides): 370 | """ Computes template vars from slides html source code. 371 | """ 372 | try: 373 | head_title = slides[0]['title'] 374 | except (IndexError, TypeError): 375 | head_title = "Untitled Presentation" 376 | 377 | for slide_index, slide_vars in enumerate(slides): 378 | if not slide_vars: 379 | continue 380 | self.num_slides += 1 381 | slide_number = slide_vars['number'] = self.num_slides 382 | if slide_vars['level'] and slide_vars['level'] <= TOC_MAX_LEVEL: 383 | self.add_toc_entry(slide_vars['title'], slide_vars['level'], 384 | slide_number) 385 | else: 386 | # Put something in the TOC even if it doesn't have a title or level 387 | self.add_toc_entry(u"-", 1, slide_number) 388 | 389 | return {'head_title': head_title, 'num_slides': str(self.num_slides), 390 | 'slides': slides, 'toc': self.toc, 'embed': self.embed, 391 | 'css': self.get_css(), 'js': self.get_js(), 392 | 'user_css': self.user_css, 'user_js': self.user_js} 393 | 394 | def linenos_check(self, value): 395 | """ Checks and returns a valid value for the ``linenos`` option. 396 | """ 397 | return value if value in VALID_LINENOS else 'inline' 398 | 399 | def log(self, message, type='notice'): 400 | """ Logs a message (eventually, override to do something more clever). 401 | """ 402 | if self.logger and not callable(self.logger): 403 | raise ValueError(u"Invalid logger set, must be a callable") 404 | if self.verbose and self.logger: 405 | self.logger(message, type) 406 | 407 | def parse_config(self, config_source): 408 | """ Parses a landslide configuration file and returns a normalized 409 | python dict. 410 | """ 411 | self.log(u"Config %s" % config_source) 412 | try: 413 | raw_config = ConfigParser.RawConfigParser() 414 | raw_config.read(config_source) 415 | except Exception, e: 416 | raise RuntimeError(u"Invalid configuration file: %s" % e) 417 | config = {} 418 | config['source'] = raw_config.get('landslide', 'source')\ 419 | .replace('\r', '').split('\n') 420 | if raw_config.has_option('landslide', 'theme'): 421 | config['theme'] = raw_config.get('landslide', 'theme') 422 | self.log(u"Using configured theme %s" % config['theme']) 423 | if raw_config.has_option('landslide', 'destination'): 424 | config['destination'] = raw_config.get('landslide', 'destination') 425 | if raw_config.has_option('landslide', 'linenos'): 426 | config['linenos'] = raw_config.get('landslide', 'linenos') 427 | if raw_config.has_option('landslide', 'embed'): 428 | config['embed'] = raw_config.getboolean('landslide', 'embed') 429 | if raw_config.has_option('landslide', 'relative'): 430 | config['relative'] = raw_config.getboolean('landslide', 'relative') 431 | if raw_config.has_option('landslide', 'css'): 432 | config['css'] = raw_config.get('landslide', 'css')\ 433 | .replace('\r', '').split('\n') 434 | if raw_config.has_option('landslide', 'js'): 435 | config['js'] = raw_config.get('landslide', 'js')\ 436 | .replace('\r', '').split('\n') 437 | return config 438 | 439 | def process_macros(self, content, source=None): 440 | """ Processed all macros. 441 | """ 442 | macro_options = {'relative': self.relative, 'linenos': self.linenos} 443 | classes = [] 444 | for macro_class in self.macros: 445 | try: 446 | macro = macro_class(logger=self.logger, embed=self.embed, 447 | options=macro_options) 448 | content, add_classes = macro.process(content, source) 449 | if add_classes: 450 | classes += add_classes 451 | except Exception, e: 452 | self.log(u"%s processing failed in %s: %s" 453 | % (macro, source, e)) 454 | return content, classes 455 | 456 | def register_macro(self, *macros): 457 | """ Registers macro classes passed a method arguments. 458 | """ 459 | for m in macros: 460 | if inspect.isclass(m) and issubclass(m, macro_module.Macro): 461 | self.macros.append(m) 462 | else: 463 | raise TypeError("Coundn't register macro; a macro must inherit" 464 | " from macro.Macro") 465 | 466 | def render(self): 467 | """ Returns generated html code. 468 | """ 469 | template_src = codecs.open(self.template_file, encoding=self.encoding) 470 | template = jinja2.Template(template_src.read()) 471 | slides = self.fetch_contents(self.source) 472 | context = self.get_template_vars(slides) 473 | 474 | html = template.render(context) 475 | 476 | if self.embed: 477 | images = re.findall(r'\s+background(?:-image)?:\surl\((.+?)\).+;', 478 | html, re.DOTALL | re.UNICODE) 479 | 480 | for img_url in images: 481 | img_url = img_url.replace('"', '').replace("'", '') 482 | 483 | source = os.path.join(THEMES_DIR, self.theme, 'css') 484 | 485 | encoded_url = utils.encode_image_from_url(img_url, source) 486 | 487 | html = html.replace(img_url, encoded_url, 1) 488 | 489 | return html 490 | 491 | def write(self): 492 | """ Writes generated presentation code into the destination file. 493 | """ 494 | html = self.render() 495 | 496 | if self.file_type == 'pdf': 497 | self.write_pdf(html) 498 | else: 499 | outfile = codecs.open(self.destination_file, 'w', encoding='utf_8') 500 | outfile.write(html) 501 | 502 | def write_pdf(self, html): 503 | """ Tries to write a PDF export from the command line using PrinceXML 504 | if available. 505 | """ 506 | try: 507 | f = tempfile.NamedTemporaryFile(delete=False, suffix='.html') 508 | f.write(html.encode('utf_8', 'xmlcharrefreplace')) 509 | f.close() 510 | except Exception: 511 | raise IOError(u"Unable to create temporary file, aborting") 512 | 513 | dummy_fh = open(os.path.devnull, 'w') 514 | 515 | try: 516 | command = ["prince", f.name, self.destination_file] 517 | 518 | Popen(command, stderr=dummy_fh).communicate() 519 | except Exception: 520 | raise EnvironmentError(u"Unable to generate PDF file using " 521 | "prince. Is it installed and available?") 522 | finally: 523 | dummy_fh.close() 524 | -------------------------------------------------------------------------------- /src/landslide/macro.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2010 Adam Zapletal 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import re 19 | import htmlentitydefs 20 | import pygments 21 | import sys 22 | import utils 23 | 24 | from pygments.lexers import get_lexer_by_name 25 | from pygments.formatters import HtmlFormatter 26 | 27 | 28 | class Macro(object): 29 | """Base class for Macros. A Macro aims to analyse, process and eventually 30 | alter some provided HTML contents and to provide supplementary 31 | informations to the slide context. 32 | """ 33 | options = {} 34 | 35 | def __init__(self, logger=sys.stdout, embed=False, options=None): 36 | self.logger = logger 37 | self.embed = embed 38 | if options: 39 | if not isinstance(options, dict): 40 | raise ValueError(u'Macro options must be a dict instance') 41 | self.options = options 42 | 43 | def process(self, content, source=None): 44 | """Generic processor (does actually nothing)""" 45 | return content, [] 46 | 47 | 48 | class CodeHighlightingMacro(Macro): 49 | """This Macro performs syntax coloration in slide code blocks using 50 | Pygments. 51 | """ 52 | code_blocks_re = re.compile( 53 | r'(()?\s?!(\w+?)\n(.*?)()?

)', 54 | re.UNICODE | re.MULTILINE | re.DOTALL) 55 | 56 | html_entity_re = re.compile('&(\w+?);') 57 | 58 | def descape(self, string, defs=None): 59 | """Decodes html entities from a given string""" 60 | if defs is None: 61 | defs = htmlentitydefs.entitydefs 62 | f = lambda m: defs[m.group(1)] if len(m.groups()) > 0 else m.group(0) 63 | return self.html_entity_re.sub(f, string) 64 | 65 | def process(self, content, source=None): 66 | code_blocks = self.code_blocks_re.findall(content) 67 | if not code_blocks: 68 | return content, [] 69 | 70 | classes = [] 71 | for block, void1, lang, code, void2 in code_blocks: 72 | try: 73 | lexer = get_lexer_by_name(lang) 74 | except Exception: 75 | self.logger(u"Unknown pygment lexer \"%s\", skipping" 76 | % lang, 'warning') 77 | return content, classes 78 | 79 | if 'linenos' not in self.options or self.options['linenos'] =='no': 80 | self.options['linenos'] = False 81 | 82 | formatter = HtmlFormatter(linenos=self.options['linenos'], 83 | nobackground=True) 84 | pretty_code = pygments.highlight(self.descape(code), lexer, 85 | formatter) 86 | content = content.replace(block, pretty_code, 1) 87 | 88 | return content, [u'has_code'] 89 | 90 | 91 | class EmbedImagesMacro(Macro): 92 | """This Macro extracts images url and embed them using the base64 93 | algorithm. 94 | """ 95 | def process(self, content, source=None): 96 | classes = [] 97 | 98 | if not self.embed: 99 | return content, classes 100 | 101 | images = re.findall(r'', content, 102 | re.DOTALL | re.UNICODE) 103 | 104 | source_dir = os.path.dirname(source) 105 | 106 | for image_url in images: 107 | encoded_url = utils.encode_image_from_url(image_url, source_dir) 108 | 109 | if not encoded_url: 110 | self.logger(u"Failed to embed image \"%s\"" % image_url, 'warning') 111 | return content, classes 112 | 113 | content = content.replace(u"src=\"" + image_url, 114 | u"src=\"" + encoded_url, 1) 115 | 116 | self.logger(u"Embedded image %s" % image_url, 'notice') 117 | 118 | return content, classes 119 | 120 | 121 | class FixImagePathsMacro(Macro): 122 | """This Macro replaces html image paths with fully qualified absolute 123 | urls. 124 | """ 125 | relative = False 126 | 127 | def process(self, content, source=None): 128 | classes = [] 129 | 130 | if self.embed: 131 | return content, classes 132 | base_path = utils.get_path_url(source, self.options.get('relative')) 133 | base_url = os.path.split(base_path)[0] 134 | 135 | images = re.findall(r'', content, 136 | re.DOTALL | re.UNICODE) 137 | 138 | for image in images: 139 | full_path = os.path.join(base_url, image) 140 | 141 | content = content.replace(image, full_path) 142 | 143 | return content, classes 144 | 145 | 146 | class FxMacro(Macro): 147 | """This Macro processes fx directives, ie adds specific css classes 148 | named after what the parser found in them. 149 | """ 150 | def process(self, content, source=None): 151 | classes = [] 152 | 153 | fx_match = re.search(r'(

\.fx:\s?(.*?)

\n?)', content, 154 | re.DOTALL | re.UNICODE) 155 | if fx_match: 156 | classes = fx_match.group(2).split(u' ') 157 | content = content.replace(fx_match.group(1), '', 1) 158 | 159 | return content, classes 160 | 161 | 162 | class NotesMacro(Macro): 163 | """This Macro processes Notes.""" 164 | def process(self, content, source=None): 165 | classes = [] 166 | 167 | new_content = re.sub(r'

\.notes:\s?(.*?)

', 168 | r'

\1

', content) 169 | 170 | if content != new_content: 171 | classes.append(u'has_notes') 172 | 173 | return new_content, classes 174 | 175 | 176 | class QRMacro(Macro): 177 | """This Macro generates a QR Code with Google Chart API.""" 178 | def process(self, content, source=None): 179 | classes = [] 180 | 181 | new_content = re.sub(r'

\.qr:\s?(\d*?)\|(.*?)

', 182 | r'

QR Code

', 183 | content) 184 | 185 | if content != new_content: 186 | classes.append(u'has_qr') 187 | 188 | return new_content, classes 189 | -------------------------------------------------------------------------------- /src/landslide/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2010 Adam Zapletal 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import sys 19 | 20 | try: 21 | from landslide import generator 22 | except ImportError: 23 | import generator 24 | 25 | from optparse import OptionParser 26 | 27 | 28 | def _parse_options(): 29 | """ Parses ``landslide`` args options. 30 | """ 31 | 32 | parser = OptionParser( 33 | usage="%prog [options] input.md ...", 34 | description="Generates an HTML5 or PDF " 35 | "slideshow from Markdown or other formats", 36 | epilog="Note: PDF export requires the `prince` program: " 37 | "http://princexml.com/") 38 | 39 | parser.add_option( 40 | "-c", "--copy-theme", 41 | action="store_true", 42 | dest="copy_theme", 43 | help="Copy theme directory into current presentation source directory", 44 | default=False) 45 | 46 | parser.add_option( 47 | "-b", "--debug", 48 | action="store_true", 49 | dest="debug", 50 | help="Will display any exception trace to stdout", 51 | default=False) 52 | 53 | parser.add_option( 54 | "-d", "--destination", 55 | dest="destination_file", 56 | help="The path to the to the destination file: .html or " 57 | ".pdf extensions allowed (default: presentation.html)", 58 | metavar="FILE", 59 | default="presentation.html") 60 | 61 | parser.add_option( 62 | "-e", "--encoding", 63 | dest="encoding", 64 | help="The encoding of your files (defaults to utf8)", 65 | metavar="ENCODING", 66 | default="utf8") 67 | 68 | parser.add_option( 69 | "-i", "--embed", 70 | action="store_true", 71 | dest="embed", 72 | help="Embed stylesheet and javascript contents, " 73 | "base64-encoded images in presentation to make a " 74 | "standalone document", 75 | default=False) 76 | 77 | parser.add_option( 78 | "-l", "--linenos", 79 | type="choice", 80 | choices=generator.VALID_LINENOS, 81 | dest="linenos", 82 | help="How to output linenos in source code. Three options availables: " 83 | "no (no line numbers); " 84 | "inline (inside
 tag); "
 85 |         "table (lines numbers in another cell, copy-paste friendly)",
 86 |         default="inline",
 87 |     )
 88 | 
 89 |     parser.add_option(
 90 |         "-o", "--direct-output",
 91 |         action="store_true",
 92 |         dest="direct",
 93 |         help="Prints the generated HTML code to stdout; won't work with PDF "
 94 |              "export",
 95 |         default=False)
 96 | 
 97 |     parser.add_option(
 98 |         "-P", "--no-presenter-notes",
 99 |         action="store_false",
100 |         dest="presenter_notes",
101 |         help="Don't include presenter notes in the output",
102 |         default=False)
103 | 
104 |     parser.add_option(
105 |         "-q", "--quiet",
106 |         action="store_false",
107 |         dest="verbose",
108 |         help="Won't write anything to stdout (silent mode)",
109 |         default=False)
110 | 
111 |     parser.add_option(
112 |         "-r", "--relative",
113 |         action="store_true",
114 |         dest="relative",
115 |         help="Make your presentation asset links relative to current pwd; "
116 |              "This may be useful if you intend to publish your html "
117 |              "presentation online.",
118 |         default=False,
119 |     )
120 | 
121 |     parser.add_option(
122 |         "-t", "--theme",
123 |         dest="theme",
124 |         help="A theme name, or path to a landlside theme directory",
125 |         default='default')
126 | 
127 |     parser.add_option(
128 |         "-v", "--verbose",
129 |         action="store_true",
130 |         dest="verbose",
131 |         help="Write informational messages to stdout (enabled by default)",
132 |         default=True)
133 | 
134 |     parser.add_option(
135 |         "-x", "--extensions",
136 |         dest="extensions",
137 |         help="Comma-separated list of extensions for Markdown",
138 |         default='',
139 |     )
140 | 
141 |     (options, args) = parser.parse_args()
142 | 
143 |     if not args:
144 |         parser.print_help()
145 |         sys.exit(1)
146 | 
147 |     return options, args[0]
148 | 
149 | 
150 | def log(message, type):
151 |     """Basic logger, print output directly to stdout and errors to stderr.
152 |     """
153 |     (sys.stdout if type == 'notice' else sys.stderr).write(message + "\n")
154 | 
155 | 
156 | def run(input_file, options):
157 |     """ Runs the Generator using parsed options.
158 |     """
159 |     options.logger = log
160 |     generator.Generator(input_file, **options.__dict__).execute()
161 | 
162 | 
163 | def main():
164 |     """ Main program entry point.
165 |     """
166 |     options, input_file = _parse_options()
167 | 
168 |     if (options.debug):
169 |         run(input_file, options)
170 |     else:
171 |         try:
172 |             run(input_file, options)
173 |         except Exception, e:
174 |             sys.stderr.write("Error: %s\n" % e)
175 |             sys.exit(1)
176 | 
177 | 
178 | if __name__ == '__main__':
179 |     main()
180 | 


--------------------------------------------------------------------------------
/src/landslide/parser.py:
--------------------------------------------------------------------------------
 1 | # -*- coding: utf-8 -*-
 2 | 
 3 | #  Copyright 2010 Adam Zapletal
 4 | #
 5 | #  Licensed under the Apache License, Version 2.0 (the "License");
 6 | #  you may not use this file except in compliance with the License.
 7 | #  You may obtain a copy of the License at
 8 | #
 9 | #     http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | #  Unless required by applicable law or agreed to in writing, software
12 | #  distributed under the License is distributed on an "AS IS" BASIS,
13 | #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | #  See the License for the specific language governing permissions and
15 | #  limitations under the License.
16 | 
17 | import re
18 | 
19 | SUPPORTED_FORMATS = {
20 |     'markdown':         ['.mdown', '.markdown', '.markdn', '.md', '.mdn'],
21 |     'restructuredtext': ['.rst', '.rest'],
22 |     'textile':          ['.textile'],
23 | }
24 | 
25 | 
26 | class Parser(object):
27 |     """This class generates the HTML code depending on which syntax is used in
28 |        the souce document.
29 | 
30 |        The Parser currently supports both Markdown and restructuredText
31 |        syntaxes.
32 |     """
33 |     RST_REPLACEMENTS = [
34 |             (r'', r'', re.UNICODE),
35 |             (r'', r'', re.UNICODE),
36 |             (r'

.*?

', r'', re.UNICODE), 37 | (r'Document or section may not begin with a transition\.', 38 | r'', re.UNICODE), 39 | (r'', r'', re.DOTALL | re.UNICODE), 40 | (r'\n', r'
\n', re.DOTALL | re.UNICODE), 41 | ] 42 | md_extensions = '' 43 | 44 | def __init__(self, extension, encoding='utf8', md_extensions=''): 45 | """Configures this parser. 46 | """ 47 | self.encoding = encoding 48 | self.format = None 49 | for supp_format, supp_extensions in SUPPORTED_FORMATS.items(): 50 | for supp_extension in supp_extensions: 51 | if supp_extension == extension: 52 | self.format = supp_format 53 | if not self.format: 54 | raise NotImplementedError(u"Unsupported format %s" % extension) 55 | if md_extensions: 56 | exts = (value.strip() for value in md_extensions.split(',')) 57 | self.md_extensions = filter(None, exts) 58 | 59 | def parse(self, text): 60 | """Parses and renders a text as HTML regarding current format. 61 | """ 62 | if self.format == 'markdown': 63 | try: 64 | import markdown 65 | except ImportError: 66 | raise RuntimeError(u"Looks like markdown is not installed") 67 | 68 | if text.startswith(u'\ufeff'): # check for unicode BOM 69 | text = text[1:] 70 | 71 | return markdown.markdown(text, self.md_extensions) 72 | elif self.format == 'restructuredtext': 73 | try: 74 | from rst import html_body 75 | except ImportError: 76 | raise RuntimeError(u"Looks like docutils are not installed") 77 | html = html_body(text, input_encoding=self.encoding) 78 | # RST generates pretty much markup to be removed in our case 79 | for (pattern, replacement, mode) in self.RST_REPLACEMENTS: 80 | html = re.sub(re.compile(pattern, mode), replacement, html, 0) 81 | return html.strip() 82 | elif self.format == 'textile': 83 | try: 84 | import textile 85 | except ImportError: 86 | raise RuntimeError(u"Looks like textile is not installed") 87 | 88 | return textile.textile(text, encoding=self.encoding) 89 | else: 90 | raise NotImplementedError(u"Unsupported format %s, cannot parse" 91 | % self.format) 92 | -------------------------------------------------------------------------------- /src/landslide/rst.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2010 Adam Zapletal 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from docutils import core, nodes 18 | from docutils.parsers.rst import directives, Directive 19 | 20 | from pygments import highlight 21 | from pygments.formatters import HtmlFormatter 22 | from pygments.lexers import get_lexer_by_name, TextLexer 23 | 24 | 25 | DEFAULT = HtmlFormatter(noclasses=False) 26 | VARIANTS = {} 27 | 28 | 29 | class Pygments(Directive): 30 | """ Source code syntax hightlighting for ReST syntax.""" 31 | required_arguments = 1 32 | optional_arguments = 0 33 | final_argument_whitespace = True 34 | option_spec = dict([(key, directives.flag) for key in VARIANTS]) 35 | has_content = True 36 | 37 | def run(self): 38 | self.assert_has_content() 39 | try: 40 | lexer = get_lexer_by_name(self.arguments[0]) 41 | except ValueError: 42 | # no lexer found - use the text one instead of an exception 43 | lexer = TextLexer() 44 | # take an arbitrary option if more than one is given 45 | formatter = (self.options and VARIANTS[self.options.keys()[0]] 46 | or DEFAULT) 47 | parsed = highlight(u'\n'.join(self.content), lexer, formatter) 48 | return [nodes.raw('', parsed, format='html')] 49 | 50 | 51 | directives.register_directive('sourcecode', Pygments) 52 | directives.register_directive('code-block', Pygments) 53 | 54 | 55 | def html_parts(input_string, source_path=None, destination_path=None, 56 | input_encoding='unicode', doctitle=1, initial_header_level=1): 57 | """ 58 | Given an input string, returns a dictionary of HTML document parts. 59 | 60 | Dictionary keys are the names of parts, and values are Unicode strings; 61 | encoding is up to the client. 62 | 63 | Parameters: 64 | 65 | - `input_string`: A multi-line text string; required. 66 | - `source_path`: Path to the source file or object. Optional, but useful 67 | for diagnostic output (system messages). 68 | - `destination_path`: Path to the file or object which will receive the 69 | output; optional. Used for determining relative paths (stylesheets, 70 | source links, etc.). 71 | - `input_encoding`: The encoding of `input_string`. If it is an encoded 72 | 8-bit string, provide the correct encoding. If it is a Unicode string, 73 | use "unicode", the default. 74 | - `doctitle`: Disable the promotion of a lone top-level section title to 75 | document title (and subsequent section title to document subtitle 76 | promotion); enabled by default. 77 | - `initial_header_level`: The initial level for header elements (e.g. 1 78 | for "

"). 79 | """ 80 | overrides = {'input_encoding': input_encoding, 81 | 'doctitle_xform': doctitle, 82 | 'initial_header_level': initial_header_level} 83 | parts = core.publish_parts( 84 | source=input_string, source_path=source_path, 85 | destination_path=destination_path, 86 | writer_name='html', settings_overrides=overrides) 87 | return parts 88 | 89 | 90 | def html_body(input_string, source_path=None, destination_path=None, 91 | input_encoding='unicode', doctitle=1, initial_header_level=1): 92 | """ 93 | Given an input string, returns an HTML fragment as a string. 94 | 95 | The return value is the contents of the element. 96 | 97 | Parameters (see `html_parts()` for the remainder): 98 | 99 | - `output_encoding`: The desired encoding of the output. If a Unicode 100 | string is desired, use the default value of "unicode" . 101 | """ 102 | parts = html_parts( 103 | input_string=input_string, source_path=source_path, 104 | destination_path=destination_path, 105 | input_encoding=input_encoding, doctitle=doctitle, 106 | initial_header_level=initial_header_level) 107 | fragment = parts['html_body'] 108 | return fragment 109 | -------------------------------------------------------------------------------- /src/landslide/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2010 Adam Zapletal 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import macro 18 | import os 19 | import re 20 | import unittest 21 | import codecs 22 | 23 | from generator import Generator 24 | from parser import Parser 25 | 26 | 27 | SAMPLES_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'samples') 28 | if (not os.path.exists(SAMPLES_DIR)): 29 | raise IOError('Sample source files not found, cannot run tests') 30 | 31 | 32 | class BaseTestCase(unittest.TestCase): 33 | def logtest(self, message, type='notice'): 34 | if type == 'warning': 35 | raise WarningMessage(message) 36 | elif type == 'error': 37 | raise ErrorMessage(message) 38 | 39 | 40 | class GeneratorTest(BaseTestCase): 41 | def test___init__(self): 42 | self.assertRaises(IOError, Generator, None) 43 | self.assertRaises(IOError, Generator, 'foo.md') 44 | 45 | def test_add_user_assets(self): 46 | base_dir = os.path.join(SAMPLES_DIR, 'example1', 'slides.md') 47 | g = Generator(base_dir, logger=self.logtest) 48 | g.add_user_css(os.path.join(SAMPLES_DIR, 'test.css')) 49 | g.add_user_js(os.path.join(SAMPLES_DIR, 'test.js')) 50 | self.assertEquals(g.user_css[0]['contents'], '* {color: red;}') 51 | self.assertEquals(g.user_js[0]['contents'], "alert('foo');") 52 | 53 | def test_get_toc(self): 54 | base_dir = os.path.join(SAMPLES_DIR, 'example1', 'slides.md') 55 | g = Generator(base_dir, logger=self.logtest) 56 | g.add_toc_entry('Section 1', 1, 1) 57 | g.add_toc_entry('Section 1.1', 2, 2) 58 | g.add_toc_entry('Section 1.2', 2, 3) 59 | g.add_toc_entry('Section 2', 1, 4) 60 | g.add_toc_entry('Section 2.1', 2, 5) 61 | g.add_toc_entry('Section 3', 1, 6) 62 | toc = g.toc 63 | self.assertEquals(len(toc), 3) 64 | self.assertEquals(toc[0]['title'], 'Section 1') 65 | self.assertEquals(len(toc[0]['sub']), 2) 66 | self.assertEquals(toc[0]['sub'][1]['title'], 'Section 1.2') 67 | self.assertEquals(toc[1]['title'], 'Section 2') 68 | self.assertEquals(len(toc[1]['sub']), 1) 69 | self.assertEquals(toc[2]['title'], 'Section 3') 70 | self.assertEquals(len(toc[2]['sub']), 0) 71 | 72 | def test_get_slide_vars(self): 73 | g = Generator(os.path.join(SAMPLES_DIR, 'example1', 'slides.md')) 74 | svars = g.get_slide_vars("

heading

\n

foo

\n

bar

\n") 75 | self.assertEquals(svars['title'], 'heading') 76 | self.assertEquals(svars['level'], 1) 77 | self.assertEquals(svars['header'], '

heading

') 78 | self.assertEquals(svars['content'], '

foo

\n

bar

') 79 | self.assertEquals(svars['source'], {}) 80 | self.assertEquals(svars['classes'], []) 81 | 82 | def test_unicode(self): 83 | g = Generator(os.path.join(SAMPLES_DIR, 'example3', 'slides.rst')) 84 | g.execute() 85 | s = g.render() 86 | self.assertTrue(s.find('
') != -1)
 87 |         self.assertEquals(len(re.findall('
foo

\n

.notes: bar

\n

baz

') 115 | self.assertEquals(r[0].find('

bar

'), 11) 116 | self.assertEquals(r[1], [u'has_notes']) 117 | # FXs 118 | content = '

foo

\n

.fx: blah blob

\n

baz

' 119 | r = g.process_macros(content) 120 | self.assertEquals(r[0], '

foo

\n

baz

') 121 | self.assertEquals(r[1][0], 'blah') 122 | self.assertEquals(r[1][1], 'blob') 123 | 124 | def test_register_macro(self): 125 | g = Generator(os.path.join(SAMPLES_DIR, 'example1', 'slides.md')) 126 | 127 | class SampleMacro(macro.Macro): 128 | pass 129 | 130 | g.register_macro(SampleMacro) 131 | self.assertTrue(SampleMacro in g.macros) 132 | 133 | def plop(foo): 134 | pass 135 | 136 | self.assertRaises(TypeError, g.register_macro, plop) 137 | 138 | def test_presenter_notes(self): 139 | g = Generator(os.path.join(SAMPLES_DIR, 'example1', 'slides.md')) 140 | svars = g.get_slide_vars("

heading

\n

foo

\n" 141 | "

Presenter Notes

\n

bar

\n") 142 | self.assertEquals(svars['presenter_notes'], "

bar

") 143 | 144 | def test_skip_presenter_notes(self): 145 | g = Generator(os.path.join(SAMPLES_DIR, 'example1', 'slides.md'), 146 | presenter_notes=False) 147 | svars = g.get_slide_vars("

heading

\n

foo

\n" 148 | "

Presenter Notes

\n

bar

\n") 149 | self.assertEquals(svars['presenter_notes'], None) 150 | 151 | 152 | class CodeHighlightingMacroTest(BaseTestCase): 153 | def setUp(self): 154 | self.sample_html = '''

Let me give you this snippet:

155 |
156 | !python
157 | def foo():
158 |     "just a test"
159 |     print bar
160 | 
161 |

Then this one:

162 |
163 | !php
164 | 
167 | 
168 |

Then this other one:

169 |
170 | !xml
171 | 
172 |     baz
173 | 
174 | 
175 |

End here.

''' 176 | 177 | def test_parsing_code_blocks(self): 178 | m = macro.CodeHighlightingMacro(self.logtest) 179 | blocks = m.code_blocks_re.findall(self.sample_html) 180 | self.assertEquals(len(blocks), 3) 181 | self.assertEquals(blocks[0][2], 'python') 182 | self.assertTrue(blocks[0][3].startswith('def foo():')) 183 | self.assertEquals(blocks[1][2], 'php') 184 | self.assertTrue(blocks[1][3].startswith('')) 187 | 188 | def test_descape(self): 189 | m = macro.CodeHighlightingMacro(self.logtest) 190 | self.assertEquals(m.descape('foo'), 'foo') 191 | self.assertEquals(m.descape('>'), '>') 192 | self.assertEquals(m.descape('<'), '<') 193 | self.assertEquals(m.descape('&lt;'), '<') 194 | self.assertEquals(m.descape('<span>'), '') 195 | self.assertEquals(m.descape('<spam&eggs>'), '') 196 | 197 | def test_process(self): 198 | m = macro.CodeHighlightingMacro(self.logtest) 199 | hl = m.process("
!php\n$foo;
") 200 | self.assertTrue(hl[0].startswith('
Let me give you this')) 210 | self.assertTrue(hl[0].find('

Then this one') > 0) 211 | self.assertTrue(hl[0].find('

Then this other one') > 0) 212 | self.assertTrue(hl[0].find('

0) 213 | self.assertEquals(hl[1][0], u'has_code') 214 | 215 | 216 | class EmbedImagesMacroTest(BaseTestCase): 217 | def test_process(self): 218 | base_dir = os.path.join(SAMPLES_DIR, 'example1', 'slides.md') 219 | m = macro.EmbedImagesMacro(self.logtest, True) 220 | self.assertRaises(WarningMessage, m.process, 221 | '', '.') 222 | content, classes = m.process('', base_dir) 223 | self.assertTrue(re.match(r'', 224 | content)) 225 | 226 | 227 | class FixImagePathsMacroTest(BaseTestCase): 228 | def test_process(self): 229 | base_dir = os.path.join(SAMPLES_DIR, 'example1', 'slides.md') 230 | m = macro.FixImagePathsMacro(self.logtest, False) 231 | content, classes = m.process('', base_dir) 232 | self.assertTrue(re.match(r'', 233 | content)) 234 | 235 | 236 | class FxMacroTest(BaseTestCase): 237 | def test_process(self): 238 | m = macro.FxMacro(self.logtest) 239 | content = '

foo

\n

.fx: blah blob

\n

baz

' 240 | r = m.process(content) 241 | self.assertEquals(r[0], '

foo

\n

baz

') 242 | self.assertEquals(r[1][0], 'blah') 243 | self.assertEquals(r[1][1], 'blob') 244 | 245 | 246 | class NotesMacroTest(BaseTestCase): 247 | def test_process(self): 248 | m = macro.NotesMacro(self.logtest) 249 | r = m.process('

foo

\n

.notes: bar

\n

baz

') 250 | self.assertEquals(r[0].find('

bar

'), 11) 251 | self.assertEquals(r[1], [u'has_notes']) 252 | 253 | 254 | class ParserTest(BaseTestCase): 255 | def test___init__(self): 256 | self.assertEquals(Parser('.md').format, 'markdown') 257 | self.assertEquals(Parser('.markdown').format, 'markdown') 258 | self.assertEquals(Parser('.rst').format, 'restructuredtext') 259 | self.assertRaises(NotImplementedError, Parser, '.txt') 260 | 261 | 262 | class WarningMessage(Exception): 263 | pass 264 | 265 | 266 | class ErrorMessage(Exception): 267 | pass 268 | 269 | if __name__ == '__main__': 270 | unittest.main() 271 | -------------------------------------------------------------------------------- /src/landslide/themes/default/base.html: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | 27 | 28 | {{ head_title }} 29 | 30 | {% if embed %} 31 | 34 | 37 | {% else %} 38 | 39 | 40 | {% endif %} 41 | {% for css in user_css %} 42 | {% if embed %} 43 | 46 | {% else %} 47 | 48 | {% endif %} 49 | {% endfor %} 50 | 51 | 52 | {% if embed %} 53 | 56 | {% else %} 57 | 58 | {% endif %} 59 | {% for js in user_js %} 60 | {% if embed %} 61 | 64 | {% else %} 65 | 66 | {% endif %} 67 | {% endfor %} 68 | 69 | 70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 | {% for slide in slides %} 78 | 79 |
80 |
81 |
82 | {% if slide.header %} 83 |
{{ slide.header }}
84 | {% endif %} 85 | {% if slide.content %} 86 |
{{ slide.content }}
87 | {% endif %} 88 |
89 |
90 |

Presenter Notes

91 |
92 | {% if slide.presenter_notes %} 93 | {{ slide.presenter_notes }} 94 | {% endif %} 95 |
96 |
97 |
98 | {% if slide.source %} 99 | 102 | {% endif %} 103 | 106 |
107 |
108 |
109 | {% endfor %} 110 |
111 |
112 | {% if toc %} 113 | 133 | {% endif %} 134 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/landslide/themes/default/css/print.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | @page { 7 | size: landscape; 8 | } 9 | 10 | body { 11 | font: 100% "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 12 | padding: 0; 13 | margin: 0; 14 | } 15 | 16 | div.slide { 17 | min-width: 800px; 18 | min-height: 600px; 19 | padding: 1em; 20 | overflow: hidden; 21 | page-break-after: always; 22 | border: 1px solid black; 23 | border-radius: 20px; 24 | } 25 | 26 | div.slide div.inner { 27 | width: 800px; 28 | height: 600px; 29 | margin: auto; 30 | display: table-cell; 31 | } 32 | 33 | h1 { 34 | font-size: 2.4em; 35 | } 36 | 37 | h2 { 38 | font-size: 1.4em; 39 | } 40 | 41 | h3 { 42 | margin: 1em 0; 43 | } 44 | 45 | ul { 46 | margin: 0; 47 | padding: 0; 48 | } 49 | 50 | p, li, pre { 51 | margin: 1em 0; 52 | } 53 | 54 | li { 55 | margin-left: 2em; 56 | } 57 | 58 | a { 59 | color: #000000; 60 | } 61 | 62 | pre, code { 63 | max-width: 800px; 64 | background: #eee; 65 | font-family: Monaco, monospace; 66 | font-size: 90%; 67 | } 68 | 69 | pre { 70 | padding: .2em .5em; 71 | overflow: hidden; 72 | border-radius: .8em; 73 | } 74 | 75 | code { 76 | padding: 0 .2em; 77 | } 78 | 79 | .slide header:only-child h1 { 80 | line-height: 180%; 81 | text-align: center; 82 | display: table-cell; 83 | vertical-align: middle; 84 | height: 600px; 85 | width: 800px; 86 | font-size: 48px; 87 | margin-top:100px; 88 | margin-bottom:100px; 89 | } 90 | 91 | #toc, #help, .slide aside, .slide footer, .slide .notes, 92 | .presenter_notes, #current_presenter_notes, #presenter_note { 93 | display: none; 94 | } 95 | -------------------------------------------------------------------------------- /src/landslide/themes/default/css/screen.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 14px "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 3 | background: #778; 4 | padding: 0; 5 | margin: 0; 6 | overflow: hidden; 7 | } 8 | 9 | div.presentation { 10 | position: absolute; 11 | width: 100%; 12 | display: table-cell; 13 | vertical-align: middle; 14 | height: 100%; 15 | background: inherit; 16 | } 17 | 18 | div.presentation > h1 { 19 | display: none; 20 | } 21 | 22 | div.slides, body.expose div.slides.nocontext { 23 | width: 100%; 24 | height: 100%; 25 | left: 0; 26 | top: 0; 27 | position: absolute; 28 | display: block; 29 | } 30 | 31 | div.slides.nocontext { 32 | width: 900px; 33 | margin: 0 auto; 34 | overflow: hidden; 35 | position: relative; 36 | left: auto; 37 | top: auto; 38 | } 39 | 40 | div.slide { 41 | display: inline; 42 | position: absolute; 43 | overflow: hidden; 44 | width: 900px; 45 | height: 700px; 46 | margin-top: -350px; 47 | margin-left: -400px; 48 | left: 50%; 49 | top: 50%; 50 | background: -webkit-gradient(linear, left bottom, left top, from(#bbd), to(#fff)); 51 | background-color: #eee; 52 | background: -moz-linear-gradient(bottom, #bbd, #fff); 53 | -webkit-transition: margin 0.25s ease-in-out; 54 | -moz-transition: margin 0.25s ease-in-out; 55 | -o-transition: margin 0.25s ease-in-out; 56 | border-top-left-radius: 20px; 57 | -moz-border-radius-topleft: 20px; 58 | -webkit-border-top-left-radius: 20px; 59 | border-top-right-radius: 20px; 60 | -moz-border-radius-topright: 20px; 61 | -webkit-border-top-right-radius: 20px; 62 | border-bottom-right-radius: 20px; 63 | -moz-border-radius-bottomright: 20px; 64 | -webkit-border-bottom-right-radius: 20px; 65 | border-bottom-left-radius: 20px; 66 | -moz-border-radius-bottomleft: 20px; 67 | -webkit-border-bottom-left-radius: 20px; 68 | } 69 | 70 | /* Expose */ 71 | 72 | body.expose div.slides { 73 | float: left; 74 | position: relative; 75 | overflow: auto; 76 | margin-bottom: 10px; 77 | } 78 | 79 | body.expose div.slide { 80 | display: block; 81 | float: left; 82 | position: relative; 83 | left: auto !important; 84 | top: auto !important; 85 | margin: 10px !important; 86 | -webkit-transition: none; 87 | -moz-transition: none; 88 | -o-transition: none; 89 | -moz-transform: scale(.33, .33); 90 | -moz-transform-origin: 0 0; 91 | -webkit-transform: scale(.33, .33); 92 | -webkit-transform-origin: 0 0; 93 | -o-transform: scale(.33, .33); 94 | -o-transform-origin: 0 0; 95 | -webkit-transition: none; 96 | -moz-transition: none; 97 | -o-transition: none; 98 | cursor: pointer; 99 | } 100 | 101 | body.expose div.slide:hover { 102 | background: -webkit-gradient(linear, left bottom, left top, from(#bdd), to(#fff)); 103 | background-color: #eee; 104 | background: -moz-linear-gradient(bottom, #bdd, #fff); 105 | } 106 | 107 | body.expose .slide-wrapper { 108 | float: left; 109 | position: relative; 110 | margin: .5%; 111 | width: 300px; 112 | height: 233px; 113 | } 114 | 115 | body.expose .slide footer { 116 | } 117 | 118 | body.expose .slide .inner { 119 | } 120 | 121 | body.expose .slide.far-past, 122 | body.expose .slide.past, 123 | body.expose .slide.future, 124 | body.expose .slide.far-future { 125 | margin-left: 0; 126 | } 127 | 128 | body.expose .slide.current { 129 | background: -webkit-gradient(linear, left bottom, left top, from(#ddb), to(#fff)); 130 | background-color: #eee; 131 | background: -moz-linear-gradient(bottom, #ddb, #fff); 132 | border: 16px solid #fff; 133 | -moz-transform: scale(.315, .315); 134 | -moz-transform-origin: 0 0; 135 | -webkit-transform: scale(.315, .315); 136 | -webkit-transform-origin: 0 0; 137 | -o-transform: scale(.315, .315); 138 | -o-transform-origin: 0 0; 139 | } 140 | 141 | /* Presenter Mode */ 142 | 143 | body.presenter_view div.slide { 144 | display: inline; 145 | position: absolute; 146 | overflow: hidden; 147 | -moz-transform: scale(.5, .5); 148 | -moz-transform-origin: 0 0; 149 | -webkit-transform: scale(.5, .5); 150 | -webkit-transform-origin: 0 0; 151 | -o-transform: scale(.5, .5); 152 | -o-transform-origin: 0 0; 153 | margin-top: -300px; 154 | } 155 | 156 | body.presenter_view .slide.far-past { 157 | display: block; 158 | margin-left: -1500px; 159 | } 160 | 161 | body.presenter_view .slide.past { 162 | display: block; 163 | margin-left: -975px; 164 | } 165 | 166 | body.presenter_view .slide.current { 167 | display: block; 168 | margin-left: -475px; 169 | border: 8px solid maroon; 170 | z-index: 2; 171 | } 172 | 173 | body.presenter_view .slide.future { 174 | display: block; 175 | margin-left: 25px; 176 | z-index: 1; 177 | } 178 | 179 | body.presenter_view .slide.far-future { 180 | display: block; 181 | margin-left: 525px; 182 | } 183 | 184 | body.presenter_view div#current_presenter_notes { 185 | visibility: visible; 186 | display: block; 187 | position: absolute; 188 | overflow: auto; 189 | vertical-align: middle; 190 | left: 50%; 191 | top: 50%; 192 | margin-left: -475px; 193 | margin-top: 100px; 194 | z-index: 2; 195 | width: 950px; 196 | border-style: solid; 197 | height: 30%; 198 | background-color: silver; 199 | } 200 | 201 | body.presenter_view div#current_presenter_notes section { 202 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 203 | color: black; 204 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 205 | display: block; 206 | overflow: visible; 207 | position: relative; 208 | background-color: #fffeff; 209 | height: 120px; 210 | margin-right: 30px; 211 | margin-top: 60px; 212 | margin-left: 30px; 213 | padding-right: 10px; 214 | padding-left: 10px; 215 | padding-top: 10px; 216 | } 217 | 218 | body.presenter_view div#current_presenter_notes section p { 219 | margin: 0; 220 | } 221 | 222 | body.presenter_view div#current_presenter_notes h1 { 223 | font-size: 50%; 224 | display: block; 225 | } 226 | 227 | div#current_presenter_notes { 228 | display: none; 229 | } 230 | 231 | div.slide div.presenter_notes, div.slides div.presenter_notes { 232 | display: none; 233 | } 234 | 235 | /* Slide styles */ 236 | 237 | div.slide p { 238 | font-size: 20px; 239 | } 240 | 241 | .slide.far-past { 242 | display: block; 243 | margin-left: -2400px; 244 | } 245 | 246 | .slide.past { 247 | display: block; 248 | margin-left: -1400px; 249 | } 250 | 251 | .slide.current { 252 | display: block; 253 | margin-left: -450px; 254 | } 255 | 256 | .slide.future { 257 | display: block; 258 | margin-left: 500px; 259 | } 260 | 261 | .slide.far-future { 262 | display: block; 263 | margin-left: 1500px; 264 | } 265 | 266 | body.three-d div.slides { 267 | -webkit-transform: translateX(50px) scale(0.8) rotateY(10deg); 268 | -moz-transform: translateX(50px) scale(0.8) rotateY(10deg); 269 | -o-transform: translateX(50px) scale(0.8) rotateY(10deg); 270 | } 271 | 272 | 273 | /* Content */ 274 | 275 | header:not(:only-child) { 276 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 277 | font-weight: normal; 278 | font-size: 50px; 279 | letter-spacing: -.05em; 280 | color: black; 281 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 282 | position: absolute; 283 | left: 30px; 284 | top: 25px; 285 | margin: 0; 286 | padding: 0; 287 | } 288 | 289 | header h1, header h2, header h3, header h4, header h5, header h6 { 290 | display: inline; 291 | font-size: 100%; 292 | font-weight: normal; 293 | padding: 0; 294 | margin: 0; 295 | } 296 | 297 | header h2:first-child { 298 | margin-top: 0; 299 | } 300 | 301 | section, .slide header:only-child h1 { 302 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 303 | color: #3f3f3f; 304 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 305 | margin-left: 30px; 306 | margin-right: 30px; 307 | margin-top: 100px; 308 | display: block; 309 | overflow: hidden; 310 | } 311 | 312 | img { display: block; margin: auto; } 313 | 314 | section img.align-center { 315 | display: block; 316 | margin-left: auto; 317 | margin-right: auto; 318 | } 319 | 320 | section img.align-right { 321 | display: block; 322 | margin-left: auto; 323 | margin-right: 0; 324 | } 325 | 326 | section img.align-left { 327 | display: block; 328 | margin-right: auto; 329 | margin-left: 0; 330 | } 331 | 332 | a { 333 | color: inherit; 334 | display: inline-block; 335 | text-decoration: none; 336 | line-height: 110%; 337 | border-bottom: 2px solid #3f3f3f; 338 | } 339 | 340 | pre { 341 | font-size: 16px; 342 | font-family: Monaco, Courier, monospace; 343 | } 344 | 345 | li { 346 | padding: 10px 0; 347 | font-size: 20px; 348 | } 349 | 350 | .slide header:only-child h1 { 351 | line-height: 180%; 352 | text-align: center; 353 | display: table-cell; 354 | vertical-align: middle; 355 | height: 700px; 356 | width: 900px; 357 | font-size: 50px; 358 | margin-top:100px; 359 | margin-bottom:100px; 360 | } 361 | 362 | .sidebar { 363 | clear: both; 364 | background: -webkit-gradient(linear, top right, bottom right, from(#dde), to(#fff)); 365 | -webkit-transition: margin 0.25s ease-in-out; 366 | background-color: #eee; 367 | background: -moz-linear-gradient(right, #dde, #fff); 368 | border-right: 5px solid #ccd; 369 | z-index: 9999999; 370 | height: 100%; 371 | overflow: hidden; 372 | top: 0; 373 | position: absolute; 374 | display: block; 375 | margin: 0; 376 | margin-left: -400px; 377 | padding: 10px 16px; 378 | overflow: auto; 379 | -webkit-transition: margin 0.2s ease-in-out; 380 | -moz-transition: margin 0.2s ease-in-out; 381 | -o-transition: margin 0.2s ease-in-out; 382 | } 383 | 384 | .sidebar h2 { 385 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 386 | margin: 0 0 16px; 387 | padding: 0; 388 | } 389 | 390 | .sidebar table { 391 | width: 100%; 392 | margin: 0; 393 | padding: 0; 394 | border-collapse: collapse; 395 | } 396 | 397 | .sidebar table caption { 398 | display: none; 399 | } 400 | 401 | .sidebar tr { 402 | margin: 2px 0; 403 | border-bottom: 1px solid #ccc; 404 | } 405 | 406 | .sidebar th { 407 | text-align: left; 408 | font-weight: normal; 409 | max-width: 300px; 410 | overflow: hidden; 411 | } 412 | 413 | .sidebar tr.sub th { 414 | text-indent: 20px; 415 | } 416 | 417 | .sidebar td { 418 | text-align: right; 419 | min-width: 20px; 420 | } 421 | 422 | .sidebar a { 423 | display: block; 424 | text-decoration: none; 425 | border-bottom: none; 426 | padding: 4px 0; 427 | } 428 | 429 | .sidebar tr.active { 430 | background: #ff0; 431 | } 432 | 433 | aside { 434 | display: none; 435 | } 436 | aside.source { 437 | position: absolute; 438 | bottom: 6px; 439 | left: 10px; 440 | text-indent: 10px; 441 | } 442 | aside.page_number { 443 | position: absolute; 444 | bottom: 6px; 445 | right: 10px; 446 | text-indent: 10px; 447 | } 448 | 449 | .notes { 450 | display: none; 451 | padding: 10px; 452 | background: #ccc; 453 | border-radius: 10px; 454 | -moz-border-radius: 10px; 455 | -webkit-border-radius: 10px; 456 | } 457 | div.slide p.notes { 458 | font-size: 90%; 459 | } 460 | 461 | /* Pygments default theme */ 462 | .hll { background-color: #ffffcc } 463 | .c { color: #408080; font-style: italic } /* Comment */ 464 | .err { border: 1px solid #FF0000 } /* Error */ 465 | .k { color: #008000; font-weight: bold } /* Keyword */ 466 | .o { color: #666666 } /* Operator */ 467 | .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 468 | .cp { color: #BC7A00 } /* Comment.Preproc */ 469 | .c1 { color: #408080; font-style: italic } /* Comment.Single */ 470 | .cs { color: #408080; font-style: italic } /* Comment.Special */ 471 | .gd { color: #A00000 } /* Generic.Deleted */ 472 | .ge { font-style: italic } /* Generic.Emph */ 473 | .gr { color: #FF0000 } /* Generic.Error */ 474 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 475 | .gi { color: #00A000 } /* Generic.Inserted */ 476 | .go { color: #808080 } /* Generic.Output */ 477 | .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 478 | .gs { font-weight: bold } /* Generic.Strong */ 479 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 480 | .gt { color: #0040D0 } /* Generic.Traceback */ 481 | .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 482 | .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 483 | .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 484 | .kp { color: #008000 } /* Keyword.Pseudo */ 485 | .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 486 | .kt { color: #B00040 } /* Keyword.Type */ 487 | .m { color: #666666 } /* Literal.Number */ 488 | .s { color: #BA2121 } /* Literal.String */ 489 | .na { color: #7D9029 } /* Name.Attribute */ 490 | .nb { color: #008000 } /* Name.Builtin */ 491 | .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 492 | .no { color: #880000 } /* Name.Constant */ 493 | .nd { color: #AA22FF } /* Name.Decorator */ 494 | .ni { color: #999999; font-weight: bold } /* Name.Entity */ 495 | .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 496 | .nf { color: #0000FF } /* Name.Function */ 497 | .nl { color: #A0A000 } /* Name.Label */ 498 | .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 499 | .nt { color: #008000; font-weight: bold } /* Name.Tag */ 500 | .nv { color: #19177C } /* Name.Variable */ 501 | .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 502 | .w { color: #bbbbbb } /* Text.Whitespace */ 503 | .mf { color: #666666 } /* Literal.Number.Float */ 504 | .mh { color: #666666 } /* Literal.Number.Hex */ 505 | .mi { color: #666666 } /* Literal.Number.Integer */ 506 | .mo { color: #666666 } /* Literal.Number.Oct */ 507 | .sb { color: #BA2121 } /* Literal.String.Backtick */ 508 | .sc { color: #BA2121 } /* Literal.String.Char */ 509 | .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 510 | .s2 { color: #BA2121 } /* Literal.String.Double */ 511 | .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 512 | .sh { color: #BA2121 } /* Literal.String.Heredoc */ 513 | .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 514 | .sx { color: #008000 } /* Literal.String.Other */ 515 | .sr { color: #BB6688 } /* Literal.String.Regex */ 516 | .s1 { color: #BA2121 } /* Literal.String.Single */ 517 | .ss { color: #19177C } /* Literal.String.Symbol */ 518 | .bp { color: #008000 } /* Name.Builtin.Pseudo */ 519 | .vc { color: #19177C } /* Name.Variable.Class */ 520 | .vg { color: #19177C } /* Name.Variable.Global */ 521 | .vi { color: #19177C } /* Name.Variable.Instance */ 522 | .il { color: #666666 } /* Literal.Number.Integer.Long */ 523 | 524 | #blank { 525 | position: absolute; 526 | top: 0; 527 | left: 0; 528 | background-color: black; 529 | width: 100%; 530 | height: 100%; 531 | z-index: 64; 532 | display: none; 533 | } 534 | -------------------------------------------------------------------------------- /src/landslide/themes/default/js/slides.js: -------------------------------------------------------------------------------- 1 | function main() { 2 | // Since we don't have the fallback of attachEvent and 3 | // other IE only stuff we won't try to run JS for IE. 4 | // It will run though when using Google Chrome Frame 5 | if (document.all) { return; } 6 | 7 | var currentSlideNo; 8 | var notesOn = false; 9 | var expanded = false; 10 | var hiddenContext = false; 11 | var blanked = false; 12 | var slides = document.getElementsByClassName('slide'); 13 | var touchStartX = 0; 14 | var spaces = /\s+/, a1 = ['']; 15 | var tocOpened = false; 16 | var helpOpened = false; 17 | var overviewActive = false; 18 | var modifierKeyDown = false; 19 | var scale = 1; 20 | var showingPresenterView = false; 21 | var presenterViewWin = null; 22 | var isPresenterView = false; 23 | 24 | var str2array = function(s) { 25 | if (typeof s == 'string' || s instanceof String) { 26 | if (s.indexOf(' ') < 0) { 27 | a1[0] = s; 28 | return a1; 29 | } else { 30 | return s.split(spaces); 31 | } 32 | } 33 | return s; 34 | }; 35 | 36 | var trim = function(str) { 37 | return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 38 | }; 39 | 40 | var addClass = function(node, classStr) { 41 | classStr = str2array(classStr); 42 | var cls = ' ' + node.className + ' '; 43 | for (var i = 0, len = classStr.length, c; i < len; ++i) { 44 | c = classStr[i]; 45 | if (c && cls.indexOf(' ' + c + ' ') < 0) { 46 | cls += c + ' '; 47 | } 48 | } 49 | node.className = trim(cls); 50 | }; 51 | 52 | var removeClass = function(node, classStr) { 53 | var cls; 54 | if (!node) { 55 | throw 'no node provided'; 56 | } 57 | if (classStr !== undefined) { 58 | classStr = str2array(classStr); 59 | cls = ' ' + node.className + ' '; 60 | for (var i = 0, len = classStr.length; i < len; ++i) { 61 | cls = cls.replace(' ' + classStr[i] + ' ', ' '); 62 | } 63 | cls = trim(cls); 64 | } else { 65 | cls = ''; 66 | } 67 | if (node.className != cls) { 68 | node.className = cls; 69 | } 70 | }; 71 | 72 | var getSlideEl = function(slideNo) { 73 | if (slideNo > 0) { 74 | return slides[slideNo - 1]; 75 | } else { 76 | return null; 77 | } 78 | }; 79 | 80 | var getSlideTitle = function(slideNo) { 81 | var el = getSlideEl(slideNo); 82 | if (el) { 83 | var headers = el.getElementsByTagName('header'); 84 | if (headers.length > 0) { 85 | return el.getElementsByTagName('header')[0].innerText; 86 | } 87 | } 88 | return null; 89 | }; 90 | 91 | var getSlidePresenterNote = function(slideNo) { 92 | var el = getSlideEl(slideNo); 93 | if (el) { 94 | var n = el.getElementsByClassName('presenter_notes'); 95 | if (n.length > 0) { 96 | return n[0]; 97 | } 98 | } 99 | return null; 100 | }; 101 | 102 | var changeSlideElClass = function(slideNo, className) { 103 | var el = getSlideEl(slideNo); 104 | if (el) { 105 | removeClass(el, 'far-past past current future far-future'); 106 | addClass(el, className); 107 | } 108 | }; 109 | 110 | var updateSlideClasses = function(updateOther) { 111 | window.location.hash = (isPresenterView ? "presenter" : "slide") + currentSlideNo; 112 | 113 | for (var i=1; i 1) { 178 | currentSlideNo--; 179 | } 180 | updateSlideClasses(true); 181 | }; 182 | 183 | var showNotes = function() { 184 | var notes = getSlideEl(currentSlideNo).getElementsByClassName('notes'); 185 | for (var i = 0, len = notes.length; i < len; i++) { 186 | notes.item(i).style.display = (notesOn) ? 'none':'block'; 187 | } 188 | notesOn = !notesOn; 189 | }; 190 | 191 | var showSlideNumbers = function() { 192 | var asides = document.getElementsByClassName('page_number'); 193 | var hidden = asides[0].style.display != 'block'; 194 | for (var i = 0; i < asides.length; i++) { 195 | asides.item(i).style.display = hidden ? 'block' : 'none'; 196 | } 197 | }; 198 | 199 | var showSlideSources = function() { 200 | var asides = document.getElementsByClassName('source'); 201 | var hidden = asides[0].style.display != 'block'; 202 | for (var i = 0; i < asides.length; i++) { 203 | asides.item(i).style.display = hidden ? 'block' : 'none'; 204 | } 205 | }; 206 | 207 | var showToc = function() { 208 | if (helpOpened) { 209 | showHelp(); 210 | } 211 | var toc = document.getElementById('toc'); 212 | if (toc) { 213 | toc.style.marginLeft = tocOpened ? '-' + (toc.clientWidth + 20) + 'px' : '0px'; 214 | tocOpened = !tocOpened; 215 | } 216 | updateOverview(); 217 | }; 218 | 219 | var showHelp = function() { 220 | if (tocOpened) { 221 | showToc(); 222 | } 223 | 224 | var help = document.getElementById('help'); 225 | 226 | if (help) { 227 | help.style.marginLeft = helpOpened ? '-' + (help.clientWidth + 20) + 'px' : '0px'; 228 | helpOpened = !helpOpened; 229 | } 230 | }; 231 | 232 | var showPresenterView = function() { 233 | if (isPresenterView) { return; } 234 | 235 | if (showingPresenterView) { 236 | presenterViewWin.close(); 237 | presenterViewWin = null; 238 | showingPresenterView = false; 239 | } else { 240 | presenterViewWin = open(window.location.pathname + "#presenter" + currentSlideNo, 'presenter_notes', 241 | 'directories=no,location=no,toolbar=no,menubar=no,copyhistory=no'); 242 | showingPresenterView = true; 243 | } 244 | }; 245 | 246 | var switch3D = function() { 247 | if (document.body.className.indexOf('three-d') == -1) { 248 | document.getElementsByClassName('presentation')[0].style.webkitPerspective = '1000px'; 249 | document.body.className += ' three-d'; 250 | } else { 251 | window.setTimeout('document.getElementsByClassName(\'presentation\')[0].style.webkitPerspective = \'0\';', 2000); 252 | document.body.className = document.body.className.replace(/three-d/, ''); 253 | } 254 | }; 255 | 256 | var toggleOverview = function() { 257 | if (!overviewActive) { 258 | addClass(document.body, 'expose'); 259 | overviewActive = true; 260 | setScale(1); 261 | } else { 262 | removeClass(document.body, 'expose'); 263 | overviewActive = false; 264 | if (expanded) { 265 | setScale(scale); // restore scale 266 | } 267 | } 268 | processContext(); 269 | updateOverview(); 270 | }; 271 | 272 | var updateOverview = function() { 273 | try { 274 | var presentation = document.getElementsByClassName('presentation')[0]; 275 | } catch (e) { 276 | return; 277 | } 278 | 279 | if (isPresenterView) { 280 | var action = overviewActive ? removeClass : addClass; 281 | action(document.body, 'presenter_view'); 282 | } 283 | 284 | var toc = document.getElementById('toc'); 285 | 286 | if (!toc) { 287 | return; 288 | } 289 | 290 | if (!tocOpened || !overviewActive) { 291 | presentation.style.marginLeft = '0px'; 292 | presentation.style.width = '100%'; 293 | } else { 294 | presentation.style.marginLeft = toc.clientWidth + 'px'; 295 | presentation.style.width = (presentation.clientWidth - toc.clientWidth) + 'px'; 296 | } 297 | }; 298 | 299 | var computeScale = function() { 300 | var cSlide = document.getElementsByClassName('current')[0]; 301 | var sx = cSlide.clientWidth / window.innerWidth; 302 | var sy = cSlide.clientHeight / window.innerHeight; 303 | return 1 / Math.max(sx, sy); 304 | }; 305 | 306 | var setScale = function(scale) { 307 | var presentation = document.getElementsByClassName('slides')[0]; 308 | var transform = 'scale(' + scale + ')'; 309 | presentation.style.MozTransform = transform; 310 | presentation.style.WebkitTransform = transform; 311 | presentation.style.OTransform = transform; 312 | presentation.style.msTransform = transform; 313 | presentation.style.transform = transform; 314 | }; 315 | 316 | var expandSlides = function() { 317 | if (overviewActive) { 318 | return; 319 | } 320 | if (expanded) { 321 | setScale(1); 322 | expanded = false; 323 | } else { 324 | scale = computeScale(); 325 | setScale(scale); 326 | expanded = true; 327 | } 328 | }; 329 | 330 | var showContext = function() { 331 | try { 332 | var presentation = document.getElementsByClassName('slides')[0]; 333 | removeClass(presentation, 'nocontext'); 334 | } catch (e) {} 335 | }; 336 | 337 | var hideContext = function() { 338 | try { 339 | var presentation = document.getElementsByClassName('slides')[0]; 340 | addClass(presentation, 'nocontext'); 341 | } catch (e) {} 342 | }; 343 | 344 | var processContext = function() { 345 | if (hiddenContext) { 346 | hideContext(); 347 | } else { 348 | showContext(); 349 | } 350 | }; 351 | 352 | var toggleContext = function() { 353 | hiddenContext = !hiddenContext; 354 | processContext(); 355 | }; 356 | 357 | var toggleBlank = function() { 358 | blank_elem = document.getElementById('blank'); 359 | 360 | blank_elem.style.display = blanked ? 'none' : 'block'; 361 | 362 | blanked = !blanked; 363 | }; 364 | 365 | var isModifierKey = function(keyCode) { 366 | switch (keyCode) { 367 | case 16: // shift 368 | case 17: // ctrl 369 | case 18: // alt 370 | case 91: // command 371 | return true; 372 | break; 373 | default: 374 | return false; 375 | break; 376 | } 377 | }; 378 | 379 | var checkModifierKeyUp = function(event) { 380 | if (isModifierKey(event.keyCode)) { 381 | modifierKeyDown = false; 382 | } 383 | }; 384 | 385 | var checkModifierKeyDown = function(event) { 386 | if (isModifierKey(event.keyCode)) { 387 | modifierKeyDown = true; 388 | } 389 | }; 390 | 391 | var handleBodyKeyDown = function(event) { 392 | switch (event.keyCode) { 393 | case 13: // Enter 394 | if (overviewActive) { 395 | toggleOverview(); 396 | } 397 | break; 398 | case 27: // ESC 399 | toggleOverview(); 400 | break; 401 | case 37: // left arrow 402 | case 33: // page up 403 | event.preventDefault(); 404 | prevSlide(); 405 | break; 406 | case 39: // right arrow 407 | case 32: // space 408 | case 34: // page down 409 | event.preventDefault(); 410 | nextSlide(); 411 | break; 412 | case 50: // 2 413 | if (!modifierKeyDown) { 414 | showNotes(); 415 | } 416 | break; 417 | case 51: // 3 418 | if (!modifierKeyDown && !overviewActive) { 419 | switch3D(); 420 | } 421 | break; 422 | case 190: // . 423 | case 48: // 0 424 | case 66: // b 425 | if (!modifierKeyDown && !overviewActive) { 426 | toggleBlank(); 427 | } 428 | break; 429 | case 67: // c 430 | if (!modifierKeyDown && !overviewActive) { 431 | toggleContext(); 432 | } 433 | break; 434 | case 69: // e 435 | if (!modifierKeyDown && !overviewActive) { 436 | expandSlides(); 437 | } 438 | break; 439 | case 72: // h 440 | showHelp(); 441 | break; 442 | case 78: // n 443 | if (!modifierKeyDown && !overviewActive) { 444 | showSlideNumbers(); 445 | } 446 | break; 447 | case 80: // p 448 | if (!modifierKeyDown && !overviewActive) { 449 | showPresenterView(); 450 | } 451 | break; 452 | case 83: // s 453 | if (!modifierKeyDown && !overviewActive) { 454 | showSlideSources(); 455 | } 456 | break; 457 | case 84: // t 458 | showToc(); 459 | break; 460 | } 461 | }; 462 | 463 | var handleWheel = function(event) { 464 | if (tocOpened || helpOpened || overviewActive) { 465 | return; 466 | } 467 | 468 | var delta = 0; 469 | 470 | if (!event) { 471 | event = window.event; 472 | } 473 | 474 | if (event.wheelDelta) { 475 | delta = event.wheelDelta/120; 476 | if (window.opera) delta = -delta; 477 | } else if (event.detail) { 478 | delta = -event.detail/3; 479 | } 480 | 481 | if (delta && delta <0) { 482 | nextSlide(); 483 | } else if (delta) { 484 | prevSlide(); 485 | } 486 | }; 487 | 488 | var addSlideClickListeners = function() { 489 | for (var i=0; i < slides.length; i++) { 490 | var slide = slides.item(i); 491 | slide.num = i + 1; 492 | slide.addEventListener('click', function(e) { 493 | if (overviewActive) { 494 | currentSlideNo = this.num; 495 | toggleOverview(); 496 | updateSlideClasses(true); 497 | e.preventDefault(); 498 | } 499 | return false; 500 | }, true); 501 | } 502 | }; 503 | 504 | var addRemoteWindowControls = function() { 505 | window.addEventListener("message", function(e) { 506 | if (e.data.indexOf("slide#") != -1) { 507 | currentSlideNo = Number(e.data.replace('slide#', '')); 508 | updateSlideClasses(false); 509 | } 510 | }, false); 511 | }; 512 | 513 | var addTouchListeners = function() { 514 | document.addEventListener('touchstart', function(e) { 515 | touchStartX = e.touches[0].pageX; 516 | }, false); 517 | document.addEventListener('touchend', function(e) { 518 | var pixelsMoved = touchStartX - e.changedTouches[0].pageX; 519 | var SWIPE_SIZE = 150; 520 | if (pixelsMoved > SWIPE_SIZE) { 521 | nextSlide(); 522 | } 523 | else if (pixelsMoved < -SWIPE_SIZE) { 524 | prevSlide(); 525 | } 526 | }, false); 527 | }; 528 | 529 | var addTocLinksListeners = function() { 530 | var toc = document.getElementById('toc'); 531 | if (toc) { 532 | var tocLinks = toc.getElementsByTagName('a'); 533 | for (var i=0; i < tocLinks.length; i++) { 534 | tocLinks.item(i).addEventListener('click', function(e) { 535 | currentSlideNo = Number(this.attributes['href'].value.replace('#slide', '')); 536 | updateSlideClasses(true); 537 | e.preventDefault(); 538 | }, true); 539 | } 540 | } 541 | }; 542 | 543 | // initialize 544 | 545 | (function() { 546 | if (window.location.hash == "") { 547 | currentSlideNo = 1; 548 | } else if (window.location.hash.indexOf("#presenter") != -1) { 549 | currentSlideNo = Number(window.location.hash.replace('#presenter', '')); 550 | isPresenterView = true; 551 | showingPresenterView = true; 552 | presenterViewWin = window; 553 | addClass(document.body, 'presenter_view'); 554 | } else { 555 | currentSlideNo = Number(window.location.hash.replace('#slide', '')); 556 | } 557 | 558 | document.addEventListener('keyup', checkModifierKeyUp, false); 559 | document.addEventListener('keydown', handleBodyKeyDown, false); 560 | document.addEventListener('keydown', checkModifierKeyDown, false); 561 | document.addEventListener('DOMMouseScroll', handleWheel, false); 562 | 563 | window.onmousewheel = document.onmousewheel = handleWheel; 564 | window.onresize = expandSlides; 565 | 566 | for (var i = 0, el; el = slides[i]; i++) { 567 | addClass(el, 'slide far-future'); 568 | } 569 | updateSlideClasses(false); 570 | 571 | // add support for finger events (filter it by property detection?) 572 | addTouchListeners(); 573 | 574 | addTocLinksListeners(); 575 | 576 | addSlideClickListeners(); 577 | 578 | addRemoteWindowControls(); 579 | })(); 580 | } 581 | -------------------------------------------------------------------------------- /src/landslide/themes/light/css/screen.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 14px "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 3 | background: #fff; 4 | padding: 0; 5 | margin: 0; 6 | overflow: hidden; 7 | } 8 | 9 | div.presentation { 10 | position: absolute; 11 | width: 100%; 12 | display: table-cell; 13 | vertical-align: middle; 14 | height: 100%; 15 | background: inherit; 16 | } 17 | 18 | div.slides, body.expose div.slides.nocontext { 19 | width: 100%; 20 | height: 100%; 21 | left: 0; 22 | top: 0; 23 | position: absolute; 24 | display: block; 25 | background-color: #fff; 26 | } 27 | 28 | 29 | div.slides.nocontext { 30 | width: 900px; 31 | margin: 0 auto; 32 | overflow: hidden; 33 | position: relative; 34 | left: auto; 35 | top: auto; 36 | } 37 | 38 | div.slide { 39 | display: none; 40 | position: absolute; 41 | overflow: hidden; 42 | width: 900px; 43 | height: 700px; 44 | left: 50%; 45 | top: 50%; 46 | margin-top: -350px; 47 | background: -webkit-gradient(linear, left bottom, left top, from(#fff), to(#eeeeec)); 48 | background-color: #eee; 49 | background: -moz-linear-gradient(bottom, #fff, #eeeeec); 50 | -webkit-transition: margin 0.25s ease-in-out; 51 | -moz-transition: margin 0.25s ease-in-out; 52 | -o-transition: margin 0.25s ease-in-out; 53 | border-top-right-radius: 20px; 54 | -moz-border-radius-topright: 20px; 55 | -webkit-border-top-right-radius: 20px; 56 | border-bottom-left-radius: 0px; 57 | -moz-border-radius-bottomleft: 0px; 58 | -webkit-border-bottom-left-radius: 0px; 59 | border-top-left-radius: 20px; 60 | -moz-border-radius-topleft: 20px; 61 | -webkit-border-top-left-radius: 20px; 62 | border-bottom-right-radius: 0px; 63 | -moz-border-radius-bottomright: 0px; 64 | -webkit-border-bottom-right-radius: 0px; 65 | 66 | } 67 | 68 | div.slide p { 69 | font-size: 20px; 70 | } 71 | 72 | .slide.far-past { 73 | display: block; 74 | margin-left: -2400px; 75 | } 76 | 77 | .slide.past { 78 | display: block; 79 | margin-left: -1400px; 80 | } 81 | 82 | .slide.current { 83 | display: block; 84 | margin-left: -450px; 85 | } 86 | 87 | .slide.future { 88 | display: block; 89 | margin-left: 500px; 90 | } 91 | 92 | .slide.far-future { 93 | display: block; 94 | margin-left: 1500px; 95 | } 96 | 97 | body.three-d div.slides { 98 | -webkit-transform: translateX(50px) scale(0.8) rotateY(10deg); 99 | -moz-transform: translateX(50px) scale(0.8) rotateY(10deg); 100 | -o-transform: translateX(50px) scale(0.8) rotateY(10deg); 101 | } 102 | 103 | 104 | /* Content */ 105 | 106 | header:not(:only-child) { 107 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 108 | font-weight: normal; 109 | font-size: 50px; 110 | letter-spacing: -.05em; 111 | color: white; 112 | color: black; 113 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 114 | position: absolute; 115 | left: 30px; 116 | top: 25px; 117 | margin: 0; 118 | padding: 0; 119 | } 120 | 121 | header h1, header h2, header h3, header h4, header h5, header h6 { 122 | display: inline; 123 | font-size: 100%; 124 | font-weight: normal; 125 | padding: 0; 126 | margin: 0; 127 | } 128 | 129 | header h2:first-child { 130 | margin-top: 0; 131 | } 132 | 133 | section, .slide header:only-child h1 { 134 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 135 | color: #3f3f3f; 136 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 137 | margin-left: 30px; 138 | margin-right: 30px; 139 | margin-top: 100px; 140 | display: block; 141 | overflow: hidden; 142 | } 143 | 144 | section img.align-center { 145 | display: block; 146 | margin-left: auto; 147 | margin-right: auto; 148 | } 149 | 150 | section img.align-right { 151 | display: block; 152 | margin-left: auto; 153 | margin-right: 0; 154 | } 155 | 156 | section img.align-left { 157 | display: block; 158 | margin-right: auto; 159 | margin-left: 0; 160 | } 161 | 162 | a { 163 | color: inherit; 164 | display: inline-block; 165 | text-decoration: none; 166 | line-height: 110%; 167 | border-bottom: 2px solid #3f3f3f; 168 | } 169 | 170 | pre { 171 | font-size: 16px; 172 | font-family: Monaco, Courier, monospace; 173 | } 174 | 175 | li { 176 | padding: 10px 0; 177 | font-size: 20px; 178 | } 179 | 180 | .slide header:only-child h1 { 181 | line-height: 180%; 182 | text-align: center; 183 | display: table-cell; 184 | vertical-align: middle; 185 | height: 700px; 186 | width: 900px; 187 | font-size: 50px; 188 | margin-top:100px; 189 | margin-bottom:100px; 190 | } 191 | 192 | .sidebar { 193 | background: -webkit-gradient(linear, top right, bottom right, from(#dde), to(#fff)); 194 | -webkit-transition: margin 0.25s ease-in-out; 195 | background-color: #eee; 196 | background: -moz-linear-gradient(right, #dde, #fff); 197 | border-right: 5px solid #ccd; 198 | z-index: 9999999; 199 | height: 100%; 200 | overflow: hidden; 201 | top: 0; 202 | position: absolute; 203 | display: block; 204 | margin: 0; 205 | margin-left: -400px; 206 | padding: 10px 16px; 207 | overflow: auto; 208 | -webkit-transition: margin 0.2s ease-in-out; 209 | -moz-transition: margin 0.2s ease-in-out; 210 | -o-transition: margin 0.2s ease-in-out; 211 | } 212 | 213 | .sidebar h2 { 214 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 215 | margin: 0 0 16px; 216 | padding: 0; 217 | } 218 | 219 | .sidebar table { 220 | width: 100%; 221 | margin: 0; 222 | padding: 0; 223 | border-collapse: collapse; 224 | } 225 | 226 | .sidebar table caption { 227 | display: none; 228 | } 229 | 230 | .sidebar tr { 231 | margin: 2px 0; 232 | border-bottom: 1px solid #ccc; 233 | } 234 | 235 | .sidebar th { 236 | text-align: left; 237 | font-weight: normal; 238 | max-width: 300px; 239 | overflow: hidden; 240 | } 241 | 242 | .sidebar tr.sub th { 243 | text-indent: 20px; 244 | } 245 | 246 | .sidebar td { 247 | text-align: right; 248 | min-width: 20px; 249 | } 250 | 251 | .sidebar a { 252 | display: block; 253 | text-decoration: none; 254 | border-bottom: none; 255 | padding: 4px 0; 256 | } 257 | 258 | .sidebar tr.active { 259 | background: #ff0; 260 | } 261 | 262 | aside { 263 | display: none; 264 | } 265 | aside.source { 266 | position: absolute; 267 | bottom: 6px; 268 | left: 10px; 269 | text-indent: 10px; 270 | } 271 | aside.page_number { 272 | position: absolute; 273 | bottom: 6px; 274 | right: 10px; 275 | text-indent: 10px; 276 | } 277 | 278 | .notes { 279 | display: none; 280 | padding: 10px; 281 | background: #ccc; 282 | border-radius: 10px; 283 | -moz-border-radius: 10px; 284 | -webkit-border-radius: 10px; 285 | } 286 | div.slide p.notes { 287 | font-size: 90%; 288 | } 289 | 290 | img { display: block; margin: auto; } 291 | 292 | /* Expose */ 293 | 294 | body.expose div.slides { 295 | float: left; 296 | position: relative; 297 | overflow: auto; 298 | margin-bottom: 10px; 299 | } 300 | 301 | body.expose div.slide { 302 | display: block; 303 | float: left; 304 | position: relative; 305 | left: auto !important; 306 | top: auto !important; 307 | margin: 10px !important; 308 | -webkit-transition: none; 309 | -moz-transition: none; 310 | -o-transition: none; 311 | -moz-transform: scale(.33, .33); 312 | -moz-transform-origin: 0 0; 313 | -webkit-transform: scale(.33, .33); 314 | -webkit-transform-origin: 0 0; 315 | -o-transform: scale(.33, .33); 316 | -o-transform-origin: 0 0; 317 | -webkit-transition: none; 318 | -moz-transition: none; 319 | -o-transition: none; 320 | cursor: pointer; 321 | } 322 | 323 | body.expose div.slide:hover { 324 | background: -webkit-gradient(linear, left bottom, left top, from(#bdd), to(#fff)); 325 | background-color: #eee; 326 | background: -moz-linear-gradient(bottom, #bdd, #fff); 327 | } 328 | 329 | body.expose .slide-wrapper { 330 | float: left; 331 | position: relative; 332 | margin: .5%; 333 | width: 300px; 334 | height: 233px; 335 | } 336 | 337 | body.expose .slide footer { 338 | } 339 | 340 | body.expose .slide .inner { 341 | } 342 | 343 | body.expose .slide.far-past, 344 | body.expose .slide.past, 345 | body.expose .slide.future, 346 | body.expose .slide.far-future { 347 | margin-left: 0; 348 | } 349 | 350 | body.expose .slide.current { 351 | background: -webkit-gradient(linear, left bottom, left top, from(#ddb), to(#fff)); 352 | background-color: #eee; 353 | background: -moz-linear-gradient(bottom, #ddb, #fff); 354 | border: 16px solid #fff; 355 | -moz-transform: scale(.315, .315); 356 | -moz-transform-origin: 0 0; 357 | -webkit-transform: scale(.315, .315); 358 | -webkit-transform-origin: 0 0; 359 | -o-transform: scale(.315, .315); 360 | -o-transform-origin: 0 0; 361 | } 362 | 363 | /* Pygments default theme */ 364 | .hll { background-color: #ffffcc } 365 | .c { color: #408080; font-style: italic } /* Comment */ 366 | .err { border: 1px solid #FF0000 } /* Error */ 367 | .k { color: #008000; font-weight: bold } /* Keyword */ 368 | .o { color: #666666 } /* Operator */ 369 | .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 370 | .cp { color: #BC7A00 } /* Comment.Preproc */ 371 | .c1 { color: #408080; font-style: italic } /* Comment.Single */ 372 | .cs { color: #408080; font-style: italic } /* Comment.Special */ 373 | .gd { color: #A00000 } /* Generic.Deleted */ 374 | .ge { font-style: italic } /* Generic.Emph */ 375 | .gr { color: #FF0000 } /* Generic.Error */ 376 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 377 | .gi { color: #00A000 } /* Generic.Inserted */ 378 | .go { color: #808080 } /* Generic.Output */ 379 | .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 380 | .gs { font-weight: bold } /* Generic.Strong */ 381 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 382 | .gt { color: #0040D0 } /* Generic.Traceback */ 383 | .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 384 | .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 385 | .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 386 | .kp { color: #008000 } /* Keyword.Pseudo */ 387 | .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 388 | .kt { color: #B00040 } /* Keyword.Type */ 389 | .m { color: #666666 } /* Literal.Number */ 390 | .s { color: #BA2121 } /* Literal.String */ 391 | .na { color: #7D9029 } /* Name.Attribute */ 392 | .nb { color: #008000 } /* Name.Builtin */ 393 | .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 394 | .no { color: #880000 } /* Name.Constant */ 395 | .nd { color: #AA22FF } /* Name.Decorator */ 396 | .ni { color: #999999; font-weight: bold } /* Name.Entity */ 397 | .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 398 | .nf { color: #0000FF } /* Name.Function */ 399 | .nl { color: #A0A000 } /* Name.Label */ 400 | .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 401 | .nt { color: #008000; font-weight: bold } /* Name.Tag */ 402 | .nv { color: #19177C } /* Name.Variable */ 403 | .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 404 | .w { color: #bbbbbb } /* Text.Whitespace */ 405 | .mf { color: #666666 } /* Literal.Number.Float */ 406 | .mh { color: #666666 } /* Literal.Number.Hex */ 407 | .mi { color: #666666 } /* Literal.Number.Integer */ 408 | .mo { color: #666666 } /* Literal.Number.Oct */ 409 | .sb { color: #BA2121 } /* Literal.String.Backtick */ 410 | .sc { color: #BA2121 } /* Literal.String.Char */ 411 | .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 412 | .s2 { color: #BA2121 } /* Literal.String.Double */ 413 | .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 414 | .sh { color: #BA2121 } /* Literal.String.Heredoc */ 415 | .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 416 | .sx { color: #008000 } /* Literal.String.Other */ 417 | .sr { color: #BB6688 } /* Literal.String.Regex */ 418 | .s1 { color: #BA2121 } /* Literal.String.Single */ 419 | .ss { color: #19177C } /* Literal.String.Symbol */ 420 | .bp { color: #008000 } /* Name.Builtin.Pseudo */ 421 | .vc { color: #19177C } /* Name.Variable.Class */ 422 | .vg { color: #19177C } /* Name.Variable.Global */ 423 | .vi { color: #19177C } /* Name.Variable.Instance */ 424 | .il { color: #666666 } /* Literal.Number.Integer.Long */ 425 | 426 | /* Presenter Mode */ 427 | 428 | body.presenter_view div.slide { 429 | display: inline; 430 | position: absolute; 431 | overflow: hidden; 432 | -moz-transform: scale(.5, .5); 433 | -moz-transform-origin: 0 0; 434 | -webkit-transform: scale(.5, .5); 435 | -webkit-transform-origin: 0 0; 436 | -o-transform: scale(.5, .5); 437 | -o-transform-origin: 0 0; 438 | margin-top: -300px; 439 | } 440 | 441 | body.presenter_view .slide.far-past { 442 | display: block; 443 | margin-left: -1500px; 444 | } 445 | 446 | body.presenter_view .slide.past { 447 | display: block; 448 | margin-left: -975px; 449 | } 450 | 451 | body.presenter_view .slide.current { 452 | display: block; 453 | margin-left: -475px; 454 | border: 8px solid maroon; 455 | z-index: 2; 456 | } 457 | 458 | body.presenter_view .slide.future { 459 | display: block; 460 | margin-left: 25px; 461 | z-index: 1; 462 | } 463 | 464 | body.presenter_view .slide.far-future { 465 | display: block; 466 | margin-left: 525px; 467 | } 468 | 469 | body.presenter_view div#current_presenter_notes { 470 | visibility: visible; 471 | display: block; 472 | position: absolute; 473 | overflow: auto; 474 | vertical-align: middle; 475 | left: 50%; 476 | top: 50%; 477 | margin-left: -475px; 478 | margin-top: 100px; 479 | z-index: 2; 480 | width: 950px; 481 | border-style: solid; 482 | height: 30%; 483 | background-color: silver; 484 | } 485 | 486 | body.presenter_view div#current_presenter_notes section { 487 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 488 | color: black; 489 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 490 | display: block; 491 | overflow: visible; 492 | position: relative; 493 | background-color: #fffeff; 494 | height: 120px; 495 | margin-right: 30px; 496 | margin-top: 60px; 497 | margin-left: 30px; 498 | padding-right: 10px; 499 | padding-left: 10px; 500 | padding-top: 10px; 501 | } 502 | 503 | body.presenter_view div#current_presenter_notes section p { 504 | margin: 0; 505 | } 506 | 507 | body.presenter_view div#current_presenter_notes h1 { 508 | font-size: 50%; 509 | display: block; 510 | } 511 | 512 | div#current_presenter_notes { 513 | display: none; 514 | } 515 | 516 | div.slide div.presenter_notes { 517 | display: none; 518 | } 519 | 520 | #blank { 521 | position: absolute; 522 | top: 0; 523 | left: 0; 524 | background-color: black; 525 | width: 100%; 526 | height: 100%; 527 | z-index: 64; 528 | display: none; 529 | } 530 | -------------------------------------------------------------------------------- /src/landslide/themes/tango/css/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/landslide/bdf1807e1fce403d48ecfb6cc1d6c7f06c003392/src/landslide/themes/tango/css/background.png -------------------------------------------------------------------------------- /src/landslide/themes/tango/css/screen.css: -------------------------------------------------------------------------------- 1 | h1, h2, h3, h4, h5, h6, p, ul, li { 2 | font-family: sans-serif !important; 3 | } 4 | 5 | p, ul, li { 6 | color: #555753 !important; 7 | font-size: 120% !important; 8 | } 9 | 10 | header h1, header h2, header h3, header h4, header h5, header h6 { 11 | color: #204a87 !important; 12 | font-weight: bold !important; 13 | } 14 | 15 | section h1, section h2, section h3, section h4, section h5, section h6 { 16 | color: #3465a4 !important; 17 | } 18 | 19 | img { display: block; margin: auto; } 20 | 21 | body { 22 | font: 14px "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 23 | background: #555753; 24 | padding: 0; 25 | margin: 0; 26 | overflow: hidden; 27 | } 28 | 29 | div.presentation { 30 | position: absolute; 31 | width: 100%; 32 | display: table-cell; 33 | vertical-align: middle; 34 | height: 100%; 35 | background: -webkit-gradient(linear, left bottom, left top, from(#2e3436), to(#555753)); 36 | background-color: #555753; 37 | background: -moz-linear-gradient(bottom, #555753, #2e3436); 38 | } 39 | 40 | div.slides, body.expose div.slides.nocontext { 41 | width: 100%; 42 | height: 100%; 43 | left: 0; 44 | top: 0; 45 | position: absolute; 46 | display: block; 47 | } 48 | 49 | div.slides.nocontext { 50 | width: 900px; 51 | margin: 0 auto; 52 | overflow: hidden; 53 | position: relative; 54 | left: auto; 55 | top: auto; 56 | } 57 | 58 | div.slide { 59 | background: url('background.png') bottom center no-repeat; 60 | display: none; 61 | position: absolute; 62 | overflow: hidden; 63 | width: 900px; 64 | height: 700px; 65 | left: 50%; 66 | top: 50%; 67 | margin-top: -350px; 68 | background-color: #fff; 69 | -webkit-transition: margin 0.25s ease-in-out; 70 | -moz-transition: margin 0.25s ease-in-out; 71 | -o-transition: margin 0.25s ease-in-out; 72 | border-top-left-radius: 20px; 73 | -moz-border-radius-topleft: 20px; 74 | -webkit-border-top-left-radius: 20px; 75 | border-top-right-radius: 20px; 76 | -moz-border-radius-topright: 20px; 77 | -webkit-border-top-right-radius: 20px; 78 | border-bottom-right-radius: 20px; 79 | -moz-border-radius-bottomright: 20px; 80 | -webkit-border-bottom-right-radius: 20px; 81 | border-bottom-left-radius: 20px; 82 | -moz-border-radius-bottomleft: 20px; 83 | -webkit-border-bottom-left-radius: 20px; 84 | } 85 | 86 | div.slide p { 87 | font-size: 20px; 88 | } 89 | 90 | .slide.far-past { 91 | display: block; 92 | margin-left: -2400px; 93 | } 94 | 95 | .slide.past { 96 | display: block; 97 | margin-left: -1400px; 98 | } 99 | 100 | .slide.current { 101 | display: block; 102 | margin-left: -450px; 103 | } 104 | 105 | .slide.future { 106 | display: block; 107 | margin-left: 500px; 108 | } 109 | 110 | .slide.far-future { 111 | display: block; 112 | margin-left: 1500px; 113 | } 114 | 115 | body.three-d div.slides { 116 | -webkit-transform: translateX(50px) scale(0.8) rotateY(10deg); 117 | -moz-transform: translateX(50px) scale(0.8) rotateY(10deg); 118 | -o-transform: translateX(50px) scale(0.8) rotateY(10deg); 119 | } 120 | 121 | 122 | /* Content */ 123 | 124 | header:not(:only-child) { 125 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 126 | font-weight: normal; 127 | font-size: 50px; 128 | letter-spacing: -.05em; 129 | color: white; 130 | color: black; 131 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 132 | position: absolute; 133 | left: 30px; 134 | top: 25px; 135 | margin: 0; 136 | padding: 0; 137 | } 138 | 139 | header h1, header h2, header h3, header h4, header h5, header h6 { 140 | display: inline; 141 | font-size: 100%; 142 | font-weight: normal; 143 | padding: 0; 144 | margin: 0; 145 | } 146 | 147 | header h2:first-child { 148 | margin-top: 0; 149 | } 150 | 151 | section, .slide header:only-child h1 { 152 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 153 | color: #3f3f3f; 154 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 155 | margin-left: 30px; 156 | margin-right: 30px; 157 | margin-top: 100px; 158 | display: block; 159 | overflow: hidden; 160 | } 161 | 162 | section img.align-center { 163 | display: block; 164 | margin-left: auto; 165 | margin-right: auto; 166 | } 167 | 168 | section img.align-right { 169 | display: block; 170 | margin-left: auto; 171 | margin-right: 0; 172 | } 173 | 174 | section img.align-left { 175 | display: block; 176 | margin-right: auto; 177 | margin-left: 0; 178 | } 179 | 180 | a { 181 | color: inherit; 182 | display: inline-block; 183 | text-decoration: none; 184 | line-height: 110%; 185 | border-bottom: 2px solid #3f3f3f; 186 | } 187 | 188 | pre { 189 | font-size: 16px; 190 | font-family: Monaco, Courier, monospace; 191 | } 192 | 193 | li { 194 | padding: 10px 0; 195 | font-size: 20px; 196 | } 197 | 198 | .slide header:only-child h1 { 199 | line-height: 180%; 200 | text-align: center; 201 | display: table-cell; 202 | vertical-align: middle; 203 | height: 700px; 204 | width: 900px; 205 | font-size: 50px; 206 | margin-top:100px; 207 | margin-bottom:100px; 208 | } 209 | 210 | .sidebar { 211 | background: -webkit-gradient(linear, top right, bottom right, from(#dde), to(#fff)); 212 | -webkit-transition: margin 0.25s ease-in-out; 213 | background-color: #eee; 214 | background: -moz-linear-gradient(right, #dde, #fff); 215 | border-right: 5px solid #ccd; 216 | z-index: 9999999; 217 | height: 100%; 218 | overflow: hidden; 219 | top: 0; 220 | position: absolute; 221 | display: block; 222 | margin: 0; 223 | margin-left: -400px; 224 | padding: 10px 16px; 225 | overflow: auto; 226 | -webkit-transition: margin 0.2s ease-in-out; 227 | -moz-transition: margin 0.2s ease-in-out; 228 | -o-transition: margin 0.2s ease-in-out; 229 | } 230 | 231 | .sidebar h2 { 232 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 233 | margin: 0 0 16px; 234 | padding: 0; 235 | } 236 | 237 | .sidebar table { 238 | width: 100%; 239 | margin: 0; 240 | padding: 0; 241 | border-collapse: collapse; 242 | } 243 | 244 | .sidebar table caption { 245 | display: none; 246 | } 247 | 248 | .sidebar tr { 249 | margin: 2px 0; 250 | border-bottom: 1px solid #ccc; 251 | } 252 | 253 | .sidebar th { 254 | text-align: left; 255 | font-weight: normal; 256 | max-width: 300px; 257 | overflow: hidden; 258 | } 259 | 260 | .sidebar tr.sub th { 261 | text-indent: 20px; 262 | } 263 | 264 | .sidebar td { 265 | text-align: right; 266 | min-width: 20px; 267 | } 268 | 269 | .sidebar a { 270 | display: block; 271 | text-decoration: none; 272 | border-bottom: none; 273 | padding: 4px 0; 274 | } 275 | 276 | .sidebar tr.active { 277 | background: #ff0; 278 | } 279 | 280 | aside { 281 | display: none; 282 | } 283 | aside.source { 284 | position: absolute; 285 | bottom: 6px; 286 | left: 10px; 287 | text-indent: 10px; 288 | } 289 | aside.page_number { 290 | position: absolute; 291 | bottom: 6px; 292 | right: 10px; 293 | text-indent: 10px; 294 | } 295 | 296 | .notes { 297 | display: none; 298 | padding: 10px; 299 | background: #ccc; 300 | border-radius: 10px; 301 | -moz-border-radius: 10px; 302 | -webkit-border-radius: 10px; 303 | } 304 | div.slide p.notes { 305 | font-size: 90%; 306 | } 307 | 308 | /* Expose */ 309 | 310 | body.expose div.slides { 311 | float: left; 312 | position: relative; 313 | overflow: auto; 314 | margin-bottom: 10px; 315 | } 316 | 317 | body.expose div.slide { 318 | display: block; 319 | float: left; 320 | position: relative; 321 | left: auto !important; 322 | top: auto !important; 323 | margin: 10px !important; 324 | -webkit-transition: none; 325 | -moz-transition: none; 326 | -o-transition: none; 327 | -moz-transform: scale(.33, .33); 328 | -moz-transform-origin: 0 0; 329 | -webkit-transform: scale(.33, .33); 330 | -webkit-transform-origin: 0 0; 331 | -o-transform: scale(.33, .33); 332 | -o-transform-origin: 0 0; 333 | cursor: pointer; 334 | } 335 | 336 | body.expose div.slide:hover { 337 | background: -webkit-gradient(linear, left bottom, left top, from(#bdd), to(#fff)); 338 | background-color: #eee; 339 | background: -moz-linear-gradient(bottom, #bdd, #fff); 340 | } 341 | 342 | body.expose .slide-wrapper { 343 | float: left; 344 | position: relative; 345 | margin: .5%; 346 | width: 300px; 347 | height: 233px; 348 | } 349 | 350 | body.expose .slide footer { 351 | } 352 | 353 | body.expose .slide .inner { 354 | } 355 | 356 | body.expose .slide.far-past, 357 | body.expose .slide.past, 358 | body.expose .slide.future, 359 | body.expose .slide.far-future { 360 | margin-left: 0; 361 | } 362 | 363 | body.expose .slide.current { 364 | background: -webkit-gradient(linear, left bottom, left top, from(#ddb), to(#fff)); 365 | background-color: #eee; 366 | background: -moz-linear-gradient(bottom, #ddb, #fff); 367 | border: 16px solid #fff; 368 | -moz-transform: scale(.315, .315); 369 | -moz-transform-origin: 0 0; 370 | -webkit-transform: scale(.315, .315); 371 | -webkit-transform-origin: 0 0; 372 | -o-transform: scale(.315, .315); 373 | -o-transform-origin: 0 0; 374 | } 375 | 376 | /* Pygments default theme */ 377 | .hll { background-color: #ffffcc } 378 | .c { color: #408080; font-style: italic } /* Comment */ 379 | .err { border: 1px solid #FF0000 } /* Error */ 380 | .k { color: #008000; font-weight: bold } /* Keyword */ 381 | .o { color: #666666 } /* Operator */ 382 | .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 383 | .cp { color: #BC7A00 } /* Comment.Preproc */ 384 | .c1 { color: #408080; font-style: italic } /* Comment.Single */ 385 | .cs { color: #408080; font-style: italic } /* Comment.Special */ 386 | .gd { color: #A00000 } /* Generic.Deleted */ 387 | .ge { font-style: italic } /* Generic.Emph */ 388 | .gr { color: #FF0000 } /* Generic.Error */ 389 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 390 | .gi { color: #00A000 } /* Generic.Inserted */ 391 | .go { color: #808080 } /* Generic.Output */ 392 | .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 393 | .gs { font-weight: bold } /* Generic.Strong */ 394 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 395 | .gt { color: #0040D0 } /* Generic.Traceback */ 396 | .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 397 | .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 398 | .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 399 | .kp { color: #008000 } /* Keyword.Pseudo */ 400 | .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 401 | .kt { color: #B00040 } /* Keyword.Type */ 402 | .m { color: #666666 } /* Literal.Number */ 403 | .s { color: #BA2121 } /* Literal.String */ 404 | .na { color: #7D9029 } /* Name.Attribute */ 405 | .nb { color: #008000 } /* Name.Builtin */ 406 | .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 407 | .no { color: #880000 } /* Name.Constant */ 408 | .nd { color: #AA22FF } /* Name.Decorator */ 409 | .ni { color: #999999; font-weight: bold } /* Name.Entity */ 410 | .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 411 | .nf { color: #0000FF } /* Name.Function */ 412 | .nl { color: #A0A000 } /* Name.Label */ 413 | .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 414 | .nt { color: #008000; font-weight: bold } /* Name.Tag */ 415 | .nv { color: #19177C } /* Name.Variable */ 416 | .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 417 | .w { color: #bbbbbb } /* Text.Whitespace */ 418 | .mf { color: #666666 } /* Literal.Number.Float */ 419 | .mh { color: #666666 } /* Literal.Number.Hex */ 420 | .mi { color: #666666 } /* Literal.Number.Integer */ 421 | .mo { color: #666666 } /* Literal.Number.Oct */ 422 | .sb { color: #BA2121 } /* Literal.String.Backtick */ 423 | .sc { color: #BA2121 } /* Literal.String.Char */ 424 | .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 425 | .s2 { color: #BA2121 } /* Literal.String.Double */ 426 | .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 427 | .sh { color: #BA2121 } /* Literal.String.Heredoc */ 428 | .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 429 | .sx { color: #008000 } /* Literal.String.Other */ 430 | .sr { color: #BB6688 } /* Literal.String.Regex */ 431 | .s1 { color: #BA2121 } /* Literal.String.Single */ 432 | .ss { color: #19177C } /* Literal.String.Symbol */ 433 | .bp { color: #008000 } /* Name.Builtin.Pseudo */ 434 | .vc { color: #19177C } /* Name.Variable.Class */ 435 | .vg { color: #19177C } /* Name.Variable.Global */ 436 | .vi { color: #19177C } /* Name.Variable.Instance */ 437 | .il { color: #666666 } /* Literal.Number.Integer.Long */ 438 | 439 | /* Presenter Mode */ 440 | 441 | body.presenter_view div.slide { 442 | display: inline; 443 | position: absolute; 444 | overflow: hidden; 445 | -moz-transform: scale(.5, .5); 446 | -moz-transform-origin: 0 0; 447 | -webkit-transform: scale(.5, .5); 448 | -webkit-transform-origin: 0 0; 449 | -o-transform: scale(.5, .5); 450 | -o-transform-origin: 0 0; 451 | margin-top: -300px; 452 | } 453 | 454 | body.presenter_view .slide.far-past { 455 | display: block; 456 | margin-left: -1500px; 457 | } 458 | 459 | body.presenter_view .slide.past { 460 | display: block; 461 | margin-left: -975px; 462 | } 463 | 464 | body.presenter_view .slide.current { 465 | display: block; 466 | margin-left: -475px; 467 | border: 8px solid maroon; 468 | z-index: 2; 469 | } 470 | 471 | body.presenter_view .slide.future { 472 | display: block; 473 | margin-left: 25px; 474 | z-index: 1; 475 | } 476 | 477 | body.presenter_view .slide.far-future { 478 | display: block; 479 | margin-left: 525px; 480 | } 481 | 482 | body.presenter_view div#current_presenter_notes { 483 | visibility: visible; 484 | display: block; 485 | position: absolute; 486 | overflow: auto; 487 | vertical-align: middle; 488 | left: 50%; 489 | top: 50%; 490 | margin-left: -475px; 491 | margin-top: 100px; 492 | z-index: 2; 493 | width: 950px; 494 | border-style: solid; 495 | height: 30%; 496 | background-color: silver; 497 | } 498 | 499 | body.presenter_view div#current_presenter_notes section { 500 | font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; 501 | color: black; 502 | text-shadow: rgba(0, 0, 0, 0.2) 0 2px 5px; 503 | display: block; 504 | overflow: visible; 505 | position: relative; 506 | background-color: #fffeff; 507 | height: 120px; 508 | margin-right: 30px; 509 | margin-top: 60px; 510 | margin-left: 30px; 511 | padding-right: 10px; 512 | padding-left: 10px; 513 | padding-top: 10px; 514 | } 515 | 516 | body.presenter_view div#current_presenter_notes section p { 517 | margin: 0; 518 | } 519 | 520 | body.presenter_view div#current_presenter_notes h1 { 521 | font-size: 50%; 522 | display: block; 523 | } 524 | 525 | div#current_presenter_notes { 526 | display: none; 527 | } 528 | 529 | div.slide div.presenter_notes { 530 | display: none; 531 | } 532 | 533 | #blank { 534 | position: absolute; 535 | top: 0; 536 | left: 0; 537 | background-color: black; 538 | width: 100%; 539 | height: 100%; 540 | z-index: 64; 541 | display: none; 542 | } 543 | -------------------------------------------------------------------------------- /src/landslide/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2010 Adam Zapletal 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import base64 19 | import mimetypes 20 | 21 | def get_abs_path_url(path): 22 | """ Returns the absolute url for a given local path. 23 | """ 24 | return "file://%s" % os.path.abspath(path) 25 | 26 | 27 | def get_path_url(abs_path, relative=False): 28 | """ Returns an absolute or relative path url from an absolute path. 29 | """ 30 | if relative: 31 | return get_rel_path_url(abs_path) 32 | else: 33 | return get_abs_path_url(abs_path) 34 | 35 | 36 | def get_rel_path_url(path, base_path=os.getcwd()): 37 | """ Returns a relative path from the absolute one passed as argument. 38 | Silently returns originally provided path on failure. 39 | """ 40 | try: 41 | path_url = path.split(base_path)[1] 42 | if path_url.startswith('/'): 43 | return path_url[1:] 44 | else: 45 | return path_url 46 | except (IndexError, TypeError): 47 | return path 48 | 49 | def encode_image_from_url(url, source_path): 50 | if not url or url.startswith('data:') or url.startswith('file://'): 51 | return False 52 | 53 | if (url.startswith('http://') or url.startswith('https://')): 54 | return False 55 | 56 | real_path = url if os.path.isabs(url) else os.path.join(source_path, url) 57 | 58 | if not os.path.exists(real_path): 59 | print '%s was not found, skipping' % url 60 | return False 61 | 62 | mime_type, encoding = mimetypes.guess_type(real_path) 63 | 64 | if not mime_type: 65 | print 'Unrecognized mime type for %s, skipping' % url 66 | return False 67 | 68 | try: 69 | image_contents = open(real_path).read() 70 | encoded_image = base64.b64encode(image_contents) 71 | except IOError: 72 | return False 73 | except Exception: 74 | return False 75 | 76 | return u"data:%s;base64,%s" % (mime_type, encoded_image) 77 | --------------------------------------------------------------------------------