distorted 0.5.3 → 0.6.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 +4 -4
- data/LICENSE +661 -0
- data/README.md +4 -139
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Less_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/More_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/less_more_perfect_dos_vga_437.html +52 -0
- data/font/1252/LICENSE/PerfectDOSVGA437/font-comment.php@file=perfect_dos_vga_437.html +5 -0
- data/font/1252/LessPerfectDOSVGA.ttf +0 -0
- data/font/1252/MorePerfectDOSVGA.ttf +0 -0
- data/font/1252/Perfect DOS VGA 437 Win.ttf +0 -0
- data/font/437/Perfect DOS VGA 437.ttf +0 -0
- data/font/437/dos437.txt +72 -0
- data/font/65001/Anonymous Pro B.ttf +0 -0
- data/font/65001/Anonymous Pro BI.ttf +0 -0
- data/font/65001/Anonymous Pro I.ttf +0 -0
- data/font/65001/Anonymous Pro.ttf +0 -0
- data/font/65001/LICENSE/AnonymousPro/FONTLOG.txt +45 -0
- data/font/65001/LICENSE/AnonymousPro/OFL-FAQ.txt +235 -0
- data/font/65001/LICENSE/AnonymousPro/OFL.txt +94 -0
- data/font/65001/LICENSE/AnonymousPro/README.txt +55 -0
- data/font/850/ProFont-Bold-01/LICENSE +22 -0
- data/font/850/ProFont-Bold-01/readme.txt +28 -0
- data/font/850/ProFontWindows-Bold.ttf +0 -0
- data/font/850/ProFontWindows.ttf +0 -0
- data/font/850/Profont/LICENSE +22 -0
- data/font/850/Profont/readme.txt +31 -0
- data/font/932/LICENSE/README-ttf.txt +213 -0
- data/font/932/mona.ttf +0 -0
- data/lib/distorted/checking_you_out.rb +116 -0
- data/lib/distorted/error_code.rb +51 -0
- data/lib/distorted/injection_of_love.rb +247 -0
- data/lib/distorted/modular_technology/pango.rb +90 -0
- data/lib/distorted/modular_technology/triple_counter.rb +45 -0
- data/lib/distorted/modular_technology/ttfunk.rb +48 -0
- data/lib/distorted/modular_technology/vips.rb +17 -0
- data/lib/distorted/modular_technology/vips_load.rb +77 -0
- data/lib/distorted/modular_technology/vips_save.rb +172 -0
- data/lib/distorted/molecule/C18H27NO3.rb +10 -0
- data/lib/distorted/molecule/font.rb +198 -0
- data/lib/distorted/molecule/image.rb +36 -0
- data/lib/distorted/molecule/pdf.rb +119 -0
- data/lib/distorted/molecule/svg.rb +60 -0
- data/lib/distorted/molecule/text.rb +225 -0
- data/lib/distorted/molecule/video.rb +195 -0
- data/lib/distorted/monkey_business/hash.rb +33 -0
- data/lib/distorted/monkey_business/mnemoniq.rb +8 -0
- data/lib/distorted/monkey_business/set.rb +15 -0
- data/lib/distorted/monkey_business/string.rb +6 -0
- data/lib/distorted/types/README +4 -0
- data/lib/distorted/types/application.yaml +8 -0
- data/lib/distorted/types/font.yaml +29 -0
- data/lib/distorted/version.rb +22 -0
- data/test/distorted_test.rb +11 -0
- data/test/test_helper.rb +4 -0
- metadata +102 -5
data/font/932/mona.ttf
ADDED
Binary file
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'mime/types'
|
4
|
+
require 'ruby-filemagic'
|
5
|
+
|
6
|
+
module MIME
|
7
|
+
class Type
|
8
|
+
# Give MIME::Type objects an easy way to get the DistorteD saver method name.
|
9
|
+
def distorted_method
|
10
|
+
# Standardize MIME::Types' media_type+sub_type to DistorteD method mapping
|
11
|
+
# by replacing all the combining characters with underscores (snake case)
|
12
|
+
# to match Ruby conventions:
|
13
|
+
# https://rubystyle.guide/#snake-case-symbols-methods-vars
|
14
|
+
#
|
15
|
+
# For the worst possible example, an intended outout Type of
|
16
|
+
# "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
17
|
+
# (a.k.a. a MSWord `docx` file) would map to a DistorteD saver method
|
18
|
+
# :to_application_vnd_openxmlformats_officedocument_wordprocessingml_document
|
19
|
+
# which would most likely be defined by the :included method of a library-specific
|
20
|
+
# module for handling OpenXML MS Office documents.
|
21
|
+
"to_#{self.media_type}_#{self.sub_type.gsub(/[-+\.]/, '_'.freeze)}".to_sym
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module CHECKING
|
27
|
+
class YOU
|
28
|
+
|
29
|
+
# Returns a Set of MIME::Type for a given file path, by default only
|
30
|
+
# based on the file extension. If the file extension is unavailable—
|
31
|
+
# or if `so_deep` is enabled—the `path` will be used as an actual
|
32
|
+
# path to look at the magic bytes with ruby-filemagic.
|
33
|
+
def self.OUT(path, so_deep: false)
|
34
|
+
unless so_deep || types.type_for(path).empty?
|
35
|
+
# NOTE: `type_for`'s return order is supposed to be deterministic:
|
36
|
+
# https://github.com/mime-types/ruby-mime-types/issues/148
|
37
|
+
# My use case so far has never required order but has required
|
38
|
+
# many Set comparisons, so I am going to return a Set here
|
39
|
+
# and possibly throw the order away.
|
40
|
+
# In my experience the order is usually preserved anyway:
|
41
|
+
# irb(main)> MIME::Types.type_for(File.expand_path('lol.ttf'))
|
42
|
+
# => [#<MIME::Type: font/ttf>, #<MIME::Type: application/font-sfnt>, #<MIME::Type: application/x-font-truetype>, #<MIME::Type: application/x-font-ttf>]
|
43
|
+
# irb(main)> MIME::Types.type_for('lol.ttf')).to_set
|
44
|
+
# => #<Set: {#<MIME::Type: font/ttf>, #<MIME::Type: application/font-sfnt>, #<MIME::Type: application/x-font-truetype>, #<MIME::Type: application/x-font-ttf>}>
|
45
|
+
return types.type_for(path).to_set
|
46
|
+
else
|
47
|
+
# Did we fail to guess any MIME::Types from the given filename?
|
48
|
+
# We're going to have to look at the actual file
|
49
|
+
# (or at least its first four bytes).
|
50
|
+
FileMagic.open(:mime) do |fm|
|
51
|
+
# The second argument makes fm.file return just the simple
|
52
|
+
# MIME::Type String, e.g.:
|
53
|
+
#
|
54
|
+
# irb(main)> fm.file('/home/okeeblow/IIDX-turntable.svg')
|
55
|
+
# => "image/svg+xml; charset=us-ascii"
|
56
|
+
# irb(main)> fm.file('/home/okeeblow/IIDX-turntable.svg', true)
|
57
|
+
# => "image/svg"
|
58
|
+
#
|
59
|
+
# However MIME::Types won't take short variants like 'image/svg',
|
60
|
+
# so explicitly have FM return long types and split it ourself
|
61
|
+
# on the semicolon:
|
62
|
+
#
|
63
|
+
# irb(main)> "image/svg+xml; charset=us-ascii".split(';').first
|
64
|
+
# => "image/svg+xml"
|
65
|
+
mime = types[fm.file(path, false).split(';'.freeze).first].to_set
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a Set of MIME::Type objects matching a String search key of the
|
71
|
+
# format MEDIA_TYPE/SUB_TYPE.
|
72
|
+
# This can return multiple Types, e.g. 'font/collection' TTC/OTC variations:
|
73
|
+
# [#<MIME::Type: font/collection>, #<MIME::Type: font/collection>]
|
74
|
+
def self.IN(type)
|
75
|
+
types[type, :complete => type.is_a?(Regexp)].to_set
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the MIME::Types container or loads one
|
79
|
+
def self.types
|
80
|
+
@@types ||= types_loader
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a loaded MIME::Types container containing both the upstream
|
84
|
+
# mime-types-data and our own local data.
|
85
|
+
def self.types_loader
|
86
|
+
container = MIME::Types.new
|
87
|
+
|
88
|
+
# Load the upstream mime-types-data by providing a nil `path`:
|
89
|
+
# path || ENV['RUBY_MIME_TYPES_DATA'] || MIME::Types::Data::PATH
|
90
|
+
loader = MIME::Types::Loader.new(nil, container)
|
91
|
+
loader.load_columnar
|
92
|
+
|
93
|
+
# Change default JPEG file extension from .jpeg to .jpg
|
94
|
+
# because it pisses me off lol
|
95
|
+
container['image/jpeg'].last.preferred_extension = 'jpg'
|
96
|
+
|
97
|
+
# Override the loader's path with the path to our local data directory
|
98
|
+
# after we've loaded the upstream data.
|
99
|
+
# :@path is set up in Loader::initialize and only has an attr_reader
|
100
|
+
# but we can reach in and change it.
|
101
|
+
loader.instance_variable_set(:@path, File.join(__dir__, 'types'.freeze))
|
102
|
+
|
103
|
+
# Load our local types data. The YAML files are separated by type,
|
104
|
+
# and :load_yaml will load all of them in the :@path we just set.
|
105
|
+
# MAYBE: Integrate MIME::Types YAML conversion scripts and commit
|
106
|
+
# JSON/Columnar artifacts for SPEEEEEED, but YAML is probably fine
|
107
|
+
# since we will have so few custom types compared to upstream.
|
108
|
+
# Convert.from_yaml_to_json
|
109
|
+
# Convert::Columnar.from_yaml_to_columnar
|
110
|
+
loader.load_yaml
|
111
|
+
|
112
|
+
container
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# https://ruby-doc.org/core/Exception.html sez:
|
2
|
+
# "It is recommended that a library should have one subclass of StandardError
|
3
|
+
# or RuntimeError and have specific exception types inherit from it.
|
4
|
+
# This allows the user to rescue a generic exception type to catch
|
5
|
+
# all exceptions the library may raise even if future versions of
|
6
|
+
# the library add new exception subclasses."
|
7
|
+
class StandardDistorteDError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# The built-in NotImplementedError is for "when a feature is not implemented
|
11
|
+
# on the current platform", so make our own more appropriate ones.
|
12
|
+
class MediaTypeNotImplementedError < StandardDistorteDError
|
13
|
+
attr_reader :name
|
14
|
+
def initialize(name)
|
15
|
+
super
|
16
|
+
@name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def message
|
20
|
+
"No supported media type for #{name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MediaTypeOutputNotImplementedError < MediaTypeNotImplementedError
|
25
|
+
attr_reader :type, :context
|
26
|
+
def initialize(name, type, context)
|
27
|
+
super(name)
|
28
|
+
@type = type
|
29
|
+
@context = context
|
30
|
+
end
|
31
|
+
|
32
|
+
def message
|
33
|
+
"Unable to save #{name} as #{type.to_s} from #{context}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class MediaTypeNotFoundError < StandardDistorteDError
|
38
|
+
attr_reader :name
|
39
|
+
def initialize(name)
|
40
|
+
super
|
41
|
+
@name = name
|
42
|
+
end
|
43
|
+
|
44
|
+
def message
|
45
|
+
"Failed to detect media type for #{name}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
class OutOfDateLibraryError < LoadError
|
51
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'distorted/monkey_business/set'
|
3
|
+
|
4
|
+
|
5
|
+
# This Module supports Module "Piles"* in DistorteD by merging
|
6
|
+
# an arbitrarily-deep nest of attribute-definition constants
|
7
|
+
# into a single combined datastructure per constant at include-/
|
8
|
+
# extend-/prepend-time.
|
9
|
+
# [*] 'Monad' doesn't feel quite right, but http://www.geekculture.com/joyoftech/joyimages/469.gif
|
10
|
+
#
|
11
|
+
# The combined structures can be accessed in one shot and trusted as
|
12
|
+
# a source of truth, versus inspecting Module::nesting or whatever
|
13
|
+
# to iterate over the masked same-Symbol constants we'd get when
|
14
|
+
# including/extending/prepending in Ruby normally:
|
15
|
+
# - https://ruby-doc.org/core/Module.html#method-c-nesting
|
16
|
+
# - http://valve.github.io/blog/2013/10/26/constant-resolution-in-ruby/
|
17
|
+
|
18
|
+
# There's some general redundancy here with Bundler's const_get_safely:
|
19
|
+
# https://ruby-doc.org/stdlib/libdoc/bundler/rdoc/Bundler/SharedHelpers.html#method-i-const_get_safely
|
20
|
+
#
|
21
|
+
# …but even though I use (and enjoy using) Bundler it feels Wrong™ to me to have
|
22
|
+
# that method in stdlib and especially in Core but not as part of Module since
|
23
|
+
# 'Bundler' still feels like a third-party namespace to me v(._. )v
|
24
|
+
|
25
|
+
|
26
|
+
module Cooltrainer; end
|
27
|
+
module Cooltrainer::DistorteD; end
|
28
|
+
module Cooltrainer::DistorteD::InjectionOfLove
|
29
|
+
|
30
|
+
# These hold (possibly-runtime-generated) Sets of MIME::Types (from our loader)*
|
31
|
+
# describing any supported input media-types (:LOWER_WORLD)
|
32
|
+
# and any supported output media-types (:OUTER_LIMITS).
|
33
|
+
TYPE_CONSTANTS = Set[
|
34
|
+
:LOWER_WORLD,
|
35
|
+
:OUTER_LIMITS,
|
36
|
+
]
|
37
|
+
# These hold Hashes or Sets describing supported attributes,
|
38
|
+
# supported attribute-values (otherwise freeform),
|
39
|
+
# attribute defaults if any, and mappings for any of those things
|
40
|
+
# to any aliased equivalents for normalization and localization.
|
41
|
+
ATTRIBUTE_CONSTANTS = Set[
|
42
|
+
:ATTRIBUTES,
|
43
|
+
:ATTRIBUTES_DEFAULT,
|
44
|
+
:ATTRIBUTES_VALUES,
|
45
|
+
]
|
46
|
+
# 🄒 All of the above.
|
47
|
+
DISTORTED_CONSTANTS = Set[].merge(TYPE_CONSTANTS).merge(ATTRIBUTE_CONSTANTS)
|
48
|
+
|
49
|
+
# Name of our fully-merged-Hash's class variable.
|
50
|
+
AFTERPARTY = :@@DistorteD
|
51
|
+
|
52
|
+
|
53
|
+
# Activate this module when it's included.
|
54
|
+
# We will merge DistorteD attributes to the singleton class from
|
55
|
+
# our including context and from out including context's included_modules,
|
56
|
+
# then we will define methods in the including context to perpetuate
|
57
|
+
# the merging process when that context is included/extended/prepended.
|
58
|
+
def self.included(otra)
|
59
|
+
self::Injection_Of_Love.call(otra)
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
# "Attribute" fragments are processed in one additional step to support
|
64
|
+
# aliased/equivalent attribute names and values.
|
65
|
+
# This is a quality-of-life feature to help normalize/localize attributes
|
66
|
+
# defined in a wide range of places by multiple unrelated upstream devs.
|
67
|
+
#
|
68
|
+
# For example, libvips savers expect a single-character upper-case
|
69
|
+
# `Q` argument for their 1–100 integer quality factor,
|
70
|
+
# and my VipsSave module's `:ATTRIBUTES` additionally aliases it
|
71
|
+
# to the more typical `quality` to provide consistent UX
|
72
|
+
# with other attributes from VIPS and other sources.
|
73
|
+
# https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave
|
74
|
+
#
|
75
|
+
# VIPS also provides our example of the need for attribute-value equivalents,
|
76
|
+
# such as how it only accepts the spelling of "centre" and not "center"
|
77
|
+
# like myself and many millions of other people will reflexively enter :)
|
78
|
+
# https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsInteresting
|
79
|
+
def self.so_deep(fragment)
|
80
|
+
fragment.each_with_object(Array[]) { |(attribute, raw), to_merge|
|
81
|
+
# Each attribute's :raw may be an object (probably a Symbol),
|
82
|
+
# a Set (e.g. of aliases), or nil (for all values in a Set.to_hash)
|
83
|
+
case raw
|
84
|
+
when Set then raw.add(attribute)
|
85
|
+
when NilClass then [attribute]
|
86
|
+
else [attribute]
|
87
|
+
end.each{ |equivalent|
|
88
|
+
to_merge << [equivalent, attribute]
|
89
|
+
}
|
90
|
+
}.to_h
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a block that will define methods in a given context
|
94
|
+
# such that when the given context is included/extended/prepended
|
95
|
+
# we will also merge our DD attributes into the new layer.
|
96
|
+
Injection_Of_Love = Proc.new { |otra|
|
97
|
+
# These are the methods that actively perform the include/extend/prepend process.
|
98
|
+
[:append_features, :prepend_features, :extend_object].each { |m|
|
99
|
+
otra.define_singleton_method(m) do |winter|
|
100
|
+
# Perform the normal include/extend/prepend that will mask our constants.
|
101
|
+
super(winter)
|
102
|
+
|
103
|
+
# Get new values to override masked constants.
|
104
|
+
pile = Cooltrainer::DistorteD::InjectionOfLove::trip_machine(winter)
|
105
|
+
|
106
|
+
# Record each constant individually as well as the entire pile.
|
107
|
+
# This doesn't currently get used, as this :class_variable_set call
|
108
|
+
# is broken in KRI:
|
109
|
+
# - https://bugs.ruby-lang.org/issues/7475
|
110
|
+
# - https://bugs.ruby-lang.org/issues/8297
|
111
|
+
# - https://bugs.ruby-lang.org/issues/11022
|
112
|
+
winter.singleton_class.class_variable_set(AFTERPARTY, pile)
|
113
|
+
pile.each_pair{ |k, v|
|
114
|
+
if winter.singleton_class.const_defined?(k, false)
|
115
|
+
# Since we are setting constants in the singleton_class
|
116
|
+
# we must remove any old ones first to avoid a warning.
|
117
|
+
winter.singleton_class.send(:remove_const, k)
|
118
|
+
end
|
119
|
+
winter.singleton_class.const_set(k, v)
|
120
|
+
}
|
121
|
+
end
|
122
|
+
}
|
123
|
+
# These are the callback methods called after the above methods fire.
|
124
|
+
# Use them to perpetuate our merge by calling the thing that calls us :)
|
125
|
+
[:included, :prepended, :extended].each { |m|
|
126
|
+
otra.define_singleton_method(m) do |winter|
|
127
|
+
Cooltrainer::DistorteD::InjectionOfLove::Injection_Of_Love.call(winter)
|
128
|
+
super(winter)
|
129
|
+
end
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
# Returns an instance-level copy of the complete attribute pile.
|
134
|
+
def trip_machine
|
135
|
+
@DistorteD ||= Cooltrainer::DistorteD::InjectionOfLove::trip_machine(self.singleton_class)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Builds the attribute pile (e.g. suported atrs, values, defaults, etc) for any given scope.
|
139
|
+
def self.trip_machine(scope)
|
140
|
+
attribute_aliases = Hash[]
|
141
|
+
alias_attributes = Hash[]
|
142
|
+
values = Hash[]
|
143
|
+
defaults = Hash[]
|
144
|
+
|
145
|
+
scope&.ancestors.each { |otra|
|
146
|
+
DISTORTED_CONSTANTS.each { |invitation| # OUT OF CONTROL / MY WHEELS IN CONSTANT MOTION
|
147
|
+
if otra.const_defined?(invitation)
|
148
|
+
part = otra.const_get(invitation) rescue Hash[]
|
149
|
+
|
150
|
+
if invitation == :ATTRIBUTES
|
151
|
+
# Support both alias-to-attribute and attribute-to-aliases
|
152
|
+
attribute_aliases.merge!(part) { |invitation, old, new|
|
153
|
+
if old.nil?
|
154
|
+
new.nil? ? Set[invitation] : Set[new]
|
155
|
+
elsif new.nil?
|
156
|
+
old
|
157
|
+
elsif new.is_a?(Enumerable)
|
158
|
+
old.merge(new)
|
159
|
+
else
|
160
|
+
old << new
|
161
|
+
end
|
162
|
+
}
|
163
|
+
alias_attributes.merge!(Cooltrainer::DistorteD::InjectionOfLove::so_deep(part))
|
164
|
+
elsif invitation == :ATTRIBUTES_VALUES
|
165
|
+
# Regexes currently override Enumerables
|
166
|
+
to_merge = {}
|
167
|
+
part.each_pair { |attribute, values|
|
168
|
+
if values.is_a?(Regexp)
|
169
|
+
to_merge.update(attribute => values)
|
170
|
+
else
|
171
|
+
to_merge.update(attribute => Cooltrainer::DistorteD::InjectionOfLove::so_deep(values))
|
172
|
+
end
|
173
|
+
}
|
174
|
+
values.merge!(to_merge)
|
175
|
+
elsif invitation == :ATTRIBUTES_DEFAULT
|
176
|
+
defaults.merge!(part)
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
return {
|
184
|
+
:ATTRIBUTE_ALIASES => attribute_aliases,
|
185
|
+
:ALIAS_ATTRIBUTES => alias_attributes,
|
186
|
+
:ATTRIBUTES_VALUES => values,
|
187
|
+
:ATTRIBUTES_DEFAULT => defaults,
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns a value for any attribute.
|
192
|
+
# In order of priority, that means:
|
193
|
+
# - A user-given value (Liquid, CLI, etc) iff it passes a validity check,
|
194
|
+
# - the default value if the given value is not in the accepted Set,
|
195
|
+
# - nil for unset attributes with no default defined.
|
196
|
+
def abstract(argument)
|
197
|
+
# Reject any unknown arguments.
|
198
|
+
if trip_machine.dig(:ATTRIBUTE_ALIASES)&.keys.include?(argument)
|
199
|
+
alias_possibilities = trip_machine.dig(:ATTRIBUTE_ALIASES)&.dig(argument) || Set[]
|
200
|
+
possibilities = user_arguments&.keys.to_set & alias_possibilities
|
201
|
+
|
202
|
+
# How many matching user-defined attributes are there for our aliases?
|
203
|
+
case possibilities.length
|
204
|
+
when 0
|
205
|
+
# None; take the default.
|
206
|
+
trip_machine.dig(:ATTRIBUTES_DEFAULT)&.dig(argument)
|
207
|
+
when 1
|
208
|
+
# One; does it look valid?
|
209
|
+
is_valid = false
|
210
|
+
user_value = user_arguments&.dig(argument)
|
211
|
+
|
212
|
+
# Supported values may be declared as:
|
213
|
+
# - A Hash of values-and-their-aliases to values.
|
214
|
+
# - A Regex.
|
215
|
+
# - nil for freeform input.
|
216
|
+
valid_value = trip_machine.dig(:ATTRIBUTES_VALUES)&.dig(argument)
|
217
|
+
if valid_value.is_a?(Enumerable)
|
218
|
+
if valid_value.include?(user_value)
|
219
|
+
is_valid = true
|
220
|
+
end
|
221
|
+
elsif valid_value.is_a?(Regexp)
|
222
|
+
if valid_value.match(user_value.to_s)
|
223
|
+
is_valid = true
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Return a valid user value, a default, or nil if all else fails.
|
228
|
+
if is_valid
|
229
|
+
# TODO: boolean casting
|
230
|
+
user_value
|
231
|
+
else
|
232
|
+
trip_machine.dig(:ATTRIBUTES_DEFAULT)&.dig(argument)
|
233
|
+
end
|
234
|
+
|
235
|
+
else # case user_values.length
|
236
|
+
# Two or more; what do??
|
237
|
+
raise RuntimeError("Can't have multiple settings for #{argument} and its aliases.")
|
238
|
+
end
|
239
|
+
else
|
240
|
+
# The programmer asked for the value of an attribute that is
|
241
|
+
# not supported by its MediaMolecule. This is most likely a bug.
|
242
|
+
raise RuntimeError("#{argument} is not supported for #{@name}")
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Cooltrainer
|
2
|
+
module DistorteD
|
3
|
+
module Technology
|
4
|
+
module Pango
|
5
|
+
|
6
|
+
# Escape text as necessary for Pango Markup, which is what Vips::Image.text()
|
7
|
+
# expects for its argv. This code should be in GLib but is unimplemented in Ruby's:
|
8
|
+
#
|
9
|
+
# https://ruby-gnome2.osdn.jp/hiki.cgi?Gtk%3A%3ALabel#Markup+%28styled+text%29
|
10
|
+
# "The markup passed to Gtk::Label#set_markup() must be valid; for example,
|
11
|
+
# literal </>/& characters must be escaped as <, >, and &.
|
12
|
+
# If you pass text obtained from the user, file, or a network to
|
13
|
+
# Gtk::Label#set_markup(), you'll want to escape it
|
14
|
+
# with GLib::Markup.escape_text?(not implemented yet)."
|
15
|
+
#
|
16
|
+
# Base my own implementation on the original C version found in gmarkup:
|
17
|
+
# https://gitlab.gnome.org/GNOME/glib/-/blob/master/glib/gmarkup.c
|
18
|
+
def g_markup_escape_text(text)
|
19
|
+
text.map{ |c| g_markup_escape_char(c) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a Pango-escaped Carriage Return.
|
23
|
+
# Use this for linebreaking Pango Markup output.
|
24
|
+
def cr
|
25
|
+
g_markup_escape_char(0x0D)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a Pango-escapped Line Feed.
|
29
|
+
# This isn't used/needed for anything with Pango
|
30
|
+
# but it felt weird to include CR and not LF lmao
|
31
|
+
def lf
|
32
|
+
g_markup_escape_char(0x0A)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a Pango'escaped CRLF pair.
|
36
|
+
# Also not needed for anything.
|
37
|
+
def crlf
|
38
|
+
cr << lf
|
39
|
+
end
|
40
|
+
|
41
|
+
# "Modified UTF-8" uses a normally-illegal byte sequence
|
42
|
+
# to encode the NULL character so 0x00 can exclusively
|
43
|
+
# be a string terminator.
|
44
|
+
def overlong_null
|
45
|
+
[0xC0, 0x80].pack('C*').force_encoding('UTF-8')
|
46
|
+
end
|
47
|
+
|
48
|
+
# The char-by-char actual function used by g_markup_escape_text
|
49
|
+
def g_markup_escape_char(c)
|
50
|
+
# I think a fully-working version of this function would
|
51
|
+
# be as simple as `sprintf('&#x%x;', c.ord)` ALL THE THINGS,
|
52
|
+
# but I want to copy the structure of the C implementation
|
53
|
+
# as closely as possible, which means using the named escape
|
54
|
+
# sequences for common characters and separating the
|
55
|
+
# Latin-1 Supplement range from the other
|
56
|
+
# the Unicode control characters (> 0x7f) even though three's no
|
57
|
+
# need to in Ruby.
|
58
|
+
case c.ord
|
59
|
+
when '&'.ord
|
60
|
+
'&'
|
61
|
+
when '<'.ord
|
62
|
+
'<'
|
63
|
+
when '>'.ord
|
64
|
+
'>'
|
65
|
+
when '\''.ord
|
66
|
+
'''
|
67
|
+
when '"'.ord
|
68
|
+
'"'
|
69
|
+
when 0x1..0x8, 0xb..0xc, 0xe..0x1f, 0x7f
|
70
|
+
sprintf('&#x%x;', c.ord)
|
71
|
+
when 0x80..0x84, 0x86..0x9f
|
72
|
+
# The original C implementation separates this range
|
73
|
+
# from the above range due to its need to handle the
|
74
|
+
# UTF control character bytes with gunichar:
|
75
|
+
# https://wiki.tcl-lang.org/page/UTF%2D8+bit+by+bit
|
76
|
+
# https://www.fileformat.info/info/unicode/utf8.htm
|
77
|
+
# Ruby has already done this for us here :)
|
78
|
+
sprintf('&#x%x;', c.ord)
|
79
|
+
when 0x0 # what's this…?
|
80
|
+
# Avoid a `ArgumentError: string contains null byte`
|
81
|
+
# by not printing one :)
|
82
|
+
else
|
83
|
+
c
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end # Pango
|
88
|
+
end # Tech
|
89
|
+
end # DistorteD
|
90
|
+
end # Cooltrainer
|