favicon_factory 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.
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: []