distorted 0.5.7 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,