skrift-x11 0.1.0 → 0.2.2

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: a086ab34c955c628b9b32640d98e26415a2e6766e9a784797893b6c8ef7473b0
4
- data.tar.gz: 89a0ea4e3ee0cf221037e6c951d746c63f00140dd775f1b332cb0d7afdc52c67
3
+ metadata.gz: a75688a1a4f9e322d33c58a9b0a29b2fb26c27536a137e33dd97c340a491dd98
4
+ data.tar.gz: 87a5c7f3f29918f0031e637acbc4d08a6d8d6f122b3112c597515086fa1d0317
5
5
  SHA512:
6
- metadata.gz: 8cf85e533b7d29eb13e993842a83a09bd3eaf7322fe41d93d5290637788d5bd944a90dd96f3f21f618edf2d03998f9667f7c403621470622fd552d72f6d039be
7
- data.tar.gz: e07c349839802875a8d73f13a0dd2daa3beb76c12f52677f34a7a5f5bdd76aab8acdb38dc5c5c59e5612aad1ae2716ba7ffe223a86b4ca63eebae5fde1eae21d
6
+ metadata.gz: 7db2e7d2a63a2afa679ca83652136e13e954b3eee2c11d55c5734c48ed10be641a8fee0eca3d1df779a1d4c19c7a4172027d9aea1659f4bb8f4728f6de3ded3e
7
+ data.tar.gz: 4014ba510524d9921c9eb67986b137151f98aca2cd71427e431fdcefe776c5c869e45b45de09d3a692fe77976f82da4c7df6bdb08ca687736301d43a45f5af12
data/example.rb CHANGED
@@ -7,64 +7,52 @@ require 'pp'
7
7
 
8
8
  Bundler.setup(:default, :development)
9
9
 
10
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11
- $LOAD_PATH.unshift(File.dirname(__FILE__))
10
+ #$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11
+ #$LOAD_PATH.unshift(File.dirname(__FILE__))
12
12
 
13
13
  require 'skrift/x11'
14
14
 
15
- $dpy = dpy = display = X11::Display.new
15
+ dpy = display = X11::Display.new
16
16
  screen = dpy.screens.first
17
+ visual = dpy.find_visual(0, 32).visual_id
18
+ #cmap = dpy.create_colormap(0, screen.root, visual)
17
19
 
18
- # Transparency: Check
19
- # https://stackoverflow.com/questions/13395179/empty-or-transparent-window-with-xlib-showing-border-lines-only/13397150#13397150
20
+ p visual
21
+ #p cmap
20
22
 
