favicon_factory 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4c019833e1b1397a11142ee88f5ea2c9924aeb6c101316898babdd09ec526bb
4
- data.tar.gz: b242b7a4575f87eb36047f9986481d8064b523fad955693613c85e41e618057a
3
+ metadata.gz: 1105c6329d7279577f8d478e3be7e80984c3b374b38f66686b293299fa1dc5d8
4
+ data.tar.gz: 58300ea64d615ccd2876493467916a47fa6150708e45bbe0e0183ac5b151997d
5
5
  SHA512:
6
- metadata.gz: 91e5b2c1db12ebdada6679e6d07452ad53acf26970de15e9ee3120806fc1bd6468b54e31a9146913f9c40b23e6569decdfbe8ef220d4f40a3cac5c9f9e239259
7
- data.tar.gz: afa68fb6c33b90cca3a358872f48c77574ca3ac6570ebd46faedd6f527f5c83600919dbf4f61c77264a4d4ec8efa833f7da08259b5745777a72d1a646f4b01ab
6
+ metadata.gz: d673025100bef413b41b4026f60e1b435cd8b8f5343a5d928771a6aa33aa72b2b942b8333e92f9923ac2c90798352482b0b221a3dfc38054d74f8649336a4f2f
7
+ data.tar.gz: 76ff8e945a1fe543bcb04ed2198fa3e2ebaedcc03511666b497f88a90b6972ec660f428c236904465238b53a2101d6e0965ee02bdca63c31389e23680d83743c
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FaviconFactory
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,29 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "favicon_factory/version"
4
- require "mini_magick"
5
4
  require "tty/which"
6
5
  require "tty/option"
7
6
 
8
- module FaviconFactory
9
- SVG_DENSITY = 1_000
7
+ autoload(:MiniMagick, "mini_magick")
8
+ autoload(:Vips, "vips")
10
9
 
10
+ module FaviconFactory
11
11
  Params = Data.define(:favicon_svg, :background) do
12
12
  def dir
13
13
  File.dirname(favicon_svg)
14
14
  end
15
15
  end
16
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
17
  class Command
28
18
  include TTY::Option
29
19
 
@@ -56,8 +46,8 @@ module FaviconFactory
56
46
  option :background do
57
47
  short "-b"
58
48
  long "--background string"
59
- default "white"
60
- desc "Background color for apple-touch-icon.png"
49
+ default "#ffffff"
50
+ desc "Background hex color for apple-touch-icon.png"
61
51
  end
62
52
 
63
53
  flag :help do
@@ -69,46 +59,33 @@ module FaviconFactory
69
59
 
70
60
  class Cli
71
61
  def self.call
72
- exit new(ARGV, $stderr).call
62
+ adapter = BaseAdapter.find
63
+ if adapter.nil?
64
+ stderr.puts "Error: Neither vips or imagemagick found, install one"
65
+ exit 1
66
+ end
67
+ exit new(adapter: adapter, argv: ARGV, file: File, stderr: $stderr).call
73
68
  end
74
69
 
75
- attr_reader :stderr
76
-
77
- def initialize(argv, stderr)
70
+ def initialize(adapter:, argv:, file:, stderr:)
71
+ @adapter = adapter
78
72
  @argv = argv
73
+ @file = file
79
74
  @stderr = stderr
80
75
  end
81
76
 
82
77
  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)
78
+ params, status = parse(argv)
87
79
  return status if status >= 0
88
80
 
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
-
81
+ adapter.new(file: file, params: params, stderr: stderr).create_icons
107
82
  0
108
83
  end
109
84
 
110
85
  private
111
86
 
87
+ attr_reader :stderr, :file, :adapter, :argv
88
+
112
89
  def parse(argv)
113
90
  command = Command.new.parse(argv)
114
91
  params = command.params
@@ -118,19 +95,67 @@ module FaviconFactory
118
95
  params = params.to_h
119
96
  favicon_svg = params.fetch(:favicon_svg)
120
97
  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)
98
+ return exit_message(1, "Error: #{favicon_svg} does not exist") unless file.exist?(favicon_svg)
122
99
 
123
- [Params.new(favicon_svg: favicon_svg, background: params.fetch(:background)), -1]
100
+ background = params.fetch(:background)
101
+ unless hex?(background)
102
+ return exit_message(1, "Error: #{background} is not a valid color, use a hex value like #0099ff")
103
+ end
104
+
105
+ [Params.new(favicon_svg: favicon_svg, background: background), -1]
106
+ end
107
+
108
+ def hex?(string)
109
+ string = string.delete_prefix("#")
110
+ string.split("").all? { |char| char.match?(/^[0-9a-fA-F]$/) }
124
111
  end
125
112
 
126
113
  def exit_message(status, message)
127
114
  stderr.puts message
128
115
  [nil, status]
129
116
  end
