Browse files

first commit

  • Loading branch information...
0 parents commit 8fb4d0fd1456ebd1ea89e32a84898bb66bf5da78 @raul committed Jul 9, 2011
10 Changelog.md
@@ -0,0 +1,10 @@
+# rack-fonts changelog
+
+## v0.1
+
+First release, supporting:
+- Static files with Rack::Static
+- Webfont files (ttf, otf, woff, eot, svg) setting their Content-Type by extension
+- Customizable cache expiration
+- Access-Control-Allow-Origin set to *, to make Firefox (and perhaps other browsers) happy.
+
3 Gemfile
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gemspec
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Raul Murciano raul@murciano.net
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 README.md
@@ -0,0 +1,44 @@
+# rack-fonts
+
+Rack::Static wrapper to give more love to web fonts.
+
+# Usage
+
+This middleware merely wraps the Rack::Static one so it admits the same options:
+
+ :root => "public"
+
+looks for all the assets under the `public` subdirectory of the current directory
+
+ :urls => ["/assets"]
+
+intercepts the requests beginning with `/media` and tries to serve static files
+from the directory specified by the `root` option.
+(Note that you can indicate more that one URL in the `urls` option)
+
+So, for instance:
+
+ use Rack::Fonts :root => 'public', :urls => ['/assets']
+
+will respond to a request path `/assets/images/nelson.gif`
+with the file located in `./public/images/nelson.gif`
+
+In addition, `Rack::Fonts` supports a `:cache_time` option to easily indicate
+a cache expiration for your assets.
+
+Apart from that, it will return appropiate `Content-Type` for webfonts and will
+add set the `Access-Control-Allow-Origin` header to `*`, so strict browsers like
+Firefox will be able to use these assets from different sites.
+
+See the `examples/config.ru` file to see an example on how to use it.
+
+## Licenses
+
+The test/assets directory includes the Forum font in several formats,
+see the Forum-Font-License-OFL.txt file to see the font license.
+
+See the LICENSE file included in the distribution for the rest of the code.
+
+## Copyright
+
+Copyright (C) 2011 Raul Murciano <raul@murciano.net>.
11 RakeFile
@@ -0,0 +1,11 @@
+require 'rake'
+require 'rake/testtask'
+
+task :default => [:test]
+
+desc 'Run tests'
+Rake::TestTask.new('test') do |t|
+ t.pattern = 'test/*_test.rb'
+ t.verbose = true
+ t.warning = true
+end
47 examples/config.ru
@@ -0,0 +1,47 @@
+# This is a sample rack app to test Rack::Fonts, which also serves
+# as an example on how to run it.
+#
+# Try to run it from two different hosts: remember to point the @@fonts_host variable
+# to one of them and check if the other host can consume the webfonts from it.
+
+require 'rubygems'
+require 'rack-fonts'
+
+class ExampleApp
+
+ @@fonts_host = 'http://localhost:3000' # IMPORTANT: set this to one of the hosts running your app
+
+ @@html = <<-HTML
+<!doctype html>
+<html lang=en>
+ <head>
+ <title>rack-fonts sample page</title>
+ <meta charset=utf-8>
+ <style type="text/css" media="screen">
+ @font-face {
+ font-family: "custom";
+ src: url("#{@@fonts_host}/assets/Forum.ttf");
+ }
+ body { font-family: "custom", courier; margin-top: 20%; font-size: 50px; text-align: center; }
+ </style>
+</head>
+<body>
+ <p>If this text is using courier font, the webfont is not loaded</p>
+</body>
+</html>
+ HTML
+
+ def call(env)
+ if @@fonts_host.empty?
+ [500, {'Content-Type' => 'text/html'}, ["Please set the @@fonts_host variable"] ]
+ else
+ [200, {'Content-Type' => 'text/html'}, [@@html]]
+ end
+ end
+
+end
+
+assets_path = File.join(File.dirname(__FILE__), '..', 'test')
+
+use Rack::Fonts, :urls => ['/assets'], :root => assets_path
+run ExampleApp.new
72 lib/rack-fonts.rb
@@ -0,0 +1,72 @@
+require 'rack'
+
+module Rack
+
+ # This middleware merely wraps the Rack::Static one so it admits the same options:
+ #
+ # :root => "public"
+ # looks for all the assets under the `public` subdirectory of the current directory
+ #
+ # :urls => ["/assets"]
+ # intercepts the requests beginning with `/media` and tries to serve static files
+ # from the directory specified by the `root` option.
+ # (Note that you can indicate more that one URL in the `urls` option)
+ #
+ # So, for instance:
+ #
+ # use Rack::Assets :root => 'public', :urls => ['/assets']
+ #
+ # will respond to a request path `/assets/images/nelson.gif`
+ # with the file located in `./public/images/nelson.gif`
+ #
+ # In addition, `Rack::Assets` supports a `:cache_time` option to easily indicate
+ # a cache expiration for your assets.
+ #
+ # Apart from that, it will return appropiate `Content-Type` for webfonts and will
+ # add set the `Access-Control-Allow-Origin` header to `*`, so strict browsers like
+ # Firefox will be able to use these assets from different sites.
+
+ class Fonts
+
+ VERSION = '0.1'
+
+ def initialize(app, options = {})
+ @original_app = app
+ @urls = options[:urls] || ["/favicon.ico"]
+ @cache_time = options.delete(:cache_time) || 31536000 # 1 year in seconds
+ @statics_app = ::Rack::Static.new(@original_app, options)
+ end
+
+ def call(env)
+ static?(env) ? serve_static(env) : @original_app.call(env)
+ end
+
+ def static?(env)
+ @urls.any?{ |url| env["PATH_INFO"].index(url) == 0 }
+ end
+
+ def serve_static(env)
+ status, headers, body = @statics_app.call(env)
+
+ headers['Cache-Control'] = "public, max-age=#{@cache_time}"
+ headers['Expires'] = (Time.now + @cache_time).httpdate
+ headers['Access-Control-Allow-Origin'] = '*'
+
+ _, ext = env['PATH_INFO'].match(/\.(\w+)$/).to_a
+ (headers['Content-Type'] = content_type_for_extension(ext) || headers['Content-Type']) if ext
+ [status, headers, body]
+ end
+
+ def content_type_for_extension(ext)
+ {
+ :ttf => 'font/truetype',
+ :otf => 'font/opentype',
+ :woff => 'font/woff',
+ :eot => 'application/vnd.ms-fontobject',
+ :svg => 'image/svg+xml'
+ }[ext.to_sym]
+ end
+
+ end
+
+end
94 test/assets/Forum-Font-License-OFL.txt
@@ -0,0 +1,94 @@
+Copyright (c) 2011, Denis Masharov <denis.masharov@gmail.com>,
+with Reserved Font Name "Forum".
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
BIN test/assets/Forum.eot
Binary file not shown.
BIN test/assets/Forum.otf
Binary file not shown.
10,501 test/assets/Forum.svg
10,501 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
BIN test/assets/Forum.ttf
Binary file not shown.
BIN test/assets/Forum.woff
Binary file not shown.
4 test/assets/sample.css
@@ -0,0 +1,4 @@
+/* sample css asset */
+body {
+ color: green;
+}
2 test/assets/sample.js
@@ -0,0 +1,2 @@
+// sample js file
+alert('all green!');
146 test/rack-fonts_test.rb
@@ -0,0 +1,146 @@
+require './test/test_helper'
+require 'rack/mock'
+
+class RackFontsTest < MiniTest::Unit::TestCase
+
+ # test original app responds unaltered
+
+ def test_the_original_app_responds_unaltered_headers
+ @res = request.get('/')
+ assert_status 200
+ assert_equal 2, @res.headers.keys.size
+ assert_h 'Content-Type', 'text/html'
+ assert_h 'Content-Length', '11'
+ end
+
+ def test_original_app_responds_unaltered_content
+ @res = request.get('/')
+ assert_equal 'Hello World', @res.body
+ end
+
+ # testing content types
+
+ def test_css_file
+ @res = request.get('/assets/sample.css')
+ assert_status 200
+ assert_h 'Content-Type', 'text/css'
+ assert_equal asset_file_content('sample.css'), @res.body
+ end
+
+ def test_js_file
+ @res = request.get('/assets/sample.js')
+ assert_status 200
+ assert_h 'Content-Type', 'application/javascript'
+ assert_equal asset_file_content('sample.js'), @res.body
+ end
+
+ def test_ttf_file
+ @res = request.get('/assets/Forum.ttf')
+ assert_status 200
+ assert_h 'Content-Type', 'font/truetype'
+ assert_equal asset_file_content('Forum.ttf'), @res.body
+ end
+
+ def test_otf_file
+ @res = request.get('/assets/Forum.otf')
+ assert_status 200
+ assert_h 'Content-Type', 'font/opentype'
+ assert_equal asset_file_content('Forum.otf'), @res.body
+ end
+
+ def test_woff_file
+ @res = request.get('/assets/Forum.woff')
+ assert_status 200
+ assert_h 'Content-Type', 'font/woff'
+ assert_equal asset_file_content('Forum.woff'), @res.body
+ end
+
+ def test_eot_file
+ @res = request.get('/assets/Forum.eot')
+ assert_status 200
+ assert_h 'Content-Type', 'application/vnd.ms-fontobject'
+ assert_equal asset_file_content('Forum.eot'), @res.body
+ end
+
+ def test_svg_file
+ @res = request.get('/assets/Forum.svg')
+ assert_status 200
+ assert_h 'Content-Type', 'image/svg+xml'
+ assert_equal asset_file_content('Forum.svg'), @res.body
+ end
+
+ def test_wildcard_access_control_allow_origin
+ @res = request.get('/assets/Forum.svg')
+ assert_status 200
+ assert_h 'Access-Control-Allow-Origin', '*'
+ end
+
+ def test_cache_control_lasts_a_year
+ @res = request.get('/assets/Forum.svg')
+ assert_status 200
+ assert_h 'Cache-Control', 'public, max-age=31536000'
+ end
+
+ def test_expires_in_a_year
+ @res = request.get('/assets/Forum.svg')
+ assert_status 200
+ assert_h 'Expires', (Time.now + 31536000).httpdate
+ end
+
+ def test_allows_customizing_cache_time
+ custom_cache_time = 3600
+ web_app = lambda { |env| [ 200, {}, ["Hello World"] ] }
+ app = Rack::Fonts.new(web_app, :urls => %w[/], :root => assets_dir, :cache_time => custom_cache_time)
+ request = Rack::MockRequest.new(app)
+ res = request.get('/Forum.svg')
+
+ assert_equal 200, res.status
+ assert_equal((Time.now + custom_cache_time).httpdate, res.headers['Expires'])
+ assert_equal "public, max-age=#{custom_cache_time}", res.headers['Cache-Control']
+ end
+
+ # helpers
+
+ def assert_h(header, expected)
+ error_msg = "Response header #{header} returns #{@res[header]}, but should be #{expected}"
+ assert_equal expected, @res.headers[header], error_msg
+ end
+
+ def assert_status(expected)
+ assert_equal expected, @res.status
+ end
+
+ def inspect(obj)
+ require 'yaml'
+ puts obj.to_yaml
+ end
+
+ def asset_file_content(filename)
+ path = File.join(assets_dir, filename)
+ open(path, "rb") {|io| io.read }
+ end
+
+ def assets_dir
+ File.join tests_dir, 'assets'
+ end
+
+ def tests_dir
+ File.dirname(File.expand_path(__FILE__))
+ end
+
+ def request(options = {})
+ web_app = lambda do |env|
+ [
+ 200,
+ {
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => '11'
+ },
+ ["Hello World"]
+ ]
+ end
+ app = Rack::Fonts.new(web_app, {:urls => ['/assets'], :root => tests_dir})
+ request = Rack::MockRequest.new(app)
+ end
+
+end
3 test/test_helper.rb
@@ -0,0 +1,3 @@
+require 'rubygems'
+require 'minitest/autorun'
+require './lib/rack-fonts'

0 comments on commit 8fb4d0f

Please sign in to comment.