Class: Fontisan::Ufo::Glyph
- Inherits:
-
Object
- Object
- Fontisan::Ufo::Glyph
- Defined in:
- lib/fontisan/ufo/glyph.rb
Overview
A glyph in a UFO source. Holds contours, components, anchors,
guidelines, images, unicode codepoints, advance width/height,
and a custom-data bag (lib).
The .glif XML format has multiple revisions (1, 2, 3); the
parser accepts all of them.
Constant Summary collapse
- DEFAULT_UNITS_PER_EM =
OpenType requires GID 0 to be a glyph named
.notdef. UFO sources that lack an explicit.notdef(synthetic SVG donors, programmatic sources) need one injected before compile. This constant is the default width used for that injected glyph, and doubles as the fallback unitsPerEm in Head.build when UFO info omits the field. Single source of truth for both call sites. 1000
Instance Attribute Summary collapse
-
#anchors ⇒ Object
readonly
Returns the value of attribute anchors.
-
#components ⇒ Object
readonly
Returns the value of attribute components.
-
#contours ⇒ Object
readonly
Returns the value of attribute contours.
-
#guidelines ⇒ Object
readonly
Returns the value of attribute guidelines.
-
#height ⇒ Object
Returns the value of attribute height.
-
#images ⇒ Object
readonly
Returns the value of attribute images.
-
#lib ⇒ Object
Returns the value of attribute lib.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#note ⇒ Object
Returns the value of attribute note.
-
#unicodes ⇒ Object
readonly
Returns the value of attribute unicodes.
-
#width ⇒ Object
Returns the value of attribute width.
Class Method Summary collapse
-
.from_glif(xml) ⇒ Glyph
Parsed glyph.
-
.notdef(units_per_em: nil) ⇒ Glyph
Synthesize an empty
.notdefglyph suitable for GID 0.
Instance Method Summary collapse
- #add_anchor(anchor) ⇒ Object
- #add_component(component) ⇒ Object
- #add_contour(contour) ⇒ Object
- #add_guideline(guideline) ⇒ Object
- #add_image(image) ⇒ Object
- #add_unicode(codepoint) ⇒ Object
-
#bbox ⇒ BoundingBox?
Axis-aligned bbox of contours.
-
#composite? ⇒ Boolean
Composite glyphs reference other glyphs via components.
-
#initialize(name:) ⇒ Glyph
constructor
A new instance of Glyph.
-
#point_count ⇒ Object
Total number of points across all contours.
-
#read_from(root) ⇒ Object
Populate this glyph from a
Nokogiri node. -
#to_glif ⇒ String
Render this glyph back to .glif XML.
-
#to_outline ⇒ Fontisan::Models::Outline
Convert this glyph to a fontisan Models::Outline for use by the CFF charstring builder.
Constructor Details
#initialize(name:) ⇒ Glyph
Returns a new instance of Glyph.
42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/fontisan/ufo/glyph.rb', line 42 def initialize(name:) @name = name.to_s @unicodes = [] @width = 0.0 @height = 0.0 @contours = [] @components = [] @anchors = [] @guidelines = [] @images = [] @note = nil @lib = Lib.new end |
Instance Attribute Details
#anchors ⇒ Object (readonly)
Returns the value of attribute anchors.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def anchors @anchors end |
#components ⇒ Object (readonly)
Returns the value of attribute components.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def components @components end |
#contours ⇒ Object (readonly)
Returns the value of attribute contours.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def contours @contours end |
#guidelines ⇒ Object (readonly)
Returns the value of attribute guidelines.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def guidelines @guidelines end |
#height ⇒ Object
Returns the value of attribute height.
38 39 40 |
# File 'lib/fontisan/ufo/glyph.rb', line 38 def height @height end |
#images ⇒ Object (readonly)
Returns the value of attribute images.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def images @images end |
#lib ⇒ Object
Returns the value of attribute lib.
38 39 40 |
# File 'lib/fontisan/ufo/glyph.rb', line 38 def lib @lib end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def name @name end |
#note ⇒ Object
Returns the value of attribute note.
38 39 40 |
# File 'lib/fontisan/ufo/glyph.rb', line 38 def note @note end |
#unicodes ⇒ Object (readonly)
Returns the value of attribute unicodes.
39 40 41 |
# File 'lib/fontisan/ufo/glyph.rb', line 39 def unicodes @unicodes end |
#width ⇒ Object
Returns the value of attribute width.
38 39 40 |
# File 'lib/fontisan/ufo/glyph.rb', line 38 def width @width end |
Class Method Details
.from_glif(xml) ⇒ Glyph
Returns parsed glyph.
112 113 114 115 116 |
# File 'lib/fontisan/ufo/glyph.rb', line 112 def self.from_glif(xml) doc = Nokogiri::XML(xml) root = doc.root new(name: root["name"]).tap { |g| g.read_from(root) } end |
.notdef(units_per_em: nil) ⇒ Glyph
Synthesize an empty .notdef glyph suitable for GID 0.
OpenType mandates GID 0 be .notdef. UFO sources that lack an
explicit .notdef (synthetic SVG donors, programmatic sources)
need one injected before compile. The width defaults to the
font's units_per_em so the .notdef consumes one em of advance.
32 33 34 35 36 |
# File 'lib/fontisan/ufo/glyph.rb', line 32 def self.notdef(units_per_em: nil) new(name: ".notdef").tap do |g| g.width = units_per_em&.to_i || DEFAULT_UNITS_PER_EM end end |
Instance Method Details
#add_anchor(anchor) ⇒ Object
70 71 72 73 |
# File 'lib/fontisan/ufo/glyph.rb', line 70 def add_anchor(anchor) @anchors << anchor anchor end |
#add_component(component) ⇒ Object
65 66 67 68 |
# File 'lib/fontisan/ufo/glyph.rb', line 65 def add_component(component) @components << component component end |
#add_contour(contour) ⇒ Object
60 61 62 63 |
# File 'lib/fontisan/ufo/glyph.rb', line 60 def add_contour(contour) @contours << contour contour end |
#add_guideline(guideline) ⇒ Object
75 76 77 78 |
# File 'lib/fontisan/ufo/glyph.rb', line 75 def add_guideline(guideline) @guidelines << guideline guideline end |
#add_image(image) ⇒ Object
80 81 82 83 |
# File 'lib/fontisan/ufo/glyph.rb', line 80 def add_image(image) @images << image image end |
#add_unicode(codepoint) ⇒ Object
56 57 58 |
# File 'lib/fontisan/ufo/glyph.rb', line 56 def add_unicode(codepoint) @unicodes << codepoint.to_i end |
#bbox ⇒ BoundingBox?
Returns axis-aligned bbox of contours.
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/fontisan/ufo/glyph.rb', line 96 def bbox return nil if @contours.empty? points = @contours.flat_map(&:points) return nil if points.empty? BoundingBox.new( x_min: points.map(&:x).min, y_min: points.map(&:y).min, x_max: points.map(&:x).max, y_max: points.map(&:y).max, ) end |
#composite? ⇒ Boolean
Composite glyphs reference other glyphs via components.
86 87 88 |
# File 'lib/fontisan/ufo/glyph.rb', line 86 def composite? !@components.empty? end |
#point_count ⇒ Object
Total number of points across all contours.
91 92 93 |
# File 'lib/fontisan/ufo/glyph.rb', line 91 def point_count @contours.sum(&:point_count) end |
#read_from(root) ⇒ Object
Populate this glyph from a
134 135 136 137 138 139 140 141 142 143 |
# File 'lib/fontisan/ufo/glyph.rb', line 134 def read_from(root) read_advance(root) read_unicodes(root) read_outline(root) read_anchors(root) read_guidelines(root) read_image(root) read_lib(root) read_note(root) end |
#to_glif ⇒ String
Render this glyph back to .glif XML.
120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/fontisan/ufo/glyph.rb', line 120 def to_glif builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml| xml.glyph(name: @name, format: "2") do emit_advance(xml) emit_unicodes(xml) emit_outline(xml) emit_lib(xml) emit_note(xml) end end builder.to_xml end |
#to_outline ⇒ Fontisan::Models::Outline
Convert this glyph to a fontisan Models::Outline for use by the CFF charstring builder.
UFO contours use cubic Bezier curves ("curve" type with two off-curve controls). Single off-curves are interpreted as quadratic and degree-elevated to cubic.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/fontisan/ufo/glyph.rb', line 153 def to_outline commands = [] bbox_hash = { x_min: 0, y_min: 0, x_max: 0, y_max: 0 } @contours.each do |contour| next if contour.points.empty? points = contour.points commands << { type: :move_to, x: points.first.x, y: points.first.y } i = 1 while i < points.size pt = points[i] if pt.on_curve? commands << { type: :line_to, x: pt.x, y: pt.y } i += 1 elsif i + 2 < points.size && !points[i + 1].on_curve? && points[i + 2].on_curve? commands << { type: :curve_to, cx1: points[i].x, cy1: points[i].y, cx2: points[i + 1].x, cy2: points[i + 1].y, x: points[i + 2].x, y: points[i + 2].y } i += 3 elsif i + 1 < points.size && points[i + 1].on_curve? prev = points[i - 1] nxt = points[i + 1] cx1 = prev.x + (2.0 / 3.0) * (pt.x - prev.x) cy1 = prev.y + (2.0 / 3.0) * (pt.y - prev.y) cx2 = nxt.x + (2.0 / 3.0) * (pt.x - nxt.x) cy2 = nxt.y + (2.0 / 3.0) * (pt.y - nxt.y) commands << { type: :curve_to, cx1: cx1, cy1: cy1, cx2: cx2, cy2: cy2, x: nxt.x, y: nxt.y } i += 2 else i += 1 end end commands << { type: :close_path } end bb = bbox if bb bbox_hash = { x_min: bb.x_min.to_i, y_min: bb.y_min.to_i, x_max: bb.x_max.to_i, y_max: bb.y_max.to_i } end Fontisan::Models::Outline.new( glyph_id: 0, commands: commands, bbox: bbox_hash, width: @width.to_i, ) end |