distorted 0.5.7 → 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.
@@ -0,0 +1,45 @@
1
+ TripleCounter = Struct.new(:major, :minor, :micro) do
2
+ attr_reader :major, :minor, :micro
3
+
4
+ def initialize(major = 0, minor = 0, micro = 0)
5
+ @major = major
6
+ @minor = minor
7
+ @micro = micro
8
+ end
9
+
10
+ def to_s
11
+ [major, minor, micro].join('.'.freeze)
12
+ end
13
+
14
+ def ==(otra)
15
+ major == otra.major && minor == otra.minor
16
+ end
17
+
18
+ def ===(otra)
19
+ all_operator(otra, :==)
20
+ end
21
+
22
+ def >=(otra)
23
+ all_operator(otra, :>=)
24
+ end
25
+
26
+ def <=(otra)
27
+ all_operator(otra, :<=)
28
+ end
29
+
30
+ def >(otra)
31
+ all_operator(otra, :>)
32
+ end
33
+
34
+ def <(otra)
35
+ all_operator(otra, :<)
36
+ end
37
+
38
+ def to_array
39
+ [major, minor, micro]
40
+ end
41
+
42
+ def all_operator(otra, operator)
43
+ to_array.zip(otra.to_array).all?{|us, otra| us.send(operator, otra)}
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ require 'ttfunk'
2
+
3
+ module Cooltrainer; end
4
+ module Cooltrainer::DistorteD; end
5
+ module Cooltrainer::DistorteD::Technology; end
6
+ module Cooltrainer::DistorteD::Technology::TTFunk
7
+
8
+ def to_ttfunk
9
+ # TODO: Check that src exists, because TTFunk won't and will just
10
+ # give us an unusable object instead.
11
+ @ttfunk_file ||= TTFunk::File.open(font_path)
12
+ end
13
+
14
+ # Returns a boolean for whether or not this font is monospaced.
15
+ # true == monospace
16
+ # false == proportional
17
+ def font_spacing
18
+ # Monospace fonts will (read: should) have the same width
19
+ # for every glyph, so we can tell a monospace font by
20
+ # checking if a deduplicated widths table has size == 1:
21
+ # irb(main)> font.horizontal_metrics.widths.count
22
+ # => 256
23
+ # irb(main)> font.horizontal_metrics.widths.uniq.compact.length
24
+ # => 1
25
+ to_ttfunk.horizontal_metrics.widths.uniq.compact.length == 1 ? :monospace : :proportional
26
+ end
27
+
28
+ # Returns the Family and Subfamily as one string suitable for libvips
29
+ def font_name
30
+ "#{to_ttfunk.name.font_family.first.encode('UTF-8')} #{to_ttfunk.name.font_subfamily.first.encode('UTF-8')}"
31
+ end
32
+
33
+ # Returns the Pango-Markup-encoded UTF-8 String version + revision of the font
34
+ def font_version
35
+ g_markup_escape_text(to_ttfunk.name&.version&.first&.encode('UTF-8').to_s)
36
+ end
37
+
38
+ # Returns the Pango-Markup-encoded UTF-8 String font file description
39
+ def font_description
40
+ g_markup_escape_text(to_ttfunk.name&.description&.first&.encode('UTF-8').to_s)
41
+ end
42
+
43
+ # Returns the Pango-Markup-encoded UTF-8 String copyright information of the font
44
+ def font_copyright
45
+ g_markup_escape_text(to_ttfunk.name&.copyright&.first&.encode('UTF-8').to_s)
46
+ end
47
+
48
+ end
@@ -0,0 +1,17 @@
1
+ require 'set'
2
+
3
+ require 'distorted/checking_you_out'
4
+
5
+ require 'distorted/modular_technology/vips_load'
6
+ require 'distorted/modular_technology/vips_save'
7
+
8
+
9
+ module Cooltrainer; end
10
+ module Cooltrainer::DistorteD; end
11
+ module Cooltrainer::DistorteD::Technology; end
12
+ module Cooltrainer::DistorteD::Technology::Vips
13
+
14
+ include Cooltrainer::DistorteD::Technology::VipsSave
15
+ include Cooltrainer::DistorteD::Technology::VipsLoad
16
+
17
+ end
@@ -0,0 +1,77 @@
1
+
2
+ require 'set'
3
+
4
+ require 'distorted/checking_you_out'
5
+ require 'distorted/modular_technology/vips_save'
6
+
7
+
8
+ module Cooltrainer; end
9
+ module Cooltrainer::DistorteD; end
10
+ module Cooltrainer::DistorteD::Technology; end
11
+ module Cooltrainer::DistorteD::Technology::VipsLoad
12
+
13
+ # Returns a Set of MIME::Types based on libvips LipsForeignLoad capabilities.
14
+ # NOTE: libvips only declares support (via :get_suffixes) for the "saver" types,
15
+ # but libvips can use additional external libraries for wider media-types support, e.g.:
16
+ #
17
+ # - SVG with librsvg2★ / libcairo. [*]
18
+ # - PDF with PDFium if available, otherwise with libpoppler-glib / libcairo.
19
+ # - OpenEXR/libIlmImf — ILM high dynamic range image format.
20
+ # - maybe more: https://github.com/libvips/libvips/blob/master/configure.ac
21
+ #
22
+ # [FITS]: https://heasarc.gsfc.nasa.gov/docs/heasarc/fits.html
23
+ #
24
+ # [RSVG2]: This is the normal SVG library for the GNOME/GLib world and is
25
+ # probably fine for 95% of use-cases, but I'm pissed off at it because of:
26
+ #
27
+ # - https://gitlab.gnome.org/GNOME/librsvg/-/issues/56
28
+ # - https://gitlab.gnome.org/GNOME/librsvg/-/issues/100
29
+ # - https://gitlab.gnome.org/GNOME/librsvg/-/issues/183
30
+ # - https://gitlab.gnome.org/GNOME/librsvg/-/issues/494
31
+ # - https://bugzilla.gnome.org/show_bug.cgi?id=666477
32
+ # - https://phabricator.wikimedia.org/T35245
33
+ #
34
+ # TLDR: SVG <tspan> elements' [:x, :y, :dy, :dx] attributes can be
35
+ # a space-delimited list of position values for individual
36
+ # characters in the <tspan>, but librsvg2 only supported reading
37
+ # those attributes as a single one-shot numeric value.
38
+ # Documents using this totally-common and totally-in-spec feature
39
+ # rendered incorrectly with librsvg2. Effected <tspan> elements'
40
+ # subsequent children would hug one edge of the rendered output.
41
+ #
42
+ # And wouldn't you know it but the one (1) SVG on my website
43
+ # at the time I built this feature (IIDX-Turntable-parts.svg) used
44
+ # this feature for the double-digit parts diagram labels.
45
+ # I ended up having to edit my input document to just squash the
46
+ # offending <tspan>s down to a single child each.
47
+ # I guess that's semantically more correct in my document since they are
48
+ # numbers like Eleven and not two separate characters like '1 1'
49
+ # but still ugh lol
50
+ #
51
+ # This was finally fixed in 2019 as of librsvg2 version 2.45.91 :)
52
+ # https://gitlab.gnome.org/GNOME/librsvg/-/issues/494#note_579774
53
+ #
54
+
55
+ # TODO: Figure out how to detect non-Magick non-Saver Loader support,
56
+ # by which I mean "everything not included in :get_suffixes".
57
+ # NOTE: The Magick-based '.bmp' loader is broken/missing in libvips <= 8.9.1:
58
+ # https://github.com/libvips/libvips/issues/1528
59
+ # irb> MIME::Types.type_for('.bmp')
60
+ # => [#<MIME::Type: image/bmp>, #<MIME::Type: image/x-bmp>, #<MIME::Type: image/x-ms-bmp>]
61
+ # irb> MIME::Types.type_for('.bmp').map(&:preferred_extension)
62
+ # => ["bmp", "bmp", "bmp"]
63
+ LOWER_WORLD = (VIPS_AVAILABLE_VER < TripleCounter.new(8, 9, 1)) ?
64
+ Cooltrainer::DistorteD::Technology::VipsSave::OUTER_LIMITS.keep_if { |t| t.preferred_extension != 'bmp'.freeze } :
65
+ Cooltrainer::DistorteD::Technology::VipsSave::OUTER_LIMITS
66
+
67
+ def to_vips_image
68
+ # TODO: Learn more about what VipsAccess means for our use case,
69
+ # if the default should be changed, and if it should be
70
+ # a user-controllable attr or not.
71
+ # https://libvips.github.io/libvips/API/current/VipsImage.html#VipsAccess
72
+ # https://libvips.github.io/libvips/API/current/How-it-opens-files.md.html
73
+ @vips_image ||= Vips::Image.new_from_file(path)
74
+ end
75
+
76
+
77
+ end
@@ -0,0 +1,172 @@
1
+
2
+ # Requiring libvips 8.8 for HEIC/HEIF (moo) support, `justify` support in the
3
+ # Vips::Image text operator, animated WebP support, and more:
4
+ # https://libvips.github.io/libvips/2019/04/22/What's-new-in-8.8.html
5
+
6
+ require 'distorted/modular_technology/triple_counter'
7
+ VIPS_MINIMUM_VER = TripleCounter.new(8, 8, 0)
8
+
9
+ # Tell the user to install the shared library if it's missing.
10
+ begin
11
+ require 'vips'
12
+ VIPS_AVAILABLE_VER = TripleCounter.new(Vips::version(0), Vips::version(1), Vips::version(2))
13
+
14
+ unless VIPS_AVAILABLE_VER >= VIPS_MINIMUM_VER
15
+ raise LoadError.new(
16
+ "DistorteD needs libvips #{VIPS_MINIMUM_VER}, but the available version is '#{Vips::version_string}'"
17
+ )
18
+ end
19
+
20
+ rescue LoadError => le
21
+ # Only match libvips.so load failure
22
+ raise unless le.message =~ /libvips.so/
23
+
24
+ # Multiple OS help
25
+ help = <<~INSTALL
26
+
27
+ Please install the VIPS (libvips) image processing library, version #{VIPS_MINIMUM_VER} or later.
28
+
29
+ FreeBSD:
30
+ pkg install graphics/vips
31
+
32
+ macOS:
33
+ brew install vips
34
+
35
+ Debian/Ubuntu/Mint:
36
+ apt install libvips libvips-dev
37
+ INSTALL
38
+
39
+ # Re-raise with install message
40
+ raise $!, "#{help}\n#{$!}", $!.backtrace
41
+ end
42
+
43
+
44
+ require 'set'
45
+
46
+ require 'distorted/checking_you_out'
47
+
48
+
49
+ module Cooltrainer; end
50
+ module Cooltrainer::DistorteD; end
51
+ module Cooltrainer::DistorteD::Technology; end
52
+ module Cooltrainer::DistorteD::Technology::VipsSave
53
+
54
+ ATTRIBUTES = {
55
+ :crop => nil,
56
+ :Q => Set[:quality],
57
+ }
58
+ ATTRIBUTES_DEFAULT = {
59
+ :crop => :attention,
60
+ }
61
+ ATTRIBUTES_VALUES = {
62
+ # https://www.rubydoc.info/gems/ruby-vips/Vips/Interesting
63
+ :crop => {
64
+ :none => nil,
65
+ :centre => Set[:center], # America, FUCK YEAH!
66
+ :entropy => nil,
67
+ :attention => nil,
68
+ },
69
+ }
70
+
71
+ # Returns a Set of MIME::Types based on libvips VipsForeignSave capabilities.
72
+ # https://libvips.github.io/libvips/API/current/VipsForeignSave.html
73
+ #
74
+ # There is one (only one) native libvips image format, with file extname `.vips`.
75
+ # As I write this—running libvips 8.8—the :get_suffixes function does not include
76
+ # its own '.vips' as a supported extension.
77
+ # There also (as of mid 2020) seems to be no official media-type assigned
78
+ # for VIPS format, so I am going to make one up in CHECKING::YOU::OUT's local-data.
79
+ # - Raw pixel data
80
+ #
81
+ # [RAW]: https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-rawload
82
+ # https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-csvload
83
+ #
84
+ # Most libvips installations, even very minimally-built ones,
85
+ # will almost certainly support a few very common formats:
86
+ # - JPEG with libjpeg.
87
+ # - PNG with libpng.
88
+ # - GIF with giflib.
89
+ # - WebP with libwebp.
90
+ # - TIFF with libtiff.
91
+ #
92
+ # Normal libvips installations probably also support many less-mainstream formats:
93
+ # - HEIF/HEIC with libheif.
94
+ # - ICC profiles with liblcms2.
95
+ # - Matlab with matio/libhdf5.
96
+ # - FITS★ with cfitsio.
97
+ # - Styled text with Pango/ft2.
98
+ # - Saving GIF/BMP with Magick.
99
+ # NOTE that GIFs are *loaded* using giflib,
100
+ # and that BMP loading is unsupported.
101
+ # - Various simple ASCII/binary-based formats with libgsf★
102
+ # · Comma-separated values
103
+ # · Netpbm★
104
+ # · VIPS (non-Matlab) matrices★
105
+ #
106
+ # [NETPBM]: https://en.wikipedia.org/wiki/Netpbm#File_formats
107
+ # [LIBGSF]: https://developer.gnome.org/gsf/
108
+ # [MATRIX]: https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-matrixload
109
+
110
+ # Vips allows us to query supported *SAVE* types by suffix.
111
+ # There's a simple relationship between filetype and extension since
112
+ # libvips uses the suffix to pick the Saver module.
113
+ #
114
+ # Loader modules, on the other hand, are picked by sniffing the
115
+ # first few bytes of the file, so a list of file extensions for
116
+ # supported loadable formats won't always be complete.
117
+ # For example, SVG and PDF are usually supported as loaders
118
+ # (via rsvg and PDFium/Poppler)
119
+ # https://github.com/libvips/ruby-vips/issues/186
120
+ #
121
+ # irb(main)> Vips.get_suffixes
122
+ # => [".csv", ".mat", ".v", ".vips", ".ppm", ".pgm", ".pbm", ".pfm",
123
+ # ".hdr", ".dz", ".png", ".jpg", ".jpeg", ".jpe", ".webp", ".tif",
124
+ # ".tiff", ".fits", ".fit", ".fts", ".gif", ".bmp"]
125
+ OUTER_LIMITS = Vips.get_suffixes.map{ |t|
126
+ # A single call to this will return a Set of MIME::Types for a String input
127
+ CHECKING::YOU::OUT(t)
128
+ }.reduce { |c,t|
129
+ # Flatten the Set-of-Sets-of-Types into a Set-of-Types
130
+ (c || Set[]).merge(t)
131
+ }.keep_if { |t|
132
+ # Filter out any of libvips' supported output Types that aren't
133
+ # actually images (e.g. CSV)
134
+ t.media_type == 'image'
135
+ }
136
+
137
+ # Define a to_<mediatype>_<subtype> method for each MIME::Type supported by libvips,
138
+ # e.g. a supported Type 'image/png' will define a method :to_image_png in any
139
+ # context where this module is included.
140
+ self::OUTER_LIMITS.each { |t|
141
+ define_method(t.distorted_method) { |*a, **k, &b|
142
+ vips_save(*a, **k, &b)
143
+ }
144
+ }
145
+
146
+ protected
147
+
148
+ # Generic Vips saver method, optionally handling resizing and cropping.
149
+ # NOTE: libvips chooses a saver (internally) based on the extname of the destination path.
150
+ def vips_save(dest, width: nil, **kw)
151
+ begin
152
+ if width.nil? or width == :full
153
+ return to_vips_image.write_to_file(dest)
154
+ elsif width.respond_to?(:to_i)
155
+ ver = to_vips_image.thumbnail_image(
156
+ width.to_i,
157
+ # Use `self` namespace for constants so subclasses can redefine
158
+ **{:crop => abstract(:crop)},
159
+ )
160
+ return ver.write_to_file(dest)
161
+ end
162
+ rescue Vips::Error => v
163
+ if v.message.include?('No known saver')
164
+ # TODO: Handle missing output formats. Replacements? Skip it? Die?
165
+ return nil
166
+ else
167
+ raise
168
+ end
169
+ end
170
+ end # save
171
+
172
+ end
@@ -0,0 +1,10 @@
1
+ require 'distorted/injection_of_love'
2
+
3
+ module Cooltrainer; end
4
+ module Cooltrainer::DistorteD; end
5
+ module Cooltrainer::DistorteD::Molecule; end
6
+ module Cooltrainer::DistorteD::Molecule::C18H27NO3
7
+
8
+ BOOLEAN_ATTR_VALUES = Set[0, 1, false, true, '0'.freeze, '1'.freeze, 'false'.freeze, 'true'.freeze]
9
+
10
+ end
@@ -3,35 +3,48 @@ require 'set'
3
3
  # Font metadata extraction