21
- # FIXME: Hack; see XFindVisualMatch
22
- visual = 123
23
-
24
- cmap = $dpy.create_colormap(0, screen.root, visual)
25
-
26
- wid = display.new_id
27
- $dpy.create_window(
28
- 32,
29
- wid, screen.root,
30
- 0, 0, # x,y
23
+ wid = dpy.create_window(
24
+ 0, 0, # x,y
31
25
  1000, 600, # w,h
32
- 0,
33
- X11::Form::InputOutput,
34
- visual, #X11::Form::CopyFromParent -- Must be provided if e.g. depth is different than root.
35
- X11::Form::CWBackPixel | X11::Form::CWBorderPixel |
36
- X11::Form::CWEventMask | X11::Form::CWColorMap,
37
- [0x00000000, # ARGB background
38
- 0x0, # Border pixel; Necessary when depth != screen.root_depth
39
- X11::Form::SubstructureNotifyMask |
40
- X11::Form::StructureNotifyMask | ## Move
41
- X11::Form::ExposureMask |
42
- X11::Form::KeyPressMask |
43
- X11::Form::ButtonPressMask,
44
- cmap # Colormap. Necessary when depth != screen.root_depth
45
- ]
26
+ visual: visual,
27
+ values: {
28
+ X11::Form::CWBackPixel => 0, # ARGB background
29
+ X11::Form::CWBorderPixel => 0, # Needed in case window depth != screen.root_depth
30
+ X11::Form::CWEventMask =>
31
+ (X11::Form::SubstructureNotifyMask |
32
+ X11::Form::StructureNotifyMask |
33
+ X11::Form::ExposureMask |
34
+ X11::Form::KeyPressMask |
35
+ X11::Form::ButtonPressMask)#,
36
+ # X11::Form::CWColorMap => cmap # Needed for same reason as borderpixel
37
+ }
46
38
  )
39
+ p wid
47
40
 
48
41
  dpy.map_window(wid) # Window won't be visible until this
49
42
 
50
43
  # A "picture" object for us to draw in the window with.
51
44
  fmt = dpy.render_find_visual_format(visual)
45
+ p fmt
52
46
  $pic = dpy.render_create_picture(wid, fmt)
53
47
 
54
- # The easy way
55
- #$fgpic = dpy.render_create_solid_fill(0xffff,0xffff,0,0)
56
-
57
-
58
- $gc = dpy.create_gc(wid, foreground: 0xffff0000)
59
-
60
- puts "Main loop"
48
+ gc = dpy.create_gc(wid, foreground: 0xffff0000)
61
49
 
62
- $f = Font.load("resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf")
50
+ f = Font.load("resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf")
63
51
 
64
- $skrift = Skrift::X11::Glyphs.new($dpy, $f, x_scale: 40, y_scale: 40)
52
+ $skrift = Skrift::X11::Glyphs.new(dpy, f, x_scale: 40, y_scale: 40)
65
53
 
66
54
  def redraw(dpy, wid, gc)
67
- dpy.poly_fill_rectangle(wid, gc, [X11::Form::Rectangle.new(20,45, 400, 400)])
55
+ dpy.poly_fill_rectangle(wid, gc, [20,45, 400, 400])
68
56
  $skrift.render_str($pic, 0xffffff, 50,90, 'Pure Ruby w/Skrift!')
69
57
  $skrift.render_str($pic, 0xffffff, 50,140, "And unicode:")
70
58
  $skrift.render_str($pic, 0xff00ff, 50,200, "Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.")
@@ -74,9 +62,9 @@ end
74
62
  loop do
75
63
  pkt = display.next_packet
76
64
  if pkt
77
- #puts pkt.inspect[0..200]
65
+ puts pkt.inspect[0..200]
78
66
  #raise "Error" if pkt.is_a?(X11::Form::Error)
79
- redraw(display, wid, $gc) if pkt.is_a?(X11::Form::Expose)
67
+ redraw(display, wid, gc) if pkt.is_a?(X11::Form::Expose)
80
68
 
81
69
  if pkt.is_a?(X11::Form::KeyPress)
82
70
  # lookup_keysym(dpy,pkt)
@@ -0,0 +1,198 @@
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
@@ -1,25 +1,73 @@
1
1
 
2
- require 'skrift'
3
- module Skrift
4
- module X11
5
- # XRender constants currently not defined in pure-x11.
6
- # FIXME.
2
+ require_relative 'boxdrawing'
3
+
4
+ module X11
5
+ # XRender constants currently not defined in pure-x11.
6
+ # FIXME.
7
+ module Form
7
8
  PictOpSrc=1
8
9
  PictOpOver=3
9
10
  CPRepeat = 1
11
+ end
12
+ end
10
13
 
14
+ module Skrift
15
+ module X11
16
+
11
17
  class Glyphs
18
+ 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
12
45
 
13
- def initialize dpy, font, x_scale:, y_scale:, pic: nil, fixed: nil
14
46
  @dpy = dpy
15
- @sft = SFT.new(font)
47
+ @sft = SFT.new(@font)
16
48
  @sft.x_scale = x_scale
17
49
  @sft.y_scale = y_scale
18
50
  @pic = pic
19
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
20
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
66
+
67
+ @nextgid = 1
21
68
  @glyphcache = {}
22
69
  @colcache = {}
70
+ @chcache = {}
23
71
 
24
72
  @lm = @sft.lmetrics
25
73
 
@@ -29,11 +77,6 @@ module Skrift
29
77
  end
30
78
 
31
79
  attr_accessor :lm
32
- def gm(ch)
33
- gid = @sft.lookup(ch.ord)
34
- @sft.gmetrics(gid)
35
- end
36
-
37
80
 
38
81
  def fill_for_col(col)
39
82
  return @colcache[col] if @colcache[col]
@@ -47,25 +90,74 @@ module Skrift
47
90
  @colcache[col] ||= @dpy.render_create_solid_fill(r,g,b,0xffff)
48
91
  end
49
92
 
50
- def fixed_width
51
- return @fixed_width if @fixed_width
52
- if @fixed
53
- # *Ensure* that the glyphs are equal width.
54
- g = gm("M")
55
- @fixed_width = g.advance_width.ceil
56
- else
57
- nil
58
- end
93
+ private
94
+
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
59
99
  end
60
-
61
-
62
- def cache_glyph(gid, baseline)
100
+
101
+
102
+ def cache_special(ch)
103
+ # Box drawing
104
+ return nil if !((0x2500 .. 0x257f) === ch.ord)
105
+
106
+
107
+ img = cache_box(ch.ord)
108
+
109
+
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
+ )
119
+
120
+ gsgid = @nextgid
121
+ @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?
63
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
64
152
 
