fontcustom-2.0.0/000077500000000000000000000000001312032244500137065ustar00rootroot00000000000000fontcustom-2.0.0/.gitignore000066400000000000000000000003621312032244500156770ustar00rootroot00000000000000*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp spec/fixtures/sandbox/* spec/fixtures/.fontcustom-manifest.json .DS_Store .ruby-version fontcustom-2.0.0/.travis.yml000066400000000000000000000021351312032244500160200ustar00rootroot00000000000000sudo: required language: ruby addons: apt: sources: - ubuntu-toolchain-r-test packages: - fontforge-nox - gcc-4.8 - g++-4.8 before_install: - wget http://people.mozilla.com/~jkew/woff/woff-code-latest.zip - unzip woff-code-latest.zip -d sfnt2woff && cd sfnt2woff && make && mkdir -p bin && mv sfnt2woff bin && cd .. - export PATH=$PATH:$PWD/sfnt2woff/bin/ - if [ $TRAVIS_OS_NAME == "linux" ]; then export CC="gcc-4.8"; export CXX="g++-4.8"; export LINK="gcc-4.8"; export LINKXX="g++-4.8"; fi - git clone --recursive https://github.com/google/woff2.git && cd woff2 && make clean all && sudo mv woff2_compress /usr/local/bin/ && sudo mv woff2_decompress /usr/local/bin/ - bundle rvm: - 2.2.2 - 2.1.1 - 2.0.0 - 1.9.3 gemfile: - Gemfile - gemfiles/Gemfile.listen_1 - gemfiles/Gemfile.listen_2 matrix: exclude: - gemfile: Gemfile rvm: 1.9.3 - gemfile: Gemfile rvm: 2.0.0 - gemfile: gemfiles/Gemfile.listen_2 rvm: 1.9.3 - gemfile: gemfiles/Gemfile.listen_2 rvm: 2.0.0 script: bundle exec rake fontcustom-2.0.0/CHANGELOG.md000066400000000000000000000227611312032244500155270ustar00rootroot00000000000000## 2.0.0 (6/14/2017) * Adds support for Woff2 ([#313](https://github.com/FontCustom/fontcustom/pull/313)) * Minimum ruby version bumped to 1.9.3 * Support listen 3 ([#283](https://github.com/FontCustom/fontcustom/pull/283)) * Support Python 3 ([#276](https://github.com/FontCustom/fontcustom/pull/276)) * Compatible with Windows ([#289](https://github.com/FontCustom/fontcustom/pull/289)) * Set glyph name when creating char in fontforge ([#286](https://github.com/FontCustom/fontcustom/pull/286)) * Allow specification of copyright information ([#287](https://github.com/FontCustom/fontcustom/pull/287)) * Enable CSS3 pseudo selectors '::' vs ':' ([#310](https://github.com/FontCustom/fontcustom/pull/310)) * Update installation instructions with zlib for linux machines ([#224](https://github.com/FontCustom/fontcustom/pull/224)) * Works with updated Travis CI configuration * Fix issue with relative paths in check_template_paths * Be more Unix-y and fail when there is an error ([#295](https://github.com/FontCustom/fontcustom/pull/295)) ## 1.3.4 (10/11/2014) * Updates rspec tests to be compatible with rspec v3.1.6 * Add additional metrics to make it easier to have different size icon fonts ([#175](https://github.com/FontCustom/fontcustom/pull/175)) * Add woff data uri to generated CSS + template helper ([#182](https://github.com/FontCustom/fontcustom/pull/182)) * Support listen v1 and v2 ([#191](https://github.com/FontCustom/fontcustom/pull/191)) * Add multiple classes to config file ([#174](https://github.com/FontCustom/fontcustom/issues/174)) * Don't strip "%" symbol (and other potentially valid characters) from CSS selector ([#173](https://github.com/FontCustom/fontcustom/issues/173)) * Fix bug where custom template path appears in output filenames ([#198](https://github.com/FontCustom/fontcustom/pull/198), [#172](https://github.com/FontCustom/fontcustom/issues/172)) * SCSS content variables like Font Awesome ([#151](https://github.com/FontCustom/fontcustom/issues/151)) * Running compile on a folder containing directories shouldn't throw an error ## 1.3.3 (2/20/2014) * Removes ttfautohint ([#160c](https://github.com/FontCustom/fontcustom/pull/160#issuecomment-34593191)) * Fixes rails-scss template helper ([#185](https://github.com/FontCustom/fontcustom/issues/185)) * Adds `text-rendering: optimizeLegibility` ([#181](https://github.com/FontCustom/fontcustom/pull/181)) ## 1.3.2 (1/31/2014) * Fixes `preprocessor_path` for Rails asset pipeline / Sprockets ([#162](https://github.com/FontCustom/fontcustom/pull/162), [#167](https://github.com/FontCustom/fontcustom/pull/167)) * Fixes bug where `preprocessor_path` was ignored by the scss template ([#171](https://github.com/FontCustom/fontcustom/issues/171)) * Fixes bug where relative output paths containing ".." would fail to compile ## 1.3.1 (12/28/2013) * Fixes syntax error in generate.py that affects Python 2.6 ## 1.3.0 (12/24/2013) **If upgrading from 1.2.0, delete your old `.fontcustom-manifest.json` and output directories first.** The big news: fixed glyph code points. Automatically assigned for now, but changing them by hand is just a matter of modifying the generated `.fontcustom-manifest.json`. A few breaking changes (`css_prefix`, custom template syntax, possibly others). * Adds fixed glyph code points ([#56](https://github.com/FontCustom/fontcustom/issues/56)) * Drops bootstrap templates (maintenance overhead, unsure if anyone was using them) * Stores relative paths for collaborative editing ([#149](https://github.com/FontCustom/fontcustom/pull/149)) * Changes `css_prefix` to `css_selector` to allow greater flexibility ([#126](https://github.com/FontCustom/fontcustom/pull/126)) * Skips compilation if inputs have not changed (and `force` option to bypass checks) * Adds CSS template helpers for convenience and DRYness * Improves rendering on Chrome Windows ([#143](https://github.com/FontCustom/fontcustom/pull/143)) * Improves Windows hinting ([#160](https://github.com/FontCustom/fontcustom/pull/160)) * Fixes Python 2.6 optsparse syntax ([#159](https://github.com/FontCustom/fontcustom/issues/159)) * Fixes bug where changes in custom templates were not detected by `watch` * Improves error and debuggging messages ## 1.2.0 (11/2/2013) * Preparation for fixed glyph code points. * Tweaks command line options (more semantic aliases) * Renames :data_cache to :manifest * Sets the stage for a more streamlined, predictable workflow * Drops EPS support (was buggy and unused) * Turns glyph width adjustment into an option (off by default) ([#137](https://github.com/FontCustom/fontcustom/pull/137)) * Relaxes all dependency version requirements ([#127](https://github.com/FontCustom/fontcustom/issues/127)) ## 1.1.1 (10/16/2013) * Preview characters are turned off by default in the preview template. * Relaxes JSON version requirement ([#125](https://github.com/FontCustom/fontcustom/pull/125)) * Fixes ttf hinting ([#124](https://github.com/FontCustom/fontcustom/pull/124)) * Cleans up README, fontcustom.yml template, .gitignore ([#123](https://github.com/FontCustom/fontcustom/pull/123), [#128](https://github.com/FontCustom/fontcustom/pull/128)) ## 1.1.0 (9/22/2013) More customizable interface for vastly improved workflow. * Specify where input vectors/templates are stored ([#89](https://github.com/FontCustom/fontcustom/issues/89)) * Specify where output fonts/templates are saved ([#89](https://github.com/FontCustom/fontcustom/issues/89)) * Stock templates are saved as `#{font_name}.css` instead of `_fontcustom.css` * More robust path handling (relative paths, customizable `project_root`) * User-friendly variables for usage in custom templates * Rails-friendly template * Enable HTML data-attributes usage ([#118](https://github.com/FontCustom/fontcustom/pull/118)) * Helper characters in preview ([#107](https://github.com/FontCustom/fontcustom/pull/107)) * More robust execution of fontforge command ([#114](https://github.com/FontCustom/fontcustom/pull/114)) * Allow captial letters in font names ([#92](https://github.com/FontCustom/fontcustom/issues/92)) * More helpful, colorful messages * More intuitive flags (`--verbose=false` => `--quiet`, `--file-hash=false` => `--no-hash`) * More intuitive version (`fontcustom version` => `fontcustom --version`) ([#115](https://github.com/FontCustom/fontcustom/issues/115)) ## 1.0.1 (7/21/2013) Various bugfixes. * Set glyph widths automatically ([#95](https://github.com/FontCustom/fontcustom/issues/95)) * Fixes Ruby 1.8.7 syntax error ([#94](https://github.com/FontCustom/fontcustom/issues/94)) * More robust fontforge error handling ([#99](https://github.com/FontCustom/fontcustom/issues/99)) ## 1.0.0 (4/18/2013) Big changes, more flexibility, better workflow. Be sure to check out the [docs](http://fontcustom.com) to see how it all ties together. * Improved preview html to show glyphs at various sizes * Added support for fontcustom.yml config file ([#49](https://github.com/FontCustom/fontcustom/issues/49)) * Added support for .fontcustom-data file ([#55](https://github.com/FontCustom/fontcustom/pull/55)) * Added support for custom templates ([#39](https://github.com/FontCustom/fontcustom/pull/39), [#48](https://github.com/FontCustom/fontcustom/issues/48)) * Added support for custom CSS selector namespaces ([#32](https://github.com/FontCustom/fontcustom/issues/32)) * Added support for --verbose=false ([#54](https://github.com/FontCustom/fontcustom/pull/54)) * Improved ascent/decent heights ([#33](https://github.com/FontCustom/fontcustom/issues/33)) * Added clean Ruby API ([#62](https://github.com/FontCustom/fontcustom/issues/62)) * Workaround for Sprockets compatibility ([#61](https://github.com/FontCustom/fontcustom/pull/61)) * Added clean (bootstrap free) CSS and made it the default choice ([#59](https://github.com/FontCustom/fontcustom/pull/59)) * Added option to pass different path to @font-face for SCSS partials ([#64](https://github.com/FontCustom/fontcustom/issues/64)) * Addes SCSS versions of Bootstrap and IE7 stylesheets * Fixed CSS bug on IE8 and IE9's compatibility mode * Fixed gem bug where watcher could fall into an infinite loop * Added error messages for faulty input * Refactored gem internals to use Thor more sanely * Refactored tests ## 0.1.4 (2/19/2013) * Instructions for stopping watcher ([#46](https://github.com/FontCustom/fontcustom/issues/46)) * Dev/contribution instructions ([#45](https://github.com/FontCustom/fontcustom/issues/45)) ## 0.1.3 (2/2/2013) * Add --debug CLI option, which shows fontforge output ([#37](https://github.com/FontCustom/fontcustom/issues/37)) * Patch for Illustrator CS6 SVG output ([#42](https://github.com/FontCustom/fontcustom/pull/42)) * Generate IE7 stylesheet ([#43](https://github.com/FontCustom/fontcustom/pull/43)) * Option to set custom font path for @font-face ([#43](https://github.com/FontCustom/fontcustom/pull/43)) * Option to generate test HTML file showing all glyphs ([#43](https://github.com/FontCustom/fontcustom/pull/43)) * Use eotlite.py instead of mkeot ([#43](https://github.com/FontCustom/fontcustom/pull/43)) ## 0.1.0 (12/2/2012) * Changed API to use Thor `class_option`s * Added option to change the name of the font and generated files ([#6](https://github.com/FontCustom/fontcustom/issues/6)) * Added option to disable the file name hash ([#13](https://github.com/FontCustom/fontcustom/issues/13)) * `fontcustom watch` compiles automatically on the first run * Better help messages ## 0.0.2 (11/26/2012) * Fixed gemspec dependency bug ([#2](https://github.com/FontCustom/fontcustom/pull/2)) * Fixed Windows Chrome PUA bug ([#1](https://github.com/FontCustom/fontcustom/issues/1)) fontcustom-2.0.0/CONTRIBUTING.md000066400000000000000000000027301312032244500161410ustar00rootroot00000000000000## Help make Font Custom better! This project was born out of an overheard conversation between two devs in a NYC coffee shop — it's come a long ways thanks to your support. Here's what's on the menu: * **Ruby on Rails integration** * **Compass integration** * Templates for LESS, stylus, etc. * Ligature support * Windows support * Make better use of Thor Just [file an issue](https://github.com/FontCustom/fontcustom/issues) if you have an idea or would like to claim one. ### Rules of Thumb If you catch a typo or a block of code that could be more elegant — please let us know. No such thing as too small of an improvement. * Spaces instead of tabs, please. * Develop in a topic branch. * Include passing tests if applicable. * Follow the [Github ruby styleguide](https://github.com/styleguide/ruby) as much as possible. ### Getting Started You'll need: * Fontforge with Python scripting (easiest via [Homebrew](http://brew.sh/) on Mac) * Ruby 1.9.3+ (via [rbenv](https://github.com/sstephenson/rbenv), [RVM](https://rvm.io/), etc.) * Rubygems * Bundler * Rake * Rspec Some helpful links: * http://createdbypete.com/articles/ruby-on-rails-development-with-mac-os-x-mountain-lion/ * http://guides.rubygems.org/make-your-own-gem/ --- That's all there is to it. Thanks again, and please don't hesitate to reach out: [Github Issues](https://github.com/FontCustom/fontcustom/issues)
[@kaizau](https://twitter.com/kaizau)
[@endtwist](https://twitter.com/endtwist) fontcustom-2.0.0/Gemfile000066400000000000000000000001371312032244500152020ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in fontcustom.gemspec gemspec fontcustom-2.0.0/LICENSES.txt000066400000000000000000000054321312032244500156600ustar00rootroot00000000000000fontcustom Copyright (c) 2013 Kai Zau, Joshua Gross MIT License 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. sfnt2woff Version: MPL 1.1/GPL 2.0/LGPL 2.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is WOFF font packaging code. The Initial Developer of the Original Code is Mozilla Corporation. Portions created by the Initial Developer are Copyright (C) 2009 the Initial Developer. All Rights Reserved. Contributor(s): Jonathan Kew Alternatively, the contents of this file may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the LGPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of either the GPL or the LGPL, and not to allow others to use your version of this file under the terms of the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL or the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the MPL, the GPL or the LGPL. fontcustom-2.0.0/README.md000066400000000000000000000162161312032244500151730ustar00rootroot00000000000000[![Gem Version](https://badge.fury.io/rb/fontcustom.png)](http://badge.fury.io/rb/fontcustom) [![Build Status](https://api.travis-ci.org/FontCustom/fontcustom.png)](https://travis-ci.org/FontCustom/fontcustom) [![Code Quality](https://codeclimate.com/github/FontCustom/fontcustom.png)](https://codeclimate.com/github/FontCustom/fontcustom) [![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=32953)](https://www.bountysource.com/trackers/32953-endtwist-fontcustom?utm_source=32953&utm_medium=shield&utm_campaign=TRACKER_BADGE) ## Font Custom **Icon fonts from the command line.** Generate cross-browser icon fonts and supporting files (@font-face CSS, etc.) from a collection of SVGs ([example](https://rawgit.com/FontCustom/fontcustom/master/spec/fixtures/example/example-preview.html)). [Changelog](https://github.com/FontCustom/fontcustom/blob/master/CHANGELOG.md)
[Bugs/Support](https://github.com/FontCustom/fontcustom/issues)
[Contribute!](https://github.com/FontCustom/fontcustom/blob/master/CONTRIBUTING.md) ### Installation Requires **Ruby 1.9.3+**, **WOFF2**, **FontForge** with Python scripting. ```sh # On Mac brew tap bramstein/webfonttools brew update brew install woff2 brew install fontforge --with-python brew install eot-utils gem install fontcustom # On Linux sudo apt-get install zlib1g-dev fontforge wget http://people.mozilla.com/~jkew/woff/woff-code-latest.zip unzip woff-code-latest.zip -d sfnt2woff && cd sfnt2woff && make && sudo mv sfnt2woff /usr/local/bin/ git clone --recursive https://github.com/google/woff2.git && cd woff2 && make clean all && sudo mv woff2_compress /usr/local/bin/ && sudo mv woff2_decompress /usr/local/bin/ gem install fontcustom ``` ####Note for windows: 1. Install fontforge: http://fontforge.github.io/en-US/downloads/windows/ - Install to a path without spaces, eg c:\FontForgeBuilds - At the end of the installer check the 'run fontforge' box. It finishes some set up. 2. Add the installation path to your System PATH variable (c:\FontForgeBuilds\bin) 3. Open up a new command prompt and test it. `fontforge -help` 4. gem install fontcustom ### Quick Start ```sh fontcustom compile my/vectors # Compiles icons into `fontcustom/` fontcustom watch my/vectors # Compiles when vectors are changed/added/removed fontcustom compile # Uses options from `./fontcustom.yml` or `config/fontcustom.yml` fontcustom config # Generate a blank a config file fontcustom help # See all options ``` ### Configuration To manage settings between compiles, run `fontcustom config` to generate a config file. Inside, you'll find a list of [**all possible options**](https://github.com/FontCustom/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.yml). Each option is also available as a dash-case command line flag (e.g. `--css-selector`) that overrides the config file. ### SVG Guidelines * All colors will be rendered identically. Watch out for white fills! * Use only solid colors. SVGs with transparency will be skipped. * For greater precision in curved icons, use fills instead strokes and [try these solutions](https://github.com/FontCustom/fontcustom/issues/85). * Activating `autowidth` trims horizontal white space from each glyph. This can be much easier than centering dozens of SVGs by hand. ### Advanced **For use with Compass and/or Rails** Set `templates` to include `scss-rails` to generate a SCSS partial with the compatible font-url() helper. You'll most likely also need to set `preprocessor_path` as the relative path from your compiled CSS to your output directory. **Example Use in Rails** Add `gem 'fontcustom'` to your gem file. ``` bundle ``` Create a `fontcustom.yml` file with something like this: ```yml # config/fontcustom.yml font_name: icons css_selector: .icon-{{glyph}} preprocessor_path: "" autowidth: false no_hash: true force: false debug: false quiet: false input: vectors: app/assets/icons output: fonts: app/assets/fonts css: app/assets/stylesheets templates: - scss ``` This tells the gem to take the vectors from `app/assets/icons` and create fonts and stylesheets for them. Create a file in lib/tasks called `icons.rake` : ```ruby namespace :icons do task :compile do puts "Compiling icons..." puts %x(fontcustom compile) end end ``` Load up the icons directory and test it out. Run this command with ```sh rake icons:compile ``` This should run the installed and configured gem to create your icons: ```sh Compiling icons... create .fontcustom-manifest.json create app/assets/fonts create app/assets/fonts/icons.ttf app/assets/fonts/icons.svg app/assets/fonts/icons.woff app/assets/fonts/icons.eot create app/assets/stylesheets/_icons.scss ``` Access these new icons by creating a tag with the class `icon-{{glyph}}` where the {{glyph}} is the name of the svg you put in the icon folder. For example, if you added a file called 'cars54' icon would look something like this: ```html ``` Now the font is adjustable to css 'font-size' and 'color'. **Save CSS and fonts to different locations** You can save generated fonts, CSS, and other files to different locations by using `fontcustom.yml`. Font Custom can also read input vectors and templates from different places. Just edit the `input` and `output` YAML hashes and their corresponding keys. **Tweak font settings** By default, Font Custom assumes a square viewBox, 512 by 512, and 16 pica points. Change `font_design_size`, `font_em`, `font_ascent`, `font_descent`, and `autowidth` to suit your own needs. **Generate LESS, Stylus, and other text files** Custom templates give you the flexibility to generate just about anything you want with Font Custom's output data. Any non-SVG file in your input directory (or input:templates directory if you set it in `fontcustom.yml`) will be available as a custom template to copy into the output directory after compilation. You just need to specify the file name under the `templates` hash. Any embedded ruby in the templates will be processed, along with the following helpers: * `font_name` * `font_face`: [FontSpring's Bulletproof @Font-Face Syntax](http://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax) * `glyph_selectors`: comma-separated list of all icon CSS selectors * `glyphs`: all selectors and their codepoint assignments (`.icon-example:before { content: "\f103"; }`) * `@options`: a hash of options used during compilation * `@manifest`: a hash of options, generated file paths, code points, and just about everything else Font Custom knows. * `@font_path`: the path from CSS to font files (without an extension) * `@font_path_alt`: if `preprocessor_path` was set, this is the modified path * `pseudo_element`: if `css3` was set to true, then it will print `::before`. Otherwise the PseudoElement will be `:before` `font_face` accepts a hash that modifies the CSS url() function and the path of the font files (`font_face(url: "font-url", path: @font_path_alt)`). --- [Licenses](https://github.com/FontCustom/fontcustom/blob/master/LICENSES.txt) Brought to you by [@endtwist](https://github.com/endtwist) and [@kaizau](https://github.com/kaizau) fontcustom-2.0.0/Rakefile000066400000000000000000000002621312032244500153530ustar00rootroot00000000000000require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new "spec" do |s| s.rspec_opts = "--color --format documentation" end task :default => :spec fontcustom-2.0.0/bin/000077500000000000000000000000001312032244500144565ustar00rootroot00000000000000fontcustom-2.0.0/bin/fontcustom000077500000000000000000000001131312032244500166000ustar00rootroot00000000000000#!/usr/bin/env ruby require 'fontcustom/cli' Fontcustom::CLI.start(ARGV) fontcustom-2.0.0/fontcustom.gemspec000066400000000000000000000024551312032244500174620ustar00rootroot00000000000000# -*- encoding: utf-8 -*- lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "fontcustom/version" Gem::Specification.new do |gem| gem.name = "fontcustom" gem.version = Fontcustom::VERSION gem.authors = ["Kai Zau", "Joshua Gross"] gem.email = ["kai@kaizau.com", "joshua@gross.is"] gem.summary = "Generate icon fonts from the command line." gem.description = "Font Custom makes using vector icons easy. Generate icon fonts and supporting templates (e.g. @font-face CSS) from a collection of SVGs." gem.homepage = "http://fontcustom.com" gem.post_install_message = ">> Thanks for installing Font Custom! Please ensure that fontforge is installed before compiling any icons. Visit for instructions." gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.add_dependency "json", "~>1.4" gem.add_dependency "thor", "~>0.14" gem.add_dependency "listen", ">=1.0","<4.0" gem.add_development_dependency "rake", "~> 10" gem.add_development_dependency "bundler" gem.add_development_dependency "rspec", "~>3.1.0" end fontcustom-2.0.0/gemfiles/000077500000000000000000000000001312032244500155015ustar00rootroot00000000000000fontcustom-2.0.0/gemfiles/Gemfile.listen_1000066400000000000000000000002041312032244500205050ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem"s dependencies in fontcustom.gemspec gemspec :path => ".." gem "listen", "~>1.0" fontcustom-2.0.0/gemfiles/Gemfile.listen_2000066400000000000000000000002041312032244500205060ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem"s dependencies in fontcustom.gemspec gemspec :path => ".." gem "listen", "~>2.0" fontcustom-2.0.0/lib/000077500000000000000000000000001312032244500144545ustar00rootroot00000000000000fontcustom-2.0.0/lib/fontcustom.rb000066400000000000000000000021671312032244500172100ustar00rootroot00000000000000require "fontcustom/version" require "fontcustom/error" require "fontcustom/utility" require "fontcustom/base" require "fontcustom/manifest" require "fontcustom/options" require "fontcustom/generator/font" require "fontcustom/generator/template" module Fontcustom def gem_lib File.expand_path(File.join(File.dirname(__FILE__), "fontcustom")) end module_function :gem_lib ## # Hack to get Thor to show more helpful defaults in `fontcustom help`. These # are overwritten in Fontcustom::Options. EXAMPLE_OPTIONS = { :output => "./FONT_NAME", :config => "./fontcustom.yml -or- ./config/fontcustom.yml", :templates => "css preview" } DEFAULT_OPTIONS = { :input => nil, :output => nil, :config => nil, :templates => %w|css preview|, :font_name => "fontcustom", :font_design_size => 16, :font_em => 512, :font_ascent => 448, :font_descent => 64, :css_selector => ".icon-{{glyph}}", :preprocessor_path => nil, :autowidth => false, :no_hash => false, :css3 => false, :debug => false, :force => false, :quiet => false, :copyright => '' } end fontcustom-2.0.0/lib/fontcustom/000077500000000000000000000000001312032244500166555ustar00rootroot00000000000000fontcustom-2.0.0/lib/fontcustom/base.rb000066400000000000000000000041761312032244500201240ustar00rootroot00000000000000require "digest/sha2" module Fontcustom class Base include Utility def initialize(raw_options) check_fontforge check_woff2 manifest = '.fontcustom-manifest.json' raw_options[:manifest] = manifest @options = Fontcustom::Options.new(raw_options).options @manifest = Fontcustom::Manifest.new(manifest, @options) end def compile current = checksum previous = @manifest.get(:checksum)[:previous] say_message :status, "Forcing compile." if @options[:force] if @options[:force] || current != previous @manifest.set :checksum, {:previous => previous, :current => current} start_generators @manifest.reload @manifest.set :checksum, {:previous => current, :current => current} else say_message :status, "No changes detected. Skipping compile." end end private def check_fontforge if !Gem.win_platform? fontforge = `which fontforge` else fontforge = `where fontforge` end if fontforge == "" || fontforge == "fontforge not found" raise Fontcustom::Error, "Please install fontforge first. Visit for instructions." end end def check_woff2 woff2 = `which woff2_compress` if woff2 == "" || woff2 == "woff2_compress not found" fail Fontcustom::Error, "Please install woff2 first. Visit for instructions." end end # Calculates a hash of vectors, options, and templates (content and filenames) def checksum files = Dir.glob(File.join(@options[:input][:vectors], "*.svg")).select { |fn| File.file?(fn) } files += Dir.glob(File.join(@options[:input][:templates], "*")).select { |fn| File.file?(fn) } content = files.map { |file| File.read(file) }.join content << files.join content << @options.flatten(2).join Digest::SHA2.hexdigest(content).to_s end def start_generators Fontcustom::Generator::Font.new(@manifest.manifest).generate Fontcustom::Generator::Template.new(@manifest.manifest).generate end end end fontcustom-2.0.0/lib/fontcustom/cli.rb000066400000000000000000000113311312032244500177500ustar00rootroot00000000000000require "thor" require "thor/actions" require "fontcustom" require "fontcustom/watcher" module Fontcustom class CLI < Thor include Utility default_task :show_help class_option :output, :aliases => "-o", :type => :string, :desc => "Where generated files are saved. Set different locations for different file types via a configuration file.", :default => EXAMPLE_OPTIONS[:output] class_option :config, :aliases => "-c", :type => :string, :desc => "Optional path to a configuration file.", :default => EXAMPLE_OPTIONS[:config] class_option :templates, :aliases => "-t", :type => :array, :desc => "Space-delinated list of files to generate alongside fonts. Use stock templates or choose your own.", :enum => %w|preview css scss scss-rails|, :default => EXAMPLE_OPTIONS[:templates] class_option :font_name, :aliases => %w|--name -n|, :type => :string, :desc => "The font's name. Also determines the file names of generated templates.", :default => DEFAULT_OPTIONS[:font_name] class_option :font_design_size, :aliases => %s|--size -s|, :type => :numeric, :desc => "Size (in pica points) for which this font is designed.", :default => DEFAULT_OPTIONS[:font_design_size] class_option :font_em, :aliases => %w|--em -e|, :type => :numeric, :desc => "The em size. Setting this will scale the entire font to the given size.", :default => DEFAULT_OPTIONS[:font_em] class_option :font_ascent, :aliases => %w|--ascent -a|, :type => :numeric, :desc => "The font's ascent. Used to calculate the baseline.", :default => DEFAULT_OPTIONS[:font_ascent] class_option :font_descent, :aliases => %w|--descent -d|, :type => :numeric, :desc => "The font's descent. Used to calculate the baseline.", :default => DEFAULT_OPTIONS[:font_descent] class_option :css_selector, :aliases => %w|--selector -S|, :type => :string, :desc => "Format of CSS selectors. \"{{glyph}}\" is substituted for the glyph name.", :default => DEFAULT_OPTIONS[:css_selector] class_option :preprocessor_path, :aliases => %w|--prepath -p|, :type => :string, :desc => "For Rails and Compass templates, set this as the relative path from your compiled CSS to your font output directory." class_option :autowidth, :aliases => "-A", :type => :boolean, :desc => "Horizontally fit glyphs to their individual vector widths." class_option :css3, :type => :boolean, :desc => "Use CSS3 Pseudo Elements" class_option :no_hash, :aliases => "-h", :type => :boolean, :desc => "Generate fonts without asset-busting hashes." class_option :base64, :aliases => "-b", :type => :boolean, :desc => "Encode WOFF fonts into the generated CSS." class_option :debug, :aliases => "-D", :type => :boolean, :desc => "Display (possibly useful) debugging messages." class_option :force, :aliases => "-F", :type => :boolean, :desc => "Forces compilation, even if inputs have not changed." class_option :quiet, :aliases => "-q", :type => :boolean, :desc => "Hide status messages." class_option :copyright, :aliases => %w|--copyright -r|, :type => :string, :desc => "Copyright information." # Required for Thor::Actions#template def self.source_root File.join Fontcustom.gem_lib, "templates" end desc "compile [INPUT] [OPTIONS]", "Generates webfonts and templates from *.svg files in INPUT. Default: `pwd`" def compile(input = nil) Base.new(options.merge(:input => input)).compile rescue Fontcustom::Error => e say_status :error, e.message, :red puts e.backtrace.join("\n") if options[:debug] exit 1 end desc "watch [INPUT] [OPTIONS]", "Watches INPUT for changes and regenerates files automatically. Ctrl + C to stop. Default: `pwd`" method_option :skip_first, :type => :boolean, :desc => "Skip the initial compile upon watching.", :default => false def watch(input = nil) say "Font Custom is watching your icons. Press Ctrl + C to stop.", :yellow unless options[:quiet] opts = options.merge :input => input, :skip_first => !! options[:skip_first] Watcher.new(opts).watch rescue Fontcustom::Error => e say_status :error, e.message, :red exit 1 end desc "config [DIR]", "Generates a starter configuration file (fontcustom.yml) in DIR. Default: `pwd`" def config(dir = Dir.pwd) template "fontcustom.yml", File.join(dir, "fontcustom.yml") end desc "hidden", "hidden", :hide => true method_option :version, :aliases => "-v", :type => :boolean, :default => false def show_help if options[:version] puts "fontcustom-#{VERSION}" else help end end end end fontcustom-2.0.0/lib/fontcustom/error.rb000066400000000000000000000000721312032244500203320ustar00rootroot00000000000000module Fontcustom class Error < StandardError end end fontcustom-2.0.0/lib/fontcustom/generator/000077500000000000000000000000001312032244500206435ustar00rootroot00000000000000fontcustom-2.0.0/lib/fontcustom/generator/font.rb000066400000000000000000000053641312032244500221460ustar00rootroot00000000000000require "json" require "open3" module Fontcustom module Generator class Font include Utility attr_reader :manifest def initialize(manifest) @manifest = Fontcustom::Manifest.new manifest @options = @manifest.get :options end def generate create_output_dirs delete_old_fonts set_glyph_info create_fonts end private def create_output_dirs dirs = @options[:output].values.uniq dirs.each do |dir| unless File.directory? dir empty_directory dir, :verbose => false say_message :create, dir end end end def delete_old_fonts @manifest.delete :fonts end def set_glyph_info manifest_glyphs = @manifest.get :glyphs codepoint = if ! manifest_glyphs.empty? codepoints = manifest_glyphs.values.map { |data| data[:codepoint] } codepoints.max + 1 else # Offset to work around Chrome Windows bug # https://github.com/FontCustom/fontcustom/issues/1 0xf100 end files = Dir.glob File.join(@options[:input][:vectors], "*.svg") glyphs = {} files.each do |file| name = File.basename file, ".svg" name = name.strip.gsub(/\W/, "-") glyphs[name.to_sym] = { :source => file } if File.read(file).include? "rgba" say_message :warn, "`#{file}` contains transparency and will be skipped." end end # Dir.glob returns a different order depending on ruby # version/platform, so we have to sort it first glyphs = Hash[glyphs.sort_by { |key, val| key.to_s }] glyphs.each do |name, data| if manifest_glyphs.has_key? name data[:codepoint] = manifest_glyphs[name][:codepoint] else data[:codepoint] = codepoint codepoint = codepoint + 1 end end @manifest.set :glyphs, glyphs end def create_fonts cmd = "fontforge -script #{Fontcustom.gem_lib}/scripts/generate.py #{@manifest.manifest}" stdout, stderr, status = Open3::capture3(cmd) stdout = stdout.split("\n") stdout = stdout[1..-1] if stdout[0] == "CreateAllPyModules()" debug_msg = " Try again with --debug for more details." if @options[:debug] messages = stderr.split("\n") + stdout say_message :debug, messages.join(line_break) debug_msg = "" end if status.success? @manifest.reload say_changed :create, @manifest.get(:fonts) else raise Fontcustom::Error, "`fontforge` compilation failed.#{debug_msg}" end end end end end fontcustom-2.0.0/lib/fontcustom/generator/template.rb000066400000000000000000000153031312032244500230050ustar00rootroot00000000000000require "json" require "pathname" require "base64" module Fontcustom module Generator class Template include Utility attr_reader :manifest def initialize(manifest) @manifest = Fontcustom::Manifest.new manifest @options = @manifest.get :options @pseudo_element = ':before'; if @options[:css3] @pseudo_element = '::before'; end end def generate if ! @manifest.get(:fonts).empty? delete_old_templates set_relative_paths create_files else raise Fontcustom::Error, "No generated fonts were detected - aborting template generation." end end private def delete_old_templates @manifest.delete :templates end def set_relative_paths fonts = @manifest.get :fonts name = File.basename fonts.first, File.extname(fonts.first) fonts_path = Pathname.new(@options[:output][:fonts]).realdirpath css_path = Pathname.new(@options[:output][:css]).realdirpath preview_path = Pathname.new(@options[:output][:preview]).realdirpath @font_path = File.join fonts_path.relative_path_from(css_path).to_s, name @font_path_alt = if @options[:preprocessor_path].nil? @font_path elsif ! @options[:preprocessor_path] || @options[:preprocessor_path].empty? name else File.join(@options[:preprocessor_path], name) end @font_path_preview = File.join fonts_path.relative_path_from(preview_path).to_s, name end def create_files @glyphs = @manifest.get :glyphs existing = @manifest.get :templates created = [] begin @options[:templates].each do |template_name| begin source = get_source_path(template_name) target = get_target_path(source) template source, target, :verbose => false, :force => true end created << target end ensure say_changed :create, created @manifest.set :templates, (existing + created).uniq end end def get_source_path(template) template_path = File.join Fontcustom.gem_lib, "templates" case template when "preview" File.join template_path, "fontcustom-preview.html" when "css" File.join template_path, "fontcustom.css" when "scss" File.join template_path, "_fontcustom.scss" when "scss-rails" File.join template_path, "_fontcustom-rails.scss" else File.join @options[:input][:templates], template end end def get_target_path(source) ext = File.extname source base = File.basename source css_exts = %w|.css .scss .sass .less .stylus| packaged = %w|fontcustom-preview.html fontcustom.css _fontcustom.scss _fontcustom-rails.scss| target = if @options[:output].keys.include? base.to_sym File.join @options[:output][base.to_sym], base elsif ext && css_exts.include?(ext) File.join @options[:output][:css], base elsif source.match(/fontcustom-preview\.html/) File.join @options[:output][:preview], base else File.join @options[:output][:fonts], base end if packaged.include?(base) && @options[:font_name] != DEFAULT_OPTIONS[:font_name] target = File.join(File.dirname(target), File.basename(target).sub(DEFAULT_OPTIONS[:font_name], @options[:font_name])) end target end # # Template Helpers # def font_name @options[:font_name] end def font_face(style = {}) if style.is_a?(Symbol) if style == :preprocessor url = "font-url" path = @font_path_alt elsif style == :preview url = "url" path = @font_path_preview else url = "url" path = @font_path end say_message :warn, "`font_face(:#{style})` is deprecated. Use `font_face(url:'url', path:'path')` instead." else style = {:url => "url", :path => @font_path}.merge(style) url = style[:url] path = style[:path] end # Bulletproof @Font-Face # With and without Base64 if @options[:base64] string = %Q|@font-face { font-family: "#{font_name}"; src: #{url}("#{path}.eot?") format("embedded-opentype"); font-weight: normal; font-style: normal; } @font-face { font-family: "#{font_name}"; src: url("data:application/x-font-woff;charset=utf-8;base64,#{woff_base64}") format("woff"), #{url}("#{path}.woff2") format("woff2"), #{url}("#{path}.ttf") format("truetype"), #{url}("#{path}.svg##{font_name}") format("svg"); font-weight: normal; font-style: normal; }| else string = %Q|@font-face { font-family: "#{font_name}"; src: #{url}("#{path}.eot"); src: #{url}("#{path}.eot?#iefix") format("embedded-opentype"), #{url}("#{path}.woff2") format("woff2"), #{url}("#{path}.woff") format("woff"), #{url}("#{path}.ttf") format("truetype"), #{url}("#{path}.svg##{font_name}") format("svg"); font-weight: normal; font-style: normal; }| end # For Windows/Chrome string << %Q| @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: "#{font_name}"; src: #{url}("#{path}.svg##{font_name}") format("svg"); } }| string end def woff_base64 woff_path = File.join(@options[:output][:fonts], "#{@font_path}.woff") Base64.encode64(File.binread(File.join(woff_path))).gsub("\n", "") end def glyph_selectors output = @glyphs.map do |name, value| @options[:css_selector].sub("{{glyph}}", name.to_s) + @pseudo_element end output.join ",\n" end def glyph_properties %Q| display: inline-block; font-family: "#{font_name}"; font-style: normal; font-weight: normal; font-variant: normal; line-height: 1; text-decoration: inherit; text-rendering: optimizeLegibility; text-transform: none; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; font-smoothing: antialiased;| end def glyphs output = @glyphs.map do |name, value| %Q|#{@options[:css_selector].sub('{{glyph}}', name.to_s)}#{@pseudo_element} { content: "\\#{value[:codepoint].to_s(16)}"; }| end output.join "\n" end def pseudo_element @pseudo_element end end end end fontcustom-2.0.0/lib/fontcustom/manifest.rb000066400000000000000000000032371312032244500210150ustar00rootroot00000000000000module Fontcustom class Manifest include Utility attr_reader :manifest def initialize(manifest, cli_options = {}) @manifest = manifest @cli_options = symbolize_hash cli_options if File.exists? @manifest reload if ! @cli_options.empty? && get(:options) != @cli_options set :options, @cli_options end else create_manifest @cli_options end end # TODO convert paths to absolute def get(key) @data[key] end # TODO convert paths to relative def set(key, value, status = nil) if key == :all @data = value else @data[key] = value end json = JSON.pretty_generate @data write_file @manifest, json, status end def reload begin json = File.read @manifest @data = JSON.parse json, :symbolize_names => true rescue JSON::ParserError raise Fontcustom::Error, "Couldn't parse `#{@manifest}`. Fix any invalid "\ "JSON or delete the file to start from scratch." end end def delete(key) files = get(key) return if files.empty? begin deleted = [] files.each do |file| remove_file file, :verbose => false deleted << file end ensure set key, files - deleted say_changed :delete, deleted end end private def create_manifest(options) defaults = { :checksum => { :current => "", :previous => "" }, :fonts => [], :glyphs => {}, :options => options, :templates => [] } set :all, defaults, :create end end end fontcustom-2.0.0/lib/fontcustom/options.rb000066400000000000000000000143441312032244500207030ustar00rootroot00000000000000require "yaml" require "pp" module Fontcustom class Options include Utility def initialize(cli_options = {}) @manifest = cli_options[:manifest] @cli_options = symbolize_hash(cli_options) parse_options end private def parse_options overwrite_examples set_config_path load_config merge_options clean_font_name clean_css_selector set_input_paths set_output_paths check_template_paths print_debug if @options[:debug] end # We give Thor fake defaults to generate more useful help messages. # Here, we delete any CLI options that match those examples. # TODO There's *got* a be a cleaner way to customize Thor help messages. def overwrite_examples EXAMPLE_OPTIONS.keys.each do |key| @cli_options.delete(key) if @cli_options[key] == EXAMPLE_OPTIONS[key] end @cli_options = DEFAULT_OPTIONS.dup.merge @cli_options end def set_config_path @cli_options[:config] = if @cli_options[:config] path = @cli_options[:config] if File.exists?(path) && ! File.directory?(path) path elsif File.exists? File.join(path, "fontcustom.yml") File.join path, "fontcustom.yml" else raise Fontcustom::Error, "No configuration file found at `#{path}`." end else if File.exists? "fontcustom.yml" "fontcustom.yml" elsif File.exists? File.join("config", "fontcustom.yml") File.join "config", "fontcustom.yml" else false end end end def load_config @config_options = {} if @cli_options[:config] begin config = YAML.load File.open(@cli_options[:config]) if config # empty YAML returns false @config_options = symbolize_hash(config) say_message :debug, "Using settings from `#{@cli_options[:config]}`." if @cli_options[:debug] || @config_options[:debug] else say_message :warn, "`#{@cli_options[:config]}` was empty. Using defaults." end rescue Exception => e raise Fontcustom::Error, "Error parsing `#{@cli_options[:config]}`:\n#{e.message}" end end end # TODO validate keys def merge_options @cli_options.delete_if { |key, val| val == DEFAULT_OPTIONS[key] } @options = DEFAULT_OPTIONS.merge(@config_options).merge(@cli_options) @options.delete :manifest end def clean_font_name @options[:font_name] = @options[:font_name].strip.gsub(/\W/, "-") end def clean_css_selector unless @options[:css_selector].include? "{{glyph}}" raise Fontcustom::Error, "CSS selector `#{@options[:css_selector]}` should contain the \"{{glyph}}\" placeholder." end @options[:css_selector] = @options[:css_selector].strip.gsub(/[^&%=\[\]\.#\{\}""\d\w]/, "-") end def set_input_paths if @options[:input].is_a? Hash @options[:input] = symbolize_hash(@options[:input]) if @options[:input].has_key? :vectors check_input @options[:input][:vectors] else raise Fontcustom::Error, "Input paths (assigned as a hash) should have a :vectors key. Check your options." end if @options[:input].has_key? :templates check_input @options[:input][:templates] else @options[:input][:templates] = @options[:input][:vectors] end else if @options[:input] input = @options[:input] else input = "." say_message :warn, "No input directory given. Using present working directory." end check_input input @options[:input] = { :vectors => input, :templates => input } end if Dir[File.join(@options[:input][:vectors], "*.svg")].empty? raise Fontcustom::Error, "`#{@options[:input][:vectors]}` doesn't contain any SVGs." end end def set_output_paths if @options[:output].is_a? Hash @options[:output] = symbolize_hash(@options[:output]) unless @options[:output].has_key? :fonts raise Fontcustom::Error, "Output paths (assigned as a hash) should have a :fonts key. Check your options." end @options[:output].each do |key, val| @options[:output][key] = val if File.exists?(val) && ! File.directory?(val) raise Fontcustom::Error, "Output `#{@options[:output][key]}` exists but isn't a directory. Check your options." end end @options[:output][:css] ||= @options[:output][:fonts] @options[:output][:preview] ||= @options[:output][:fonts] else if @options[:output].is_a? String output = @options[:output] if File.exists?(output) && ! File.directory?(output) raise Fontcustom::Error, "Output `#{output}` exists but isn't a directory. Check your options." end else output = @options[:font_name] say_message :debug, "Generated files will be saved to `#{output}/`." if @options[:debug] end @options[:output] = { :fonts => output, :css => output, :preview => output } end end def check_template_paths @options[:templates].each do |template| next if %w|preview css scss scss-rails|.include? template if template[0] == "/" path = template else path = File.expand_path File.join(@options[:input][:templates], template) end unless File.exists? path raise Fontcustom::Error, "Custom template `#{template}` wasn't found in `#{@options[:input][:templates]}/`. Check your options." end end end def check_input(dir) if ! File.exists? dir raise Fontcustom::Error, "Input `#{dir}` doesn't exist. Check your options." elsif ! File.directory? dir raise Fontcustom::Error, "Input `#{dir}` isn't a directory. Check your options." end end def print_debug message = line_break(16) message << @options.pretty_inspect.split("\n ").join(line_break(16)) say_message :debug, "Using options:#{message}" end end end fontcustom-2.0.0/lib/fontcustom/scripts/000077500000000000000000000000001312032244500203445ustar00rootroot00000000000000fontcustom-2.0.0/lib/fontcustom/scripts/eotlitetool.py000066400000000000000000000421651312032244500232710ustar00rootroot00000000000000#!/usr/bin/env python # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is font utility code. # # The Initial Developer of the Original Code is Mozilla Corporation. # Portions created by the Initial Developer are Copyright (C) 2009 # the Initial Developer. All Rights Reserved. # # Contributor(s): # John Daggett # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** */ # eotlitetool.py - create EOT version of OpenType font for use with IE # # Usage: eotlitetool.py [-o output-filename] font1 [font2 ...] # # OpenType file structure # http://www.microsoft.com/typography/otspec/otff.htm # # Types: # # BYTE 8-bit unsigned integer. # CHAR 8-bit signed integer. # USHORT 16-bit unsigned integer. # SHORT 16-bit signed integer. # ULONG 32-bit unsigned integer. # Fixed 32-bit signed fixed-point number (16.16) # LONGDATETIME Date represented in number of seconds since 12:00 midnight, January 1, 1904. The value is represented as a signed 64-bit integer. # # SFNT Header # # Fixed sfnt version // 0x00010000 for version 1.0. # USHORT numTables // Number of tables. # USHORT searchRange // (Maximum power of 2 <= numTables) x 16. # USHORT entrySelector // Log2(maximum power of 2 <= numTables). # USHORT rangeShift // NumTables x 16-searchRange. # # Table Directory # # ULONG tag // 4-byte identifier. # ULONG checkSum // CheckSum for this table. # ULONG offset // Offset from beginning of TrueType font file. # ULONG length // Length of this table. # # OS/2 Table (Version 4) # # USHORT version // 0x0004 # SHORT xAvgCharWidth # USHORT usWeightClass # USHORT usWidthClass # USHORT fsType # SHORT ySubscriptXSize # SHORT ySubscriptYSize # SHORT ySubscriptXOffset # SHORT ySubscriptYOffset # SHORT ySuperscriptXSize # SHORT ySuperscriptYSize # SHORT ySuperscriptXOffset # SHORT ySuperscriptYOffset # SHORT yStrikeoutSize # SHORT yStrikeoutPosition # SHORT sFamilyClass # BYTE panose[10] # ULONG ulUnicodeRange1 // Bits 0-31 # ULONG ulUnicodeRange2 // Bits 32-63 # ULONG ulUnicodeRange3 // Bits 64-95 # ULONG ulUnicodeRange4 // Bits 96-127 # CHAR achVendID[4] # USHORT fsSelection # USHORT usFirstCharIndex # USHORT usLastCharIndex # SHORT sTypoAscender # SHORT sTypoDescender # SHORT sTypoLineGap # USHORT usWinAscent # USHORT usWinDescent # ULONG ulCodePageRange1 // Bits 0-31 # ULONG ulCodePageRange2 // Bits 32-63 # SHORT sxHeight # SHORT sCapHeight # USHORT usDefaultChar # USHORT usBreakChar # USHORT usMaxContext # # # The Naming Table is organized as follows: # # [name table header] # [name records] # [string data] # # Name Table Header # # USHORT format // Format selector (=0). # USHORT count // Number of name records. # USHORT stringOffset // Offset to start of string storage (from start of table). # # Name Record # # USHORT platformID // Platform ID. # USHORT encodingID // Platform-specific encoding ID. # USHORT languageID // Language ID. # USHORT nameID // Name ID. # USHORT length // String length (in bytes). # USHORT offset // String offset from start of storage area (in bytes). # # head Table # # Fixed tableVersion // Table version number 0x00010000 for version 1.0. # Fixed fontRevision // Set by font manufacturer. # ULONG checkSumAdjustment // To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum. # ULONG magicNumber // Set to 0x5F0F3CF5. # USHORT flags # USHORT unitsPerEm // Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines. # LONGDATETIME created // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer # LONGDATETIME modified // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer # SHORT xMin // For all glyph bounding boxes. # SHORT yMin # SHORT xMax # SHORT yMax # USHORT macStyle # USHORT lowestRecPPEM // Smallest readable size in pixels. # SHORT fontDirectionHint # SHORT indexToLocFormat // 0 for short offsets, 1 for long. # SHORT glyphDataFormat // 0 for current format. # # # # Embedded OpenType (EOT) file format # http://www.w3.org/Submission/EOT/ # # EOT version 0x00020001 # # An EOT font consists of a header with the original OpenType font # appended at the end. Most of the data in the EOT header is simply a # copy of data from specific tables within the font data. The exceptions # are the 'Flags' field and the root string name field. The root string # is a set of names indicating domains for which the font data can be # used. A null root string implies the font data can be used anywhere. # The EOT header is in little-endian byte order but the font data remains # in big-endian order as specified by the OpenType spec. # # Overall structure: # # [EOT header] # [EOT name records] # [font data] # # EOT header # # ULONG eotSize // Total structure length in bytes (including string and font data) # ULONG fontDataSize // Length of the OpenType font (FontData) in bytes # ULONG version // Version number of this format - 0x00020001 # ULONG flags // Processing Flags (0 == no special processing) # BYTE fontPANOSE[10] // OS/2 Table panose # BYTE charset // DEFAULT_CHARSET (0x01) # BYTE italic // 0x01 if ITALIC in OS/2 Table fsSelection is set, 0 otherwise # ULONG weight // OS/2 Table usWeightClass # USHORT fsType // OS/2 Table fsType (specifies embedding permission flags) # USHORT magicNumber // Magic number for EOT file - 0x504C. # ULONG unicodeRange1 // OS/2 Table ulUnicodeRange1 # ULONG unicodeRange2 // OS/2 Table ulUnicodeRange2 # ULONG unicodeRange3 // OS/2 Table ulUnicodeRange3 # ULONG unicodeRange4 // OS/2 Table ulUnicodeRange4 # ULONG codePageRange1 // OS/2 Table ulCodePageRange1 # ULONG codePageRange2 // OS/2 Table ulCodePageRange2 # ULONG checkSumAdjustment // head Table CheckSumAdjustment # ULONG reserved[4] // Reserved - must be 0 # USHORT padding1 // Padding - must be 0 # # EOT name records # # USHORT FamilyNameSize // Font family name size in bytes # BYTE FamilyName[FamilyNameSize] // Font family name (name ID = 1), little-endian UTF-16 # USHORT Padding2 // Padding - must be 0 # # USHORT StyleNameSize // Style name size in bytes # BYTE StyleName[StyleNameSize] // Style name (name ID = 2), little-endian UTF-16 # USHORT Padding3 // Padding - must be 0 # # USHORT VersionNameSize // Version name size in bytes # bytes VersionName[VersionNameSize] // Version name (name ID = 5), little-endian UTF-16 # USHORT Padding4 // Padding - must be 0 # # USHORT FullNameSize // Full name size in bytes # BYTE FullName[FullNameSize] // Full name (name ID = 4), little-endian UTF-16 # USHORT Padding5 // Padding - must be 0 # # USHORT RootStringSize // Root string size in bytes # BYTE RootString[RootStringSize] // Root string, little-endian UTF-16 import optparse import struct class FontError(Exception): """Error related to font handling""" pass def multichar(str): vals = struct.unpack('4B', str[:4].encode('utf-8')) return (vals[0] << 24) + (vals[1] << 16) + (vals[2] << 8) + vals[3] def multicharval(v): return struct.pack('4B', (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF) class EOT: EOT_VERSION = 0x00020001 EOT_MAGIC_NUMBER = 0x504c EOT_DEFAULT_CHARSET = 0x01 EOT_FAMILY_NAME_INDEX = 0 # order of names in variable portion of EOT header EOT_STYLE_NAME_INDEX = 1 EOT_VERSION_NAME_INDEX = 2 EOT_FULL_NAME_INDEX = 3 EOT_NUM_NAMES = 4 EOT_HEADER_PACK = '<4L10B2BL2H7L18x' class OpenType: SFNT_CFF = multichar('OTTO') # Postscript CFF SFNT version SFNT_TRUE = 0x10000 # Standard TrueType version SFNT_APPLE = multichar('true') # Apple TrueType version SFNT_UNPACK = '>I4H' TABLE_DIR_UNPACK = '>4I' TABLE_HEAD = multichar('head') # TrueType table tags TABLE_NAME = multichar('name') TABLE_OS2 = multichar('OS/2') TABLE_GLYF = multichar('glyf') TABLE_CFF = multichar('CFF ') OS2_FSSELECTION_ITALIC = 0x1 OS2_UNPACK = '>4xH2xH22x10B4L4xH14x2L' HEAD_UNPACK = '>8xL' NAME_RECORD_UNPACK = '>6H' NAME_ID_FAMILY = 1 NAME_ID_STYLE = 2 NAME_ID_UNIQUE = 3 NAME_ID_FULL = 4 NAME_ID_VERSION = 5 NAME_ID_POSTSCRIPT = 6 PLATFORM_ID_UNICODE = 0 # Mac OS uses this typically PLATFORM_ID_MICROSOFT = 3 ENCODING_ID_MICROSOFT_UNICODEBMP = 1 # with Microsoft platformID BMP-only Unicode encoding LANG_ID_MICROSOFT_EN_US = 0x0409 # with Microsoft platformID EN US lang code def eotname(ttf): i = ttf.rfind('.') if i != -1: ttf = ttf[:i] return ttf + '.eotlite' def readfont(f): data = open(f, 'rb').read() return data def get_table_directory(data): """read the SFNT header and table directory""" datalen = len(data) sfntsize = struct.calcsize(OpenType.SFNT_UNPACK) if sfntsize > datalen: raise FontError('truncated font data') sfntvers, numTables = struct.unpack(OpenType.SFNT_UNPACK, data[:sfntsize])[:2] if sfntvers != OpenType.SFNT_CFF and sfntvers != OpenType.SFNT_TRUE: raise FontError('invalid font type') font = {} font['version'] = sfntvers font['numTables'] = numTables # create set of offsets, lengths for tables table_dir_size = struct.calcsize(OpenType.TABLE_DIR_UNPACK) if sfntsize + table_dir_size * numTables > datalen: raise FontError('truncated font data, table directory extends past end of data') table_dir = {} for i in range(0, numTables): start = sfntsize + i * table_dir_size end = start + table_dir_size tag, check, bongo, dirlen = struct.unpack(OpenType.TABLE_DIR_UNPACK, data[start:end]) table_dir[tag] = {'offset': bongo, 'length': dirlen, 'checksum': check} font['tableDir'] = table_dir return font def get_name_records(nametable): """reads through the name records within name table""" name = {} # read the header headersize = 6 count, strOffset = struct.unpack('>2H', nametable[2:6]) namerecsize = struct.calcsize(OpenType.NAME_RECORD_UNPACK) if count * namerecsize + headersize > len(nametable): raise FontError('names exceed size of name table') name['count'] = count name['strOffset'] = strOffset # read through the name records namerecs = {} for i in range(0, count): start = headersize + i * namerecsize end = start + namerecsize platformID, encodingID, languageID, nameID, namelen, offset = struct.unpack(OpenType.NAME_RECORD_UNPACK, nametable[start:end]) if platformID != OpenType.PLATFORM_ID_MICROSOFT or \ encodingID != OpenType.ENCODING_ID_MICROSOFT_UNICODEBMP or \ languageID != OpenType.LANG_ID_MICROSOFT_EN_US: continue namerecs[nameID] = {'offset': offset, 'length': namelen} name['namerecords'] = namerecs return name def make_eot_name_headers(fontdata, nameTableDir): """extracts names from the name table and generates the names header portion of the EOT header""" nameoffset = nameTableDir['offset'] namelen = nameTableDir['length'] name = get_name_records(fontdata[nameoffset : nameoffset + namelen]) namestroffset = name['strOffset'] namerecs = name['namerecords'] eotnames = (OpenType.NAME_ID_FAMILY, OpenType.NAME_ID_STYLE, OpenType.NAME_ID_VERSION, OpenType.NAME_ID_FULL) nameheaders = [] for nameid in eotnames: if nameid in namerecs: namerecord = namerecs[nameid] noffset = namerecord['offset'] nlen = namerecord['length'] nformat = '%dH' % (nlen / 2) # length is in number of bytes start = nameoffset + namestroffset + noffset end = start + nlen nstr = struct.unpack('>' + nformat, fontdata[start:end]) nameheaders.append(struct.pack(' os2Dir['length']: raise FontError('OS/2 table invalid length') os2fields = struct.unpack(OpenType.OS2_UNPACK, fontdata[os2offset : os2offset + os2size]) panose = [] urange = [] codepage = [] weight, fsType = os2fields[:2] panose[:10] = os2fields[2:12] urange[:4] = os2fields[12:16] fsSelection = os2fields[16] codepage[:2] = os2fields[17:19] italic = fsSelection & OpenType.OS2_FSSELECTION_ITALIC # read in values from head table headDir = tableDir[OpenType.TABLE_HEAD] headoffset = headDir['offset'] headsize = struct.calcsize(OpenType.HEAD_UNPACK) if headsize > headDir['length']: raise FontError('head table invalid length') headfields = struct.unpack(OpenType.HEAD_UNPACK, fontdata[headoffset : headoffset + headsize]) checkSumAdjustment = headfields[0] # make name headers nameheaders = make_eot_name_headers(fontdata, tableDir[OpenType.TABLE_NAME]) rootstring = make_root_string() # calculate the total eot size eotSize = struct.calcsize(EOT.EOT_HEADER_PACK) + len(nameheaders) + len(rootstring) + fontDataSize fixed = struct.pack(EOT.EOT_HEADER_PACK, *([eotSize, fontDataSize, version, flags] + panose + [charset, italic] + [weight, fsType, magicNumber] + urange + codepage + [checkSumAdjustment])) return b''.join((fixed, nameheaders, rootstring)) def write_eot_font(eot, header, data): open(eot,'wb').write(b''.join((header, data))) return def main(): # deal with options p = optparse.OptionParser() p.add_option('--output', '-o', default="world") options, args = p.parse_args() # iterate over font files for f in args: data = readfont(f) if len(data) == 0: print('Error reading %s' % f) else: eot = eotname(f) header = make_eot_header(data) write_eot_font(eot, header, data) if __name__ == '__main__': main() fontcustom-2.0.0/lib/fontcustom/scripts/generate.py000077500000000000000000000112021312032244500225070ustar00rootroot00000000000000import fontforge import os import subprocess import tempfile import json # # Manifest / Options # Older Pythons don't have argparse, so we use optparse instead # try: import argparse parser = argparse.ArgumentParser() parser.add_argument('manifest', help='Path to .fontcustom-manifest.json') args = parser.parse_args() manifestfile = open(args.manifest, 'r+') except ImportError: import optparse parser = optparse.OptionParser() (nothing, args) = parser.parse_args() manifestfile = open(args[0], 'r+') manifest = json.load(manifestfile) options = manifest['options'] # # Font # design_px = options['font_em'] / options['font_design_size'] font = fontforge.font() font.encoding = 'UnicodeFull' font.design_size = options['font_design_size'] font.em = options['font_em'] font.ascent = options['font_ascent'] font.descent = options['font_descent'] font.fontname = options['font_name'] font.familyname = options['font_name'] font.fullname = options['font_name'] font.copyright = options['copyright'] if options['autowidth']: font.autoWidth(0, 0, options['font_em']) # # Glyphs # def removeSwitchFromSvg( file ): svgfile = open(file, 'r') svgtext = svgfile.read() svgfile.close() tmpsvgfile = tempfile.NamedTemporaryFile(suffix=".svg", delete=False) svgtext = svgtext.replace('', '') svgtext = svgtext.replace('', '') tmpsvgfile.file.write(svgtext.encode('utf-8')) tmpsvgfile.file.close() return tmpsvgfile.name def createGlyph( name, source, code ): frag, ext = os.path.splitext(source) if ext == '.svg': temp = removeSwitchFromSvg(source) glyph = font.createChar(code, name) glyph.importOutlines(temp) os.unlink(temp) if options['autowidth']: glyph.left_side_bearing = glyph.right_side_bearing = 0 glyph.round() else: glyph.width = options['font_em'] width = glyph.width - glyph.left_side_bearing - glyph.right_side_bearing aligned_to_pixel_grid = (width % design_px == 0) if (aligned_to_pixel_grid): shift = glyph.left_side_bearing % design_px glyph.left_side_bearing = glyph.left_side_bearing - shift glyph.right_side_bearing = glyph.right_side_bearing + shift # Add valid space glyph to avoid "unknown character" box on IE11 glyph = font.createChar(32) glyph.width = 200 for glyph, data in manifest['glyphs'].items(): name = createGlyph(glyph, data['source'], data['codepoint']) # # Generate Files # try: fontfile = options['output']['fonts'] + '/' + options['font_name'] if not options['no_hash']: fontfile += '_' + manifest['checksum']['current'][:32] # Generate TTF and SVG font.generate(fontfile + '.ttf') font.generate(fontfile + '.svg') manifest['fonts'].append(fontfile + '.ttf') manifest['fonts'].append(fontfile + '.svg') # Fix SVG header for webkit # from: https://github.com/fontello/font-builder/blob/master/bin/fontconvert.py svgfile = open(fontfile + '.svg', 'r+') svgtext = svgfile.read() svgfile.seek(0) svgfile.write(svgtext.replace('''''', '''''')) svgfile.close() # Convert WOFF scriptPath = os.path.dirname(os.path.realpath(__file__)) try: # check if on windows if os.name == 'nt': subprocess.Popen([scriptPath + '/sfnt2woff.exe', fontfile + '.ttf'], stdout=subprocess.PIPE) else: subprocess.Popen([scriptPath + '/sfnt2woff', fontfile + '.ttf'], stdout=subprocess.PIPE) except OSError: # If the local version of sfnt2woff fails (i.e., on Linux), try to use the # global version. This allows us to avoid forcing OS X users to compile # sfnt2woff from source, simplifying install. subprocess.call(['sfnt2woff', fontfile + '.ttf']) manifest['fonts'].append(fontfile + '.woff') # Convert EOT for IE7 subprocess.call('python ' + scriptPath + '/eotlitetool.py ' + fontfile + '.ttf -o ' + fontfile + '.eot', shell=True) # check if windows if os.name == 'nt': subprocess.call('move ' + fontfile + '.eotlite ' + fontfile + '.eot', shell=True) else: subprocess.call('mv ' + fontfile + '.eotlite ' + fontfile + '.eot', shell=True) manifest['fonts'].append(fontfile + '.eot') # Convert TTF to WOFF2 subprocess.call('woff2_compress \'' + fontfile + '.ttf\'', shell=True) manifest['fonts'].append(fontfile + '.woff2') finally: manifestfile.seek(0) manifestfile.write(json.dumps(manifest, indent=2, sort_keys=True)) manifestfile.truncate() manifestfile.close() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fontcustom-2.0.0/lib/fontcustom/templates/����������������������������������������������������������0000775�0000000�0000000�00000000000�13120322445�0020653�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������fontcustom-2.0.0/lib/fontcustom/templates/_fontcustom-rails.scss������������������������������������0000664�0000000�0000000�00000000403�13120322445�0025215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// // Icon Font: <%= font_name %> // <%= font_face(url: "font-url", path: @font_path_alt) %> [data-icon]<%= pseudo_element %> { content: attr(data-icon); } [data-icon]<%= pseudo_element %>, <%= glyph_selectors %> { <%= glyph_properties %> } <%= glyphs %> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fontcustom-2.0.0/lib/fontcustom/templates/_fontcustom.scss������������������������������������������0000664�0000000�0000000�00000000604�13120322445�0024110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// // Icon Font: <%= font_name %> // <%= font_face(path: @font_path_alt) %> [data-icon]<%= pseudo_element %> { content: attr(data-icon); } [data-icon]<%= pseudo_element %>, <%= glyph_selectors %> { <%= glyph_properties %> } <%= glyphs %> <% @glyphs.each do |name, value| %> $font-<%= font_name.gsub(/[^\w\d_]/, '-') %>-<%= name.to_s %>: "\<%= value[:codepoint].to_s(16) %>";<% end %> ����������������������������������������������������������������������������������������������������������������������������fontcustom-2.0.0/lib/fontcustom/templates/fontcustom-preview.html�����������������������������������0000664�0000000�0000000�00000010323�13120322445�0025420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<% scale = %w|12 14 16 18 21 24 36 48 60 72| %> <%= font_name %> glyphs preview

