Class: FontawesomeSubsetter::Subsetter
- Inherits:
-
Object
- Object
- FontawesomeSubsetter::Subsetter
- Defined in:
- lib/fontawesome_subsetter/subsetter.rb
Instance Method Summary collapse
- #build(files_to_scan = nil) ⇒ Object
- #build_watch ⇒ Object
-
#initialize(root: nil) ⇒ Subsetter
constructor
A new instance of Subsetter.
-
#scan_files_and_update_caches(files) ⇒ Object
Updates the cache for each style, returns the styles that have changed.
- #subset_font(style) ⇒ Object
- #subset_stylesheets ⇒ Object
-
#subset_webfonts(styles) ⇒ Object
Takes an array of styles and creates a thread to subset each font file.
Constructor Details
#initialize(root: nil) ⇒ Subsetter
Returns a new instance of Subsetter.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 53 def initialize(root: nil) @file_icon_map = {} @root = root || defined?(Rails) && Rails.root || Pathname.new(Dir.pwd) @root = Pathname.new(@root) unless @root.is_a?(Pathname) config = FontawesomeSubsetter.configuration @meta_path = config. || @root.join("vendor", "fontawesome", "metadata", "icons.yml") @fonts_dir = config.fonts_dir || @root.join("vendor", "fontawesome", "webfonts") @build_dir = config.build_dir || @root.join("app", "assets", "builds") @webfonts_dir = @build_dir.join("webfonts") @scan_globs = config.scan_globs @icon_regex = config.icon_regex @styles = { "solid" => { name: "solid", prefix: "fas", unicode_cache: Set.new, icon_cache: Set.new, sass_icon_cache: Set.new, changed: false, file: "fa-solid-900.woff2" }, "regular" => { name: "regular", prefix: "far", unicode_cache: Set.new, icon_cache: Set.new, sass_icon_cache: Set.new, changed: false, file: "fa-regular-400.woff2" }, "light" => { name: "light", prefix: "fal", unicode_cache: Set.new, icon_cache: Set.new, sass_icon_cache: Set.new, changed: false, file: "fa-thin-100.woff2" }, "thin" => { name: "thin", prefix: "fat", unicode_cache: Set.new, icon_cache: Set.new, sass_icon_cache: Set.new, changed: false, file: "fa-thin-100.woff2" }, "duotone" => { name: "duotone", prefix: "fad", unicode_cache: Set.new, icon_cache: Set.new, sass_icon_cache: Set.new, changed: false, file: "fa-duotone-900.woff2" }, "brands" => { name: "brands", prefix: "fab", unicode_cache: Set.new, icon_cache: Set.new, sass_icon_cache: Set.new, changed: false, file: "fa-brands-400.woff2" } } @watch_paths = [ @root.join("app", "views"), @root.join("app", "helpers"), @root.join("app", "components") ].select(&:exist?) @watch_file_type_regex = config.watch_file_type_regex FileUtils.rm_rf(@webfonts_dir) FileUtils.mkdir_p(@webfonts_dir) # Load FontAwesome metadata @metadata = YAML.safe_load(File.read(@meta_path)) # Build unicode map, set entry to an array of all codepoints (main + secondary) @metadata.each { | key, entry | @metadata[key]["unicodes"] = Set.new([ "U+#{ entry['unicode'].upcase }", *entry.dig("aliases", "unicodes", "secondary")&.map { | s | "U+#{ s.upcase }" } ]) } # Build alias map @metadata.merge!(@metadata.values.flat_map { | entry | (entry.dig("aliases", "names") || []).map { [ it, entry ] } }.to_h) # Strip useless metadata @metadata.each_value { it.except!("changes", "label", "voted", "search", "styles", "aliases") } end |
Instance Method Details
#build(files_to_scan = nil) ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 183 def build(files_to_scan = nil) files_to_scan ||= @scan_globs.flat_map { Dir.glob it } scan_files_and_update_caches(files_to_scan) styles = @styles.values.select { it[:changed] } # Rebuild assets only if caches have changed unless styles.empty? subset_webfonts(styles) subset_stylesheets @styles.values.each { it[:changed] = false } end end |
#build_watch ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 198 def build_watch require "listen" # Initial full scan and build build() puts "Starting FontAwesome watch mode..." listener = Listen.to(*@watch_paths) do | modified, added, removed | # NOTE: At most, it seems to take around 1.7 seconds to subset start_time = Time.now puts "Detected changes: modified=#{ modified }, added=#{ added }, removed=#{ removed }" # Scan only modified and added files build(modified + added) elapsed = Time.now - start_time puts "[FontSubsetter] Total subsetting time: #{ format('%.2f', elapsed) } seconds" end listener.start # Keep the process alive loop do sleep 1 end rescue Interrupt puts "FontAwesome watch stopped" listener&.stop end |
#scan_files_and_update_caches(files) ⇒ Object
Updates the cache for each style, returns the styles that have changed.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 101 def scan_files_and_update_caches(files) files.each do | path | text = File.read(path) text.scan(@icon_regex).each do | prefix, icon | style = @styles.find { it&.last[:prefix] == prefix }&.last next unless style normalized = icon.tr("_", "-") entry = @metadata[normalized] next unless entry # If the icon is new for this style, update the caches if style[:icon_cache].add?(normalized) style[:unicode_cache].merge(entry["unicodes"]) style[:sass_icon_cache].add("\"#{ normalized }\": \\#{ entry['unicode'] }") style[:changed] = true else nil end end end end |
#subset_font(style) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 133 def subset_font(style) file = style[:file] out_file = @webfonts_dir.join(file) cmd = "pyftsubset #{ @fonts_dir.join(file) } --flavor=woff2 --unicodes=#{ style[:unicode_cache].to_a.join(",") } --output-file=#{ out_file } --layout-features=* --no-hinting" puts "[FontSubsetter][#{ style[:name] }] #{ cmd }" stdout, stderr, status = Open3.capture3(cmd) if status.success? puts "[FontSubsetter][#{ style[:name] }] wrote #{ out_file }" else warn "[FontSubsetter][#{ style[:name] }] subset failed:\n#{ stderr }" raise "pyftsubset failed (exit #{ status.exitstatus })" end end |
#subset_stylesheets ⇒ Object
148 149 150 151 152 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 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 148 def subset_stylesheets require "sass-embedded" styles = @styles.values.select { it[:sass_icon_cache].any? } scss_template = FontawesomeSubsetter.configuration.scss_template scss_string = if scss_template scss_template.call(@styles) else default_scss_template(styles) end # Compile the dynamic SCSS with a custom importer that strips populated icon maps # from variables.scss in memory, so users don't need to modify their vendor files. scss_dir = @root.join("vendor", "fontawesome", "scss").to_s fa_importer = FontawesomeSassImporter.new(scss_dir) compiled_css = Sass.compile_string( scss_string, style: :compressed, importer: fa_importer, importers: [fa_importer], url: "file://#{@root}/fontawesome_subsetter_input.scss" ).css # Forcefully remove all comments, including "loud" /*! ... */ comments for licenses. compiled_css = compiled_css.gsub(/\/\*!.*?\*\//m, "") # Output to builds folder, which is the standard for Propshaft out_file = @build_dir.join("fontawesome.css") File.write(out_file, compiled_css) puts "[FontSubsetter] wrote #{ out_file } with styles: #{ styles.map { it[:name] }.join(', ') }" end |
#subset_webfonts(styles) ⇒ Object
Takes an array of styles and creates a thread to subset each font file
129 130 131 |
# File 'lib/fontawesome_subsetter/subsetter.rb', line 129 def subset_webfonts(styles) styles.map { | style | Thread.new(style) { subset_font(style) } }.each(&:join) end |