4
4
  require 'ttfunk'
5
5
 
6
- require 'mime/types'
7
-
8
- # No need to do all the fancy library versioning in a subclass.
9
- require 'vips'
10
-
11
- require 'distorted/text'
6
+ require 'distorted/modular_technology/pango'
7
+ require 'distorted/modular_technology/ttfunk'
8
+ require 'distorted/modular_technology/vips_save'
9
+ require 'distorted/checking_you_out'
10
+ require 'distorted/injection_of_love'
12
11
 
13
12
 
14
13
  module Cooltrainer
15
14
  module DistorteD
16
- class Font < Text
15
+ module Font
17
16
 
18
- MEDIA_TYPE = 'font'.freeze
19
17
 
20
18
  # TODO: Test OTF, OTB, and others.
21
19
  # NOTE: Traditional bitmap fonts won't be supported due to Pango 1.44
22
20
  # and later switching to Harfbuzz from Freetype:
23
21
  # https://gitlab.gnome.org/GNOME/pango/-/issues/386
24
22
  # https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/
25
- MIME_TYPES = MIME::Types[/^#{self::MEDIA_TYPE}\/ttf/].to_set
23
+ LOWER_WORLD = CHECKING::YOU::IN(/^font\/ttf/)
24
+ OUTER_LIMITS = CHECKING::YOU::IN(/^font\/ttf/)
26
25
 
27
- ATTRS = Set[
26
+ ATTRIBUTES = Set[
28
27
  :alt,
29
28
  ]
30
- ATTRS_VALUES = {
29
+ ATTRIBUTES_VALUES = {
31
30
  }
32
- ATTRS_DEFAULT = {
31
+ ATTRIBUTES_DEFAULT = {
32
+ }
33
+
34
+
35
+ # Maybe T0DO: Process output with TTFunk instead of only using it
36
+ # to generate images and metadata.
37
+ self::OUTER_LIMITS.each { |t|
38
+ define_method(t.distorted_method) { |*a, **k, &b|
39
+ copy_file(*a, **k, &b)
40
+ }
33
41
  }
34
42
 
43
+ include Cooltrainer::DistorteD::Technology::TTFunk
44
+ include Cooltrainer::DistorteD::Technology::Pango
45
+ include Cooltrainer::DistorteD::Technology::VipsSave
46
+ include Cooltrainer::DistorteD::InjectionOfLove
47
+
35
48
 
36
49
  # irb(main):089:0> chars.take(5)
37
50
  # => [[1, 255], [2, 1], [3, 2], [4, 3], [5, 4]]
@@ -49,8 +62,8 @@ module Cooltrainer
49
62
  output << "<span size='24576'> #{font_version}</span>" << cr << cr
50
63
 
51
64
  # Print a preview String in using the loaded font. Or don't.
52
- if @demo
53
- output << cr << cr << "<span size='24576' foreground='grey'> #{g_markup_escape_text(@demo)}</span>" << cr << cr << cr
65
+ if abstract(:title)
66
+ output << cr << cr << "<span size='24576' foreground='grey'> #{g_markup_escape_text(abstract(:title))}</span>" << cr << cr << cr
54
67
  end
55
68
 
56
69
  # /!\ MANDATORY READING /!\
@@ -71,7 +84,7 @@ module Cooltrainer
71
84
  # => 3
72
85
  # irb(main):175:0> font_meta.cmap.unicode.count
73
86
  # => 2
74
- @font_meta.cmap.tables.each do |table|
87
+ to_ttfunk.cmap.tables.each do |table|
75
88
  next if !table.unicode?
76
89
  # Each subtable's `code_map` is a Hash map of character codes (the Hash keys)
77
90
  # to the glyph IDs from the original font (the Hash's values).
@@ -93,7 +106,7 @@ module Cooltrainer
93
106
  encoded.each_pair { |c, (old, new)|
94
107
 
95
108
  begin
96
- if glyph = @font_meta.glyph_outlines.for(c)
109
+ if glyph = to_ttfunk.glyph_outlines.for(c)
97
110
  # Add a space on either side of the character so they aren't
98
111
  # all smooshed up against each other and unreadable.
99
112
  output << ' ' << g_markup_escape_char(c) << ' '
@@ -133,19 +146,12 @@ module Cooltrainer
133
146
  # Return the `src` as the font_path since we aren't using
134
147
  # any of the built-in fonts.
135
148
  def font_path
136
- @src
149
+ path
137
150
  end
138
151
 
139
- def initialize(src, demo: nil)
140
- @src = src
141
- @demo = demo
142
-
143
- # TODO: Check that src exists, because TTFunk won't and will just
144
- # give us an unusable object instead.
145
- @font_meta = TTFunk::File.open(src)
146
-
152
+ def to_vips_image
147
153
  # https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
148
- @image = Vips::Image.text(
154
+ Vips::Image.text(
149
155
  # This string must be well-escaped Pango Markup:
150
156
  # https://developer.gnome.org/pango/stable/pango-Markup.html
151
157
  # However the official function for escaping text is
@@ -158,7 +164,7 @@ module Cooltrainer
158
164
  # we must also specify a font family, subfamily, and size.
159
165
  :font => "#{font_name}",
160
166
  # Space between lines (in Points).
161
- :spacing => @font_meta.line_gap,
167
+ :spacing => to_ttfunk.line_gap,
162
168
  # Requires libvips 8.8
163
169
  :justify => false,
164
170
  :dpi => 144,