distorted 0.5.4 → 0.7.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +661 -0
  3. data/README.md +5 -140
  4. data/bin/console +14 -0
  5. data/bin/distorted +6 -0
  6. data/bin/setup +8 -0
  7. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Less_Perfect_DOS_VGA.png +0 -0
  8. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/More_Perfect_DOS_VGA.png +0 -0
  9. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Perfect_DOS_VGA.png +0 -0
  10. data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/less_more_perfect_dos_vga_437.html +52 -0
  11. data/font/1252/LICENSE/PerfectDOSVGA437/font-comment.php@file=perfect_dos_vga_437.html +5 -0
  12. data/font/1252/LessPerfectDOSVGA.ttf +0 -0
  13. data/font/1252/MorePerfectDOSVGA.ttf +0 -0
  14. data/font/1252/Perfect DOS VGA 437 Win.ttf +0 -0
  15. data/font/437/Perfect DOS VGA 437.ttf +0 -0
  16. data/font/437/dos437.txt +72 -0
  17. data/font/65001/Anonymous Pro B.ttf +0 -0
  18. data/font/65001/Anonymous Pro BI.ttf +0 -0
  19. data/font/65001/Anonymous Pro I.ttf +0 -0
  20. data/font/65001/Anonymous Pro.ttf +0 -0
  21. data/font/65001/LICENSE/AnonymousPro/FONTLOG.txt +45 -0
  22. data/font/65001/LICENSE/AnonymousPro/OFL-FAQ.txt +235 -0
  23. data/font/65001/LICENSE/AnonymousPro/OFL.txt +94 -0
  24. data/font/65001/LICENSE/AnonymousPro/README.txt +55 -0
  25. data/font/850/ProFont-Bold-01/LICENSE +22 -0
  26. data/font/850/ProFont-Bold-01/readme.txt +28 -0
  27. data/font/850/ProFontWindows-Bold.ttf +0 -0
  28. data/font/850/ProFontWindows.ttf +0 -0
  29. data/font/850/Profont/LICENSE +22 -0
  30. data/font/850/Profont/readme.txt +31 -0
  31. data/font/932/LICENSE/README-ttf.txt +213 -0
  32. data/font/932/mona.ttf +0 -0
  33. data/lib/distorted.rb +2 -0
  34. data/lib/distorted/checking_you_out.rb +219 -0
  35. data/lib/distorted/checking_you_out/README +4 -0
  36. data/lib/distorted/checking_you_out/application.yaml +33 -0
  37. data/lib/distorted/checking_you_out/font.yaml +29 -0
  38. data/lib/distorted/checking_you_out/image.yaml +108 -0
  39. data/lib/distorted/click_again.rb +333 -0
  40. data/lib/distorted/element_of_media.rb +2 -0
  41. data/lib/distorted/element_of_media/change.rb +119 -0
  42. data/lib/distorted/element_of_media/compound.rb +120 -0
  43. data/lib/distorted/error_code.rb +51 -0
  44. data/lib/distorted/floor.rb +17 -0
  45. data/lib/distorted/invoker.rb +97 -0
  46. data/lib/distorted/media_molecule.rb +58 -0
  47. data/lib/distorted/media_molecule/font.rb +195 -0
  48. data/lib/distorted/media_molecule/image.rb +33 -0
  49. data/lib/distorted/media_molecule/pdf.rb +44 -0
  50. data/lib/distorted/media_molecule/svg.rb +45 -0
  51. data/lib/distorted/media_molecule/text.rb +203 -0
  52. data/lib/distorted/media_molecule/video.rb +18 -0
  53. data/lib/distorted/modular_technology/gstreamer.rb +174 -0
  54. data/lib/distorted/modular_technology/pango.rb +90 -0
  55. data/lib/distorted/modular_technology/ttfunk.rb +48 -0
  56. data/lib/distorted/modular_technology/vips.rb +17 -0
  57. data/lib/distorted/modular_technology/vips/foreign.rb +489 -0
  58. data/lib/distorted/modular_technology/vips/load.rb +133 -0
  59. data/lib/distorted/modular_technology/vips/save.rb +161 -0
  60. data/lib/distorted/monkey_business/encoding.rb +317 -0
  61. data/lib/distorted/monkey_business/hash.rb +18 -0
  62. data/lib/distorted/monkey_business/set.rb +15 -0
  63. data/lib/distorted/monkey_business/string.rb +6 -0
  64. data/lib/distorted/triple_counter.rb +52 -0
  65. data/lib/distorted/version.rb +22 -0
  66. data/test/distorted_test.rb +11 -0
  67. data/test/test_helper.rb +4 -0
  68. metadata +130 -20