<%= font_name %> contains <%= @glyphs.length %> glyphs:

Toggle Preview Characters
<% @glyphs.each do |name, value| selector = @options[:css_selector].sub('{{glyph}}', name.to_s) %>
<% scale.each do |n| %>Pp<% end %>
<% scale.each do |n| %><%= n %><% end %>
<% end %>
fontcustom-2.0.0/lib/fontcustom/templates/fontcustom.css000066400000000000000000000003331312032244500235650ustar00rootroot00000000000000/* Icon Font: <%= font_name %> */ <%= font_face %> [data-icon]<%= pseudo_element %> { content: attr(data-icon); } [data-icon]<%= pseudo_element %>, <%= glyph_selectors %> { <%= glyph_properties %> } <%= glyphs %> fontcustom-2.0.0/lib/fontcustom/templates/fontcustom.yml000066400000000000000000000060711312032244500236030ustar00rootroot00000000000000# ============================================================================= # Font Custom Configuration # This file should live in the directory where you run `fontcustom compile`. # For more info, visit . # ============================================================================= # ----------------------------------------------------------------------------- # Project Info # ----------------------------------------------------------------------------- # The font's name. Also determines the file names of generated templates. #font_name: icons # Format of CSS selectors. {{glyph}} is substituted for the glyph name. #css_selector: .i-{{glyph}} # Generate fonts without asset-busting hashes. #no_hash: true # Encode WOFF fonts into the generated CSS. #base64: true # Forces compilation, even if inputs have not changed #force: true # Display (possibly useful) debugging messages. #debug: true # Hide status messages. #quiet: true # Copyright information. #copyright: # ----------------------------------------------------------------------------- # Input / Output Locations # You can save generated fonts, CSS, and other files to different locations # here. Font Custom can also read input vectors and templates from different # places. # # NOTE: # - Be sure to preserve the whitespace in these YAML hashes. # - INPUT[:vectors] and OUTPUT[:fonts] are required. Everything else is # optional. # - Specify output locations for custom templates by including their file # names as the key. # ----------------------------------------------------------------------------- #input: # vectors: my/vectors # templates: my/templates #output: # fonts: app/assets/fonts # css: app/assets/stylesheets # preview: app/views/styleguide # my-custom-template.yml: path/to/template/output # ----------------------------------------------------------------------------- # Templates # A YAML array of templates and files to generate alongside fonts. Custom # templates should be saved in the INPUT[:templates] directory and referenced # by their base file name. # # For Rails and Compass templates, set `preprocessor_path` as the relative # path from OUTPUT[:css] to OUTPUT[:fonts]. By default, these are the same # directory. # # Included in Font Custom: preview, css, scss, scss-rails # Default: css, preview # ----------------------------------------------------------------------------- #templates: #- scss-rails #- preview #- my-custom-template.yml #preprocessor_path: ../fonts/ # ----------------------------------------------------------------------------- # Font Settings (defaults shown) # ----------------------------------------------------------------------------- # Size (in pica points) for which your font is designed. #font_design_size: 16 # The em size. Setting this will scale the entire font to the given size. #font_em: 512 # The font's ascent and descent. Used to calculate the baseline. #font_ascent: 448 #font_descent: 64 # Horizontally fit glyphs to their individual vector widths. #autowidth: false fontcustom-2.0.0/lib/fontcustom/utility.rb000066400000000000000000000044641312032244500207150ustar00rootroot00000000000000require "json" require "thor/actions" require "thor/shell" require "thor/shell/basic" require "thor/shell/color" # Requires access to: # @options or @cli_options # @manifest module Fontcustom module Utility include Thor::Actions # # Hacks that allow Thor::Actions and Thor::Shell to be used in Fontcustom classes. # def self.shell @shell || Thor::Shell::Color.new end def shell Fontcustom::Utility.shell end def behavior :invoke end def say_status(*args) shell.say_status *args end def destination_root @destination_stack ||= [project_root] @destination_stack.last end def source_paths @source_paths ||= [File.join(Fontcustom.gem_lib, "templates"), Dir.pwd] end # # Options # module HashWithMethodAccess def method_missing(method, arg = nil) if method[-1, 1] == "=" self[method[0...-1].to_sym] = arg else self[method.to_sym] end end end def symbolize_hash(hash) hash.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo } end def methodize_hash(hash) hash.extend HashWithMethodAccess end # # Paths # def project_root if @manifest.is_a? String File.dirname @manifest else File.dirname @manifest.manifest end end # # File Manipulation # def write_file(file, content = "", message = nil, message_body = nil) File.open(file, "w") { |f| f.write(content) } if message body = message_body || file say_message message, body end end # # Messages # def say_message(status, message, color = nil) return if options[:quiet] && status != :error && status != :debug color = :red if [:error, :debug, :warn].include?(status) say_status status, message, color end def say_changed(status, changed) return if options[:quiet] || ! options[:debug] && status == :delete say_status status, changed.join(line_break) end # magic number for Thor say_status line breaks def line_break(n = 14) "\n#{" " * n}" end def options if @data @data[:options] else @options || @cli_options || @config_options || {} end end end end fontcustom-2.0.0/lib/fontcustom/version.rb000066400000000000000000000000521312032244500206640ustar00rootroot00000000000000module Fontcustom VERSION = "2.0.0" end fontcustom-2.0.0/lib/fontcustom/watcher.rb000066400000000000000000000044241312032244500206430ustar00rootroot00000000000000require "fontcustom" require "listen" module Fontcustom class Watcher include Utility def initialize(options, is_test = false) @base = Fontcustom::Base.new options @options = @base.options @is_test = is_test templates = @options[:templates].dup.map { |template| File.basename(template) } packaged = %w|preview css scss scss-rails| templates.delete_if { |template| packaged.include?(template) } create_listener(templates) end def watch compile unless @options[:skip_first] start rescue SignalException # Catches Ctrl + C stop end private def create_listener(templates) listen_options = {} listen_options[:polling_fallback_message] = false if @is_test listen_dirs = [@options[:input][:vectors]] listen_dirs << @options[:input][:templates] unless templates.empty? if listen_eq2 listen_options[:only] = /(#{templates.join("|")}|.+\.svg)$/ @listener = Listen.to(listen_dirs, listen_options, &callback) else listen_options[:filter] = /(#{templates.join("|")}|.+\.svg)$/ listen_options[:relative_paths] = true @listener = Listen::Listener.new(*listen_dirs, listen_options, &callback) end end def start if @is_test # Non-blocking listener @listener.start else if listen_eq2 @listener.start sleep else @listener.start! end end end def stop @listener.stop shell.say "\nFont Custom is signing off. Good night and good luck.", :yellow end def callback Proc.new do |modified, added, removed| begin say_message :changed, modified.join(", ") unless modified.empty? say_message :added, added.join(", ") unless added.empty? say_message :removed, removed.join(", ") unless removed.empty? changed = modified + added + removed compile unless changed.empty? rescue Fontcustom::Error => e say_message :error, e.message end end end def compile @base.compile end def listen_eq2 begin require 'listen/version' ::Listen::VERSION =~ /^2\./ rescue LoadError false end end end end fontcustom-2.0.0/spec/000077500000000000000000000000001312032244500146405ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/000077500000000000000000000000001312032244500165115ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/example/000077500000000000000000000000001312032244500201445ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/example/_example-rails.scss000066400000000000000000000076441312032244500237560ustar00rootroot00000000000000// // Icon Font: example // @font-face { font-family: "example"; src: font-url("../foo/bar/example.eot?") format("embedded-opentype"); font-weight: normal; font-style: normal; } @font-face { font-family: "example"; src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"), font-url("../foo/bar/example.ttf") format("truetype"), font-url("../foo/bar/example.svg#example") format("svg"); font-weight: normal; font-style: normal; } @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: "example"; src: font-url("../foo/bar/example.svg#example") format("svg"); } } [data-icon]:before { content: attr(data-icon); } [data-icon]:before, .icon-C:before, .icon-D:before, .icon-a_R3ally-eXotic-f1Le-Name:before { display: inline-block; font-family: "example"; font-style: normal; font-weight: normal; font-variant: normal; line-height: 1; text-decoration: inherit; text-rendering: optimizeLegibility; text-transform: none; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; font-smoothing: antialiased; } .icon-C:before { content: "\f100"; } .icon-D:before { content: "\f101"; } .icon-a_R3ally-eXotic-f1Le-Name:before { content: "\f102"; } fontcustom-2.0.0/spec/fixtures/example/example-preview.html000066400000000000000000000314211312032244500241450ustar00rootroot00000000000000 example glyphs preview

