skrift-x11 0.2.2 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a75688a1a4f9e322d33c58a9b0a29b2fb26c27536a137e33dd97c340a491dd98
4
- data.tar.gz: 87a5c7f3f29918f0031e637acbc4d08a6d8d6f122b3112c597515086fa1d0317
3
+ metadata.gz: ed8b7aa32d9e1eb6984384f6f22488d3d6f5be7f5eef1c671c0226cf7ff431c5
4
+ data.tar.gz: cee3cb6866a431e8f8c2d0e4543a1eed56f85883098b1561902dd52e0641386e
5
5
  SHA512:
6
- metadata.gz: 7db2e7d2a63a2afa679ca83652136e13e954b3eee2c11d55c5734c48ed10be641a8fee0eca3d1df779a1d4c19c7a4172027d9aea1659f4bb8f4728f6de3ded3e
7
- data.tar.gz: 4014ba510524d9921c9eb67986b137151f98aca2cd71427e431fdcefe776c5c869e45b45de09d3a692fe77976f82da4c7df6bdb08ca687736301d43a45f5af12
6
+ metadata.gz: be22707412fa343bb25f8a9ddbd754801090ccf1458041aeb91c18513d8a963d2121eeae293a65d34d6c782d9d7d166f57b8448bb30dd755ae6452a0a3c9bc27
7
+ data.tar.gz: 84b80afb3b159bfbdc5733a397c670305b7c6dfc4d37a8e9cfac57bdb12b775703bf65ddbf62ef5a0eeab32ccad3a8ce270b9eec7cd15b99708f196b2454f2ea
@@ -1,9 +1,15 @@
1
+ require 'skrift/boxdrawing'
1
2
 
2
- require_relative 'boxdrawing'
3
+ # Colour-glyph support is optional: only needed when a `color:` font is given.
4
+ begin
5
+ require 'skrift/color'
6
+ rescue LoadError
7
+ # skrift-color not installed; colour rendering will be unavailable.
8
+ end
3
9
 
4
10
  module X11
5
11
  # XRender constants currently not defined in pure-x11.
6
- # FIXME.
12
+ # FIXME: these are generic XRender enumerants and belong in pure-x11.
7
13
  module Form
8
14
  PictOpSrc=1
9
15
  PictOpOver=3
@@ -13,70 +19,49 @@ end
13
19
 
14
20
  module Skrift
15
21
  module X11
16
-
22
+ # Thin XRender adapter over Skrift::GlyphCache. Monochrome glyphs go into an
23
+ # A8 glyph set (coloured at composite time by a solid fill); colour glyphs
24
+ # (emoji, via the skrift-color delegate) go into an ARGB32 glyph set and are
25
+ # composited with a neutral white source so their own colours show. A string
26
+ # is drawn as runs of like-typed glyphs, one composite per run. All the
27
+ # platform-independent work (fallback, rasterisation, layout, caching, box
28
+ # drawing, colour delegation) lives in skrift core + the plugins.
17
29
  class Glyphs
18
30
  attr_reader :fixed_width
19
- attr_accessor :maxheight
20
-
21
- def load_font(index)
22
- return @fonts[index] if @fonts[index]
23
- f = @fontset[index]
24
- return nil if !f
25
- return @fonts[index] = Font.load(f) if File.exist?(f)
26
-
27
- # FIXME: Do proper resolution of XDG dirs
28
- fn = File.expand_path("~/.local/share/fonts/#{f}")
29
- return @fonts[index] = Font.load(fn) if File.exist?(fn)
30
-
31
- fn = `fc-match --format='%{file}\n' #{fn}`.split("\n").first
32
- return @fonts[index] = Font.load(fn) if File.exist?(fn)
33
- return nil
34
- end
35
-
36
- def inspect
37
- "<Glyphs #{object_id}"
38
- end
39
-
40
- def initialize dpy, font=nil, fontset: nil, x_scale:, y_scale:, pic: nil, fixed: nil, maxheight: nil, fit: false
41
- @fontset = fontset
42
- @fonts = @fontset ? [] : [font]
43
- @font = font || load_font(0)
44
- @maxheight = maxheight
31
+ attr_accessor :maxheight, :lm
45
32
 
