echoes 0.3.0 → 0.4.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.
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Echoes
4
+ # Minimal SVG element scanner. Yields one event per element to
5
+ # the block (or returns an Enumerator):
6
+ #
7
+ # [:open, tag, {attr => value, …}]
8
+ # [:close, tag, {}]
9
+ # [:self_close, tag, {attr => value, …}]
10
+ #
11
+ # Skips comments, processing instructions (`<?xml…?>`), DOCTYPE
12
+ # declarations, and CDATA sections. Text content between tags is
13
+ # ignored (the fast-path SVG renderer doesn't render text — any
14
+ # SVG that uses <text> is rejected before we walk it).
15
+ #
16
+ # Stops yielding on malformed input (no matching `>`, unbalanced
17
+ # comment, etc.). The caller treats early termination as "can't
18
+ # render — bail to WKWebView."
19
+ module SvgWalker
20
+ TAG_NAME_RE = /[a-zA-Z][\w:.-]*/
21
+ # `\G` anchors the match to the scan offset passed to Regexp#match,
22
+ # so we can reuse the same regex from any position. `\A` would
23
+ # only match at string-position 0, which is wrong once we're past
24
+ # the first tag.
25
+ CLOSE_TAG_RE = /\G<\/(#{TAG_NAME_RE})\s*>/m
26
+ OPEN_TAG_RE = /\G<(#{TAG_NAME_RE})([^>]*)>/m
27
+ ATTR_RE = /(#{TAG_NAME_RE})\s*=\s*(?:"([^"]*)"|'([^']*)')/
28
+
29
+ module_function
30
+
31
+ def events(bytes)
32
+ return enum_for(:events, bytes) unless block_given?
33
+ return if bytes.nil?
34
+
35
+ s = bytes.b
36
+ pos = 0
37
+ len = s.length
38
+ while pos < len
39
+ lt = s.index('<', pos)
40
+ break unless lt
41
+ pos = lt
42
+
43
+ # Comment: <!-- … -->
44
+ if s[pos, 4] == '<!--'
45
+ close = s.index('-->', pos + 4)
46
+ break unless close
47
+ pos = close + 3
48
+ next
49
+ end
50
+
51
+ # CDATA: <![CDATA[ … ]]>
52
+ if s[pos, 9] == '<![CDATA['
53
+ close = s.index(']]>', pos + 9)
54
+ break unless close
55
+ pos = close + 3
56
+ next
57
+ end
58
+
59
+ # Other declarations: <!DOCTYPE …>, <!ENTITY …>, etc.
60
+ if s[pos, 2] == '<!'
61
+ close = s.index('>', pos + 2)
62
+ break unless close
63
+ pos = close + 1
64
+ next
65
+ end
66
+
67
+ # Processing instruction: <?xml …?>
68
+ if s[pos, 2] == '<?'
69
+ close = s.index('?>', pos + 2)
70
+ break unless close
71
+ pos = close + 2
72
+ next
73
+ end
74
+
75
+ # Closing tag: </tag>
76
+ if s[pos, 2] == '</'
77
+ m = CLOSE_TAG_RE.match(s, pos)
78
+ break unless m && m.begin(0) == pos
79
+ yield [:close, m[1], {}]
80
+ pos = m.end(0)
81
+ next
82
+ end
83
+
84
+ # Opening or self-closing tag.
85
+ m = OPEN_TAG_RE.match(s, pos)
86
+ break unless m && m.begin(0) == pos
87
+ tag = m[1]
88
+ body = m[2]
89
+ if body.rstrip.end_with?('/')
90
+ attrs_str = body.rstrip.chomp('/')
91
+ yield [:self_close, tag, parse_attrs(attrs_str)]
92
+ else
93
+ yield [:open, tag, parse_attrs(body)]
94
+ end
95
+ pos = m.end(0)
96
+ end
97
+ end
98
+
99
+ def parse_attrs(str)
100
+ out = {}
101
+ str.scan(ATTR_RE) do |name, dq, sq|
102
+ out[name] = dq || sq || ''
103
+ end
104
+ out
105
+ end
106
+ end
107
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Echoes
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: echoes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akira Matsuda
@@ -86,6 +86,13 @@ files:
86
86
  - lib/echoes/profile.rb
87
87
  - lib/echoes/screen.rb
88
88
  - lib/echoes/sixel_decoder.rb
89
+ - lib/echoes/svg_cg_renderer.rb
90
+ - lib/echoes/svg_color.rb
91
+ - lib/echoes/svg_path_parser.rb
92
+ - lib/echoes/svg_renderer.rb
93
+ - lib/echoes/svg_sniffer.rb
94
+ - lib/echoes/svg_transform.rb
95
+ - lib/echoes/svg_walker.rb
89
96
  - lib/echoes/tab.rb
90
97
  - lib/echoes/terminal.rb
91
98
  - lib/echoes/version.rb