example contains 3 glyphs:

Toggle Preview Characters
PpPpPpPpPpPpPpPpPpPp
12141618212436486072
PpPpPpPpPpPpPpPpPpPp
12141618212436486072
PpPpPpPpPpPpPpPpPpPp
12141618212436486072
fontcustom-2.0.0/spec/fixtures/example/example.css000066400000000000000000000075531312032244500223230ustar00rootroot00000000000000/* Icon Font: example */ @font-face { font-family: "example"; src: url("./example.eot?") format("embedded-opentype"); font-weight: normal; font-style: normal; } @font-face { font-family: "example"; src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"), url("./example.ttf") format("truetype"), url("./example.svg#example") format("svg"); font-weight: normal; font-style: normal; } @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: "example"; src: url("./example.svg#example") format("svg"); } } [data-icon]:before { content: attr(data-icon); } [data-icon]:before, .icon-C:before, .icon-D:before, .icon-a_R3ally-eXotic-f1Le-Name:before { display: inline-block; font-family: "example"; font-style: normal; font-weight: normal; font-variant: normal; line-height: 1; text-decoration: inherit; text-rendering: optimizeLegibility; text-transform: none; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; font-smoothing: antialiased; } .icon-C:before { content: "\f100"; } .icon-D:before { content: "\f101"; } .icon-a_R3ally-eXotic-f1Le-Name:before { content: "\f102"; } fontcustom-2.0.0/spec/fixtures/example/example.eot000066400000000000000000000060621312032244500223140ustar00rootroot000000000000002  LPexampleRegular Version 001.000 example PFFTMq hOS/2@]cX`cmapBcvt Dgasp `glyft$headN6hhea$hmtxloca|maxpS8 namea postݝ F_< КĦКĦ. @.LfGLf PfEd.  Y;< D***TU./<2<2/<2<233'3#wffU3 Y!]iu}6"54&#""5462"54&""5462".'&'.67>/762>5676'&?6&"'&62762"&46264&"#63'3&#.'>%"'&?67676/&///""'&&7> X /?2 '   '2(    # 'V H H  $   a\`\k0&' 35M$  $&&(  ,$$(         % =" +  7    4  ' ; %  d  $=  q\q^       w   v"2BRb{76/&4766/&47676/&4767/&476766/&476'/&47676#/#0/"&7543!2'!!237'/&47676'/&476'+/&'#676/417627&7676\  `  A    :    ("8WE .   T  " 'F       m        v    e    XW==5.P   U  L  ff  ;(-2Y%!757'>7632>2>54&"75654'32&#"12#50#"'73235.547@s*Y= #  VXw,8O88%      #   #^I&7,'88O7m W    0Xp$!B . H ` Hx    2Copyright (c) 2014, KaiCopyright (c) 2014, KaiexampleexampleRegularRegularFontForge 2.0 : example : 25-11-2014FontForge 2.0 : example : 25-11-2014exampleexampleVersion 001.000 Version 001.000 exampleexampleuniF100uniF101uniF102lkzКĦКĦfontcustom-2.0.0/spec/fixtures/example/example.svg000066400000000000000000000247761312032244500223400ustar00rootroot00000000000000 Created by FontForge 20141021 at Tue Nov 25 18:43:34 2014 By Kai Copyright (c) 2014, Kai fontcustom-2.0.0/spec/fixtures/example/example.ttf000066400000000000000000000056041312032244500223230ustar00rootroot00000000000000 PFFTMq hOS/2@]cX`cmapBcvt Dgasp `glyft$headN6hhea$hmtxloca|maxpS8 namea postݝ F_< КĦКĦ. @.LfGLf PfEd.  Y;< D***TU./<2<2/<2<233'3#wffU3 Y!]iu}6"54&#""5462"54&""5462".'&'.67>/762>5676'&?6&"'&62762"&46264&"#63'3&#.'>%"'&?67676/&///""'&&7> X /?2 '   '2(    # 'V H H  $   a\`\k0&' 35M$  $&&(  ,$$(         % =" +  7    4  ' ; %  d  $=  q\q^       w   v"2BRb{76/&4766/&47676/&4767/&476766/&476'/&47676#/#0/"&7543!2'!!237'/&47676'/&476'+/&'#676/417627&7676\  `  A    :    ("8WE .   T  " 'F       m        v    e    XW==5.P   U  L  ff  ;(-2Y%!757'>7632>2>54&"75654'32&#"12#50#"'73235.547@s*Y= #  VXw,8O88%      #   #^I&7,'88O7m W    0Xp$!B . H ` Hx    2Copyright (c) 2014, KaiCopyright (c) 2014, KaiexampleexampleRegularRegularFontForge 2.0 : example : 25-11-2014FontForge 2.0 : example : 25-11-2014exampleexampleVersion 001.000 Version 001.000 exampleexampleuniF100uniF101uniF102lkzКĦКĦfontcustom-2.0.0/spec/fixtures/example/example.woff000066400000000000000000000040001312032244500224540ustar00rootroot00000000000000wOFF FFTMqOS/2J`@]ccmapBBcvt DDgaspglyfX3thead0/6Nhhea`$hmtxlocaH|maxp Snameapost-Fݝxc`d``ⶃk'|efb ,ӂ2d r0q xc`d``+0d`d@,g1 @.xc`ab`Ø2H20001r2p#G&01f 3")Q`` Y;xc```f`F| @G$P l 0&#`b@ >D***Tx]T͋EWU]ӟ3̸=݋ILbJD!. s!w Y$1<*:@W_ェ(QN#v|GP{uߎlMttnֹw@:,p^ w j | [%z}>A+tPg$!MTYu?<(д07Yۘ6ɼR4\2UdT#&&̊s7chBk  7v fSL&ˉaTt.r<'ډjSŸJ7vSa AVVi@T`L]N̐{œ\?<1X /f̑/$a=my03 1.j;Lq"!&ÞPB$|e.uuiׂʢȵ!ɬQ鑉c3,Tɩ>%WF6{nBh0Xo@ n]N/ab8%24 'f,vl]=ڧH"Nf;S)B.PsT1E C]f]m$C['x/,㓽nYh^P.KU%?m_riTfGiʺH[J }}Ɣ&bqcR; V[CpRXw ^p]hC'*\t`9 ?\?pDV3%1caaIRԧ vR\R!?| IB)x{KxY 'ļ y\F+KWmǓGEض6G ܁gmwGh,m33bcbFXcdܾX&m泲N+#v#&"̏LꪞO|w?|QsRQO(>s` 44&.b<'ܹp#CGtB86a{xPj/>ul:RD(Րxuj@EhTB &Lݸ \Hd1T??O貽E&7{ *{&p/ qxGxx妈%_G@7cB$8ȱGˍ vjfO1njVusqEod7Z\~38P$Þ͡) v9Nz`cSi}+a;Kzי1|߲!6]nN\Lҋ8Mc~-;#Hz٤> JRJޕuJxc`b$#:`212123e@iC(mǜrxc```d 9U`z֑e0M1fontcustom-2.0.0/spec/fixtures/generators/000077500000000000000000000000001312032244500206625ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/generators/.fontcustom-manifest-corrupted.json000066400000000000000000000011541312032244500276460ustar00rootroot00000000000000'Totally invalid JSON.' { "checksum": { "current": "82a59e769bc60192484f2620570bbb59e225db97c1aac3f242a2e49d6060a19c", "previous": "82a59e769bc60192484f2620570bbb59e225db97c1aac3f242a2e49d6060a19c" }, "fonts": [ "fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.woff", "fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.ttf", "fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.eot", "fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.svg" ], "glyphs": [ "a_r3ally-exotic-f1le-name", "c", "d" ], "options": { "foo": "bar", "baz": "bum" }, "templates": [ "fontcustom.css" ] } fontcustom-2.0.0/spec/fixtures/generators/.fontcustom-manifest-empty.json000066400000000000000000000000001312032244500267620ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/generators/.fontcustom-manifest.json000066400000000000000000000043471312032244500256500ustar00rootroot00000000000000{ "checksum": { "current": "82a59e769bc60192484f2620570bbb59e225db97c1aac3f242a2e49d6060a19c", "previous": "82a59e769bc60192484f2620570bbb59e225db97c1aac3f242a2e49d6060a19c" }, "fonts": [ "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.ttf", "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.svg", "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.woff", "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.eot" ], "glyphs": { "a_r3ally-exotic-f1le-name": { "codepoint": 61696, "source": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/vectors/a_R3ally-eXotic f1Le Name.svg" }, "c": { "codepoint": 61697, "source": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/vectors/C.svg" }, "d": { "codepoint": 61698, "source": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/vectors/D.svg" } }, "options": { "autowidth": false, "config": false, "css_prefix": "test-", "debug": false, "font_name": "fontcustom", "input": { "templates": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/vectors", "vectors": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/vectors" }, "manifest": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/.fontcustom-manifest-fonts.json", "no_hash": false, "output": { "css": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom", "fonts": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom", "preview": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test/fontcustom" }, "preprocessor_path": null, "project_root": "/Users/kz/Projects/fontcustom/spec/fixtures/sandbox/test", "quiet": true, "templates": [ "/Users/kz/Projects/fontcustom/lib/fontcustom/templates/fontcustom.css", "/Users/kz/Projects/fontcustom/lib/fontcustom/templates/fontcustom-preview.html" ] }, "templates": [] } fontcustom-2.0.0/spec/fixtures/generators/fontcustom.yml000066400000000000000000000000071312032244500236030ustar00rootroot00000000000000# noop fontcustom-2.0.0/spec/fixtures/generators/mixed-output/000077500000000000000000000000001312032244500233265ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/generators/mixed-output/another-font.ttf000066400000000000000000000150241312032244500264530ustar00rootroot00000000000000pFFTMedOS/2GcN`cmapxBcvt 22 fpgm' bgaspglyf/ $head׺ 6hheatN $hmtx/ <@loca| |"maxp namer postXprepGP=;;v1PfEd  8Z  <  !*3!'3#! f!X% .6MQ2#!/&?#"&5463"264'"32654.*2641!37'.7%6/5! . /]W    = Gj9V V%W /U    Xk. /C887 Fn%7/6?"264'#".5076232>5454&'.'636'%&#""/'?'&4?327>'62o {-S++ $:kB  :*, :kB  A  N:Z=  (N S-| p ,,Al:   ):+ Bl:M !>Y9N "'Jf A-53!5%536&'6'.'&'.&'&7>6762372xrm"04( 1"/<!0, $CL   4) 7##'+9x8ygt9v7u7%_'  ,-0-   1#85EU#"'"/&4?6"/&4?&'&6327626762764/&"2764/&"2 1+%i%%%)n$%h%%%,2 ,%h%%%[%i%%%+%%%h%)c$%%%i%,+ 4%%%i%]%%%h%1h # h # Oh # h # 8 "-LP[#57!7%#"&46;2'"'&?6"/&6'2+#"'&1&4?632!!72>54&"SS8SS* *D  # "# " Nh^    ^i-9Q99. %aSSa  ^  , GG ,-(99Q973 ##`"&.!!>54&'!.54>753%532#54 ;V-h] ]gfIcwxkjx6cAA<6Ƞ[[B+'+4232>54&'6!>!!   $,@@-)2#,   OqZV5:Y[um7 7'%''-'' 5bG3arb3a y!Xa3aqb3HaQ^e_Q]e`8  '!7!7'7[訥];榥8 +"/'#"&547'&546303!>323![%3$Y$^" `#  $ $ $#8 E  .6"26454/&32?332?32>54'3/526{憆憆z&& r.q  E   ~U   X( 憆G&&9 f    SL    g$, 8U  %427&'>.4>7.'2#iS/8Y* ? l77l5> *Y8/Y-&SSST(]{(8s,S_8({] L8I)ؕ1_< ;;7  7Zl!Mu :f2 LRoj bD &P       L w   Created by Joshua Gross with FontForge 2.0 (http://fontforge.sf.net)Created by Joshua Gross with FontForge 2.0 (http://fontforge.sf.net)Untitled1Untitled1MediumMediumFontForge 2.0 : Untitled1 : 26-11-2012FontForge 2.0 : Untitled1 : 26-11-2012Untitled1Untitled1Version 001.000 Version 001.000 fontcustomfontcustom2     uniF100uniF101uniF102uniF103uniF104uniF105uniF106uniF107uniF108uniF109uniF10AuniF10BuniF10C22, `f-, d P&ZE[X!#!X PPX!@Y 8PX!8YY Ead(PX! E 0PX!0Y PX f a PX` PX! ` 6PX!6``YYY+YY#PXeYY-, E %ad CPX#B#B!!Y`-,#!#! dbB #B *! C +0%QX`PaRYX#Y! @SX+!@Y#PXeY-,#B#B#BCCQXC+C`BeY-,C E EcEb`D-,C E +#%` E#a d PX!0PX @YY#PXeY%#aDD-,EaD-,` CJPX #BY CJRX #BY- , b c#a C` ` #B#- , CUX CaB +YC%BC`B %B %B# %PXC%B #a*!#a #a*!C%B%a*!Y CG CG`b EcEb`#DC>C`B- ,ETX #B `a BB` +i+"Y- , +- , +-, +-, +-, +-, +-, +-, +-, +-, +-,+ETX #B `a BB` +i+"Y-,+-,+-,+-,+-,+-,+-,+-,+-,+- , +-!, `` C#`C%%QX# <`#e!!Y-",!+!*-#, G EcEb`#a8# UX G EcEb`#a8!Y-$,ETX#*0"Y-%,+ETX#*0"Y-&, 5`-',EcEb+EcEb+D>#8&*-(, < G EcEb`Ca8-),.<-*, < G EcEb`CaCc8-+,% . G#B%IG#G#ab#B**-,,%%G#G#aE+e.# <8--,%% .G#G#a #BE+ `PX @QX  &YBB# C #G#G#a#F`Cb` + a C`d#CadPXCaC`Y%ba# &#Fa8# CF% CG#G#a` Cb`# +#C`+%a%b&a %`d#%`dPX!#!Y# &#Fa8Y-., & .G#G#a#<8-/, #B F#G+#a8-0,%%G#G#aTX. <#!%%G#G#a %%G#G#a%%I%aEc#bcEb`#.# <8#!Y-1, C .G#G#a ` `fb# <8-2,# .F%FRX ,+*-5,,+# .F%FRX 32371&54='#"&46256327&547'#"&46263237&54626327&546247'#"'6327475&547'#0#16379D#  $  Q $   <  :  *K  O ; A4  X *$N< ^,LP([! "  (-   *"  #@/ '-  L * [/@ Q=@:B[[MQE+2!454&"2'54&"54Ԗƌx*jjccxU͚33U5Z@W.#/"3 4 B[O[OSG-+('! 55 +%2"&547'#"&547'#"&4632>327&5462#"'6%%6%a((8M  M -r %6%%r b@&4&& E8(  r%%6% sEa_< ΥΥ.(((j6Cn ? 'F   ~    N n   Created by Kai Zau with FontForge 2.0 (http://fontforge.sf.net)Created by Kai Zau with FontForge 2.0 (http://fontforge.sf.net)fontcustomfontcustomMediumMediumFontForge 2.0 : fontcustom : 10-11-2013FontForge 2.0 : fontcustom : 10-11-2013fontcustomfontcustomVersion 001.000 Version 001.000 fontcustomfontcustomuniF100uniF101uniF10222, `f-, d P&ZE[X!#!X PPX!@Y 8PX!8YY Ead(PX! E 0PX!0Y PX f a PX` PX! ` 6PX!6``YYY+YY#PXeYY-, E %ad CPX#B#B!!Y`-,#!#! dbB #B *! C +0%QX`PaRYX#Y! @SX+!@Y#PXeY-,C+C`B-,#B# #Bab`*-, E EcEb`D`-, E +#%` E#a d PX!0PX @YY#PXeY%#aDD`-,EaD- ,` CJPX #BY CJRX #BY- , b c#a C` ` #B#- ,KTXDY$ e#x- ,KQXKSXDY!Y$e#x- , CUX CaB +YC%B %B %B# %PXC`%B #a *!#a #a *!C`%B%a *!Y CG CG`b EcEb`#DC>C`B-,ETX #B `a  BB` +m+"Y-,+-,+-,+-,+-,+-,+-,+-,+-,+-, +-,+ETX #B `a  BB` +m+"Y-,+-,+-,+-,+-,+-,+- ,+-!,+-",+-#, +-$, <`-%, ` ` C#`C%a`$*!-&,%+%*-', G EcEb`#a8# UX G EcEb`#a8!Y-(,ETX'*0"Y-),+ETX'*0"Y-*, 5`-+,EcEb+EcEb+D>#8**-,, < G EcEb`Ca8--,.<-., < G EcEb`CaCc8-/,% . G#B%IG#G#a Xb!Y#B.*-0,%%G#G#aE+e.# <8-1,%% .G#G#a #BE+ `PX @QX  &YBB# C #G#G#a#F`Cb` + a C`d#CadPXCaC`Y%ba# &#Fa8#CF%CG#G#a` Cb`# +#C`+%a%b&a %`d#%`dPX!#!Y# &#Fa8Y-2, & .G#G#a#<8-3, #B F#G+#a8-4,%%G#G#aTX. <#!%%G#G#a %%G#G#a%%I%aEc# Xb!YcEb`#.# <8#!Y-5, C .G#G#a ` `fb# <8-6,# .F%FRX ,1+!# <#B#8&+C.&+-?, G#B.,*-@, G#B.,*-A,-*-B,/*-C,E# . F#a8&+-D,#BC+-E,<+-F,<+-G,<+-H,<+-I,=+-J,=+-K,=+-L,=+-M,9+-N,9+-O,9+-P,9+-Q,;+-R,;+-S,;+-T,;+-U,>+-V,>+-W,>+-X,>+-Y,:+-Z,:+-[,:+-\,:+-],2+.&+-^,2+6+-_,2+7+-`,2+8+-a,3+.&+-b,3+6+-c,3+7+-d,3+8+-e,4+.&+-f,4+6+-g,4+7+-h,4+8+-i,5+.&+-j,5+6+-k,5+7+-l,5+8+-m,+e$Px0-KRXYc #D#p( ERD *D$QX@XD&QXXDYYYYDfontcustom_82a59e769bc60192484f2620570bbb59.svg000066400000000000000000000141571312032244500323530ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/generators/mixed-output Created by FontForge 20120731 at Sun Nov 10 12:45:07 2013 By Kai Zau Created by Kai Zau with FontForge 2.0 (http://fontforge.sf.net) fontcustom_82a59e769bc60192484f2620570bbb59.ttf000066400000000000000000000124341312032244500323450ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/generators/mixed-outputpFFTMivOS/2A9^h`cmapxBcvt  fpgm x; 0 gasp glyfZheadl#6hhea$hmtxloca0@maxp  name~0postݝ FprepV=ΥΥLfGLf PfEd.@ < U)@&YMQE+33'3#wffU33]@~ud_  W ^L ZYXK 51 0'#7" BocA  h  h[[[ [  O  [[[ [   [ SGzytqkjge][SROMDA<;42+)&$@ +"'2"&545/#632"&545'#"'#".547'#"'"&54>32371&54='#"&46256327&547'#"&46263237&54626327&546247'#"'6327475&547'#0#16379D#  $  Q $   <  :  *K  O ; A4  X *$N< ^,LP([! "  (-   *"  #@/ '-  L * [/@ Q=@:B[[MQE+2!454&"2'54&"54Ԗƌx*jjccxU͚33U5Z@W.#/"3 4 B[O[OSG-+('! 55 +%2"&547'#"&547'#"&4632>327&5462#"'6%%6%a((8M  M -r %6%%r b@&4&& E8(  r%%6% sEa_< ΥΥ.(((j6Cn ? 'F   ~    N n   Created by Kai Zau with FontForge 2.0 (http://fontforge.sf.net)Created by Kai Zau with FontForge 2.0 (http://fontforge.sf.net)fontcustomfontcustomMediumMediumFontForge 2.0 : fontcustom : 10-11-2013FontForge 2.0 : fontcustom : 10-11-2013fontcustomfontcustomVersion 001.000 Version 001.000 fontcustomfontcustomuniF100uniF101uniF10222, `f-, d P&ZE[X!#!X PPX!@Y 8PX!8YY Ead(PX! E 0PX!0Y PX f a PX` PX! ` 6PX!6``YYY+YY#PXeYY-, E %ad CPX#B#B!!Y`-,#!#! dbB #B *! C +0%QX`PaRYX#Y! @SX+!@Y#PXeY-,C+C`B-,#B# #Bab`*-, E EcEb`D`-, E +#%` E#a d PX!0PX @YY#PXeY%#aDD`-,EaD- ,` CJPX #BY CJRX #BY- , b c#a C` ` #B#- ,KTXDY$ e#x- ,KQXKSXDY!Y$e#x- , CUX CaB +YC%B %B %B# %PXC`%B #a *!#a #a *!C`%B%a *!Y CG CG`b EcEb`#DC>C`B-,ETX #B `a  BB` +m+"Y-,+-,+-,+-,+-,+-,+-,+-,+-,+-, +-,+ETX #B `a  BB` +m+"Y-,+-,+-,+-,+-,+-,+- ,+-!,+-",+-#, +-$, <`-%, ` ` C#`C%a`$*!-&,%+%*-', G EcEb`#a8# UX G EcEb`#a8!Y-(,ETX'*0"Y-),+ETX'*0"Y-*, 5`-+,EcEb+EcEb+D>#8**-,, < G EcEb`Ca8--,.<-., < G EcEb`CaCc8-/,% . G#B%IG#G#a Xb!Y#B.*-0,%%G#G#aE+e.# <8-1,%% .G#G#a #BE+ `PX @QX  &YBB# C #G#G#a#F`Cb` + a C`d#CadPXCaC`Y%ba# &#Fa8#CF%CG#G#a` Cb`# +#C`+%a%b&a %`d#%`dPX!#!Y# &#Fa8Y-2, & .G#G#a#<8-3, #B F#G+#a8-4,%%G#G#aTX. <#!%%G#G#a %%G#G#a%%I%aEc# Xb!YcEb`#.# <8#!Y-5, C .G#G#a ` `fb# <8-6,# .F%FRX ,1+!# <#B#8&+C.&+-?, G#B.,*-@, G#B.,*-A,-*-B,/*-C,E# . F#a8&+-D,#BC+-E,<+-F,<+-G,<+-H,<+-I,=+-J,=+-K,=+-L,=+-M,9+-N,9+-O,9+-P,9+-Q,;+-R,;+-S,;+-T,;+-U,>+-V,>+-W,>+-X,>+-Y,:+-Z,:+-[,:+-\,:+-],2+.&+-^,2+6+-_,2+7+-`,2+8+-a,3+.&+-b,3+6+-c,3+7+-d,3+8+-e,4+.&+-f,4+6+-g,4+7+-h,4+8+-i,5+.&+-j,5+6+-k,5+7+-l,5+8+-m,+e$Px0-KRXYc #D#p( ERD *D$QX@XD&QXXDYYYYDfontcustom_82a59e769bc60192484f2620570bbb59.woff000066400000000000000000000065201312032244500325100ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/generators/mixed-outputwOFF PFFTMXivOS/2tJ`A9^hcmapBBcvt fpgm x;gaspglyfZhead-6l#hhea $hmtxloca(0@maxp8  nameXX~post-Fݝprep VVxc```d3΃sK\SsHxc`ab`Ø2H20001q2p#G&01`p 3")Q``5 7xc```f`F| @G$P l 0&#`b@ >xMSKoU>g{؉ڤi2~6ENԨƄE}ZR"(,b,&A5 @<@ʢu :$)9s4p (H8ׯ +̼p@ LxzW{/04݁m0?]>zO0'?]Baӗh)InD[71ם jZ!UВ›iPtތcV}2vt.nuY(d<]$k:'@LpJ<{,r.-+nM(۳V/mI&lWIZ$J+*)='՗d-e. )3+. a6걼)!MHs.Qf2sgxy"SrZm BKB)av҆jXf IB2K8!Ug\*%L>EYrjkIӦq/KMp2MR >7qa /AQ F NRD:0 g`v(WdB-ߜ굊(KX?qd!_[ß w66wַ'0(pdmG<۱7Wӕ( mBh2d @2!#}F!>F",0j hv,5yJoc8b)R&'bq 4Ħ mrW~]ulxJqM=FΕ`хݥ'xc`d``ī6_@LL\4@w xc`d``<LL @{xcb`d&f(((j6Cn xڍN0'Q!F閅R 6i3T b`MxC VԀ+| pX' t급8n+nq,w?R]է*qxq6uܡ1rDac8PgԒef&Ȑ+ͱ)  gǐ+vgBLэ< m󝜄&v-,A")-kk7pӍ+WJ#w,8gApIu~X*II>w k#۴c6L;(9P Uk5Q^$Y*6Jk- uxc`b$#:`212123e@iC(mǜrxc`@F FaexڝUvVkBV}=$t,ϾG[g:F#*}kԡ=JI\u/ q]OI$JjP.X*Y'X' VOUg eIDD&I'g%I %PB5RաLЫq@FuXTC'5ԬF*W9F/{:1x~*?vJNTqԡV0_L*@bEt1=t:.JF((ST]q@f \JltD&RN5G\BPj~"N$FXqW B1 S9tEf]1Ja= ~ N$+gQHcugPK;2C" 3a U_4g@4K)JoLh T]6)iϚbSҞ32]}iGnV!7mi/ 7FnU:viRA4aV@֌4|i`.bDGu ri".><FݰƑ0FzYM.22L e@: `\xJCT;y_9.\wy Y rcRd-T'G+'UkC*({(^瓐=B[a#Li%^S(=RC,o)< Zĸujkz !pH)]ߴwkzr*QTFڼf2)UOQYiT49Okto8h=T|4A#U5(c45t1V~hb=OUK഻*[Fm䊟#1- ;bd ; Y&wmm?&߆ErW;yՓQ%wMvYף6GN-7r,`A1wiQetm8Wvͱtz.A #.}rv!ȹ99_C0 `;!xH9!!Hȹ '|M7FNd΢@8dFM */ .foo { color: black; }fontcustom-2.0.0/spec/fixtures/shared/templates/regular.css000066400000000000000000000000631312032244500241270ustar00rootroot00000000000000/* <%= @glyphs.inspect %> */ .foo { color: blue; } fontcustom-2.0.0/spec/fixtures/shared/vectors-empty/000077500000000000000000000000001312032244500226005ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/shared/vectors-empty/no_vectors_here.txt000066400000000000000000000000001312032244500265130ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/shared/vectors/000077500000000000000000000000001312032244500214445ustar00rootroot00000000000000fontcustom-2.0.0/spec/fixtures/shared/vectors/C.svg000066400000000000000000000077321312032244500223600ustar00rootroot00000000000000 fontcustom-2.0.0/spec/fixtures/shared/vectors/D.svg000066400000000000000000000071011312032244500223470ustar00rootroot00000000000000 fontcustom-2.0.0/spec/fixtures/shared/vectors/a_R3ally-eXotic f1Le Name.svg000066400000000000000000000031761312032244500265040ustar00rootroot00000000000000 fontcustom-2.0.0/spec/fontcustom/000077500000000000000000000000001312032244500170415ustar00rootroot00000000000000fontcustom-2.0.0/spec/fontcustom/base_spec.rb000066400000000000000000000032641312032244500213170ustar00rootroot00000000000000require "spec_helper" describe Fontcustom::Base do before(:each) { allow(Fontcustom::Manifest).to receive(:write_file) } context "#compile" do context "when [:checksum][:current] equals [:checksum][:previous]" do it "should show 'no change' message" do allow(Fontcustom::Base).to receive(:check_fontforge) options = double("options") allow(options).to receive(:options).and_return({}) allow(Fontcustom::Options).to receive(:new).and_return options output = capture(:stdout) do base = Fontcustom::Base.new({}) manifest = base.instance_variable_get :@manifest expect(manifest).to receive(:get).and_return :previous => "abc" expect(base).to receive(:checksum).and_return "abc" base.compile end expect(output).to match(/No changes/) end end end context ".check_fontforge" do it "should raise error if fontforge isn't installed" do allow_any_instance_of(Fontcustom::Base).to receive(:"`").and_return("") expect { Fontcustom::Base.new(:option => "foo") }.to raise_error Fontcustom::Error, /fontforge/ end end context ".checksum" do it "should return hash of all vectors and templates" do pending "SHA2 is different on CI servers. Why?" allow(Fontcustom::Base).to receive(:check_fontforge) base = Fontcustom::Base.new(:input => {:vectors => fixture("shared/vectors")}) base.instance_variable_set :@options, { :templates => Dir.glob(File.join(fixture("shared/templates"), "*")) } hash = base.send :checksum hash.should == "81ffd2f72877be02aad673fdf59c6f9dbfee4cc37ad0b121b9486bc2923b4b36" end end end fontcustom-2.0.0/spec/fontcustom/cli_spec.rb000066400000000000000000000025451312032244500211550ustar00rootroot00000000000000require "spec_helper" require "fontcustom/cli" describe Fontcustom::CLI do context "#compile" do it "should generate fonts and templates (integration)", :integration => true do live_test do |testdir| Fontcustom::CLI.start ["compile", "vectors", "--quiet"] manifest = File.join testdir, ".fontcustom-manifest.json" preview = File.join testdir, "fontcustom", "fontcustom-preview.html" expect(Dir.glob(File.join(testdir, "fontcustom", "fontcustom_*\.{ttf,svg,woff,eot}")).length).to eq(4) expect(File.read(manifest)).to match(/"fonts":.+fontcustom\/fontcustom_.+\.ttf"/m) expect(File.exists?(preview)).to be_truthy end end it "should generate fonts and templates according to passed options (integration)", :integration => true do live_test do |testdir| Fontcustom::CLI.start ["compile", "vectors", "--font-name", "example", "--preprocessor-path", "../foo/bar", "--templates", "css", "scss-rails", "preview", "--no-hash", "--base64", "--quiet"] manifest = File.join testdir, ".fontcustom-manifest.json" css = Dir.glob(File.join("example", "*.scss")).first expect(File.read(manifest)).to match(/"fonts":.+example\/example\.ttf"/m) expect(File.read(css)).to match("x-font-woff") expect(File.read(css)).to match("../foo/bar/") end end end end fontcustom-2.0.0/spec/fontcustom/generator/000077500000000000000000000000001312032244500210275ustar00rootroot00000000000000fontcustom-2.0.0/spec/fontcustom/generator/font_spec.rb000066400000000000000000000053411312032244500233370ustar00rootroot00000000000000require "spec_helper" describe Fontcustom::Generator::Font do def generator allow_any_instance_of(Fontcustom::Manifest).to receive(:write_file) Fontcustom::Generator::Font.new("") end context "#generate" do it "should set manifest[:glyphs] (integration)", :integration => true do live_test do |testdir| test_manifest manifest = File.join Dir.pwd, ".fontcustom-manifest.json" gen = Fontcustom::Generator::Font.new manifest allow(gen).to receive(:create_fonts) gen.generate expect(File.read(manifest)).to match(/"glyphs":.+"C":/m) end end it "should generate fonts (integration)", :integration => true do live_test do |testdir| test_manifest manifest = File.join Dir.pwd, ".fontcustom-manifest.json" Fontcustom::Generator::Font.new(manifest).generate expect(Dir.glob(File.join(testdir, "fontcustom", "fontcustom_*\.{ttf,svg,woff,eot}")).length).to eq(4) expect(File.read(manifest)).to match(/"fonts":.*fontcustom\/fontcustom_.+\.ttf"/m) end end end context ".create_output_dirs" do it "should create empty dirs if they don't exist" do gen = generator options = { :output => {:fonts => "path/fonts", :vectors => "path/vectors"}, :quiet => true } gen.instance_variable_set :@options, options expect(gen).to receive(:empty_directory).with("path/fonts", :verbose => false) expect(gen).to receive(:empty_directory).with("path/vectors", :verbose => false) gen.send :create_output_dirs end end context ".set_glyph_info" do it "should set :glyphs in manifest" do gen = generator gen.instance_variable_set :@options, :input => {:vectors => fixture("shared/vectors")} manifest = gen.instance_variable_get(:@manifest) gen.send :set_glyph_info data = manifest.instance_variable_get(:@data) expect(data[:glyphs][:C]).to include(:codepoint => 61696) expect(data[:glyphs][:D]).to include(:codepoint => 61697) expect(data[:glyphs][:"a_R3ally-eXotic-f1Le-Name"]).to include(:codepoint => 61698) end it "should not change codepoints of existing glyphs" do gen = generator gen.instance_variable_set :@options, :input => {:vectors => fixture("shared/vectors")} manifest = gen.instance_variable_get(:@manifest) manifest.set :glyphs, {:C => {:source => "foo", :codepoint => 61699}} gen.send :set_glyph_info data = manifest.instance_variable_get(:@data) expect(data[:glyphs][:C]).to include(:codepoint => 61699) expect(data[:glyphs][:D]).to include(:codepoint => 61700) expect(data[:glyphs][:"a_R3ally-eXotic-f1Le-Name"]).to include(:codepoint => 61701) end end end fontcustom-2.0.0/spec/fontcustom/generator/template_spec.rb000066400000000000000000000125051312032244500242040ustar00rootroot00000000000000require "spec_helper" describe Fontcustom::Generator::Template do context "#generate" do it "should generate templates (integration)", :integration => true do live_test do |testdir| FileUtils.cp_r fixture("generators/mixed-output"), "fontcustom" test_manifest( :input => "vectors", :quiet => true, :templates => %w|preview css scss scss-rails| ) manifest = File.join testdir, ".fontcustom-manifest.json" Fontcustom::Generator::Font.new(manifest).generate Fontcustom::Generator::Template.new(manifest).generate content = File.read manifest expect(content).to match(/fontcustom\/fontcustom-preview.html/) end end end context ".set_relative_paths" do it "should assign @font_path, @font_path_alt, and @font_path_preview" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") options = gen.instance_variable_get :@options options[:output] = {:fonts => fixture("sandbox/test/fonts"), :css => fixture("sandbox/test/css"), :preview => fixture("sandbox/test")} gen.send :set_relative_paths expect(gen.instance_variable_get(:@font_path)).to match("../fonts") expect(gen.instance_variable_get(:@font_path_alt)).to match("../fonts") expect(gen.instance_variable_get(:@font_path_preview)).to match(".") end it "should assign @font_path_alt if :preprocessor_path is set" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") options = gen.instance_variable_get :@options options[:preprocessor_path] = "fonts/fontcustom" options[:output] = {:fonts => fixture("sandbox/test/fonts"), :css => fixture("sandbox/test/css"), :preview => fixture("sandbox/test")} gen.send :set_relative_paths expect(gen.instance_variable_get(:@font_path_alt)).to match("fonts/fontcustom") end it "should assign @font_path_alt as bare font name if :preprocessor_path is false" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") options = gen.instance_variable_get :@options options[:preprocessor_path] = false options[:output] = {:fonts => fixture("sandbox/test/fonts"), :css => fixture("sandbox/test/css"), :preview => fixture("sandbox/test")} gen.send :set_relative_paths expect(gen.instance_variable_get(:@font_path_alt)).to_not match("../fonts") end it "should assign '.' when paths are the same" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") options = gen.instance_variable_get :@options options[:output] = {:fonts => fixture("sandbox/test/fonts"), :css => fixture("sandbox/test/fonts"), :preview => fixture("sandbox/test/fonts")} gen.send :set_relative_paths expect(gen.instance_variable_get(:@font_path)).to match("./") end end context ".create_files" do it "should not include the template path in custom output file paths" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") # Set options to specify a custom CSS template with a custom output location. options = gen.instance_variable_get :@options options[:input][:templates] = fixture("shared/templates") options[:templates] = ['custom.css'] options[:output][:'custom.css'] = fixture("sandbox/test") # Don't update the manifest based on these options. manifest = gen.instance_variable_get :@manifest expect(manifest).to receive(:set) # Confirm that the output file doesn't include the template path. expect(gen).to receive(:template).once do |source, target| expect(source).to match("shared/templates") expect(target).to_not match("shared/templates") end gen.send :create_files end end context ".font_face" do it "should return base64 when options are set" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") allow(gen).to receive(:woff_base64).and_return("3xampled4ta") options = gen.instance_variable_get :@options options[:base64] = true expect(gen.send(:font_face)).to match("x-font-woff") end end context ".get_target_path" do it "should generate the correct preview target when using default font_name" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") options = gen.instance_variable_get :@options options[:output] = {:fonts => fixture("sandbox/test/fonts"), :preview => fixture("sandbox/test")} expect(gen.send(:get_target_path, "sandbox/test/fontcustom-preview.html")).to match("/sandbox/test/fontcustom-preview.html") end it "should generate the correct preview target when using custom font_name with output directory containing 'fontcustom'" do gen = Fontcustom::Generator::Template.new fixture("generators/.fontcustom-manifest.json") options = gen.instance_variable_get :@options options[:font_name] = 'custom' options[:output] = {:fonts => fixture("sandbox/test-fontcustom/fonts"), :preview => fixture("sandbox/test-fontcustom")} expect(gen.send(:get_target_path, "sandbox/test/fontcustom-preview.html")).to match("/sandbox/test-fontcustom/custom-preview.html") end end end fontcustom-2.0.0/spec/fontcustom/manifest_spec.rb000066400000000000000000000011471312032244500222110ustar00rootroot00000000000000require "spec_helper" describe Fontcustom::Manifest do context "#initialize" do it "should create a manifest file and assign :options", :integration => true do live_test do |testdir| capture(:stdout) do manifest = File.join testdir, ".fontcustom-manifest.json" options = Fontcustom::Options.new(:manifest => manifest, :input => "vectors").options Fontcustom::Manifest.new manifest, options end content = File.read File.join(testdir, ".fontcustom-manifest.json") expect(content).to match(/"options":.+"input":/m) end end end end fontcustom-2.0.0/spec/fontcustom/options_spec.rb000066400000000000000000000257011312032244500221000ustar00rootroot00000000000000# encoding: utf-8 require "spec_helper" describe Fontcustom::Options do def options(args = {}) args[:manifest] = fixture(".fontcustom-manifest.json") if args[:manifest].nil? Fontcustom::Options.new(args) end before(:each) do allow_any_instance_of(Fontcustom::Options).to receive(:say_message) allow_any_instance_of(Fontcustom::Options).to receive(:parse_options) end context ".overwrite_examples" do it "should overwite example defaults with real defaults" do o = options Fontcustom::EXAMPLE_OPTIONS.dup o.send :overwrite_examples cli = o.instance_variable_get(:@cli_options) Fontcustom::EXAMPLE_OPTIONS.keys.each do |key| expect(cli[key]).to eq(Fontcustom::DEFAULT_OPTIONS[key]) end end end context ".set_config_path" do context "when :config is set" do it "should use options[:config] if it's a file" do o = options :config => "options/any-file-name.yml" o.send :set_config_path expect(o.instance_variable_get(:@cli_options)[:config]).to eq("options/any-file-name.yml") end it "should search for fontcustom.yml if options[:config] is a dir" do o = options :config => "options/config-is-in-dir" o.send :set_config_path expect(o.instance_variable_get(:@cli_options)[:config]).to eq("options/config-is-in-dir/fontcustom.yml") end it "should raise error if :config doesn't exist" do o = options :config => "does-not-exist" expect { o.send :set_config_path }.to raise_error Fontcustom::Error, /configuration file/ end end context "when :config is not set" do it "should find fontcustom.yml in the same dir as the manifest" do FileUtils.cd fixture("options") do o = options o.send :set_config_path expect(o.instance_variable_get(:@cli_options)[:config]).to eq("fontcustom.yml") end end it "should find fontcustom.yml at config/fontcustom.yml" do FileUtils.cd fixture("options/rails-like") do o = options o.send :set_config_path expect(o.instance_variable_get(:@cli_options)[:config]).to eq("config/fontcustom.yml") end end it "should be false if nothing is found" do o = options :manifest => "options/no-config-here/.fontcustom-manifest.json" o.send :set_config_path expect(o.instance_variable_get(:@cli_options)[:config]).to eq(false) end end end context ".load_config" do it "should warn if fontcustom.yml is blank" do o = options o.instance_variable_set :@cli_options, {:config => fixture("options/fontcustom-empty.yml")} expect(o).to receive(:say_message).with :warn, /was empty/ o.send :load_config end it "should raise error if fontcustom.yml isn't valid" do o = options o.instance_variable_set :@cli_options, {:config => fixture("options/fontcustom-malformed.yml")} expect { o.send :load_config }.to raise_error Fontcustom::Error, /Error parsing/ end it "should assign empty hash :config is false" do o = options o.instance_variable_set :@cli_options, {:config => false} o.send :load_config expect(o.instance_variable_get(:@config_options)).to eq({}) end context "when :debug is true" do it "should report which configuration file it's using" do o = options o.instance_variable_set :@cli_options, { :config => fixture("options/any-file-name.yml"), :debug => true } expect(o).to receive(:say_message).with :debug, /Using settings/ o.send :load_config end end end context ".merge_options" do it "should overwrite defaults with config options" do o = options o.instance_variable_set :@config_options, { :input => "config" } o.send :merge_options expect(o.options[:input]).to eq("config") end it "should overwrite config file and defaults with CLI options" do o = options o.instance_variable_set :@config_options, { :input => "config", :output => "output" } o.instance_variable_set :@cli_options, { :input => "cli" } o.send :merge_options expect(o.options[:input]).to eq("cli") expect(o.options[:output]).to eq("output") end end context ".clean_font_name" do it "should normalize the font name" do o = options o.instance_variable_set :@options, { :font_name => " A_stR4nG3 nAm3 Ø& " } o.send :clean_font_name expect(o.options[:font_name]).to eq("A_stR4nG3--nAm3---") end end context ".set_input_paths" do it "should raise error if input[:vectors] doesn't contain SVGs" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :input => "vectors-empty" } expect { o.send :set_input_paths }.to raise_error Fontcustom::Error, /doesn't contain any SVGs/ end end context "when :input is a hash" do it "should set :templates as :vectors if :templates isn't set" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :input => { :vectors => "vectors" } } o.send :set_input_paths expect(o.options[:input][:templates]).to eq("vectors") end end it "should preserve :templates if it's set" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :input => { :vectors => "vectors", :templates => "templates" } } o.send :set_input_paths expect(o.options[:input][:templates]).to eq("templates") end end it "should raise an error if :vectors isn't set" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :input => { :templates => "templates" } } expect { o.send :set_input_paths }.to raise_error Fontcustom::Error, /have a :vectors key/ end end it "should raise an error if :vectors doesn't point to an existing directory" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :config => "fontcustom.yml", :input => { :vectors => "not-a-dir" } } expect { o.send :set_input_paths }.to raise_error Fontcustom::Error, /isn't a directory/ end end end context "when :input is a string" do it "should return a hash of locations" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :input => "vectors" } o.send :set_input_paths expect(o.options[:input]).to have_key(:vectors) expect(o.options[:input]).to have_key(:templates) end end it "should set :templates to match :vectors" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :input => "vectors" } o.send :set_input_paths expect(o.options[:input][:templates]).to eq("vectors") end end it "should raise an error if :vectors doesn't point to a directory" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :config => "fontcustom.yml", :input => "not-a-dir" } expect { o.send :set_input_paths }.to raise_error Fontcustom::Error, /isn't a directory/ end end end end context ".set_output_paths" do context "when :output is nil" do context "when :debug is true" do it "should print a warning" do o = options o.instance_variable_set :@options, { :debug => true, :font_name => "Test-Font" } expect(o).to receive(:say_message).with :debug, /Test-Font/ o.send :set_output_paths end end end context "when :output is a hash" do it "should set :css and :preview to match :fonts if either aren't set" do o = options o.instance_variable_set :@options, { :output => { :fonts => "output/fonts" } } o.send :set_output_paths expect(o.options[:output][:css]).to eq("output/fonts") expect(o.options[:output][:preview]).to eq("output/fonts") end it "should preserve :css and :preview if they do exist" do o = options o.instance_variable_set :@options, { :output => { :fonts => "output/fonts", :css => "output/styles", :preview => "output/preview" } } o.send :set_output_paths expect(o.options[:output][:css]).to eq("output/styles") expect(o.options[:output][:preview]).to eq("output/preview") end it "should create additional paths if they are given" do o = options o.instance_variable_set :@options, { :output => { :fonts => "output/fonts", "special.js" => "assets/javascripts" } } o.send :set_output_paths expect(o.options[:output][:"special.js"]).to eq("assets/javascripts") end it "should raise an error if :fonts isn't set" do o = options o.instance_variable_set :@options, { :config => "fontcustom.yml", :output => { :css => "output/styles" } } expect { o.send :set_output_paths }.to raise_error Fontcustom::Error, /have a :fonts key/ end end context "when :output is a string" do it "should return a hash of output locations" do o = options o.instance_variable_set :@options, { :output => "output/fonts" } o.send :set_output_paths expect(o.options[:output]).to be_a(Hash) expect(o.options[:output]).to have_key(:fonts) expect(o.options[:output]).to have_key(:css) expect(o.options[:output]).to have_key(:preview) end it "should set :css and :preview to match :fonts" do o = options o.instance_variable_set :@options, { :output => "output/fonts" } o.send :set_output_paths expect(o.options[:output][:css]).to eq("output/fonts") expect(o.options[:output][:preview]).to eq("output/fonts") end it "should raise an error if :fonts exists but isn't a directory" do FileUtils.cd fixture("shared") do o = options o.instance_variable_set :@options, { :config => "fontcustom.yml", :output => "not-a-dir" } expect { o.send :set_output_paths }.to raise_error Fontcustom::Error, /isn't a directory/ end end end end context ".check_template_paths" do it "should raise an error if a template does not exist" do o = options o.instance_variable_set :@options, { :input => { :templates => fixture("shared/templates") }, :templates => %w|fake-template.txt| } expect { o.send :check_template_paths }.to raise_error Fontcustom::Error, /wasn't found/ end end end fontcustom-2.0.0/spec/fontcustom/utility_spec.rb000066400000000000000000000043241312032244500221060ustar00rootroot00000000000000require "spec_helper" describe Fontcustom::Utility do class Generator include Fontcustom::Utility attr_accessor :options, :manifest def initialize @options = { :quiet => false } @manifest = fixture ".fontcustom-manifest.json" end end it "should include Thor::Action methods" do gen = Generator.new %w|template add_file remove_file|.each do |method| expect(gen).to respond_to(method.to_sym) end end context "#symbolize_hash" do it "should turn string keys into symbols" do gen = Generator.new hash = gen.symbolize_hash "foo" => "bar" expect(hash).to eq({ :foo => "bar" }) end end context "#methodize_hash" do it "should define getter method" do gen = Generator.new hash = gen.methodize_hash :foo => "bar" expect(hash.foo).to eq("bar") end it "should define setter method" do gen = Generator.new hash = gen.methodize_hash :foo => "bar" hash.foo = "baz" expect(hash.foo).to eq("baz") end end context "#write_file" do it "should replace the contents of a file" do gen = Generator.new file = double "file" expect(File).to receive(:open).with(fixture("shared/test"), "w").and_yield file expect(file).to receive(:write).with("testing") gen.write_file fixture("shared/test"), "testing" end end #context "#say_message" do #it "should not respond if :quiet is true" do #pending #gen = Generator.new #gen.options[:quiet] = true #output = capture(:stdout) { gen.say_message(:test, "Hello") } #output.should == "" #end #end #context "#say_changed" do #it "should strip :project_root from changed paths" do #pending #changed = %w|a b c|.map { |file| fixture(file) } #gen = Generator.new #output = capture(:stdout) { gen.say_changed(:success, changed) } #output.should_not match(fixture) #end #it "should not respond if :quiet is true " do #pending #changed = %w|a b c|.map { |file| fixture(file) } #gen = Generator.new #gen.options[:quiet] = true #output = capture(:stdout) { gen.say_changed(:success, changed) } #output.should == "" #end #end end fontcustom-2.0.0/spec/fontcustom/watcher_spec.rb000066400000000000000000000061321312032244500220370ustar00rootroot00000000000000require "spec_helper" require "fileutils" require "fontcustom/watcher" describe Fontcustom::Watcher do # Silence messages without passing :quiet => true to everything #before(:each) do #Fontcustom::Options.any_instance.stub :say_message #end def watcher(options) allow_any_instance_of(Fontcustom::Manifest).to receive(:write_file) allow_any_instance_of(Fontcustom::Base).to receive(:compile) # undocumented — non-blocking use of watcher for testing Fontcustom::Watcher.new options, true end context "#watch" do it "should compile on init" do expect_any_instance_of(Fontcustom::Base).to receive(:compile).once w = watcher( :input => "shared/vectors", :output => "output" ) # silence output capture(:stdout) do w.watch w.send :stop end end it "should not call generators on init if options[:skip_first] is passed" do expect_any_instance_of(Fontcustom::Base).to_not receive(:compile).once w = watcher( :input => "shared/vectors", :output => "output", :skip_first => true ) capture(:stdout) do w.watch w.send :stop end end it "should call generators when vectors change" do expect_any_instance_of(Fontcustom::Base).to receive(:compile).once w = watcher( :input => "shared/vectors", :output => "output", :skip_first => true ) capture(:stdout) do begin w.watch FileUtils.cp fixture("shared/vectors/C.svg"), fixture("shared/vectors/test.svg") sleep 1 ensure w.send :stop new = fixture("shared/vectors/test.svg") FileUtils.rm(new) if File.exists?(new) end end end it "should call generators when custom templates change" do expect_any_instance_of(Fontcustom::Base).to receive(:compile) w = watcher( :input => {:vectors => "shared/vectors", :templates => "shared/templates"}, :templates => %w|css preview custom.css|, :output => "output", :skip_first => false ) capture(:stdout) do begin template = fixture "shared/templates/custom.css" content = File.read template new = content + "\n.bar { color: red; }" w.watch sleep 1 File.open(template, "w") { |file| file.write(new) } sleep 1 ensure w.send :stop File.open(template, "w") { |file| file.write(content) } end end end it "should do nothing when non-vectors change" do expect_any_instance_of(Fontcustom::Base).to_not receive(:compile).once w = watcher( :input => "shared/vectors", :output => "output", :skip_first => true ) capture(:stdout) do begin w.watch FileUtils.touch fixture("shared/vectors/non-vector-file") ensure w.send :stop new = fixture("shared/vectors/non-vector-file") FileUtils.rm(new) if File.exists?(new) end end end end end fontcustom-2.0.0/spec/spec_helper.rb000066400000000000000000000054601312032244500174630ustar00rootroot00000000000000require "rspec" require "json" require "fileutils" require File.expand_path("../../lib/fontcustom.rb", __FILE__) RSpec.configure do |c| c.before(:all) do FileUtils.cd fixture puts "Running `cd #{Dir.pwd}`" end def fixture(path = "") File.join(File.expand_path("../fixtures", __FILE__), path) end def manifest_contents { :checksum => { :current => "82a59e769bc60192484f2620570bbb59e225db97c1aac3f242a2e49d6060a19c", :previous => "82a59e769bc60192484f2620570bbb59e225db97c1aac3f242a2e49d6060a19c" }, :fonts => [ "fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.ttf", "fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.svg", "fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.woff", "fontcustom/fontcustom_82a59e769bc60192484f2620570bbb59.eot" ], :glyphs => { :"a_r3ally-exotic-f1le-name" => { :codepoint => 61696, :source => "vectors/a_R3ally-eXotic f1Le Name.svg" }, :c => { :codepoint => 61697, :source => "vectors/C.svg" }, :d => { :codepoint => 61698, :source => "vectors/D.svg"} }, :options => { :autowidth => false, :config => false, :css_selector => ".icon-{{glyph}}", :debug => false, :font_name => "fontcustom", :force => true, :input => { :templates => "vectors", :vectors => "vectors" }, :no_hash => false, :output => { :css => "fontcustom", :fonts => "fontcustom", :preview => "fontcustom" }, :preprocessor_path => nil, :quiet => true, :templates => [ "css", "scss", "preview" ] }, :templates => [] } end def fontforge_stderr "Copyright (c) 2000-2012 by George Williams.\n Executable based on sources from 14:57 GMT 31-Jul-2012-D.\n Library based on sources from 14:57 GMT 31-Jul-2012.\n" end def capture(stream) begin stream = stream.to_s eval "$#{stream} = StringIO.new" yield result = eval("$#{stream}").string ensure eval("$#{stream} = #{stream.upcase}") end result end def live_test testdir = fixture File.join("sandbox", "test") FileUtils.rm_r testdir if File.directory?(testdir) FileUtils.mkdir testdir FileUtils.cp_r fixture("shared/vectors"), testdir FileUtils.cd testdir do yield(testdir) end end def test_manifest(options = { :input => "vectors", :quiet => true }) base = Fontcustom::Base.new options manifest = base.instance_variable_get :@manifest checksum = base.send :checksum manifest.set :checksum, { :current => checksum, :previous => "" } manifest end end