33
+ # +color+ (optional) is a font (path, name or Skrift::Font) providing
34
+ # colour glyphs, e.g. an emoji font; requires the skrift-color gem.
35
+ def initialize(dpy, font=nil, fontset: nil, x_scale:, y_scale:, pic: nil, fixed: nil, maxheight: nil, fit: false, color: nil)
46
36
  @dpy = dpy
47
- @sft = SFT.new(@font)
48
- @sft.x_scale = x_scale
49
- @sft.y_scale = y_scale
50
37
  @pic = pic
51
- @fixed = fixed
52
- # When set (with a fixed cell), glyphs whose natural size exceeds the
53
- # cell are scaled down to fit and centred instead of being clamped
54
- # (and distorted) at the cell edge by the rasteriser.
55
- @fit = fit
56
-
57
- g = @sft.gmetrics(@sft.lookup("M".ord))
58
- if !g
59
- g = @sft.gmetrics(0)
60
- end
61
- if g
62
- @fixed_width = g.advance_width.ceil
63
- else
64
- @fixed_width = x_scale
65
- end
38
+ @maxheight = maxheight
66
39
 
67
- @nextgid = 1
68
- @glyphcache = {}
69
- @colcache = {}
70
- @chcache = {}
40
+ color_renderer = build_color_renderer(color, x_scale, y_scale)
41
+ @cache = Skrift::GlyphCache.new(fontset || font,
42
+ x_scale: x_scale, y_scale: y_scale,
43
+ fixed: fixed, maxheight: maxheight, fit: fit,
44
+ special: ->(cp) { @boxes.glyph(cp) },
45
+ color: color_renderer)
46
+ @boxes = Skrift::BoxDrawing.new(@cache.cell_width, @cache.cell_height)
47
+ @fixed_width = @cache.cell_width
48
+ @lm = Skrift::LMetrics.new(@cache.ascent, @cache.descent, @cache.line_gap)
71
49
 
72
- @lm = @sft.lmetrics
50
+ @colcache = {}
51
+ @gids = {} # codepoint -> [:mono|:color, glyph-set id]
52
+ @nextgid = 1
73
53
 
74
- # The glyph set
75
54
  @gfmt = @dpy.render_find_standard_format(:a8).id
76
55
  @glyphset = @dpy.render_create_glyph_set(@gfmt)
56
+
57
+ if color_renderer
58
+ @cfmt = @dpy.render_find_standard_format(:argb24).id
59
+ @cglyphset = @dpy.render_create_glyph_set(@cfmt)
60
+ @white = @dpy.render_create_solid_fill(0xffff, 0xffff, 0xffff, 0xffff)
61
+ end
77
62
  end
78
63
 
79
- attr_accessor :lm
64
+ def inspect = "<Glyphs #{object_id}>"
80
65
 
81
66
  def fill_for_col(col)
82
67
  return @colcache[col] if @colcache[col]
@@ -86,166 +71,109 @@ module Skrift
86
71
  g |= g << 8
87
72
  b = col & 0xff
88
73
  b |= b << 8
