rubyterm 0.2.4 → 0.2.5

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: 201ce77d71abc653a76c0fb9a51827e13910767e5f5946999853a13b8ab02006
4
- data.tar.gz: 4b57378d7077055158b33aca41b4e365c4f63a60bb95547e2c5e64ffa91e1e75
3
+ metadata.gz: dad934f37ee68f63661c2d7bbd2e8c7af64a8811fb8fd27b9d5bd63787a8f021
4
+ data.tar.gz: 124c7b36ea94bef960b7bbc78f9143149cce43cf443b4e7d9ed99207e40d1f01
5
5
  SHA512:
6
- metadata.gz: d60d6b223b2819739d0dff9e571b8007a2422c2f38fc5d784e733c3273242037d1833fdea48c15735e72b76ef3487e4c716ed8b1cc10b707cfaddac96e76c511
7
- data.tar.gz: 3c5b38c7f9a928f2f1bfa99b798515bb42a0d455c04d956cac11ec8687bac35adcd92fa96c2c6e6c40c28587957ced61c25c747ab990a526a2961c9d01377824
6
+ metadata.gz: ea7bf3f07aa5088ca07bc79058aa836d20af2c95d5d6887d7fe5e7fd8f44283170c4fa2df7078d5f10a271582b5d339143473e8fc3d1af51d74c42f562e3e0a5
7
+ data.tar.gz: 491468d6aab9c96754001425718102aaa1c677c994ca1f82c47a3fa07196327bb3ed132ef72c547d77904216f6ed9f700373fdd3c6b08cae3769904f36f562a9
data/lib/bitmapwindow.rb CHANGED
@@ -83,10 +83,14 @@ class BitmapWindow
83
83
  # for now - it does not affect correctness of the text, only its scale.
84
84
  def draw(x, y, str, fg, bg, _lineattrs = nil)
85
85
  fillrect(x, y, str.length * @char_w, @char_h, bg)
86
- str.each_char.with_index do |ch, i|
87
- cp = ch.ord
86
+ cps = str.each_char.map(&:ord)
87
+ cps.each_with_index do |cp, i|
88
88
  next if cp == 32 || cp == CharWidth::WIDE_SPACER # space / wide-glyph tail
89
- blit_glyph(cp, x + i * @char_w, y, fg)
89
+ # A WIDE_SPACER in the next cell means this is a double-width glyph; render
90
+ # it two cells wide so it overflows into the (skipped) spacer cell instead
91
+ # of being shrunk into one.
92
+ cells = cps[i + 1] == CharWidth::WIDE_SPACER ? 2 : 1
93
+ blit_glyph(cp, x + i * @char_w, y, fg, cells)
90
94
  end
91
95
  end
92
96
 
@@ -146,8 +150,8 @@ class BitmapWindow
146
150
  end
147
151
  end
148
152
 
149
- def blit_glyph(codepoint, cx, cy, fg)
150
- g = @cache.glyph(codepoint)
153
+ def blit_glyph(codepoint, cx, cy, fg, cells = 1)
154
+ g = @cache.glyph(codepoint, cells)
151
155
  return unless g
152
156
  if g.color?
153
157
  blit_rgba(g, codepoint, cx, cy)
data/lib/charwidth.rb CHANGED
@@ -11,7 +11,6 @@
11
11
  # * width(cp) -> 1 or 2 cells (wide = CJK/Hangul/Kana/fullwidth + emoji)
12
12
  # * emoji?(cp) -> whether to render in colour (the non-CJK wide blocks)
13
13
  #
14
- # Combining marks (width 0) are deliberately not handled yet (treated as 1).
15
14
  module CharWidth
16
15
  # Codepoint stored in the second cell of a double-width glyph: a blank,
17
16
  # advancing placeholder. The terminal writes it after a wide char so the
@@ -19,6 +18,26 @@ module CharWidth
19
18
  # advancing glyph and the bitmap/virtual backends skip it.
20
19
  WIDE_SPACER = 0
21
20
 
21
+ # Zero-width (combining) codepoints. These modify the preceding base glyph
22
+ # rather than advancing the cursor, so they occupy no column — matching tmux
23
+ # (a lone U+FE0F reports cursor_x=0; a base+U+FE0F sequence stays the base's
24
+ # width). Notably this includes the variation selectors: U+FE0F (VS16, emoji
25
+ # presentation) and U+FE0E (VS15, text presentation) request a presentation
26
+ # for the preceding char but are themselves invisible and zero-width. Treating
27
+ # them as width 1 gave each its own cell, drawing a stray glyph over the next
28
+ # column. Per-script combining marks beyond these blocks are a deferred
29
+ # non-goal (still treated as width 1).
30
+ ZERO = [
31
+ 0x0300..0x036F, # combining diacritical marks
32
+ 0x1AB0..0x1AFF, # combining diacritical marks extended
33
+ 0x1DC0..0x1DFF, # combining diacritical marks supplement
34
+ 0x200B..0x200F, # ZWSP, ZWNJ, ZWJ, LRM, RLM (zero-width format)
35
+ 0x20D0..0x20FF, # combining diacritical marks for symbols
36
+ 0xFE00..0xFE0F, # variation selectors (incl. VS15/VS16)
37
+ 0xFE20..0xFE2F, # combining half marks
38
+ 0xE0100..0xE01EF # variation selectors supplement
39
+ ].freeze
40
+
22
41
  # Wide (two-cell) blocks: CJK, Hangul, Kana, fullwidth forms, and emoji.
