favicon_factory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e4c019833e1b1397a11142ee88f5ea2c9924aeb6c101316898babdd09ec526bb
4
+ data.tar.gz: b242b7a4575f87eb36047f9986481d8064b523fad955693613c85e41e618057a
5
+ SHA512:
6
+ metadata.gz: 91e5b2c1db12ebdada6679e6d07452ad53acf26970de15e9ee3120806fc1bd6468b54e31a9146913f9c40b23e6569decdfbe8ef220d4f40a3cac5c9f9e239259
7
+ data.tar.gz: afa68fb6c33b90cca3a358872f48c77574ca3ac6570ebd46faedd6f527f5c83600919dbf4f61c77264a4d4ec8efa833f7da08259b5745777a72d1a646f4b01ab
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "favicon_factory"
5
+ FaviconFactory::Cli.call
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FaviconFactory
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "favicon_factory/version"
4
+ require "mini_magick"
5
+ require "tty/which"
6
+ require "tty/option"
7
+
8
+ module FaviconFactory
9
+ SVG_DENSITY = 1_000
10
+
11
+ Params = Data.define(:favicon_svg, :background) do
12
+ def dir
13
+ File.dirname(favicon_svg)
14
+ end
15
+ end
16
+
17
+ PngParams = Data.define(:favicon_svg, :background, :size) do
18
+ def self.from_params(size, params)
19
+ new(**params.to_h, size: size)
20
+ end
21
+
22
+ def dir
23
+ File.dirname(favicon_svg)
24
+ end
25
+ end
26
+
27
+ class Command
28
+ include TTY::Option
29
+
30
+ usage do
31
+ program "favicon_factory"
32
+ no_command
33
+
34
+ desc <<~DESC
35
+ `favicon_factory` generates from an SVG the minimal set of icons needed by modern browsers.
36
+
37
+ The source SVG is ideal for [modern browsers](https://caniuse.com/link-icon-svg). And it may contain a `<style>` tag with `@media (prefers-color-scheme: dark)` to support light/dark themes, which is ignored when generating favicons.
38
+
39
+ Icons will be generated in the same folder as the source SVG unless already existing:
40
+
41
+ - `favicon.ico` (32x32) for legacy browsers; serve it from `/favicon.ico` because tools, like RSS readers, just look there.
42
+ - `apple-touch-icon.png` (180x180) for Apple devices when adding a webpage to the home screen; a background and a padding around the icon is applied to make it look pretty.
43
+ - `manifest.webmanifest` that includes `icon-192.png` and `icon-512.png` for Android devices; the former for display on the home screen, and the latter for the splash screen while the PWA is loading.
44
+ DESC
45
+
46
+ example "favicon_factory path/to/favicon.svg"
47
+ example "favicon_factory --background red path/to/favicon.svg"
48
+ example "favicon_factory --background #000000 path/to/favicon.svg"
49
+ end
50
+
51
+ argument :favicon_svg do
52
+ name "favicon_svg"
53
+ desc "Path to the favicon.svg"
54
+ end
55
+
56
+ option :background do
57
+ short "-b"
58
+ long "--background string"
59
+ default "white"
60
+ desc "Background color for apple-touch-icon.png"
61
+ end
62
+
63
+ flag :help do
64
+ short "-h"
65
+ long "--help"
66
+ desc "Print usage"
67
+ end
68
+ end
69
+
70
+ class Cli
71
+ def self.call
72
+ exit new(ARGV, $stderr).call
73
+ end
74
+
75
+ attr_reader :stderr
76
+
77
+ def initialize(argv, stderr)
78
+ @argv = argv
79
+ @stderr = stderr
80
+ end
81
+
82
+ def call
83
+ stderr.puts "imagemagick v7 not found, please install for best results" unless MiniMagick.imagemagick7?
84
+ stderr.puts "inkscape not found, install inkscape for best results" unless TTY::Which.which("inkscape")
85
+
86
+ params, status = parse(@argv)
87
+ return status if status >= 0
88
+
89
+ [
90
+ Thread.new { create("favicon.ico", params) },
91
+ Thread.new { create("icon-192.png", PngParams.from_params(192, params)) },
92
+ Thread.new { create("icon-512.png", PngParams.from_params(512, params)) },
93
+ Thread.new { create("apple-touch-icon.png", params) },
94
+ Thread.new { create("manifest.webmanifest", params) }
95
+ ]
96
+ .each(&:join)
97
+
98
+ stderr.puts <<~TEXT
99
+ Info: Add the following to the `<head>`
100
+ <!-- favicons generated with the favicon_factory gem -->
101
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
102
+ <link rel="icon" href="/favicon.ico" sizes="32x32">
103
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
104
+ <link rel="manifest" href="/manifest.webmanifest">
105
+ TEXT
106
+
107
+ 0
108
+ end
109
+
110
+ private
111
+
112
+ def parse(argv)
113
+ command = Command.new.parse(argv)
114
+ params = command.params
115
+ return exit_message(0, command.help) if params.fetch(:help) == true
116
+ return exit_message(1, params.errors.summary) if params.errors.any?
117
+
118
+ params = params.to_h
119
+ favicon_svg = params.fetch(:favicon_svg)
120
+ return exit_message(1, "Error: #{favicon_svg} does not end with .svg") unless favicon_svg.end_with?(".svg")
121
+ return exit_message(1, "Error: #{favicon_svg} does not exist") unless File.exist?(favicon_svg)
122
+
123
+ [Params.new(favicon_svg: favicon_svg, background: params.fetch(:background)), -1]
124
+ end
125
+
126
+ def exit_message(status, message)
127
+ stderr.puts message
128
+ [nil, status]
129
+ end
130
+
131
+ def create(name, params)
132
+ path = File.join(params.dir, name)
133
+ if File.exist?(path)
134
+ stderr.puts "Info: Skipping #{path} because it already exists"
135
+ return
136
+ end
137
+
138
+ stderr.puts "Info: Generating #{path}"
139
+ create_by_name.fetch(name).call(path, params)
140
+ end
141
+
142
+ def create_by_name
143
+ {
144
+ "favicon.ico" => method(:ico!),
145
+ "icon-192.png" => method(:png!),
146
+ "icon-512.png" => method(:png!),
147
+ "apple-touch-icon.png" => method(:touch!),
148
+ "manifest.webmanifest" => method(:manifest!)
149
+ }
150
+ end
151
+
152
+ def ico!(path, params)
153
+ MiniMagick::Tool::Convert.new do |convert|
154
+ convert.density(SVG_DENSITY).background("none")
155
+ convert << params.favicon_svg
156
+ convert.resize("32x32")
157
+ convert << path
158
+ end
159
+ end
160
+
161
+ def png!(path, params)
162
+ MiniMagick::Tool::Convert.new do |convert|
163
+ convert.density(SVG_DENSITY).background("none")
164
+ convert << params.favicon_svg
165
+ convert.resize("#{params.size}x#{params.size}")
166
+ convert << path
167
+ end
168
+ end
169
+
170
+ def touch!(path, params)
171
+ MiniMagick::Tool::Convert.new do |convert|
172
+ convert.density(SVG_DENSITY).background(params.background)
173
+ convert << params.favicon_svg
174
+ convert.resize("160x160").gravity("center").extent("180x180")
175
+ convert << path
176
+ end
177
+ end
178
+
179
+ def manifest!(path, _params)
180
+ File.write(path, <<~MANIFEST)
181
+ {
182
+ "icons": [
183
+ { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
184
+ { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
185
+ ]
186
+ }
187
+ MANIFEST
188
+ end
189
+ end
190
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: favicon_factory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - 3v0k4
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mini_magick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.12'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-option
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-which
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.0
55
+ description: FaviconFactory generates from an SVG the minimal set of icons needed
56
+ by modern browsers.
57
+ email:
58
+ - riccardo.odone@gmail.com
59
+ executables:
60
+ - favicon_factory
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - exe/favicon_factory
65
+ - lib/favicon_factory.rb
66
+ - lib/favicon_factory/version.rb
67
+ homepage: https://github.com/3v0k4/favicon_factory
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ homepage_uri: https://github.com/3v0k4/favicon_factory
72
+ source_code_uri: https://github.com/3v0k4/favicon_factory
73
+ changelog_uri: https://github.com/3v0k4/favicon_factory/blob/main/CHANGELOG.md
74
+ rubygems_mfa_required: 'true'
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 2.6.0
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.4.10
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Generate favicons from an SVG
94
+ test_files: []