89
-
90
- @colcache[col] ||= @dpy.render_create_solid_fill(r,g,b,0xffff)
74
+ @colcache[col] = @dpy.render_create_solid_fill(r, g, b, 0xffff)
75
+ end
76
+
77
+ def text_width(str) = @cache.text_width(str)
78
+
79
+ # Draw +str+ as one composite per run of same-typed glyphs (mono vs
80
+ # colour). Each rendered glyph advances the pen by one cell, so a pure-text
81
+ # string is a single A8 composite exactly as before; emoji break the run.
82
+ def render_str(pic, col, x, y, str)
83
+ glyphs = str.to_s.each_char.map { |ch| gsgid_for(ch.ord) }
84
+ rendered = 0
85
+ i = 0
86
+ while i < glyphs.length
87
+ (i += 1; next) if glyphs[i].nil? # unmapped: skip, no advance
88
+ type = glyphs[i].first
89
+ run_x = x + rendered * @fixed_width
90
+ ids = []
91
+ while i < glyphs.length && glyphs[i] && glyphs[i].first == type
92
+ ids << glyphs[i].last
93
+ rendered += 1
94
+ i += 1
95
+ end
96
+ composite_run(type, pic, col, run_x, y, ids)
97
+ end
91
98
  end
92
99
 
93
100
  private
94
101
 
95
- def boxw = @fixed_width
96
- def stride = (@fixed_width +3)&~3
97
- def boxh
98
- @boxh ||= [@maxheight, @lm.ascender.ceil-@lm.descender.ceil].compact.min
102
+ def build_color_renderer(color, x_scale, y_scale)
103
+ return nil unless color
104
+ # A caller can pass its own delegate (e.g. one that gates which
105
+ # codepoints render in colour); we use it as-is. Otherwise treat the
106
+ # argument as a font and build an ungated Color::Renderer from it.
107
+ return color if color.respond_to?(:render)
108
+ unless defined?(Skrift::Color::Renderer)
109
+ raise "colour glyphs require the skrift-color gem"
110
+ end
111
+ font = Skrift::FontSet.new(color)[0]
112
+ Skrift::Color::Renderer.new(font, x_scale: x_scale, y_scale: y_scale)
99
113
  end
100
-
101
-
102
- def cache_special(ch)
103
- # Box drawing
104
- return nil if !((0x2500 .. 0x257f) === ch.ord)
105
114
 
115
+ def composite_run(type, pic, col, x, y, ids)
116
+ if type == :color
117
+ @dpy.render_composite_glyphs32(:PictOpOver, @white, pic, @cfmt, @cglyphset, 0, 0, [x, y, ids])
118
+ else
119
+ @dpy.render_composite_glyphs32(:PictOpOver, fill_for_col(col), pic, @gfmt, @glyphset, 0, 0, [x, y, ids])
120
+ end
121
+ end
106
122
 
107
- img = cache_box(ch.ord)
123
+ # [:mono|:color, glyph-set id] for a codepoint, uploading on first use, or
124
+ # nil for a codepoint no font maps (and which isn't box drawing/colour).
125
+ def gsgid_for(cp)
126
+ return @gids[cp] if @gids.key?(cp)
127
+ @gids[cp] = upload(cp)
128
+ end
108
129
 
130
+ def upload(cp)
131
+ # NUL doubles as the double-width spacer (the trailing cell of a wide
132
+ # glyph): a blank cell that only advances, letting the wide glyph in the
133
+ # previous cell overflow into it.
134
+ return upload_blank if cp.zero?
109
135
 
110
- data = img.pixels.pack("C*")
111
- info = ::X11::Form::GlyphInfo.new(
112
- img.width, # w
113
- img.height, # h
114
- 0, # x
115
- 0, # y
116
- fixed_width,
117
- 0
118
- )
136
+ g = @cache.glyph(cp)
137
+ return nil unless g
119
138
 
120
139
  gsgid = @nextgid
121
140
  @nextgid += 1