117
+ end
118
+
119
+ class BaseAdapter
120
+ class << self
121
+ def find
122
+ if TTY::Which.which("vips") || TTY::Which.which("libvips")
123
+ VipsAdapter
124
+ elsif TTY::Which.which("magick") || TTY::Which.which("convert")
125
+ ImageMagickAdapter
126
+ end
127
+ end
128
+ end
129
+
130
+ def initialize(file:, params:, stderr:)
131
+ @file = file
132
+ @params = params
133
+ @stderr = stderr
134
+ end
135
+
136
+ def create_icons
137
+ create_by_name
138
+ .keys
139
+ .map { |name| Thread.new { create(name, params) } }
140
+ .each(&:join)
141
+
142
+ stderr.puts <<~TEXT
143
+ Info: Add the following to the `<head>`
144
+ <!-- favicons generated with the favicon_factory gem -->
145
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
146
+ <link rel="icon" href="/favicon.ico" sizes="32x32">
147
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
148
+ <link rel="manifest" href="/manifest.webmanifest">
149
+ TEXT
150
+ end
151
+
152
+ private
153
+
154
+ attr_reader :params, :stderr, :file
130
155
 
131
156
  def create(name, params)
132
- path = File.join(params.dir, name)
133
- if File.exist?(path)
157
+ path = file.join(params.dir, name)
158
+ if file.exist?(path)
134
159
  stderr.puts "Info: Skipping #{path} because it already exists"
135
160
  return
136
161
  end
@@ -142,13 +167,76 @@ module FaviconFactory
142
167
  def create_by_name
143
168
  {
144
169
  "favicon.ico" => method(:ico!),
145
- "icon-192.png" => method(:png!),
146
- "icon-512.png" => method(:png!),
170
+ "icon-192.png" => method(:png_192!),
171
+ "icon-512.png" => method(:png_512!),
147
172
  "apple-touch-icon.png" => method(:touch!),
148
173
  "manifest.webmanifest" => method(:manifest!)
149
174
  }
150
175
  end
151
176
 
177
+ def png_192!(path, params)
178
+ png!(path, params, 192)
179
+ end
180
+
181
+ def png_512!(path, params)
182
+ png!(path, params, 512)
183
+ end
184
+
185
+ def manifest!(path, _params)
186
+ file.write(path, <<~MANIFEST)
187
+ {
188
+ "icons": [
189
+ { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
190
+ { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
191
+ ]
192
+ }
193
+ MANIFEST
194
+ end
195
+ end
196
+
197
+ class VipsAdapter < BaseAdapter
198
+ def ico!(path, params)
199
+ png = Vips::Image.thumbnail(params.favicon_svg, 32).write_to_buffer(".png")
200
+ # https://www.meziantou.net/creating-ico-files-from-multiple-images-in-dotnet.htm
201
+ ico = [0, 1, 1].pack("S<*") + [32, 32, 0, 0].pack("C*") + [1, 32].pack("S<*") + [png.size, 22].pack("L<*") + png
202
+ file.write(path, ico)
203
+ end
204
+
205
+ def png!(path, params, size)
206
+ Vips::Image.thumbnail(params.favicon_svg, size).write_to_file(path)
207
+ end
208
+
209
+ def touch!(path, params)
210
+ svg = Vips::Image.thumbnail(params.favicon_svg, 160).gravity("centre", 180, 180)
211
+ image = square(180, params.background).composite(svg, :over)
212
+ image.write_to_file(path)
213
+ end
214
+
215
+ private
216
+
217
+ def square(size, hex)
218
+ pixel = (Vips::Image.black(1, 1) + hex2rgb(hex)).cast(:uchar)
219
+ pixel.embed 0, 0, size, size, extend: :copy
220
+ end
221
+
222
+ def hex2rgb(hex)
223
+ hex = hex.delete_prefix("#")
224
+ r = hex[0..1].to_i(16)
225
+ g = hex[2..3].to_i(16)
226
+ b = hex[4..5].to_i(16)
227
+ [r, g, b]
228
+ end
229
+ end
230
+
231
+ class ImageMagickAdapter < BaseAdapter
232
+ SVG_DENSITY = 1_000
233
+
234
+ def initialize(**)
235
+ super
236
+ stderr.puts "Warn: Install imagemagick v7 for best results, using v6" unless MiniMagick.imagemagick7?
237
+ stderr.puts "Warn: Inkscape not found, install it for best results" unless TTY::Which.which("inkscape")
238
+ end
239
+
152
240
  def ico!(path, params)
153
241
  MiniMagick::Tool::Convert.new do |convert|
154
242
  convert.density(SVG_DENSITY).background("none")
@@ -158,11 +246,11 @@ module FaviconFactory
158
246
  end
159
247
  end
160
248
 
161
- def png!(path, params)
249
+ def png!(path, params, size)
162
250
  MiniMagick::Tool::Convert.new do |convert|
163
251
  convert.density(SVG_DENSITY).background("none")
164
252
  convert << params.favicon_svg
165
- convert.resize("#{params.size}x#{params.size}")
253
+ convert.resize("#{size}x#{size}")
166
254
  convert << path
167
255
  end
168
256
  end
@@ -175,16 +263,5 @@ module FaviconFactory
175
263
  convert << path
176
264
  end
177
265
  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
266
  end
190
267
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: favicon_factory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 3v0k4
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-27 00:00:00.000000000 Z
11
+ date: 2024-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mini_magick
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-vips
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: tty-option
29
43
  requirement: !ruby/object:Gem::Requirement