@@ -0,0 +1,195 @@
1
+ require 'set'
2
+
3
+ # Font metadata extraction
4
+ require 'ttfunk'
5
+
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
+
11
+
12
+ module Cooltrainer; end
13
+ module Cooltrainer::DistorteD; end
14
+ module Cooltrainer::DistorteD::Molecule; end
15
+ module Cooltrainer::DistorteD::Molecule::Font
16
+
17
+
18
+ # TODO: Test OTF, OTB, and others.
19
+ # NOTE: Traditional bitmap fonts won't be supported due to Pango 1.44
20
+ # and later switching to Harfbuzz from Freetype:
21
+ # https://gitlab.gnome.org/GNOME/pango/-/issues/386
22
+ # https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/
23
+ LOWER_WORLD = CHECKING::YOU::IN(/^font\/ttf/).to_hash
24
+ OUTER_LIMITS = CHECKING::YOU::IN(/^font\/ttf/).to_hash
25
+
26
+ ATTRIBUTES = Set[
27
+ :alt,
28
+ ]
29
+ ATTRIBUTES_VALUES = {
30
+ }
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::LOWER_WORLD.keys.each { |t|
38
+ define_method(t.distorted_file_method) { |dest_root, change|
39
+ copy_file(change.paths(dest_root).first)
40
+ }
41
+ }
42
+
43
+ include Cooltrainer::DistorteD::Technology::TTFunk
44
+ include Cooltrainer::DistorteD::Technology::Pango
45
+ include Cooltrainer::DistorteD::Technology::Vips::Save
46
+
47
+
48
+ # irb(main):089:0> chars.take(5)
49
+ # => [[1, 255], [2, 1], [3, 2], [4, 3], [5, 4]]
50
+ # irb(main):090:0> chars.values.take(5)
51
+ # => [255, 1, 2, 3, 4]
52
+ # irb(main):091:0> chars.values.map(&:chr).take(5)
53
+ # => ["\xFF", "\x01", "\x02", "\x03", "\x04"]
54
+ def to_pango
55
+ output = '' << cr << '<span>' << cr
56
+
57
+ output << "<span size='35387'> #{font_name}</span>" << cr << cr
58
+
59
+ output << "<span size='24576'> #{font_description}</span>" << cr
60
+ output << "<span size='24576'> #{font_copyright}</span>" << cr
61
+ output << "<span size='24576'> #{font_version}</span>" << cr << cr
62
+
63
+ # Print a preview String in using the loaded font. Or don't.
64
+ if abstract(:title)
65
+ output << cr << cr << "<span size='24576' foreground='grey'> #{g_markup_escape_text(abstract(:title))}</span>" << cr << cr << cr
66
+ end
67
+
68
+ # /!\ MANDATORY READING /!\
69
+ # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
70
+ #
71
+ # "The 'cmap' table maps character codes to glyph indices.
72
+ # The choice of encoding for a particular font is dependent upon the conventions
73
+ # used by the intended platform. A font intended to run on multiple platforms
74
+ # with different encoding conventions will require multiple encoding tables.
75
+ # As a result, the 'cmap' table may contain multiple subtables,
76
+ # one for each supported encoding scheme."
77
+ #
78
+ # Cmap#unicode is a convenient shortcut to sorting the subtables
79
+ # and removing any unusable ones:
80
+ # https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/cmap.rb
81
+ #
82
+ # irb(main):174:0> font_meta.cmap.tables.count
83
+ # => 3
84
+ # irb(main):175:0> font_meta.cmap.unicode.count
85
+ # => 2
86
+ to_ttfunk.cmap.tables.each do |table|
87
+ next if !table.unicode?
88
+ # Each subtable's `code_map` is a Hash map of character codes (the Hash keys)
89
+ # to the glyph IDs from the original font (the Hash's values).
90
+ #
91
+ # Subtable::encode takes:
92
+ # - a Hash mapping character codes to original font glyph IDs.
93
+ # - the desired output encoding — Set[:mac_roman, :unicode, :unicode_ucs4]
94
+ # https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/cmap/subtable.rb
95
+ # …and returns a Hash with keys:
96
+ # - :charmap — Hash mapping the characters in the input charmap
97
+ # to a another hash containing both the `:old`
98
+ # and `:new` glyph ids for each character code.
99
+ # - :subtable — String encoded subtable for the given encoding.
100
+ encoded = TTFunk::Table::Cmap::Subtable::encode(table&.code_map, :unicode).dig(:charmap)
101
+
102
+ output << "<span size='49152'>"
103
+
104
+ i = 0
105
+ encoded.each_pair { |c, (old, new)|
106
+
107
+ begin
108
+ if glyph = to_ttfunk.glyph_outlines.for(c)
109
+ # Add a space on either side of the character so they aren't
110
+ # all smooshed up against each other and unreadable.
111
+ output << ' ' << g_markup_escape_char(c) << ' '
112
+ if i >= 15
113
+ output << cr
114
+ i = 0
115
+ else
116
+ i = i + 1
117
+ end
118
+ else
119
+ end
120
+ rescue NoMethodError => nme
121
+ # TTFunk's `glyph_outlines.for()` will raise this if we call it
122
+ # for a codepoint that does not exist in the font, which we will
123
+ # not do because we are enumerating the codepoints in the font,
124
+ # but we should still handle the possibility.
125
+ # irb(main):060:0> font.glyph_outlines.for(555555)
126
+ #
127
+ # Traceback (most recent call last):
128
+ # 6: from /usr/bin/irb:23:in `<main>'
129
+ # 5: from /usr/bin/irb:23:in `load'
130
+ # 4: from /home/okeeblow/.gems/gems/irb-1.2.4/exe/irb:11:in `<top (required)>'
131
+ # 3: from (irb):60
132
+ # 2: from /home/okeeblow/.gems/gems/ttfunk-1.6.2.1/lib/ttfunk/table/glyf.rb:35:in `for'
133
+ # 1: from /home/okeeblow/.gems/gems/ttfunk-1.6.2.1/lib/ttfunk/table/loca.rb:35:in `size_of'
134
+ # NoMethodError (undefined method `-' for nil:NilClass)
135
+ end
136
+ }
137
+
138
+ output << '</span>' << cr
139
+ end
140
+
141
+ output << '</span>'
142
+ output
143
+ end
144
+
145
+ # Return the `src` as the font_path since we aren't using
146
+ # any of the built-in fonts.
147
+ def font_path
148
+ path
149
+ end
150
+
151
+ def to_vips_image
152
+ # https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
153
+ Vips::Image.text(
154
+ # This string must be well-escaped Pango Markup:
155
+ # https://developer.gnome.org/pango/stable/pango-Markup.html
156
+ # However the official function for escaping text is
157
+ # not implemented in Ruby GLib, so we have to do it ourselves.
158
+ to_pango,
159
+ **{
160
+ # String absolute path to TTF
161
+ :fontfile => font_path,
162
+ # It's not enough to just specify the TTF path;
163
+ # we must also specify a font family, subfamily, and size.
164
+ :font => "#{font_name}",
165
+ # Space between lines (in Points).
166
+ :spacing => to_ttfunk.line_gap,
167
+ # Requires libvips 8.8
168
+ :justify => false,
169
+ :dpi => 144,
170
+ },
171
+ )
172
+ end
173
+
174
+ end # Font
175
+
176
+
177
+ # Notes on file-format specifics and software-library-specifics
178
+ #
179
+ # # TTF (via TTFunk)
180
+ #
181
+ # ## Cmap
182
+ #
183
+ # Each TTFunk::Table::Cmap::Format<whatever> class responds to `:supported?`
184
+ # with its own internal boolean telling us if that Format is usable in TTFunk.
185
+ # This has nothing to do with any font file itself, just the library code.
186
+ # irb(main)> font.cmap.tables.map{|t| t.supported?}
187
+ # => [true, true, true]
188
+ #
189
+ # Any subclass of TTFunk::Table::Cmap::Subtable responds to `:unicode?`
190
+ # with a boolean calculated from the instance `@platform_id` and `@encoding_id`,
191
+ # and those numeric IDs are assigned to the symbolic (e.g. `:macroman`) names in:
192
+ # https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/cmap/subtable.rb
193
+ # irb(main)> font.cmap.tables.map{|t| t.unicode?}
194
+ # => [true, false, true]
195
+ #
@@ -0,0 +1,33 @@
1
+
2
+ require 'set'
3
+
4
+ require 'distorted/checking_you_out'
5
+ require 'distorted/modular_technology/vips'
6
+
7
+
8
+ module Cooltrainer; end
9
+ module Cooltrainer::DistorteD; end
10
+ module Cooltrainer::DistorteD::Molecule; end
11
+ module Cooltrainer::DistorteD::Molecule::Image
12
+
13
+
14
+ # Attributes for our <picture>/<img>.
15
+ # Automatically enabled as attrs for DD Liquid Tag.
16
+ # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture#Attributes
17
+ # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes
18
+ # https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
19
+ ATTRIBUTES = Set[:alt, :caption, :href, :loading]
20
+
21
+ # Defaults for HTML Element attributes.
22
+ # Not every attr has to be listed here.
23
+ # Many need no default and just won't render.
24
+ ATTRIBUTES_DEFAULT = {
25
+ :loading => :eager,
26
+ }
27
+ ATTRIBUTES_VALUES = {
28
+ :loading => Set[:eager, :lazy],
29
+ }
30
+
31
+ include Cooltrainer::DistorteD::Technology::Vips
32
+
33
+ end # Image
@@ -0,0 +1,44 @@
1
+ require 'set'
2
+
3
+ require 'hexapdf'
4
+
5
+ require 'distorted/checking_you_out'
6
+
7
+
8
+ module Cooltrainer; end
9
+ module Cooltrainer::DistorteD; end
10
+ module Cooltrainer::DistorteD::Molecule; end
11
+ module Cooltrainer::DistorteD::Molecule::PDF
12
+
13
+
14
+ # https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Document/index.html#method-c-new
15
+ # https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/index.html#DefaultDocumentConfiguration
16
+ # https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Task/Optimize.html
17
+ PDF_TYPE = CHECKING::YOU::OUT['application/pdf']
18
+ LOWER_WORLD = Hash[
19
+ PDF_TYPE => nil,
20
+ ]
21
+ OUTER_LIMITS = Hash[
22
+ PDF_TYPE => nil,
23
+ ]
24
+
25
+ # TODO: Use MuPDF instead of libvips magick-based PDF loader.
26
+
27
+ def self.optimize(src, dest)
28
+ HexaPDF::Document.open(src) do |doc|
29
+ doc.task(
30
+ :optimize,
31
+ compact: true,
32
+ object_streams: :generate,
33
+ xref_streams: :generate,
34
+ compress_pages: false,
35
+ )
36
+ doc.write(dest)
37
+ end
38
+ end
39
+
40
+ define_method(PDF_TYPE.distorted_file_method) { |dest_root, change|
41
+ copy_file(change.paths(dest_root).first)
42
+ }
43
+
44
+ end # PDF
@@ -0,0 +1,45 @@
1
+ require 'set'
2
+
3
+ require 'svg_optimizer' # https://github.com/fnando/svg_optimizer
4
+
5
+ require 'distorted/checking_you_out'
6
+ require 'distorted/modular_technology/vips/save'
7
+
8
+
9
+ module Cooltrainer; end
10
+ module Cooltrainer::DistorteD; end
11
+ module Cooltrainer::DistorteD::Molecule; end
12
+ module Cooltrainer::DistorteD::Molecule::SVG
13
+
14
+ include Cooltrainer::DistorteD::Technology::Vips::Save
15
+
16
+ LOWER_WORLD = Hash[
17
+ CHECKING::YOU::OUT['image/svg+xml'] => Cooltrainer::DistorteD::Technology::Vips::vips_get_options(
18
+ Cooltrainer::DistorteD::Technology::Vips::vips_foreign_find_load_suffix(".#{CHECKING::YOU::OUT['image/svg+xml'].preferred_extension}")
19
+ ).merge(Hash[
20
+ :optimize => Cooltrainer::Compound.new(:optimize, valid: Cooltrainer::BOOLEAN_VALUES, default: false, blurb: 'SvgOptimizer'),
21
+ ])
22
+ ]
23
+
24
+ # WISHLIST: Support VML for old IE compatibility.
25
+ # Example: RaphaëlJS — https://en.wikipedia.org/wiki/Rapha%C3%ABl_(JavaScript_library)
26
+ OUTER_LIMITS = Hash[
27
+ CHECKING::YOU::OUT['image/svg+xml'] => nil,
28
+ ]
29
+
30
+ def to_vips_image
31
+ # NOTE: libvips 8.9 added the `unlimited` argument to svgload.
32
+ # Loading SVGs >= 10MiB in size will fail on older libvips.
33
+ # https://github.com/libvips/libvips/commit/55e49831b801e05ddd974b1e2102fda7956c53f5
34
+ @vips_image ||= Vips::Image.new_from_file(path)
35
+ end
36
+
37
+ define_method(CHECKING::YOU::OUT['image/svg+xml'].distorted_file_method) { |dest_root, change|
38
+ if change.optimize
39
+ SvgOptimizer.optimize_file(path, change.paths(dest_root).first, SvgOptimizer::DEFAULT_PLUGINS)
40
+ else
41
+ copy_file(change.paths(dest_root).first)
42
+ end
43
+ }
44
+
45
+ end
@@ -0,0 +1,203 @@
1
+ require 'set'
2
+
3
+ require 'charlock_holmes' # Text file charset detection
4
+
5
+ require 'distorted/monkey_business/encoding'
6
+ require 'distorted/monkey_business/string' # String#map
7
+ require 'distorted/modular_technology/pango'
8
+ require 'distorted/modular_technology/ttfunk'
9
+ require 'distorted/modular_technology/vips/save'
10
+
11
+ require 'distorted/checking_you_out'
12
+
13
+
14
+ module Cooltrainer; end
15
+ module Cooltrainer::DistorteD; end
16
+ module Cooltrainer::DistorteD::Molecule; end
17
+ module Cooltrainer::DistorteD::Molecule::Text
18
+
19
+ #TODO: Generate separate images per-size to stop text being blurry from resizing.
20
+
21
+ include Cooltrainer::DistorteD::Technology::TTFunk
22
+ include Cooltrainer::DistorteD::Technology::Pango
23
+ include Cooltrainer::DistorteD::Technology::Vips::Save
24
+
25
+ # Track supported fonts by codepage.
26
+ # Avoid renaming these from the original archives / websites.
27
+ # Try not to go nuts here bloating the size of our Gem for a
28
+ # very niche feature, but I want to ensure good coverage too.
29
+ #
30
+ # Treat codepage 8859 documents as codepage 1252 to avoid breaking smart-
31
+ # quotes and other printable chars in 1252 that are control chars in 8859.
32
+ # https://encoding.spec.whatwg.org/#names-and-labels
33
+ #
34
+ # Numeric key for UTF-8 is codepage 65001 like Win32:
35
+ # https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
36
+ FONT_FILENAME = {
37
+ :anonpro => 'Anonymous Pro.ttf'.freeze,
38
+ :anonpro_b => 'Anonymous Pro B.ttf'.freeze,
39
+ :anonpro_bi => 'Anonymous Pro BI.ttf'.freeze,
40
+ :anonpro_i => 'Anonymous Pro I.ttf'.freeze,
41
+ :lessperfectdosvga => 'LessPerfectDOSVGA.ttf'.freeze,
42
+ :moreperfectdisvga => 'MorePerfectDOSVGA.ttf'.freeze,
43
+ :perfectdosvgawin => 'Perfect DOS VGA 437 Win.ttf'.freeze,
44
+ :mona => 'mona.ttf'.freeze,
45
+ :perfectdosvga => 'Perfect DOS VGA 437.ttf'.freeze,
46
+ :profont => 'ProFontWindows.ttf'.freeze,
47
+ :profont_b => 'ProFontWindows-Bold.ttf'.freeze,
48
+ }
49
+ # Certain fonts are more suitable for certain codepages,
50
+ # so track each codepage's available fonts…
51
+ CODEPAGE_FONT = {
52
+ 65001 => [
53
+ :anonpro,
54
+ :anonpro_b,
55
+ :anonpro_bi,
56
+ :anonpro_i,
57
+ ],
58
+ 1252 => [
59
+ :lessperfectdosvga,
60
+ :moreperfectdosvga,
61
+ :perfectdosvgawin,
62
+ ],
63
+ 932 => [
64
+ :mona,
65
+ ],
66
+ 850 => [
67
+ :profont,
68
+ :profont_b,
69
+ ],
70
+ 437 => [
71
+ :perfectdosvga,
72
+ ],
73
+ }
74
+ # …as well as the inverse, the numeric codepage for each font:
75
+ FONT_CODEPAGE = self::CODEPAGE_FONT.reduce(Hash.new([])) { |memo, (key, values)|
76
+ values.each { |value| memo[value] = key }
77
+ memo
78
+ }
79
+
80
+
81
+ LOWER_WORLD = CHECKING::YOU::IN(/^text\/(plain|x-nfo)/).to_hash.transform_values { |v| Hash[
82
+ :encoding => Cooltrainer::Compound.new(:encoding, blurb: 'Character encoding used in this document.', default: 'UTF-8'.freeze),
83
+ ]}
84
+ OUTER_LIMITS = CHECKING::YOU::IN(/^text\/(plain|x-nfo)/).to_hash.merge(
85
+ Cooltrainer::DistorteD::Technology::Vips::Save::OUTER_LIMITS.dup.transform_values{ |v| Hash[
86
+ :spacing => Cooltrainer::Compound.new(:spacing, blurb: 'Document-wide character spacing style.', valid: Set[:monospace, :proportional]),
87
+ :dpi => Cooltrainer::Compound.new(:dpi, blurb: 'Dots per inch for text rendering.', valid: Integer, default: 144),
88
+ :font => Cooltrainer::Compound.new(:font, blurb: 'Font to use for text rendering.', valid: self::FONT_FILENAME.keys.to_set),
89
+ ]}
90
+ )
91
+
92
+ self::LOWER_WORLD.keys.each { |t|
93
+ define_method(t.distorted_file_method) { |dest_root, change|
94
+ copy_file(change.paths(dest_root).first)
95
+ }
96
+ }
97
+
98
+
99
+ # Return a Pango Markup escaped version of the document.
100
+ def to_pango
101
+ # https://developer.gnome.org/glib/stable/glib-Simple-XML-Subset-Parser.html#g-markup-escape-text
102
+ escaped = text_file_utf8_content.map{ |c|
103
+ g_markup_escape_char(c)
104
+ }
105
+ if font_spacing == :monospace
106
+ "<tt>" << escaped << "</tt>"
107
+ else
108
+ escaped
109
+ end
110
+ end
111
+
112
+ protected
113
+
114
+ def text_file_content
115
+ # VIPS makes us provide the text content as a single variable,
116
+ # so we may as well just one-shot File.read() it into memory.
117
+ # https://kunststube.net/encoding/
118
+ @text_file_content ||= File.read(path)
119
+ end
120
+
121
+ def text_file_utf8_content
122
+ CharlockHolmes::Converter.convert(text_file_content, text_file_encoding.to_s, 'UTF-8'.freeze)
123
+ end
124
+
125
+ def text_file_encoding
126
+ # It's not easy or even possible in some cases to tell the "true" codepage
127
+ # we should use for any given text document, but using character detection
128
+ # is worth a shot if the user gave us nothing.
129
+ #
130
+ # TODO: Figure out if/how we can get IBM437 files to not be detected as ISO-8859-1
131
+ @text_file_encoding ||= Encoding::find(
132
+ abstract(:encoding).to_s ||
133
+ CharlockHolmes::EncodingDetector.detect(text_file_content)[:encoding] ||
134
+ 'UTF-8'.freeze
135
+ )
136
+ end
137
+
138
+ def vips_font
139
+ # Set the shorthand Symbol key for our chosen font.
140
+ return abstract(:font)&.to_sym || CODEPAGE_FONT[text_file_encoding.code_page].first
141
+ end
142
+
143
+ def to_vips_image
144
+ # Load font metadata directly from the file so we don't have to
145
+ # duplicate it here to feed to Vips/Pango.
146
+ #
147
+ # irb(main)> font_meta.name.font_name
148
+ # => ["Perfect DOS VGA 437", "\x00P\x00e\x00r\x00f\x00e\x00c\x00t\x00 \x00D\x00O\x00S\x00 \x00V\x00G\x00A\x00 \x004\x003\x007"]
149
+ # irb(main)> font_meta.name.font_family
150
+ # => ["Perfect DOS VGA 437", "\x00P\x00e\x00r\x00f\x00e\x00c\x00t\x00 \x00D\x00O\x00S\x00 \x00V\x00G\x00A\x00 \x004\x003\x007"]
151
+ # irb(main)> font_meta.name.font_subfamily
152
+ # => ["Regular", "\x00R\x00e\x00g\x00u\x00l\x00a\x00r"]
153
+ # irb(main)> font_meta.name.postscript_name
154
+ # => "PerfectDOSVGA437"
155
+ # irb(main)> font_meta.line_gap
156
+ # => 0
157
+
158
+ # https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
159
+ Vips::Image.text(
160
+ # This string must be well-escaped Pango Markup:
161
+ # https://developer.gnome.org/pango/stable/pango-Markup.html
162
+ # However the official function for escaping text is
163
+ # not implemented in Ruby GLib, so we have to do it ourselves.
164
+ to_pango,
165
+ **{
166
+ # String absolute path to TTF
167
+ :fontfile => font_path,
168
+ # It's not enough to just specify the TTF path;
169
+ # we must also specify a font family, subfamily, and size.
170
+ :font => "#{font_name} 16",
171
+ # Space between lines (in Points).
172
+ :spacing => to_ttfunk.line_gap,
173
+ :justify => true, # Requires libvips 8.8
174
+ :dpi => abstract(:dpi)&.to_i,
175
+ },
176
+ )
177
+ end
178
+
179
+ # Return the String absolute path to the TTF file
180
+ def font_path
181
+ File.join(
182
+ File.dirname(__FILE__), # molecule
183
+ '..'.freeze, # distorted
184
+ '..'.freeze, # lib
185
+ '..'.freeze, # DistorteD-Floor
186
+ 'font'.freeze,
187
+ font_codepage.to_s,
188
+ font_filename,
189
+ )
190
+ end
191
+
192
+ # Returns the numeric representation of the codepage
193
+ # covered by our font.
194
+ def font_codepage
195
+ FONT_CODEPAGE.dig(vips_font).to_s
196
+ end
197
+
198
+ # Returns the basename (with file extension) of our font.
199
+ def font_filename
200
+ FONT_FILENAME.dig(vips_font)
201
+ end
202
+
203
+ end # Text