122
- @dpy.render_add_glyphs(@glyphset, gsgid, info, data)
123
- @glyphcache[gsgid] = fixed_width
124
- @chcache[ch] = {gsgid: gsgid}
125
- end
126
-
127
- def cache_glyph(gsgid,gid, baseline)
128
- return if gid.nil?
129
- mtx = @sft.gmetrics(gid)
130
- scaled = false
131
- saved = nil
132
-
133
- # Oversized glyph in a fixed cell: re-render it at a reduced scale so
134
- # it fits the cell, then position it through the normal
135
- # baseline-relative path below (no vertical shift). Otherwise the
136
- # rasteriser clamps the overflow at the cell edge and distorts the
137
- # shape. Only glyphs clearly wider/taller than the cell are scaled -
138
- # a marginal overshoot (e.g. a 'W' a pixel past the cell) is left to
139
- # the clamp, so ordinary text is untouched.
140
- if @fit && @fixed && mtx
141
- sx = (mtx.min_width || 0) > boxw * 1.1 ? boxw.to_f / mtx.min_width : 1.0
142
- sy = (mtx.min_height || 0) > boxh * 1.1 ? boxh.to_f / mtx.min_height : 1.0
143
- s = [sx, sy].min
144
- if s < 1.0
145
- saved = [@sft.x_scale, @sft.y_scale]
146
- @sft.x_scale = @sft.x_scale * s
147
- @sft.y_scale = @sft.y_scale * s
148
- mtx = @sft.gmetrics(gid)
149
- scaled = true
150
- end
151
- end
152
-
153
- # FIXME: Not sure what to do if mtx.nil? here.
154
- # Maybe use x/y scale?
155
- if @fixed
156
- w = fixed_width
141
+ if g.color?
142
+ @dpy.render_add_glyphs(@cglyphset, gsgid, glyph_info(g), argb_data(g.rgba))
143
+ [:color, gsgid]
144
+ elsif g.alpha
145
+ @dpy.render_add_glyphs(@glyphset, gsgid, glyph_info(g), g.alpha.pack("C*"))
146
+ [:mono, gsgid]
157
147
  else
158
- w = mtx.min_width || 0
148
+ upload_blank # no outline (e.g. space)
159
149
  end
160
- h = [mtx&.min_height || 1, @maxheight].compact.min
161
- #p [w,h]
162
- img = Image.new((w+3)&~3, h)
163
- if !@sft.render(gid, img)
164
- #STDERR.puts "Unable to render #{gid}\n"
165
- data = "\0"*(w*h)
166
- else
167
- data = img.pixels.pack("C*")
168
- end
169
-
170
- yoff = mtx.y_offset || baseline
171
-
172
- info = ::X11::Form::GlyphInfo.new(
173
- img.width, # w
174
- img.height, # h
175
- -mtx.left_side_bearing, # x
176
- yoff-baseline, # y
177
- fixed_width || mtx.advance_width, # || mtx.advance_width, # x_off
178
- 0
179
- )
180
-
181
- @dpy.render_add_glyphs(@glyphset, gsgid, info, data)
182
- @glyphcache[gsgid] = scaled ? (fixed_width || mtx.advance_width) : mtx.advance_width
183
- ensure
184
- @sft.x_scale, @sft.y_scale = saved if saved
185
150
  end
186
151
 
187
- public
188
-
189
- def text_width(str)
190
- # We *presume* that if you call text_width, you intend
191
- # to render the string. Maybe we shouldn't?
192
- gl = cache_glyphs(str)
193
- gl.inject(0) {|sum,gl|
194
- @glyphcache[gl].to_i + sum
195
- }
196
- end
197
-
198
- private
199
- def each_font
200
- Array(@fontset).each_index do |i|
201
- yield load_font(i)
202
- end
152
+ # A blank glyph that only advances one cell.
153
+ def upload_blank
154
+ gsgid = @nextgid
155
+ @nextgid += 1
156
+ info = ::X11::Form::GlyphInfo.new(0, 0, 0, 0, @fixed_width, 0)
157
+ @dpy.render_add_glyphs(@glyphset, gsgid, info, "".b)
158
+ [:mono, gsgid]
203
159
  end