23
42
  #
24
43
  # In the BMP symbol zone (Misc Symbols, Dingbats, Misc Symbols & Arrows)
@@ -45,9 +64,16 @@ module CharWidth
45
64
  0x1FA70..0x1FAD6, 0x20000..0x2EBE0, 0x2F800..0x2FA1D, 0x30000..0x3134A
46
65
  ].freeze
47
66
 
48
- # The emoji subset of WIDE (excludes CJK/Hangul/Kana/fullwidth): these render
49
- # in colour when a colour font provides them. Digits, '#', '*' etc. are not
50
- # here, so they stay as ordinary text even though Noto maps colour keycaps.
67
+ # Emoji code blocks (excludes CJK/Hangul/Kana/fullwidth): a codepoint renders
68
+ # in colour only if it is BOTH in one of these blocks AND wide (width 2) —
69
+ # see emoji?. The width-2 gate is what distinguishes emoji-presentation
70
+ # codepoints (⚡ U+26A1, ✨ U+2728 — colour, width 2) from text-presentation
71
+ # dingbats that merely share a block (❤ U+2764, ✔ U+2714, ⚠ U+26A0 — width 1).
72
+ # Both properties derive from the same Unicode Emoji_Presentation flag, so
73
+ # this is not "width deciding colour": rather, colour is the subset of these
74
+ # blocks that is wide enough to render a square colour glyph without spilling
75
+ # into the next cell. Digits, '#', '*' are absent, so they stay ordinary text
76
+ # even though Noto maps colour keycaps.
51
77
  EMOJI = [
52
78
  0x231A..0x232A, 0x23E9..0x23F3, 0x25FD..0x27BF, 0x2B1B..0x2B55,
53
79
  0x1F18E..0x1F19A, 0x1F200..0x1F265, 0x1F300..0x1F5A4, 0x1F5FB..0x1F6FC,
@@ -56,18 +82,22 @@ module CharWidth
56
82
 
57
83
  module_function
58
84
 
59
- # Number of terminal cells a codepoint occupies (1 or 2).
85
+ # Number of terminal cells a codepoint occupies (0, 1 or 2).
60
86
  def width(cp)
61
- return 1 if cp < 0x1100 # fast path: ASCII/Latin and most text
87
+ return 1 if cp < 0x0300 # fast path: ASCII/Latin-1 (no combining below)
88
+ return 0 if in_ranges?(ZERO, cp)
89
+ return 1 if cp < 0x1100 # fast path: most other text
62
90
  in_ranges?(WIDE, cp) ? 2 : 1
63
91
  end
64
92
 
65
93
  def wide?(cp) = width(cp) == 2
66
94
 
67
95
  # Whether a codepoint should render as a colour emoji (when the font has it).
96
+ # Gated to width-2 emoji: a colour glyph is rasterised square (~2 cells wide),
97
+ # so colouring a width-1 cell would overflow and corrupt the next column.
68
98
  def emoji?(cp)
69
99
  return false if cp < 0x231A
70
- in_ranges?(EMOJI, cp)
100
+ in_ranges?(EMOJI, cp) && width(cp) == 2
71
101
  end
72
102
 
73
103
  # Binary search over a sorted array of non-overlapping ranges.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RubyTerm
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/term.rb CHANGED
@@ -565,6 +565,11 @@ class Term
565
565
  return if ch == 127 # DEL is ignored in the data stream
566
566
 
567
567
  cw = CharWidth.width(ch)
568
+ # Zero-width codepoints (variation selectors like U+FE0F, combining marks,
569
+ # zero-width joiners) modify the preceding glyph. This buffer stores one
570
+ # codepoint per cell and can't compose, so drop them rather than letting
571
+ # them claim a cell of their own — which would paint over the next column.
572
+ return if cw == 0
568
573
  # A double-width glyph can't straddle the right margin: if only one
569
574
  # column is left, blank it and wrap so the glyph starts the next line.
570
575
  if cw == 2 && @wraparound && @x == line_width - 1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyterm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-26 00:00:00.000000000 Z
11
+ date: 2026-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pure-x11