blacksmith 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>")