204
-
205
- def cache_glyphs(str)
206
- gl = str.to_s.each_char.map do |ch|
207
-
208
- cache = @chcache[ch]
209
-
210
- if cache.nil?
211
- cache = cache_special(ch)
212
- end
213
-
214
- if cache.nil?
215
- each_font do |font|
216
- # FIXME: This is stupid and must be untangled
217
- @sft.font = font
218
- gid = @sft.lookup(ch.ord)
219
- if gid
220
- cache = @chcache[ch] = {font: font, gid: gid, gsgid: @nextgid}
221
- @nextgid += 1
222
- break
223
- end
224
- end
225
- end
226
-
227
- if cache
228
- data = @glyphcache[cache[:gsgid]]
229
- if !data
230
- @sft.font = cache[:font]
231
- data = cache_glyph(cache[:gsgid], cache[:gid], @lm.ascender)
232
- end
233
160
 
234
- cache[:gsgid]
235
- else
236
- 0
237
- end
238
- end
161
+ def glyph_info(g)
162
+ ::X11::Form::GlyphInfo.new(g.width, g.height,
163
+ -g.left_side_bearing, g.y_offset - @cache.baseline,
164
+ @fixed_width, 0)
239
165
  end
240
166
 
241
- public
242
- def render_str(pic, col, x,y, str)
243
- fill = fill_for_col(col)
244
- gl = cache_glyphs(str)
245
- @dpy.render_composite_glyphs32(
246
- :PictOpOver, fill, pic, @gfmt,
247
- @glyphset, 0,0, [x, y, gl]
248
- )
167
+ # Pack straight-alpha 0xRRGGBBAA pixels as premultiplied ARGB32 in the
168
+ # little-endian byte order XRender's argb format expects (B, G, R, A).
169
+ def argb_data(rgba)
170
+ rgba.map do |px|
171
+ a = px & 0xff
172
+ r = (((px >> 24) & 0xff) * a) / 255
173
+ g = (((px >> 16) & 0xff) * a) / 255
174
+ b = (((px >> 8) & 0xff) * a) / 255
175
+ (a << 24) | (r << 16) | (g << 8) | b
176
+ end.pack("L<*")
249
177
  end
250
178
  end
251
179
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Skrift
4
4
  module X11
5
- VERSION = "0.2.2"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/skrift-x11.gemspec CHANGED
@@ -34,7 +34,8 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  # Uncomment to register a new dependency of your gem
36
36
  # spec.add_dependency "example-gem", "~> 1.0"
37
- spec.add_dependency "skrift"
37
+ spec.add_dependency "skrift", ">= 0.3.0"
38
+ spec.add_dependency "skrift-boxdrawing", ">= 0.2.0"
38
39
  spec.add_dependency "pure-x11"
39
40
 
40
41
  # For more information and examples about making a new gem, check out our
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skrift-x11
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-13 00:00:00.000000000 Z
11
+ date: 2026-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: skrift
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 0.3.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: skrift-boxdrawing
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pure-x11
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -51,7 +65,6 @@ files:
51
65
  - example.png
52
66
  - example.rb
53
67
  - lib/skrift/x11.rb
54
- - lib/skrift/x11/boxdrawing.rb
55
68
  - lib/skrift/x11/glyphs.rb
56
69
  - lib/skrift/x11/version.rb
57
70
  - resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf
