ascii_pngfy 0.2.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: 1d0d17138de3a7998ba69e9cdb8ddb45bc4b0aa259f570b5b76e3f32824512b0
4
+ data.tar.gz: '08a2046403c7c76e8d0d150224d3ce1df4776539be57b8a0a56cdc16930ceab3'
5
+ SHA512:
6
+ metadata.gz: f4d7bb12da4c527081fa049e940e3af6e98333ff2850fe796371a6465579f8586409d27b157547abdf9daedcd48ba087d2afba3fe022c159ac42b5a2a4185d4b
7
+ data.tar.gz: 2345644e3d1f5bf09dbf0f366743dacd555943e6b8e5cbd72e3db9fddb1a5f96fdc491e01e1d7cdd9b4b3990d9ce5568b30b6bfddfce1d60f26bc207e285394e
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Reponsibilities
4
+ # - Top level namespace that contains all AsciiPngfy
5
+ # sub-namespaces and general constants
6
+ # - Requires all files needed to use the AsciiPngfy Gem
7
+ module AsciiPngfy
8
+ MAX_RESULT_PNG_IMAGE_WIDTH = 3840
9
+ MAX_RESULT_PNG_IMAGE_HEIGHT = 2160
10
+ GLYPH_DESIGN_WIDTH = 5
11
+ GLYPH_DESIGN_HEIGHT = 9
12
+ SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE_RANGE = (32..126).freeze
13
+ SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE = SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE_RANGE.to_a.freeze
14
+ SUPPORTED_ASCII_CODES = ([10] + SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE).freeze
15
+ SUPPORTED_ASCII_CHARACTERS = SUPPORTED_ASCII_CODES.map(&:chr).freeze
16
+ end
17
+
18
+ require 'ascii_pngfy/pngfyer'
19
+
20
+ require 'ascii_pngfy/exceptions'
21
+
22
+ require 'ascii_pngfy/settings'
23
+ require 'ascii_pngfy/settings/settings_snapshot'
24
+ require 'ascii_pngfy/settings/setable_getable_settings'
25
+
26
+ require 'ascii_pngfy/settings/setable_getable'
27
+ require 'ascii_pngfy/settings/color_setting'
28
+ require 'ascii_pngfy/settings/font_height_setting'
29
+ require 'ascii_pngfy/settings/horizontal_spacing_setting'
30
+ require 'ascii_pngfy/settings/vertical_spacing_setting'
31
+ require 'ascii_pngfy/settings/text_setting'
32
+
33
+ require 'ascii_pngfy/vec2i'
34
+ require 'ascii_pngfy/aabb'
35
+ require 'ascii_pngfy/rendering_rules'
36
+ require 'ascii_pngfy/settings_renderer'
37
+
38
+ require 'ascii_pngfy/glyphs'
39
+ require 'ascii_pngfy/color_rgba'
40
+ require 'ascii_pngfy/result'
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiPngfy
4
+ # Reponsibilities
5
+ # - Represents an axis aligned bounding box through a minimum and
6
+ # maximum coordinate pair
7
+ # - Public getters for the min and max coordinate pair
8
+ # - Provides a way to iterate all the pixel coordinates in
9
+ # the respective bounding box with and without the pixel index
10
+ #
11
+ # This pixel index follows the conventions used for the glyph
12
+ # design string where the index increases based on the iterated
13
+ # pixel from:
14
+ # - topmost row to bottommost row
15
+ # - leftmost pixel to rightmost pixel for each of these rows
16
+ class AABB
17
+ attr_reader(:min, :max)
18
+
19
+ def initialize(min_x, min_y, max_x, max_y)
20
+ self.min = Vec2i.new(min_x, min_y)
21
+ self.max = Vec2i.new(max_x, max_y)
22
+ end
23
+
24
+ def each_pixel(&yielder)
25
+ min.y.upto(max.y) do |pixel_y|
26
+ min.x.upto(max.x) do |pixel_x|
27
+ yielder.call(pixel_x, pixel_y)
28
+ end
29
+ end
30
+ end
31
+
32
+ def each_pixel_with_index(&yielder)
33
+ pixel_index = 0
34
+ each_pixel do |pixel_x, pixel_y|
35
+ yielder.call(pixel_x, pixel_y, pixel_index)
36
+
37
+ pixel_index += 1
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ "min#{min} max#{max}"
43
+ end
44
+
45
+ private
46
+
47
+ attr_writer(:min, :max)
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiPngfy
4
+ # Reponsibilities
5
+ # - Provides RGBA color handling and validation
6
+ class ColorRGBA
7
+ VALID_RGBA_COLOR_RANGE = (0..255).freeze
8
+ attr_reader(:red, :green, :blue, :alpha)
9
+
10
+ def initialize(red, green, blue, alpha)
11
+ self.red = red
12
+ self.green = green
13
+ self.blue = blue
14
+ self.alpha = alpha
15
+ end
16
+
17
+ def red=(new_red)
18
+ @red = validate_color_value(new_red, :red)
19
+ end
20
+
21
+ def green=(new_green)
22
+ @green = validate_color_value(new_green, :green)
23
+ end
24
+
25
+ def blue=(new_blue)
26
+ @blue = validate_color_value(new_blue, :blue)
27
+ end
28
+
29
+ def alpha=(new_alpha)
30
+ @alpha = validate_color_value(new_alpha, :alpha)
31
+ end
32
+
33
+ def ==(other)
34
+ other.red == red &&
35
+ other.green == green &&
36
+ other.blue == blue &&
37
+ other.alpha == alpha
38
+ end
39
+
40
+ private
41
+
42
+ def validate_color_value(color_value, color_component)
43
+ return color_value if valid_color_value?(color_value)
44
+
45
+ error_message = String.new
46
+ error_message << "#{color_value.inspect} is not a valid #{color_component} color component value. "
47
+ error_message << "Must be an Integer in the range (#{VALID_RGBA_COLOR_RANGE})."
48
+
49
+ raise Exceptions::InvalidRGBAColorValueError, error_message
50
+ end
51
+
52
+ def valid_color_value?(color_value)
53
+ color_value.is_a?(Integer) && VALID_RGBA_COLOR_RANGE.cover?(color_value)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiPngfy
4
+ # Provides a custom AsciiPngfy Exceptions hierarchy
5
+ module Exceptions
6
+ # Base class to classify AsciiPngfy errors under StandardError
7
+ class AsciiPngfyError < StandardError; end
8
+
9
+ class InvalidRGBAColorValueError < AsciiPngfyError; end
10
+
11
+ class InvalidFontHeightError < AsciiPngfyError; end
12
+
13
+ class InvalidSpacingError < AsciiPngfyError; end
14
+
15
+ class InvalidHorizontalSpacingError < InvalidSpacingError; end
16
+
17
+ class InvalidVerticalSpacingError < InvalidSpacingError; end
18
+
19
+ class InvalidReplacementTextError < AsciiPngfyError; end
20
+
21
+ class InvalidCharacterError < AsciiPngfyError; end
22
+
23
+ class EmptyTextError < AsciiPngfyError; end
24
+
25
+ class TextLineTooLongError < AsciiPngfyError; end
26
+
27
+ class TooManyTextLinesError < AsciiPngfyError; end
28
+ end
29
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiPngfy
4
+ # Reponsibilities
5
+ # - Provides pixel plotting designs for all supported non-control
6
+ # ASCII characters
7
+ # - Decides which design character, '.' or '#', represents the
8
+ # font layer or the background layer
9
+ # rubocop: disable Metrics/ModuleLength
10
+ module Glyphs
11
+ DESIGNS = {
12
+ ' ' => '.............................................',
13
+ '!' => '..#....#....#....#....#.........#............',
14
+ '"' => '.#.#..#.#..#.#...............................',
15
+ '#' => '......#.#.#####.#.#..#.#.#####.#.#...........',
16
+ '$' => '..#...#####.#...###...#.#####...#............',
17
+ '%' => '#...##...#...#...#...#...#...##...#..........',
18
+ '&' => '.##..#..#.#..#..#####..#.#..#..##.#..........',
19
+ "'" => '..#....#....#................................',
20
+ '(' => '...#...#....#....#....#....#.....#...........',
21
+ ')' => '..#.....#....#....#....#....#...#............',
22
+ '*' => '.......#..#.#.#.###.#.#.#..#.................',
23
+ '+' => '.......#....#..#####..#....#.................',
24
+ ',' => '...........................#....#...#........',
25
+ '-' => '...............#####.........................',
26
+ '.' => '...........................#....#............',
27
+ '/' => '....#....#...#...#...#...#....#..............',
28
+ '0' => '.###.#...##..###.#.###..##...#.###...........',
29
+ '1' => '..#...##....#....#....#....#..#####..........',
30
+ '2' => '.###.#...#....#...#...#...#...#####..........',
31
+ '3' => '.###.#...#....#.###.....##...#.###...........',
32
+ '4' => '.#..#.#..##...######....#....#....#..........',
33
+ '5' => '######....#....####.....##...#.###...........',
34
+ '6' => '.###.#....#....####.#...##...#.###...........',
35
+ '7' => '#####....#....#...#...#....#....#............',
36
+ '8' => '.###.#...##...#.###.#...##...#.###...........',
37
+ '9' => '.###.#...##...#.####....##...#.###...........',
38
+ ':' => '.......#....#..............#....#............',
39
+ ';' => '.......#....#..............#....#...#........',
40
+ '<' => '........##.##..#.....##.....##...............',
41
+ '=' => '..........#####.....#####....................',
42
+ '>' => '.....##.....##.....#..##.##..................',
43
+ '?' => '.###.#...#....#...#...#.........#............',
44
+ '@' => '.###.#..###.#.##.#.##..###.....###...........',
45
+ 'A' => '.###.#...##...##...#######...##...#..........',
46
+ 'B' => '####.#...##...#####.#...##...#####...........',
47
+ 'C' => '.###.#...##....#....#....#...#.###...........',
48
+ 'D' => '####.#...##...##...##...##...#####...........',
49
+ 'E' => '######....#....####.#....#....#####..........',
50
+ 'F' => '######....#....####.#....#....#..............',
51
+ 'G' => '.###.#...##....#.####...##...#.###...........',
52
+ 'H' => '#...##...##...#######...##...##...#..........',
53
+ 'I' => '#####..#....#....#....#....#..#####..........',
54
+ 'J' => '....#....#....#....##...##...#.###...........',
55
+ 'K' => '#...##..#.#.#..##...#.#..#..#.#...#..........',
56
+ 'L' => '#....#....#....#....#....#....#####..........',
57
+ 'M' => '#...###.###.#.##...##...##...##...#..........',
58
+ 'N' => '#...##...###..##.#.##..###...##...#..........',
59
+ 'O' => '.###.#...##...##...##...##...#.###...........',
60
+ 'P' => '####.#...##...#####.#....#....#..............',
61
+ 'Q' => '.###.#...##...##...##...##...#.###....##.....',
62
+ 'R' => '####.#...##...#####.#...##...##...#..........',
63
+ 'S' => '.###.#...##.....###.....##...#.###...........',
64
+ 'T' => '#####..#....#....#....#....#....#............',
65
+ 'U' => '#...##...##...##...##...##...#.###...........',
66
+ 'V' => '#...##...##...##...#.#.#..#.#...#............',
67
+ 'W' => '#...##...##...##...##.#.###.###...#..........',
68
+ 'X' => '#...##...#.#.#...#...#.#.#...##...#..........',
69
+ 'Y' => '#...##...#.#.#...#....#....#....#............',
70
+ 'Z' => '#####....#...#...#...#...#....#####..........',
71
+ '[' => '..###..#....#....#....#....#....###..........',
72
+ '\\' => '#....#.....#.....#.....#.....#....#..........',
73
+ ']' => '###....#....#....#....#....#..###............',
74
+ '^' => '..#...#.#.#...#..............................',
75
+ '_' => '..............................#####..........',
76
+ '`' => '.#.....#.....................................',
77
+ 'a' => '...........#####...##...##...#.####..........',
78
+ 'b' => '#....#....####.#...##...##...#####...........',
79
+ 'c' => '...........###.#...##....#...#.###...........',
80
+ 'd' => '....#....#.#####...##...##...#.####..........',
81
+ 'e' => '...........###.#...#######.....###...........',
82
+ 'f' => '..##..#..#.#...####..#....#....#.............',
83
+ 'g' => '...........#####...##...##...#.####....#.###.',
84
+ 'h' => '#....#....####.#...##...##...##...#..........',
85
+ 'i' => '..#........##....#....#....#..#####..........',
86
+ 'j' => '....#........##....#....#....#....##...#.###.',
87
+ 'k' => '#....#....#...##..#.###..#..#.#...#..........',
88
+ 'l' => '##....#....#....#....#....#.....###..........',
89
+ 'm' => '..........####.#.#.##.#.##.#.##.#.#..........',
90
+ 'n' => '..........####.#...##...##...##...#..........',
91
+ 'o' => '...........###.#...##...##...#.###...........',
92
+ 'p' => '..........####.#...##...##...#####.#....#....',
93
+ 'q' => '...........#####...##...##...#.####....#....#',
94
+ 'r' => '..........#.##.##..###..##....#..............',
95
+ 's' => '...........#####.....###.....#####...........',
96
+ 't' => '.#....#...####..#....#....#.....###..........',
97
+ 'u' => '..........#...##...##...##...#.####..........',
98
+ 'v' => '..........#...##...##...#.#.#...#............',
99
+ 'w' => '..........#...##...##.#.##.#.#.#.#...........',
100
+ 'x' => '..........#...#.#.#...#...#.#.#...#..........',
101
+ 'y' => '..........#...##...##...##...#.####....#.###.',
102
+ 'z' => '..........#####...#...#...#...#####..........',
103
+ '{' => '...#...#....#...#.....#....#.....#...........',
104
+ '|' => '..#....#....#....#....#....#....#............',
105
+ '}' => '.#.....#....#.....#...#....#...#.............',
106
+ '~' => '................#..##.##.....................'
107
+ }.freeze
108
+
109
+ def self.font_layer_design_character?(some_design_character)
110
+ some_design_character == '#'
111
+ end
112
+
113
+ def self.background_layer_design_character?(some_design_character)
114
+ some_design_character == '.'
115
+ end
116
+ end
117
+ # rubocop: enable Metrics/ModuleLength
118
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiPngfy
4
+ # Reponsibilities
5
+ # - Provide the complete interface of this gem dynamically
6
+ # in order to force the interface of the Settings onto the caller
7
+ # - Orchestrates the Settings and the SettingsRenderer
8
+ class Pngfyer
9
+ def initialize(use_glyph_designs: true)
10
+ self.settings_renderer = SettingsRenderer.new(use_glyph_designs: use_glyph_designs)
11
+ self.settings = Settings::SetableGetableSettings.new
12
+ end
13
+
14
+ def respond_to_missing?(method_name, _)
15
+ setter?(method_name) || super
16
+ end
17
+
18
+ def method_missing(method_name, *arguments)
19
+ # forward only set_* calls to the settings so that the respective setting
20
+ # can enforce it's interface and any unsupported setting setters results
21
+ # in an undefined method error
22
+ if setter?(method_name)
23
+ setting_call = method_name
24
+ settings.public_send(setting_call, *arguments)
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def pngfy
31
+ settings_snapshot = settings.snapshot
32
+ settings_renderer.render_result(settings_snapshot)
33
+ end
34
+
35
+ private
36
+
37
+ attr_accessor(:settings, :settings_renderer)
38
+
39
+ def setter?(method_name)
40
+ method_name.start_with?('set_')
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiPngfy
4
+ # Reponsibilities
5
+ # - The reason for this particulare interface with singleton methods
6
+ # at this point, is to avoid coupling between the settings themselves
7
+ # and because the architecture is about to change in terms of the
8
+ # current implementation of the Settings
9
+ #
10
+ # - Provide a single point of reference for all computations that
11
+ # contribute to the renderer result of the #pngfy process
12
+ #
13
+ # - Computations based on setting values such as color;
14
+ # font height; spacing and text
15
+ #
16
+ # - General enough so that any settings related context can
17
+ # use this functionality
18
+ # rubocop: disable Metrics/ModuleLength
19
+ module RenderingRules
20
+ def self.text_lines(text)
21
+ text.split("\n", -1)
22
+ end
23
+
24
+ def self.longest_text_line(text)
25
+ text_lines(text).max_by(&:length)
26
+ end
27
+
28
+ def self.png_width(settings, override_text = nil)
29
+ use_text = override_text || settings.text
30
+
31
+ longest_text_line_length = longest_text_line(use_text).length
32
+ horizontal_spacing_count = longest_text_line_length - 1
33
+
34
+ (longest_text_line_length * GLYPH_DESIGN_WIDTH) + (horizontal_spacing_count * settings.horizontal_spacing)
35
+ end
36
+
37
+ def self.png_height(settings, override_text = nil)
38
+ use_text = override_text || settings.text
39
+
40
+ text_line_count = text_lines(use_text).size
41
+ vertical_spacing_count = text_line_count - 1
42
+
43
+ (text_line_count * GLYPH_DESIGN_HEIGHT) + (vertical_spacing_count * settings.vertical_spacing)
44
+ end
45
+
46
+ def self.font_multiplier(settings)
47
+ settings.font_height / GLYPH_DESIGN_HEIGHT
48
+ end
49
+
50
+ def self.render_width(settings)
51
+ png_width(settings) * font_multiplier(settings)
52
+ end
53
+
54
+ def self.render_height(settings)
55
+ png_height(settings) * font_multiplier(settings)
56
+ end
57
+
58
+ def self.text_lines_characters(settings)
59
+ text_lines(settings.text).map(&:chars)
60
+ end
61
+
62
+ def self.font_region(settings, character_column_index, character_row_index)
63
+ font_region_top_left_x = character_column_index * (settings.horizontal_spacing + GLYPH_DESIGN_WIDTH)
64
+ font_region_top_left_y = character_row_index * (settings.vertical_spacing + GLYPH_DESIGN_HEIGHT)
65
+ font_region_bottom_right_x = font_region_top_left_x + (GLYPH_DESIGN_WIDTH - 1)
66
+ font_region_bottom_right_y = font_region_top_left_y + (GLYPH_DESIGN_HEIGHT - 1)
67
+
68
+ AABB.new(
69
+ font_region_top_left_x,
70
+ font_region_top_left_y,
71
+ font_region_bottom_right_x,
72
+ font_region_bottom_right_y
73
+ )
74
+ end
75
+
76
+ def self.each_font_region_with_associated_character(settings, &yielder)
77
+ text_lines_characters(settings).each_with_index do |line_characters, row_index|
78
+ line_characters.each_with_index do |character, column_index|
79
+ font_region = font_region(settings, column_index, row_index)
80
+
81
+ yielder.call(font_region, character)
82
+ end
83
+ end
84
+ end
85
+
86
+ def self.each_font_region(settings, &yielder)
87
+ each_font_region_with_associated_character(settings) do |font_region, _font_region_character|
88
+ yielder.call(font_region)
89
+ end
90
+ end
91
+
92
+ def self.color_rgba_to_chunky_png_integer(color_rgba)
93
+ ChunkyPNG::Color.rgba(
94
+ color_rgba.red,
95
+ color_rgba.green,
96
+ color_rgba.blue,
97
+ color_rgba.alpha
98
+ )
99
+ end
100
+
101
+ def self.straight_alpha_composite_color_value(over_component, over_alpha, under_component, under_alpha)
102
+ # over refers to the top layer, i.e. the font layer
103
+ ca = over_component
104
+ aa = over_alpha.fdiv(255)
105
+
106
+ # under refers to the bottom layer, i.e. the background layer
107
+ cb = under_component
108
+ ab = under_alpha.fdiv(255)
109
+
110
+ # return alpha composited color component as integer in range 0..255
111
+ # avoid divisions by zero
112
+ numerator = (ca * aa + cb * ab * (1 - aa))
113
+ denumerator = (aa + ab * (1 - aa))
114
+
115
+ return 0 if denumerator.zero? || numerator.zero?
116
+
117
+ (numerator / denumerator).to_i
118
+ end
119
+
120
+ def self.straight_alpha_composite_alpha_value(over_alpha, under_alpha)
121
+ aa = over_alpha.fdiv(255)
122
+ ab = under_alpha.fdiv(255)
123
+
124
+ # return alpha composited alpha component as integer in range 0..255
125
+ ((aa + ab * (1 - aa)) * 255).to_i
126
+ end
127
+
128
+ def self.straight_alpha_composite_color(over_color, under_color)
129
+ over_color_alpha = over_color.alpha
130
+ under_color_alpha = under_color.alpha
131
+
132
+ AsciiPngfy::ColorRGBA.new(
133
+ straight_alpha_composite_color_value(over_color.red, over_color_alpha, under_color.red, under_color_alpha),
134
+ straight_alpha_composite_color_value(over_color.green, over_color_alpha, under_color.green, under_color_alpha),
135
+ straight_alpha_composite_color_value(over_color.blue, over_color_alpha, under_color.blue, under_color_alpha),
136
+ straight_alpha_composite_alpha_value(over_color_alpha, under_color_alpha)
137
+ )
138
+ end
139
+
140
+ def self.possibly_blended_font_and_background_color(settings)
141
+ case settings.font_color.alpha
142
+ when 255
143
+ settings.font_color
144
+ else
145
+ straight_alpha_composite_color(settings.font_color, settings.background_color)
146
+ end
147
+ end
148
+
149
+ def self.design_character_pixel_color(settings, design_character)
150
+ if AsciiPngfy::Glyphs.font_layer_design_character?(design_character)
151
+ possibly_blended_font_and_background_color(settings)
152
+ elsif AsciiPngfy::Glyphs.background_layer_design_character?(design_character)
153
+ settings.background_color
154
+ end
155
+ end
156
+
157
+ def self.plot_font_regions_with_design(settings, png)
158
+ each_font_region_with_associated_character(settings) do |font_region, character|
159
+ # the only ASCII character that has an empty glyph desgn is the space
160
+ # so avoid unnecessary work for spaces
161
+ next if character == ' '
162
+
163
+ # mirror the font design for each font region into the png
164
+ font_region_character_design = AsciiPngfy::Glyphs::DESIGNS[character]
165
+
166
+ font_region.each_pixel_with_index do |font_pixel_x, font_pixel_y, font_pixel_index|
167
+ png_pixel_design_character = font_region_character_design[font_pixel_index]
168
+
169
+ png_pixel_plot_color = design_character_pixel_color(settings, png_pixel_design_character)
170
+ png_pixel_plot_color_as_integer = color_rgba_to_chunky_png_integer(png_pixel_plot_color)
171
+
172
+ png[font_pixel_x, font_pixel_y] = png_pixel_plot_color_as_integer
173
+ end
174
+ end
175
+ end
176
+
177
+ def self.plot_font_regions_without_design(settings, png)
178
+ final_font_color = possibly_blended_font_and_background_color(settings)
179
+ final_font_color_as_integer = color_rgba_to_chunky_png_integer(final_font_color)
180
+
181
+ each_font_region(settings) do |font_region|
182
+ # fill every font region entirely with, the potentially mixed, font and backround color
183
+ font_region.each_pixel do |font_region_x, font_region_y|
184
+ png[font_region_x, font_region_y] = final_font_color_as_integer
185
+ end
186
+ end
187
+ end
188
+
189
+ def self.plot_settings(settings, use_glyph_designs, png)
190
+ if use_glyph_designs
191
+ plot_font_regions_with_design(settings, png)
192
+ else
193
+ plot_font_regions_without_design(settings, png)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ # rubocop: enable Metrics/ModuleLength