blacksmith 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -17,4 +17,9 @@ test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
19
 
20
- .DS_Store
20
+ .DS_Store
21
+ examples/*.ttf
22
+ examples/*.svg
23
+ examples/*.afm
24
+ examples/*.eot
25
+ examples/*.woff
data/README.md CHANGED
@@ -15,6 +15,19 @@ And then execute:
15
15
  Or install it yourself as:
16
16
 
17
17
  $ gem install blacksmith
18
+
19
+ ## Pitfalls
20
+
21
+ ### SVG Viewport
22
+
23
+ Make sure that all your SVGs contain a `viewBox` definition. Otherwise, weird
24
+ things might happen.
25
+
26
+ ### OS X and ttfautohint
27
+
28
+ If you don't have `ttfautohint` installed and don't want to compile it from
29
+ source, you can grab a precompiled version from
30
+ [SourceForge](http://sourceforge.net/projects/freetype/files/ttfautohint/).
18
31
 
19
32
  ## Usage
20
33
 
@@ -3,12 +3,8 @@ require 'blacksmith'
3
3
 
4
4
  Blacksmith.forge do
5
5
 
6
- name "Blacksmith Medium"
7
6
  family "Blacksmith"
8
7
 
9
- source File.expand_path('../src', __FILE__)
10
- target File.expand_path('../blacksmith-medium.ttf', __FILE__)
11
-
12
8
  glyph 'star', code: 0x0061
13
9
 
14
10
  end
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg x="0" y="0" width="128px" height="128px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" version="1.1">
3
+ <g fill="rgb(215,215,215)" id="Page 1">
4
+ <g fill="rgb(0,0,0)" id="Star 56"></g>
5
+ <path id="Star 56" d="M63,88 L26,115 L40,71 L3,44 L49,43 L64,0 L78,43 L124,44 L87,71 L101,115 L63,88 Z M63,88" fill="rgb(0,0,0)"></path>
6
+ <path id="Star 56 decoration" d="" fill="rgb(0,0,0)"></path>
7
+ </g>
8
+ </svg>
@@ -1,58 +1,75 @@
1
1
  require 'smart_properties'
2
2
  require 'erb'
3
3
  require 'tempfile'
4
+ require 'singleton'
4
5
 
5
- require 'blacksmith/font'
6
- require 'blacksmith/font_builder'
7
- require 'blacksmith/glyph'
8
- require 'blacksmith/version'
9
-
10
- module Blacksmith
6
+ class Blacksmith
7
+ Point = Struct.new(:x, :y)
11
8
 
9
+ class Error < ::RuntimeError; end
10
+ class DependencyMissing < Error; end
11
+
12
12
  class << self
13
+
14
+ def forge(*args, &block)
15
+ new(*args, &block).forge
16
+ end
13
17
 
14
- def forge(filename = nil, &block)
15
- font = prepare_font(&block)
16
- forge_font(font)
17
- end
18
-
19
- private
20
-
21
- def prepare_font(&block)
22
- FontBuilder.new(&block).build
23
- end
24
-
25
- def forge_font(font)
26
- path = create_forging_instructions(font)
27
- create_font_from_instructions(path)
28
- File.unlink(path)
29
- end
30
-
31
- def create_forging_instructions(font)
32
- script = Tempfile.new('fontforge-instructions')
33
-
34
- script << font.instance_exec(forging_template) do |template|
35
- template.result(binding)
36
- end
37
-
38
- script.close
39
- script.path
40
- end
41
-
42
- def forging_template
43
- template = File.read(File.join(root_directory, 'support', 'build.py.erb'))
44
- ERB.new(template, nil, '<>')
45
- end
46
-
47
- def root_directory
48
- File.expand_path('../..', __FILE__)
49
- end
50
-
51
- def create_font_from_instructions(path)
52
- `fontforge -lang=py -script #{path}`
53
- end
18
+ def root_directory
19
+ File.expand_path('../..', __FILE__)
20
+ end
54
21
 
22
+ def support_directory
23
+ File.join(root_directory, 'support')
24
+ end
25
+
55
26
  end
56
27
 
28
+ def initialize(filename = nil, &block)
29
+ @font = build_font(&block)
30
+ end
57
31
 
32
+ def forge
33
+ check_environment
34
+
35
+ forge_font
36
+ auto_hint_font
37
+ convert_font
38
+ end
39
+
40
+ protected
41
+
42
+ attr_reader :font
43
+
44
+ def build_font(&block)
45
+ FontBuilder.execute(&block)
46
+ end
47
+
48
+ def check_environment
49
+ FontForge.check_dependency!
50
+ TTFAutoHint.check_dependency!
51
+ end
52
+
53
+ def forge_font
54
+ FontForge.execute(font.to_fontforge_build_instructions)
55
+ end
56
+
57
+ def auto_hint_font
58
+ TTFAutoHint.execute(font.ttf_path)
59
+ end
60
+
61
+ def convert_font
62
+ FontForge.execute(font.to_fontforge_conversion_instructions)
63
+ end
64
+
58
65
  end
66
+
67
+ require 'blacksmith/executable'
68
+ require 'blacksmith/font_forge'
69
+ require 'blacksmith/ttf_auto_hint'
70
+
71
+ require 'blacksmith/font'
72
+ require 'blacksmith/font_builder'
73
+ require 'blacksmith/glyph'
74
+
75
+ require 'blacksmith/version'
@@ -0,0 +1,33 @@
1
+ class Blacksmith::Executable
2
+ include Singleton
3
+
4
+ class << self
5
+
6
+ def method_missing(name, *args, &block)
7
+ instance.send(name, *args, &block)
8
+ end
9
+
10
+ end
11
+
12
+ def executable
13
+ raise NotImplementedError, "#{self.class} is expected to implement #executable"
14
+ end
15
+
16
+ def installed?
17
+ @installed unless @installed.nil?
18
+
19
+ paths = ENV['PATH'].split(File::PATH_SEPARATOR)
20
+ extensions = ENV['PATHEXT'] ? ENV['PATHEXT'].split(File::PATH_SEPARATOR) : ['']
21
+
22
+ @installed = !!paths.find do |path|
23
+ extensions.find do |ext|
24
+ File.executable?(File.join(path, "#{executable}#{ext}"))
25
+ end
26
+ end
27
+ end
28
+
29
+ def check_dependency!
30
+ raise Blacksmith::DependencyMissing, "#{executable} not installed" unless installed?
31
+ end
32
+
33
+ end
@@ -1,52 +1,112 @@
1
- module Blacksmith
2
- class Font
3
- include SmartProperties
4
-
5
- property :name, :required => true,
1
+ class Blacksmith::Font
2
+ include SmartProperties
3
+
4
+ property :name, :converts => :to_s
5
+
6
+ property :family, :required => true,
6
7
  :converts => :to_s
7
-
8
- property :family, :required => true,
9
- :converts => :to_s
10
-
11
- property :copyright, :converts => :to_s
12
-
13
- property :ascent, :required => true,
14
- :converts => :to_i,
15
- :accepts => lambda { |number| number > 0 },
16
- :default => 800
17
-
18
- property :descent, :required => true,
19
- :converts => :to_i,
20
- :accepts => lambda { |number| number > 0 },
21
- :default => 200
22
8
 
23
- property :weight, :required => true,
24
- :default => 'Medium'
25
-
26
- property :version, :required => true,
27
- :converts => :to_s,
28
- :accepts => lambda { |v| /\d+\.\d+(\.\d+)?/.match(v) },
29
- :default => '1.0'
9
+ property :weight, :required => true,
10
+ :default => 'Regular'
11
+
12
+ property :copyright, :converts => :to_s
13
+
14
+ property :ascent, :required => true,
15
+ :converts => :to_i,
16
+ :accepts => lambda { |number| number > 0 },
17
+ :default => 800
18
+
19
+ property :descent, :required => true,
20
+ :converts => :to_i,
21
+ :accepts => lambda { |number| number > 0 },
22
+ :default => 200
23
+
24
+ property :version, :required => true,
25
+ :converts => :to_s,
26
+ :accepts => lambda { |v| /\d+\.\d+(\.\d+)?/.match(v) },
27
+ :default => '1.0'
28
+
29
+ property :baseline, :converts => :to_f,
30
+ :accepts => lambda { |baseline| baseline > 0.0 }
31
+
32
+ property :scale, :converts => :to_f,
33
+ :accepts => lambda { |scale| scale > 0.0 }
34
+
35
+ property :offset, :converts => :to_f,
36
+ :accepts => lambda { |offset| offset <= 1.0 and offset >= -1.0 }
37
+
38
+ property :source, :required => true,
39
+ :converts => :to_s,
40
+ :accepts => lambda { |path| File.directory?(path) },
41
+ :default => 'glyphs'
42
+
43
+ property :target, :required => true,
44
+ :converts => :to_s,
45
+ :accepts => lambda { |path| File.directory?(path) },
46
+ :default => '.'
47
+
48
+ [:ttf, :eot, :woff, :svg].each do |extension|
49
+ name = "#{extension}_path"
30
50
 
31
- property :filename, :required => true,
32
- :converts => :to_s,
33
- :accepts => lambda { |target|
34
- File.extname(target) == '.ttf' and
35
- File.directory?(File.dirname(target))
36
- }
51
+ property name, :converts => :to_s,
52
+ :accepts => lambda { |filename|
53
+ File.extname(filename) == ".#{extension}" and
54
+ File.directory?(File.dirname(filename))
55
+ }
37
56
 
38
- def identifier
39
- name.gsub(/\W+/, '_')
57
+ define_method(name) do
58
+ super() || File.join(target, "#{basename}.#{extension}")
40
59
  end
41
60
 
42
- def glyphs
43
- (@glyphs || []).dup
61
+ end
62
+
63
+ def name
64
+ super || [family, weight].join(' ')
65
+ end
66
+
67
+ def identifier
68
+ name.gsub(/\W+/, '_').downcase
69
+ end
70
+
71
+ def basename
72
+ [family.gsub(/\W+/, ''), weight.gsub(/\W+/, '')].join('-')
73
+ end
74
+
75
+ def glyphs
76
+ (@glyphs || []).dup
77
+ end
78
+
79
+ def <<(glyph)
80
+ @glyphs ||= []
81
+ @glyphs << glyph
82
+ end
83
+
84
+ def baseline
85
+ super or (1.0 * descent) / (ascent + descent)
86
+ end
87
+
88
+ def origin
89
+ Blacksmith::Point.new(0, (ascent + descent) * baseline - descent)
90
+ end
91
+
92
+ def to_fontforge_build_instructions
93
+ fontforge_build_instructions_template.result(binding)
94
+ end
95
+
96
+ def to_fontforge_conversion_instructions
97
+ fontforge_conversion_instructions_template.result(binding)
98
+ end
99
+
100
+ private
101
+
102
+ def fontforge_build_instructions_template
103
+ template = File.read(File.join(Blacksmith.support_directory, 'fontforge_build_instructions.py.erb'))
104
+ ERB.new(template)
44
105
  end
45
106
 
46
- def <<(glyph)
47
- @glyphs ||= []
48
- @glyphs << glyph
107
+ def fontforge_conversion_instructions_template
108
+ template = File.read(File.join(Blacksmith.support_directory, 'fontforge_conversion_instructions.py.erb'))
109
+ ERB.new(template)
49
110
  end
50
-
51
- end
111
+
52
112
  end
@@ -1,55 +1,45 @@
1
- module Blacksmith
2
- class FontBuilder
1
+ class Blacksmith::FontBuilder
2
+ class << self
3
3
 
4
- def initialize(&block)
5
- @_instructions = block
6
- @_attributes = {}
7
- @_glyphs = {}
8
- @_source = '.'
4
+ def execute(&block)
5
+ new(&block).execute
9
6
  end
10
7
 
11
- def build
12
- instance_eval(&@_instructions)
13
-
14
- font = Font.new(@_attributes)
15
-
16
- @_glyphs.each do |name, attrs|
17
- attrs[:outline] ||= File.join(@_source, "#{name}.svg")
18
- font << Glyph.new(attrs)
19
- end
20
-
21
- font
22
- end
23
-
24
- def source(path = nil)
25
- if path
26
- raise ArgumentError, "Directory does not exist: #{path}" unless File.directory?(path)
27
- @_source = path
28
- else
29
- @_source
30
- end
31
- end
8
+ end
9
+
10
+ def initialize(&block)
11
+ @_instructions = block
12
+ @_attributes = {}
13
+ @_glyphs = {}
14
+ @_source = '.'
15
+ end
16
+
17
+ def execute
18
+ instance_eval(&@_instructions)
32
19
 
33
- def target(path = nil)
34
- if path
35
- raise ArgumentError, "Directory does not exist: #{path}" unless File.directory?(File.dirname(path))
36
- @_attributes[:filename] = path
37
- else
38
- @_attributes[:filename]
39
- end
40
- end
20
+ font = Blacksmith::Font.new(@_attributes)
41
21
 
42
- def glyph(name, attrs)
43
- @_glyphs[name] = attrs
22
+ @_glyphs.each do |name, attrs|
23
+ attrs[:scale] ||= font.scale
24
+ attrs[:offset] ||= font.offset
25
+ attrs[:outline] ||= File.join(font.source, "#{name}.svg")
26
+
27
+ font << Blacksmith::Glyph.new(attrs)
44
28
  end
45
29
 
46
- def method_missing(name, *args)
47
- if args.length == 1
48
- @_attributes[name] = args[0]
49
- else
50
- super
51
- end
30
+ font
31
+ end
32
+
33
+ def glyph(name, attrs)
34
+ @_glyphs[name] = attrs
35
+ end
36
+
37
+ def method_missing(name, *args)
38
+ if args.length == 1
39
+ @_attributes[name] = args[0]
40
+ else
41
+ super
52
42
  end
53
-
54
43
  end
44
+
55
45
  end
@@ -0,0 +1,40 @@
1
+ class Blacksmith::FontForge < Blacksmith::Executable
2
+
3
+ def executable
4
+ 'fontforge'
5
+ end
6
+
7
+ def execute(file_or_instructions)
8
+ check_dependency!
9
+
10
+ case file_or_instructions
11
+ when String
12
+ with_temporary_instuctions_file(file_or_instructions) do |path|
13
+ execute!(path)
14
+ end
15
+ when File
16
+ path = file_or_instructions.path
17
+ execute!(path)
18
+ else
19
+ raise ArgumentError, "FontForge#execute expects a File or String"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def execute!(path)
26
+ system(executable, '-lang=py', '-script', path)
27
+ end
28
+
29
+ def with_temporary_instuctions_file(instructions)
30
+ script = Tempfile.new('fontforge-instructions')
31
+ script.puts(instructions)
32
+ script.close
33
+
34
+ yield script.path
35
+ ensure
36
+ script.close unless script.closed?
37
+ script.unlink
38
+ end
39
+
40
+ end
@@ -1,24 +1,28 @@
1
- module Blacksmith
2
- class Glyph
3
- include SmartProperties
4
-
5
- property :outline, :required => true,
6
- :converts => :to_s,
7
- :accepts => lambda { |filename| File.exist?(filename) }
8
-
9
- property :code, :required => true
10
-
11
- property :left_side_bearing, :required => true,
12
- :converts => :to_i,
13
- :default => 15
1
+ class Blacksmith::Glyph
2
+ include SmartProperties
3
+
4
+ property :outline, :required => true,
5
+ :converts => :to_s,
6
+ :accepts => lambda { |filename| File.exist?(filename) }
7
+
8
+ property :code, :required => true
9
+
10
+ property :left_side_bearing, :required => true,
11
+ :converts => :to_i,
12
+ :default => 15
14
13
 
15
- property :right_side_bearing, :required => true,
16
- :converts => :to_i,
17
- :default => 15
18
-
19
- def name
20
- File.basename(outline)
21
- end
22
-
14
+ property :right_side_bearing, :required => true,
15
+ :converts => :to_i,
16
+ :default => 15
17
+
18
+ property :scale, :converts => :to_f,
19
+ :accepts => lambda { |scale| scale > 0.0 }
20
+
21
+ property :offset, :converts => :to_f,
22
+ :accepts => lambda { |offset| offset <= 1.0 and offset >= -1.0 }
23
+
24
+ def name
25
+ File.basename(outline)
23
26
  end
27
+
24
28
  end
@@ -0,0 +1,37 @@
1
+ class Blacksmith::TTFAutoHint < Blacksmith::Executable
2
+
3
+ def executable
4
+ 'ttfautohint'
5
+ end
6
+
7
+ def execute(source)
8
+ check_dependency!
9
+
10
+ target = begin
11
+ f = Tempfile.new(File.basename(source))
12
+ f.close
13
+ f.path
14
+ end
15
+
16
+ build_hinted_font(source, target)
17
+ replace_file(source, target)
18
+ end
19
+
20
+ private
21
+
22
+ def replace_file(source, target)
23
+ FileUtils.mv(target, source)
24
+ end
25
+
26
+ def build_hinted_font(source, target)
27
+ args = %w{
28
+ --latin-fallback
29
+ --hinting-limit=200
30
+ --hinting-range-max=50
31
+ --symbol
32
+ }
33
+
34
+ system(executable, *args, source, target)
35
+ end
36
+
37
+ end
@@ -1,3 +1,3 @@
1
- module Blacksmith
2
- VERSION = "0.0.1"
1
+ class Blacksmith
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,56 @@
1
+ import fontforge
2
+ import psMat
3
+
4
+ # --------------------------------------------------------------------------
5
+ # Helper functions
6
+ # --------------------------------------------------------------------------
7
+
8
+ def scale(char, origin, scale):
9
+ """Rescale glyph"""
10
+ # move scale origin point to (0, 0)
11
+ sx, sy = origin
12
+ translate_matrix = psMat.translate(-sx, -sy)
13
+ char.transform(translate_matrix)
14
+
15
+ # scale around (0, 0)
16
+ scale_matrix = psMat.scale(scale)
17
+ char.transform(scale_matrix)
18
+
19
+ # move scale origin point back to its old position
20
+ translate_matrix = psMat.translate(origin)
21
+ char.transform(translate_matrix)
22
+
23
+ def position(char, offset):
24
+ """Repositions the glyph"""
25
+ offset_matrix = psMat.translate(0, offset * <%= ascent + descent %>)
26
+ char.transform(offset_matrix)
27
+
28
+ # --------------------------------------------------------------------------
29
+ # Forging instructions
30
+ # --------------------------------------------------------------------------
31
+
32
+ font = fontforge.font()
33
+
34
+ font.encoding = 'UnicodeFull'
35
+ font.version = "<%= version %>"
36
+ font.fontname = "<%= identifier %>"
37
+ font.fullname = "<%= name %>"
38
+ font.familyname = "<%= family %>"
39
+ font.copyright = "<%= copyright %>"
40
+ font.ascent = <%= ascent %>
41
+ font.descent = <%= descent %>
42
+ font.weight = "<%= weight %>"
43
+
44
+ <% for glyph in glyphs do %>
45
+ # <%= glyph.name %>
46
+ c = font.createChar(<%= glyph.code %>)
47
+ c.importOutlines("<%= glyph.outline %>")
48
+ c.left_side_bearing = <%= glyph.left_side_bearing %>
49
+ c.right_side_bearing = <%= glyph.right_side_bearing %>
50
+ c.simplify()
51
+ c.round()
52
+ <%= "scale(c, (#{origin.x}, #{origin.y}), #{glyph.scale})" if glyph.scale %>
53
+ <%= "position(c, #{glyph.offset})" if glyph.offset %>
54
+ <% end %>
55
+
56
+ font.generate("<%= ttf_path %>")
@@ -0,0 +1,6 @@
1
+ import fontforge
2
+
3
+ font = fontforge.open("<%= ttf_path %>")
4
+ font.generate("<%= svg_path %>")
5
+ font.generate("<%= eot_path %>")
6
+ font.generate("<%= woff_path %>")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blacksmith
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-10 00:00:00.000000000 Z
12
+ date: 2012-06-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: smart_properties
@@ -41,13 +41,17 @@ files:
41
41
  - Rakefile
42
42
  - blacksmith.gemspec
43
43
  - examples/forging_fonts.rb
44
- - examples/src/star.svg
44
+ - examples/glyphs/star.svg
45
45
  - lib/blacksmith.rb
46
+ - lib/blacksmith/executable.rb
46
47
  - lib/blacksmith/font.rb
47
48
  - lib/blacksmith/font_builder.rb
49
+ - lib/blacksmith/font_forge.rb
48
50
  - lib/blacksmith/glyph.rb
51
+ - lib/blacksmith/ttf_auto_hint.rb
49
52
  - lib/blacksmith/version.rb
50
- - support/build.py.erb
53
+ - support/fontforge_build_instructions.py.erb
54
+ - support/fontforge_conversion_instructions.py.erb
51
55
  homepage: http://github.com/t6d/blacksmith
52
56
  licenses: []
53
57
  post_install_message:
@@ -1,24 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" version="1.1">
3
- <title></title>
4
- <description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
5
- <defs>
6
- <linearGradient id="gradient-1" x1="50%" y1="0%" x2="50%" y2="100%">
7
- <stop offset="0%" style="stop-color:rgb(255,255,255); stop-opacity:100"></stop>
8
- <stop offset="100%" style="stop-color:rgb(0,0,0); stop-opacity:100"></stop>
9
- </linearGradient>
10
- <linearGradient id="gradient-2" x1="50%" y1="0%" x2="50%" y2="100%">
11
- <stop offset="0%" style="stop-color:rgb(255,255,255); stop-opacity:100"></stop>
12
- <stop offset="100%" style="stop-color:rgb(0,0,0); stop-opacity:100"></stop>
13
- </linearGradient>
14
- <linearGradient id="gradient-3" x1="50%" y1="0%" x2="50%" y2="100%">
15
- <stop offset="0%" style="stop-color:rgb(255,255,255); stop-opacity:100"></stop>
16
- <stop offset="100%" style="stop-color:rgb(0,0,0); stop-opacity:100"></stop>
17
- </linearGradient>
18
- </defs>
19
- <g fill="rgb(215,215,215)" id="Page 1">
20
- <g fill="rgb(0,0,0)" id="Star 55"></g>
21
- <path id="Star 55" d="M65,90 L35,106 L41,72 L16,48 L50,43 L65,13 L80,43 L114,48 L89,72 L95,106 L65,90 Z M65,90" fill="rgb(0,0,0)"></path>
22
- <path id="Star 55 decoration" d="" fill="rgb(0,0,0)"></path>
23
- </g>
24
- </svg>
@@ -1,25 +0,0 @@
1
- import fontforge
2
-
3
- font = fontforge.font()
4
-
5
- font.version = "<%= version %>"
6
- font.fontname = "<%= identifier %>"
7
- font.fullname = "<%= name %>"
8
- font.familyname = "<%= family %>"
9
- font.copyright = "<%= copyright %>"
10
- font.ascent = <%= ascent %>
11
- font.descent = <%= descent %>
12
- font.weight = "<%= weight %>"
13
-
14
- <% for glyph in glyphs do %>
15
- # <%= glyph.name %>
16
- c = font.createChar(<%= glyph.code %>)
17
- c.importOutlines("<%= glyph.outline %>")
18
- c.left_side_bearing = <%= glyph.left_side_bearing %>
19
- c.right_side_bearing = <%= glyph.right_side_bearing %>
20
- c.simplify()
21
- c.round()
22
-
23
- <% end %>
24
-
25
- font.generate("<%= filename %>")