@@ -1,198 +0,0 @@
1
- module Skrift
2
- module X11
3
- class Glyphs
4
-
5
- def empty_box_image
6
- img = Image.new(stride, boxh)
7
- img.pixels = Array.new(img.width*img.height,0x00)
8
- img
9
- end
10
-
11
-
12
- def cache_box(ch)
13
- @boxcache ||= {}
14
- c = @boxcache[ch] if @boxcache[ch]
15
-
16
- hx = (boxw+1)/2
17
- hy = (boxh+1)/2
18
- yoff = hy*stride
19
- img = nil
20
- h = 1 # 1/3 width of "heavy" line
21
-
22
- lh = light_h = [-255, 0,255, 0, 255]; hh = heavy_h = [-255, -h,255, h, 255]
23
- lv = light_v = [ 0,-255, 0,255, 255]; hv = heavy_v = [ -h,-255, h,255, 255]
24
-
25
- ll = light_l = [-255, 0, 0, 0, 255]; hl = heavy_l = [-255, -h, 0, h, 255]
26
- lu = light_u = [ 0,-255, 0, 0, 255]; hu = heavy_u = [ -h,-255, h, 0, 255]
27
- lr = light_r = [ 0, 0,255, 0, 255]; hr = heavy_r = [ 0, -h,255, h, 255]
28
- ld = light_d = [ 0, 0, 0,255, 255]; hd = heavy_d = [ -h, 0, h,255, 255]
29
-
30
- hc = [ -h, -h, h, h, 255] # Heavy centre
31
-
32
- d = 2
33
- dblc = [-d+1, -d+1, d-1, d-1, 0] # Gap in centre of double lines.
34
- light_vc = [0, -d, 0, d, 255] # Light vertical crossing centre of double line.
35
-
36
- dh = double_h = [-255,-d,255,-d, 255] + [-255, d,255, d, 255]
37
- dv = double_v = [-d,-255,-d,255, 255] + [d,-255,d,255, 255]
38
-
39
- mask_vbar = [-d+1,-255,d-1,255,0]
40
- mask_hbar = [-255,-d+1,255,d-1,0]
41
-
42
- mt= masktop = [-255,-255, 255, -1, 0]; maskdtop = [-255,-255, 255,-d-1, 0]
43
- ml=maskleft = [-255,-255, -1, 255, 0]; maskdleft = [-255,-255,-d-1, 255, 0]
44
- mb=maskbottom = [-255, 1, 255, 255, 0]; maskdbottom= [-255, d+1, 255, 255, 0]
45
- mr=maskright = [ 1,-255, 255, 255, 0]; maskdright = [ d+1,-255, 255, 255, 0]
46
-
47
- mask_lbar = [-255,-d+1,d-1,d-1, 0]; dlbar=double_lbar = [-255,-d,d,d, 255]
48
- mask_tbar = [-d+1,-255,d-1,d-1, 0]; dtbar=double_tbar = [-d,-255,d,d, 255]
49
- mask_rbar = [-d+1,-d+1,255,d-1, 0]; drbar=double_rbar = [-d,-d,255,d, 255]
50
- mask_dbar = [-d+1,-d+1,d-1,255, 0]; ddbar=double_dbar = [-d,-d,d,255, 255]
51
-
52
- mask_c = [0,0,0,0,0]
53
-
54
- hdl = heavy_dl = [heavy_d, heavy_l, hc]
55
- hdr = heavy_dr = [heavy_d, heavy_r, hc]
56
- hlu = heavy_lu = [heavy_l, heavy_u, hc]
57
- hru = heavy_ru = [heavy_r, heavy_u, hc]
58
- ldl = light_dl = [light_d, light_l]
59
- ldr = light_dr = [light_d, light_r]
60
- llu = light_lu = [light_l, light_u]
61
- lru = light_ru = [light_r, light_u]
62
-
63
- # FIXME Makes stipples wider when tx/ty etc. are big enough
64
- tx = hx - boxw / 3
65
- mv2 = mask_v2 = [[-tx, -255, -tx, 255, 0], [tx, -255, tx, 255,0]]
66
-
67
- ty = hy - boxh / 3
68
- mh2 = mask_h2 = [[-255,-ty, 255,-ty,0], [-255,ty,255,ty,0]]
69
-
70
- rects = {
71
- 0x2500 => lh, 0x2501 => hh, 0x2502 => lv, 0x2503 => hv,
72
- 0x2504 => lh + mv2, 0x2505 => hh + mv2, 0x2506 => lv + mh2, 0x2507 => hv + mv2,
73
- 0x2508 => lh, # FIXME: Stippled
74
- 0x2509 => hh, # FIXME: Stippled
75
- 0x250A => lv, # FIXME: Stippled
76
- 0x250B => hv, # FIXME: Stippled
77
-
78
- 0x250C => ldr, 0x250D => hr + ld, 0x250E => hd + lr, 0x250F => hdr,
79
- 0x2510 => ldl, 0x2511 => hl + ld, 0x2512 => [hd, ll], 0x2513 => hdl,
80
- 0x2514 => lru, 0x2515 => lu + hr, 0x2516 => lr + hu, 0x2517 => hru,
81
- 0x2518 => llu, 0x2519 => [hl, lu], 0x251a => [ll, hu], 0x251b => hlu,
82
- 0x251c => lv+lr, 0x251d => [lv, hr], 0x251e => ldr + hu, 0x251f => [lru, hd],
83
-
84
- 0x2520 => hv + lr, 0x2521 => hru + ld, 0x2522 => hdr + lu, 0x2523 => hv + hr,
85
- 0x2524 => ll + lv, 0x2525 => hl + lv, 0x2526 => hu + ldl, 0x2527 => hd + lu,
86
- 0x2528 => hv + ll, 0x2529 => hlu + ld, 0x252a => hdl+lu, 0x252b => hl + hv,
87
- 0x252c => lh + ld, 0x252d => [hl, ldr], 0x252e => [hr, ldr], 0x252f => [hh, ld],
88
-
89
- 0x2530 => [lh, hd], 0x2531 => [hdl, lr], 0x2532 => [ll, hdr], 0x2533 => [hh, hd],
90
- 0x2534 => [lh, lu], 0x2535 => [lru, hl], 0x2536 => [llu, hr], 0x2537 => [hh, lu],
91
- 0x2538 => [lh, hu], 0x2539 => [hlu, lr], 0x253a => [hru, ll], 0x253b => [hh, hu],
92
- 0x253c => [lv, lh], 0x253d => lv+lr+hl, 0x253e => ll+lv+hr, 0x253f => [lv, hh],
93
-
94
- 0x2540 => [lh, ld, hu], 0x2541 => [light_h, light_u, heavy_d], 0x2542 => [heavy_v, light_h], 0x2543 => [heavy_l, heavy_u, hc, light_d, light_r],
95
- 0x2544 => [ll, ld, hru], 0x2545 => [heavy_dl, light_ru], 0x2546 => [light_lu, heavy_dr], 0x2547 => [heavy_h, heavy_u, light_d],
96
- 0x2548 => [hh, hd, light_u], 0x2549 => [heavy_v, heavy_l, light_r], 0x254a => [heavy_v, light_l, heavy_r], 0x254b => [heavy_v, heavy_h],
97
- 0x254c => [lh, dblc], 0x254d => [heavy_h, dblc], 0x254e => [light_v, dblc], 0x254f => [heavy_v, dblc],
98
-
99
- 0x2550 => dh, 0x2551 => dv, 0x2552 => [dh, lv, ml, maskdtop], 0x2553 => [dv, lh, mt, maskdleft],
100
- 0x2554 => [ddbar, drbar, mask_dbar, mask_rbar], 0x2555 => [dh, mr, lv, maskdtop],
101
- 0x2556 => [dv, lh,maskdright,mt], 0x2557 => [double_dbar, double_lbar, mask_dbar, mask_lbar],
102
- 0x2558 => [lv, dh, ml, maskdbottom], 0x2559 => [lh, dv, maskdleft, mb],
103
- 0x255A => [dtbar, double_rbar, mask_tbar, mask_rbar], 0x255B => [dh, lv, maskdbottom, mr],
104
- 0x255C => [dv, lh, mb, maskdright], 0x255D => [dtbar, dlbar, mask_tbar, mask_lbar],
105
- 0x255e => [lv, dh, maskleft], 0x255f => [lh, dv, dblc, maskdleft],
106
- 0x2560 => [dv, drbar, mask_rbar, mask_vbar], 0x2561 => [lv, dh, maskright],
107
- 0x2562 => [dv, ll, dblc], 0x2563 => [dlbar, dv, mask_lbar, mask_vbar],
108
- 0x2564 => [dh, ld, dblc], 0x2565 => [lh, dv, mt],
109
- 0x2566 => [dh, ddbar,mask_hbar, mask_dbar], 0x2567 => [dh, light_u,dblc],
110
- 0x2568 => [lh, dv, mb], 0x2569 => [dh, double_tbar, mask_hbar, mask_tbar],
111
- 0x256A => dh+lv, 0x256b => dv+lh, 0x256c => [dh, dv, mask_hbar, mask_vbar],
112
-
113
- # 256d, 256e, 256f, 2570 => curves
114
- # FIXME: Current is just very slightly rounded.
115
- 0x256d => [ldr, mask_c], 0x256e => [ldl, mask_c], 0x256f => [llu, mask_c], 0x2570 => [lru, mask_c],
116
-
117
- # 2571, 2572. 2573 => diagonals, handled below.
118
- 0x2571 => nil, 0x2572 => nil, 0x2573 => nil,
119
-
120
- 0x2574 => ll, 0x2575 => lu, 0x2576 => lr, 0x2577 => ld,
121
- 0x2578 => hl, 0x2579 => hu, 0x257a => hr, 0x257b => hd,
122
- 0x257c => ll + hr, 0x257d => lu + hd, 0x257e => hl + lr, 0x257f => hu + ld
123
-
124
- }
125
-
126
- r = rects[ch.ord]
127
- if r
128
- img = empty_box_image
129
- r.flatten.each_slice(5) do |rect|
130
- p rect
131
- x1,y1,x2,y2, col = *rect
132
- x1 = (x1+hx).clamp(0,boxw-1)
133
- x2 = (x2+hx).clamp(0,boxw-1)
134
- y1 = (y1+hy).clamp(0,boxh-1)
135
- y2 = (y2+hy).clamp(0,boxh-1)
136
- col ||= 255
137
-
138
- a = Array.new(x2-x1+1,col)
139
-
140
- (y1..y2).each do |y|
141
- img.pixels[y*stride + x1 .. y*stride + x2] = a
142
- end
143
- end
144
- return img
145
- end
146
-
147
- if ch.ord == 0x2571
148
- img = empty_box_image
149
- slope = boxw.to_f / boxh
150
- x = boxw.to_f-1
151
- (0...boxh).each do |y|
152
- err = x - x.to_i
153
- img.pixels[y*stride+x.ceil] = 255 #(255*(1-err)).floor
154
- img.pixels[y*stride+x.ceil-1] = (255*err).floor if x > 0
155
- x-= slope
156
- end
157
- return img
158
- end
159
-
160
- # FIXME: If size is *at* certain levels level, this, and the next one fails?
161
-
162
- if ch.ord == 0x2572
163
- img = empty_box_image
164
- slope = boxw.to_f / boxh
165
- x = boxw.to_f-1
166
- (0...boxh).each do |y|
167
- err = x - x.to_i
168
- img.pixels[y*stride+boxw-x.ceil-1] = 255
169
- img.pixels[y*stride+boxw-x.ceil] = (255*err).floor if x > 0
170
- x-= slope
171
- end
172
- return img
173
- end
174
-
175
- if ch.ord == 0x2573
176
- img = empty_box_image
177
- slope = boxw.to_f / boxh
178
- x = boxw.to_f-1
179
- (0...boxh).each do |y|
180
- err = x - x.to_i
181
- img.pixels[y*stride+boxw-x.ceil-1] = 255
182
- img.pixels[y*stride+boxw-x.ceil] = (255*err).floor if x > 0
183
- img.pixels[y*stride+x.ceil-1] = (255*err).floor if x > 0
184
- img.pixels[y*stride+x.ceil] = 255 #(255*(1-err)).floor
185
- x-= slope
186
- end
187
- return img
188
- end
189
-
190
- if img
191
- img = img.dup
192
- img.pixels = img.pixels.dup
193
- end
194
- img || empty_box_image
195
- end
196
- end
197
- end
198
- end