65
153
  # FIXME: Not sure what to do if mtx.nil? here.
66
154
  # Maybe use x/y scale?
67
- w = fixed_width || mtx.min_width || 0
68
- h = mtx.min_height || 1
155
+ if @fixed
156
+ w = fixed_width
157
+ else
158
+ w = mtx.min_width || 0
159
+ end
160
+ h = [mtx&.min_height || 1, @maxheight].compact.min
69
161
  #p [w,h]
70
162
  img = Image.new((w+3)&~3, h)
71
163
  if !@sft.render(gid, img)
@@ -74,9 +166,9 @@ module Skrift
74
166
  else
75
167
  data = img.pixels.pack("C*")
76
168
  end
77
-
169
+
78
170
  yoff = mtx.y_offset || baseline
79
-
171
+
80
172
  info = ::X11::Form::GlyphInfo.new(
81
173
  img.width, # w
82
174
  img.height, # h
@@ -86,41 +178,72 @@ module Skrift
86
178
  0
87
179
  )
88
180
 
89
- @dpy.render_add_glyphs(@glyphset, gid, info, data)
90
- @glyphcache[gid] = mtx.advance_width#-mtx.left_side_bearing
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
91
185
  end
92
186
 
187
+ public
188
+
93
189
  def text_width(str)
94
- gl = map_glyphs(str)
95
190
  # We *presume* that if you call text_width, you intend
96
191
  # to render the string. Maybe we shouldn't?
97
- cache_glyphs(gl)
192
+ gl = cache_glyphs(str)
98
193
  gl.inject(0) {|sum,gl|
99
194
  @glyphcache[gl].to_i + sum
100
195
  }
101
196
  end
102
197
 
103
- def map_glyphs(str)
104
- # FIXME: Should probably cache by character rather than
105
- # glyph
106
- str.to_s.each_char.map { |ch| @sft.lookup(ch.ord) }
198
+ private
199
+ def each_font
200
+ Array(@fontset).each_index do |i|
201
+ yield load_font(i)
202
+ end
107
203
  end
108
204
 
109
- def cache_glyphs(gl)
110
- gl.each do |gid|
111
- data = @glyphcache[gid]
112
- if !data
113
- data = cache_glyph(gid, @lm.ascender)
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
+
234
+ cache[:gsgid]
235
+ else
236
+ 0
114
237
  end
115
238
  end
116
239
  end
117
-
240
+
241
+ public
118
242
  def render_str(pic, col, x,y, str)
119
243
  fill = fill_for_col(col)
120
- gl = map_glyphs(str)
121
- cache_glyphs(gl)
244
+ gl = cache_glyphs(str)
122
245
  @dpy.render_composite_glyphs32(
123
- PictOpOver, fill, pic, @gfmt,
246
+ :PictOpOver, fill, pic, @gfmt,
124
247
  @glyphset, 0,0, [x, y, gl]
125
248
  )
126
249
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Skrift
4
4
  module X11
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.2"
6
6
  end
7
7
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/skrift/x11/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "skrift-x11"
7
+ spec.version = Skrift::X11::VERSION
8
+ spec.authors = ["Vidar Hokstad"]
9
+ spec.email = ["vidar@hokstad.com"]
10
+
11
+ spec.summary = "Helpers to use the pure-Ruvy TrueType engine Skrift with pure Pure-X11 X bindings"
12
+ #spec.description = "TODO: Write a longer description or delete this line."
13
+ #spec.homepage = "TODO: Put your gem's website or public repo URL here."
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ #spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
+
19
+ #spec.metadata["homepage_uri"] = spec.homepage
20
+ #spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
21
+ #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency "example-gem", "~> 1.0"
37
+ spec.add_dependency "skrift"
38
+ spec.add_dependency "pure-x11"
39
+
40
+ # For more information and examples about making a new gem, check out our
41
+ # guide at: https://bundler.io/guides/creating_gem.html
42
+ end
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.1.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-25 00:00:00.000000000 Z
11
+ date: 2026-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: skrift
@@ -51,10 +51,12 @@ files:
51
51
  - example.png
52
52
  - example.rb
53
53
  - lib/skrift/x11.rb
54
+ - lib/skrift/x11/boxdrawing.rb
54
55
  - lib/skrift/x11/glyphs.rb
55
56
  - lib/skrift/x11/version.rb
56
57
  - resources/FiraGO-Regular_extended_with_NotoSansEgyptianHieroglyphs-Regular.ttf
57
58
  - sig/skrift/x11.rbs
59
+ - skrift-x11.gemspec
58
60
  homepage:
59
61
  licenses:
60
62
  - MIT