dommy 0.16.0 → 0.17.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dommy/animation.rb +1 -0
  3. data/lib/dommy/backend.rb +3 -0
  4. data/lib/dommy/blob.rb +6 -0
  5. data/lib/dommy/browser.rb +12 -4
  6. data/lib/dommy/character_data.rb +1 -13
  7. data/lib/dommy/cookie_store.rb +2 -1
  8. data/lib/dommy/document.rb +67 -30
  9. data/lib/dommy/element.rb +11 -0
  10. data/lib/dommy/element_internals.rb +12 -0
  11. data/lib/dommy/encodings/legacy_multibyte.rb +800 -0
  12. data/lib/dommy/encodings/multibyte_overrides.rb +76 -0
  13. data/lib/dommy/encodings/prescan.rb +234 -0
  14. data/lib/dommy/encodings.rb +121 -57
  15. data/lib/dommy/event.rb +107 -54
  16. data/lib/dommy/event_source.rb +1 -0
  17. data/lib/dommy/fetch.rb +244 -53
  18. data/lib/dommy/file_reader.rb +1 -0
  19. data/lib/dommy/form_data.rb +22 -9
  20. data/lib/dommy/form_entry_list.rb +3 -3
  21. data/lib/dommy/html_canvas_element.rb +85 -12
  22. data/lib/dommy/html_elements/base.rb +1 -0
  23. data/lib/dommy/html_elements/forms/input.rb +3 -0
  24. data/lib/dommy/html_elements/interactive.rb +56 -48
  25. data/lib/dommy/html_elements/metadata.rb +23 -0
  26. data/lib/dommy/interaction/form_submission.rb +24 -42
  27. data/lib/dommy/internal/accessibility_tree.rb +149 -162
  28. data/lib/dommy/internal/accessibility_visibility.rb +16 -17
  29. data/lib/dommy/internal/accessible_name.rb +231 -246
  30. data/lib/dommy/internal/aria_role.rb +182 -195
  31. data/lib/dommy/internal/aria_snapshot.rb +35 -38
  32. data/lib/dommy/internal/aria_state.rb +102 -118
  33. data/lib/dommy/internal/autofill.rb +75 -75
  34. data/lib/dommy/internal/backend_prefilter.rb +127 -130
  35. data/lib/dommy/internal/child_navigable.rb +86 -83
  36. data/lib/dommy/internal/close_watcher_manager.rb +1 -0
  37. data/lib/dommy/internal/css/ancestor_filter.rb +64 -64
  38. data/lib/dommy/internal/css/calc.rb +90 -71
  39. data/lib/dommy/internal/css/cascade.rb +69 -67
  40. data/lib/dommy/internal/css/color.rb +249 -187
  41. data/lib/dommy/internal/css/computed_style_builder.rb +34 -0
  42. data/lib/dommy/internal/css/computed_style_declaration.rb +50 -11
  43. data/lib/dommy/internal/css/counters.rb +67 -67
  44. data/lib/dommy/internal/css/custom_properties.rb +114 -114
  45. data/lib/dommy/internal/css/parser.rb +277 -239
  46. data/lib/dommy/internal/css/property_registry.rb +321 -302
  47. data/lib/dommy/internal/css/renderability.rb +37 -35
  48. data/lib/dommy/internal/css/rule_index.rb +2 -2
  49. data/lib/dommy/internal/css/rule_index_inputs.rb +2 -2
  50. data/lib/dommy/internal/css/supports.rb +115 -113
  51. data/lib/dommy/internal/css/ua_stylesheet.rb +55 -53
  52. data/lib/dommy/internal/css_rule_text.rb +121 -119
  53. data/lib/dommy/internal/css_source.rb +1 -3
  54. data/lib/dommy/internal/css_syntax.rb +157 -92
  55. data/lib/dommy/internal/declarative_shadow_roots.rb +1 -0
  56. data/lib/dommy/internal/directionality.rb +120 -123
  57. data/lib/dommy/internal/document_live_ranges.rb +26 -1
  58. data/lib/dommy/internal/dom_matching.rb +185 -184
  59. data/lib/dommy/internal/element_geometry.rb +8 -1
  60. data/lib/dommy/internal/element_matching.rb +71 -69
  61. data/lib/dommy/internal/element_shadow.rb +1 -13
  62. data/lib/dommy/internal/element_state.rb +255 -265
  63. data/lib/dommy/internal/event_handlers.rb +66 -64
  64. data/lib/dommy/internal/exception_report.rb +88 -86
  65. data/lib/dommy/internal/focusability.rb +205 -205
  66. data/lib/dommy/internal/global_functions.rb +49 -47
  67. data/lib/dommy/internal/idl_reflection.rb +99 -97
  68. data/lib/dommy/internal/js_number.rb +35 -33
  69. data/lib/dommy/internal/light_dismiss.rb +130 -132
  70. data/lib/dommy/internal/literal_lookup.rb +75 -75
  71. data/lib/dommy/internal/mime_type.rb +54 -49
  72. data/lib/dommy/internal/mutation_coordinator.rb +46 -0
  73. data/lib/dommy/internal/mutation_observer_microtask.rb +60 -0
  74. data/lib/dommy/internal/namespaces.rb +51 -49
  75. data/lib/dommy/internal/node_adopter.rb +25 -1
  76. data/lib/dommy/internal/node_equality.rb +61 -59
  77. data/lib/dommy/internal/post_insertion_steps.rb +1 -0
  78. data/lib/dommy/internal/referrer.rb +144 -0
  79. data/lib/dommy/internal/rendered_text/collector.rb +22 -3
  80. data/lib/dommy/internal/rendered_text/computed_box.rb +3 -2
  81. data/lib/dommy/internal/rendered_text/fragment.rb +37 -38
  82. data/lib/dommy/internal/rendered_text/items.rb +7 -5
  83. data/lib/dommy/internal/selector_matcher.rb +361 -359
  84. data/lib/dommy/internal/selector_parser.rb +74 -62
  85. data/lib/dommy/internal/sequential_focus_navigation.rb +150 -151
  86. data/lib/dommy/internal/shadow_root_registry.rb +7 -0
  87. data/lib/dommy/internal/slot_assignment.rb +117 -0
  88. data/lib/dommy/internal/slot_signals.rb +125 -0
  89. data/lib/dommy/internal/unsafe_html.rb +79 -77
  90. data/lib/dommy/internal/url_parser.rb +5 -29
  91. data/lib/dommy/internal/web_idl.rb +158 -153
  92. data/lib/dommy/internal/window_bound.rb +14 -0
  93. data/lib/dommy/internal/window_constructors.rb +3 -0
  94. data/lib/dommy/internal/xml_serialization.rb +298 -296
  95. data/lib/dommy/js/dom_interfaces.rb +13 -1
  96. data/lib/dommy/js/host_bridge.rb +23 -1
  97. data/lib/dommy/js/host_callbacks.rb +7 -0
  98. data/lib/dommy/js/host_runtime.js +625 -61
  99. data/lib/dommy/js/import_map.rb +1 -1
  100. data/lib/dommy/js/observable_runtime.js +295 -128
  101. data/lib/dommy/js/webidl_members.js +1 -1
  102. data/lib/dommy/js/webidl_signatures.js +35 -6
  103. data/lib/dommy/media_query_list.rb +1 -0
  104. data/lib/dommy/message_channel.rb +2 -0
  105. data/lib/dommy/mutation_observer.rb +25 -29
  106. data/lib/dommy/navigator.rb +4 -1
  107. data/lib/dommy/node.rb +4 -0
  108. data/lib/dommy/notification.rb +1 -0
  109. data/lib/dommy/promise.rb +12 -0
  110. data/lib/dommy/range.rb +20 -3
  111. data/lib/dommy/shadow_root.rb +25 -1
  112. data/lib/dommy/streams.rb +18 -0
  113. data/lib/dommy/style_declaration.rb +15 -4
  114. data/lib/dommy/text_codec.rb +7 -5
  115. data/lib/dommy/tree_walker.rb +68 -61
  116. data/lib/dommy/version.rb +1 -1
  117. data/lib/dommy/web_socket.rb +1 -0
  118. data/lib/dommy/window.rb +22 -4
  119. data/lib/dommy/worker.rb +1 -0
  120. data/lib/dommy/xml_http_request.rb +12 -2
  121. data/lib/dommy.rb +17 -2
  122. metadata +11 -3
@@ -0,0 +1,800 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "multibyte_overrides"
4
+
5
+ module Dommy
6
+ module Encodings
7
+ # The legacy multi-byte decoders (Shift_JIS, EUC-JP, ISO-2022-JP, Big5,
8
+ # EUC-KR, gb18030 — GBK's decoder is gb18030's), from the Encoding
9
+ # Standard's handlers: which byte a broken sequence gives back to the
10
+ # stream, and so how many U+FFFD it yields and what follows them, are the
11
+ # algorithm's. Only the index lookup is Ruby's: a pointer is turned back
12
+ # into the bytes that encode it, and Ruby's converter for the encoding whose
13
+ # table matches the spec's index decodes them.
14
+ #
15
+ # Spec: https://encoding.spec.whatwg.org/#legacy-multi-byte-chinese-(simplified)-encodings
16
+ class HandlerDecoder < Decoder
17
+ # Run the handler over `bytes`, then over end-of-queue when `flush`.
18
+ # The handler takes the byte (nil for end-of-queue), the queue it may
19
+ # restore bytes to, and the output, and returns :finished at the end.
20
+ #
21
+ # A restored byte is read before the rest of the input. While the
22
+ # handler holds nothing (#idle?), two shortcuts stand in for it, both
23
+ # exact: a run of ASCII bytes is a run of the same code points in every
24
+ # one of these encodings, so it is copied at once; and two bytes that
25
+ # start with a non-ASCII one decode, from the idle state, to what the
26
+ # handler made of them the first time (#pair_output), so a text's
27
+ # characters are looked up rather than run through the handler again.
28
+ def decode(bytes, flush:)
29
+ out = []
30
+ restored = []
31
+ input = bytes.b
32
+ position = 0
33
+ size = input.bytesize
34
+ loop do
35
+ if !restored.empty?
36
+ handle(restored.shift, restored, out)
37
+ elsif position < size
38
+ if idle?
39
+ stop = input.index(NON_ASCII, position) || size
40
+ if stop > position
41
+ out.concat(input.byteslice(position, stop - position).bytes)
42
+ position = stop
43
+ next
44
+ end
45
+ start = position
46
+ position = pairs(input, position, size, out)
47
+ next if position > start
48
+ end
49
+ byte = input.getbyte(position)
50
+ position += 1
51
+ handle(byte, restored, out)
52
+ else
53
+ break unless flush
54
+ break if handle(nil, restored, out) == :finished && restored.empty?
55
+ end
56
+ end
57
+ out
58
+ end
59
+
60
+ NON_ASCII = /[\x80-\xFF]/n
61
+ private_constant :NON_ASCII
62
+
63
+ private
64
+
65
+ # Whether the handler holds no partial sequence, so that the next ASCII
66
+ # byte is that code point. A stateful decoder whose ASCII means
67
+ # something else in some states answers false.
68
+ def idle? = false
69
+
70
+ def ascii?(byte) = byte && byte <= 0x7F
71
+
72
+ # From the idle state at `position`, append the output of each pair of
73
+ # bytes that starts with a non-ASCII one and that the handler, run on
74
+ # them from its idle state, leaves idle again (bytes it restores
75
+ # included) — remembered per class, so a text's characters are looked
76
+ # up rather than run through the handler again. Stops at an ASCII byte,
77
+ # the last byte, or a longer sequence (three or four bytes, left to the
78
+ # handler); not in fatal mode, where an error must raise where it is.
79
+ # Answers the position reached.
80
+ def pairs(input, position, size, out)
81
+ return position if @fatal
82
+
83
+ cache = self.class.pair_outputs
84
+ while position + 1 < size && (lead = input.getbyte(position)) > 0x7F
85
+ key = (lead << 8) | input.getbyte(position + 1)
86
+ output = cache[key]
87
+ output = cache[key] = self.class.new(false).__send__(:run_pair, lead, key & 0xFF) if output.nil?
88
+ break unless output
89
+
90
+ out.concat(output)
91
+ position += 2
92
+ end
93
+ position
94
+ end
95
+
96
+ # On a fresh decoder: handle the two bytes and whatever they restore,
97
+ # and answer the output if it ends idle, else false.
98
+ def run_pair(lead, trail)
99
+ out = []
100
+ restored = []
101
+ handle(lead, restored, out)
102
+ handle(trail, restored, out)
103
+ handle(restored.shift, restored, out) until restored.empty?
104
+ idle? ? out.freeze : false
105
+ end
106
+
107
+ class << self
108
+ def pair_outputs = (@pair_outputs ||= {})
109
+ end
110
+
111
+ # The code point Ruby's `encoding` decodes `bytes` to, or nil when they
112
+ # decode to nothing (or to more than one code point).
113
+ def self.lookup(encoding, bytes)
114
+ text = bytes.pack("C*").force_encoding(encoding).encode(::Encoding::UTF_8)
115
+ text.length == 1 ? text.ord : nil
116
+ rescue ::EncodingError
117
+ nil
118
+ end
119
+ end
120
+
121
+ # The spec's indexes. Each is read through the Ruby encoding whose table
122
+ # it nearly is, with the entries where the two differ taken from the
123
+ # spec's own index files (MULTIBYTE_INDEX_OVERRIDES, generated by
124
+ # script/build_encoding_tables.rb), and remembered.
125
+ module LegacyIndexes
126
+ CACHES = Hash.new { |h, k| h[k] = {} }
127
+ private_constant :CACHES
128
+
129
+ module_function
130
+
131
+ def jis0208(pointer) = lookup(:jis0208, pointer)
132
+ def jis0212(pointer) = lookup(:jis0212, pointer)
133
+ def big5(pointer) = lookup(:big5, pointer)
134
+ def euc_kr(pointer) = lookup(:euc_kr, pointer)
135
+ def gb18030(pointer) = lookup(:gb18030, pointer)
136
+
137
+ def lookup(name, pointer)
138
+ cache = CACHES[name]
139
+ return cache[pointer] if cache.key?(pointer)
140
+
141
+ overrides = MULTIBYTE_INDEX_OVERRIDES[name]
142
+ cache[pointer] =
143
+ if overrides.key?(pointer) then overrides[pointer]
144
+ elsif MULTIBYTE_INDEX_NULLS[name]&.any? { |range| range.cover?(pointer) } then nil
145
+ else ruby_lookup(name, pointer)
146
+ end
147
+ end
148
+
149
+ # The code point Ruby's table gives the bytes a pointer stands for —
150
+ # what the overrides are measured against.
151
+ def ruby_lookup(name, pointer)
152
+ case name
153
+ when :jis0208
154
+ # JIS X 0208 with the NEC and IBM extensions is Windows-31J: a
155
+ # pointer is its Shift_JIS byte pair.
156
+ leading, trailing = pointer.divmod(188)
157
+ bytes = [leading + (leading < 0x1F ? 0x81 : 0xC1), trailing + (trailing < 0x3F ? 0x40 : 0x41)]
158
+ HandlerDecoder.lookup(::Encoding::Windows_31J, bytes)
159
+ when :jis0212
160
+ leading, trailing = pointer.divmod(94)
161
+ HandlerDecoder.lookup(::Encoding::EUC_JP, [0x8F, leading + 0xA1, trailing + 0xA1])
162
+ when :big5
163
+ leading, trailing = pointer.divmod(157)
164
+ HandlerDecoder.lookup(::Encoding::Big5_HKSCS, [leading + 0x81, trailing + (trailing < 0x3F ? 0x40 : 0x62)])
165
+ when :euc_kr
166
+ leading, trailing = pointer.divmod(190)
167
+ HandlerDecoder.lookup(::Encoding::CP949, [leading + 0x81, trailing + 0x41])
168
+ when :gb18030
169
+ leading, trailing = pointer.divmod(190)
170
+ HandlerDecoder.lookup(::Encoding::GB18030, [leading + 0x81, trailing + (trailing < 0x3F ? 0x40 : 0x41)])
171
+ end
172
+ end
173
+
174
+ # How many pointers each index spans (its decoder's lead bytes times
175
+ # trail bytes), for the generator's comparison.
176
+ POINTER_COUNTS = {jis0208: 60 * 188, jis0212: 94 * 94, big5: 126 * 157, euc_kr: 126 * 190, gb18030: 126 * 190}.freeze
177
+
178
+ # The index gb18030 ranges code point for a four-byte pointer.
179
+ def gb18030_ranges(pointer)
180
+ return nil if (pointer > 39_419 && pointer < 189_000) || pointer > 1_237_575
181
+ return 0xE7C7 if pointer == 7457
182
+
183
+ cache = CACHES[:gb18030_ranges]
184
+ return cache[pointer] if cache.key?(pointer)
185
+
186
+ first, rest = pointer.divmod(12_600)
187
+ second, rest = rest.divmod(1260)
188
+ third, fourth = rest.divmod(10)
189
+ cache[pointer] = HandlerDecoder.lookup(::Encoding::GB18030, [first + 0x81, second + 0x30, third + 0x81, fourth + 0x30])
190
+ end
191
+ end
192
+
193
+ # Spec: https://encoding.spec.whatwg.org/#shift_jis-decoder
194
+ class ShiftJisDecoder < HandlerDecoder
195
+ private def idle? = @leading.zero?
196
+
197
+ def initialize(fatal)
198
+ super
199
+ @leading = 0x00
200
+ end
201
+
202
+ private
203
+
204
+ def handle(byte, queue, out)
205
+ if byte.nil?
206
+ return :finished if @leading.zero?
207
+
208
+ @leading = 0x00
209
+ return out << replacement!
210
+ end
211
+
212
+ unless @leading.zero?
213
+ leading = @leading
214
+ @leading = 0x00
215
+ pointer = nil
216
+ offset = byte < 0x7F ? 0x40 : 0x41
217
+ leading_offset = leading < 0xA0 ? 0x81 : 0xC1
218
+ pointer = (leading - leading_offset) * 188 + byte - offset if byte.between?(0x40, 0x7E) || byte.between?(0x80, 0xFC)
219
+ # Windows' end-user-defined characters, as the Private Use Area.
220
+ return out << (0xE000 - 8836 + pointer) if pointer&.between?(8836, 10_715)
221
+
222
+ code_point = pointer && LegacyIndexes.jis0208(pointer)
223
+ return out << code_point if code_point
224
+
225
+ queue.unshift(byte) if ascii?(byte)
226
+ return out << replacement!
227
+ end
228
+
229
+ if ascii?(byte) || byte == 0x80
230
+ out << byte
231
+ elsif byte.between?(0xA1, 0xDF)
232
+ out << (0xFF61 - 0xA1 + byte)
233
+ elsif byte.between?(0x81, 0x9F) || byte.between?(0xE0, 0xFC)
234
+ @leading = byte
235
+ else
236
+ out << replacement!
237
+ end
238
+ end
239
+ end
240
+
241
+ # Spec: https://encoding.spec.whatwg.org/#euc-jp-decoder
242
+ class EucJpDecoder < HandlerDecoder
243
+ private def idle? = @leading.zero?
244
+
245
+ def initialize(fatal)
246
+ super
247
+ @leading = 0x00
248
+ @jis0212 = false
249
+ end
250
+
251
+ private
252
+
253
+ def handle(byte, queue, out)
254
+ if byte.nil?
255
+ return :finished if @leading.zero?
256
+
257
+ @leading = 0x00
258
+ return out << replacement!
259
+ end
260
+
261
+ if @leading == 0x8E && byte.between?(0xA1, 0xDF)
262
+ @leading = 0x00
263
+ return out << (0xFF61 - 0xA1 + byte)
264
+ end
265
+ if @leading == 0x8F && byte.between?(0xA1, 0xFE)
266
+ @jis0212 = true
267
+ return @leading = byte
268
+ end
269
+
270
+ unless @leading.zero?
271
+ leading = @leading
272
+ @leading = 0x00
273
+ code_point = nil
274
+ if leading.between?(0xA1, 0xFE) && byte.between?(0xA1, 0xFE)
275
+ pointer = (leading - 0xA1) * 94 + byte - 0xA1
276
+ code_point = @jis0212 ? LegacyIndexes.jis0212(pointer) : LegacyIndexes.jis0208(pointer)
277
+ end
278
+ @jis0212 = false
279
+ return out << code_point if code_point
280
+
281
+ queue.unshift(byte) if ascii?(byte)
282
+ return out << replacement!
283
+ end
284
+
285
+ if ascii?(byte)
286
+ out << byte
287
+ elsif byte == 0x8E || byte == 0x8F || byte.between?(0xA1, 0xFE)
288
+ @leading = byte
289
+ else
290
+ out << replacement!
291
+ end
292
+ end
293
+ end
294
+
295
+ # Spec: https://encoding.spec.whatwg.org/#iso-2022-jp-decoder
296
+ class Iso2022JpDecoder < HandlerDecoder
297
+ def initialize(fatal)
298
+ super
299
+ @state = :ascii
300
+ @output_state = :ascii
301
+ @leading = 0x00
302
+ @output = false
303
+ end
304
+
305
+ private
306
+
307
+ def handle(byte, queue, out)
308
+ case @state
309
+ when :ascii, :roman, :katakana, :leading_byte
310
+ return @state = :escape_start if byte == 0x1B
311
+ return :finished if byte.nil?
312
+
313
+ @output = false
314
+ single_state(byte, out)
315
+ when :trailing_byte
316
+ if byte == 0x1B
317
+ @state = :escape_start
318
+ return out << replacement!
319
+ end
320
+ @state = :leading_byte
321
+ return out << replacement! unless byte&.between?(0x21, 0x7E)
322
+
323
+ out << (LegacyIndexes.jis0208((@leading - 0x21) * 94 + byte - 0x21) || replacement!)
324
+ when :escape_start
325
+ if byte == 0x24 || byte == 0x28
326
+ @leading = byte
327
+ return @state = :escape
328
+ end
329
+ queue.unshift(byte) unless byte.nil?
330
+ @output = false
331
+ @state = @output_state
332
+ out << replacement!
333
+ when :escape
334
+ escape(byte, queue, out)
335
+ end
336
+ end
337
+
338
+ def single_state(byte, out)
339
+ case @state
340
+ when :ascii
341
+ out << (byte <= 0x7F && byte != 0x0E && byte != 0x0F ? byte : replacement!)
342
+ when :roman
343
+ out << case byte
344
+ when 0x5C then 0xA5
345
+ when 0x7E then 0x203E
346
+ else byte <= 0x7F && byte != 0x0E && byte != 0x0F ? byte : replacement!
347
+ end
348
+ when :katakana
349
+ out << (byte.between?(0x21, 0x5F) ? 0xFF61 - 0x21 + byte : replacement!)
350
+ when :leading_byte
351
+ return out << replacement! unless byte.between?(0x21, 0x7E)
352
+
353
+ @leading = byte
354
+ @state = :trailing_byte
355
+ end
356
+ end
357
+
358
+ def escape(byte, queue, out)
359
+ leading = @leading
360
+ @leading = 0x00
361
+ state =
362
+ if leading == 0x28
363
+ {0x42 => :ascii, 0x4A => :roman, 0x49 => :katakana}[byte]
364
+ elsif leading == 0x24 && (byte == 0x40 || byte == 0x42)
365
+ :leading_byte
366
+ end
367
+ if state
368
+ @state = @output_state = state
369
+ output = @output
370
+ @output = true
371
+ # Two escapes in a row with nothing between them are an error.
372
+ return output ? out << replacement! : nil
373
+ end
374
+ byte.nil? ? queue.unshift(leading) : queue.unshift(leading, byte)
375
+ @output = false
376
+ @state = @output_state
377
+ out << replacement!
378
+ end
379
+ end
380
+
381
+ # Spec: https://encoding.spec.whatwg.org/#big5-decoder
382
+ class Big5Decoder < HandlerDecoder
383
+ private def idle? = @leading.zero?
384
+
385
+ # Pointers whose index entry is two code points.
386
+ PAIRS = {1133 => [0x00CA, 0x0304], 1135 => [0x00CA, 0x030C], 1164 => [0x00EA, 0x0304], 1166 => [0x00EA, 0x030C]}.freeze
387
+
388
+ def initialize(fatal)
389
+ super
390
+ @leading = 0x00
391
+ end
392
+
393
+ private
394
+
395
+ def handle(byte, queue, out)
396
+ if byte.nil?
397
+ return :finished if @leading.zero?
398
+
399
+ @leading = 0x00
400
+ return out << replacement!
401
+ end
402
+
403
+ unless @leading.zero?
404
+ leading = @leading
405
+ @leading = 0x00
406
+ offset = byte < 0x7F ? 0x40 : 0x62
407
+ pointer = (leading - 0x81) * 157 + (byte - offset) if byte.between?(0x40, 0x7E) || byte.between?(0xA1, 0xFE)
408
+ return out.concat(PAIRS[pointer]) if PAIRS.key?(pointer)
409
+
410
+ code_point = pointer && LegacyIndexes.big5(pointer)
411
+ return out << code_point if code_point
412
+
413
+ queue.unshift(byte) if ascii?(byte)
414
+ return out << replacement!
415
+ end
416
+
417
+ if ascii?(byte)
418
+ out << byte
419
+ elsif byte.between?(0x81, 0xFE)
420
+ @leading = byte
421
+ else
422
+ out << replacement!
423
+ end
424
+ end
425
+ end
426
+
427
+ # Spec: https://encoding.spec.whatwg.org/#euc-kr-decoder
428
+ class EucKrDecoder < HandlerDecoder
429
+ private def idle? = @leading.zero?
430
+
431
+ def initialize(fatal)
432
+ super
433
+ @leading = 0x00
434
+ end
435
+
436
+ private
437
+
438
+ def handle(byte, queue, out)
439
+ if byte.nil?
440
+ return :finished if @leading.zero?
441
+
442
+ @leading = 0x00
443
+ return out << replacement!
444
+ end
445
+
446
+ unless @leading.zero?
447
+ leading = @leading
448
+ @leading = 0x00
449
+ pointer = (leading - 0x81) * 190 + (byte - 0x41) if byte.between?(0x41, 0xFE)
450
+ code_point = pointer && LegacyIndexes.euc_kr(pointer)
451
+ return out << code_point if code_point
452
+
453
+ queue.unshift(byte) if ascii?(byte)
454
+ return out << replacement!
455
+ end
456
+
457
+ if ascii?(byte)
458
+ out << byte
459
+ elsif byte.between?(0x81, 0xFE)
460
+ @leading = byte
461
+ else
462
+ out << replacement!
463
+ end
464
+ end
465
+ end
466
+
467
+ # Spec: https://encoding.spec.whatwg.org/#gb18030-decoder
468
+ class Gb18030Decoder < HandlerDecoder
469
+ private def idle? = @first.zero? && @second.zero? && @third.zero?
470
+
471
+ def initialize(fatal)
472
+ super
473
+ @first = @second = @third = 0x00
474
+ end
475
+
476
+ private
477
+
478
+ def handle(byte, queue, out)
479
+ if byte.nil?
480
+ return :finished if @first.zero? && @second.zero? && @third.zero?
481
+
482
+ @first = @second = @third = 0x00
483
+ return out << replacement!
484
+ end
485
+
486
+ unless @third.zero?
487
+ unless byte.between?(0x30, 0x39)
488
+ queue.unshift(@second, @third, byte)
489
+ @first = @second = @third = 0x00
490
+ return out << replacement!
491
+ end
492
+ pointer = ((@first - 0x81) * 12_600) + ((@second - 0x30) * 1260) + ((@third - 0x81) * 10) + byte - 0x30
493
+ @first = @second = @third = 0x00
494
+ return out << (LegacyIndexes.gb18030_ranges(pointer) || replacement!)
495
+ end
496
+
497
+ unless @second.zero?
498
+ return @third = byte if byte.between?(0x81, 0xFE)
499
+
500
+ queue.unshift(@second, byte)
501
+ @first = @second = 0x00
502
+ return out << replacement!
503
+ end
504
+
505
+ unless @first.zero?
506
+ return @second = byte if byte.between?(0x30, 0x39)
507
+
508
+ leading = @first
509
+ @first = 0x00
510
+ offset = byte < 0x7F ? 0x40 : 0x41
511
+ pointer = (leading - 0x81) * 190 + (byte - offset) if byte.between?(0x40, 0x7E) || byte.between?(0x80, 0xFE)
512
+ code_point = pointer && LegacyIndexes.gb18030(pointer)
513
+ return out << code_point if code_point
514
+
515
+ queue.unshift(byte) if ascii?(byte)
516
+ return out << replacement!
517
+ end
518
+
519
+ if ascii?(byte)
520
+ out << byte
521
+ elsif byte == 0x80
522
+ out << 0x20AC
523
+ elsif byte.between?(0x81, 0xFE)
524
+ @first = byte
525
+ else
526
+ out << replacement!
527
+ end
528
+ end
529
+ end
530
+ end
531
+ end
532
+
533
+ module Dommy
534
+ module Encodings
535
+ # The Encoding Standard's Big5 and ISO-2022-JP encoders, which choose
536
+ # pointers (and, for ISO-2022-JP, escape sequences) as Ruby's converters
537
+ # do not. `encode` returns the bytes, with each code point the encoding
538
+ # cannot represent yielded to the block, which answers the bytes to put
539
+ # in its place (the URL parser's percent-encoded `&#N;`).
540
+ #
541
+ # Spec: https://encoding.spec.whatwg.org/#big5-encoder
542
+ module LegacyEncoders
543
+ module_function
544
+
545
+ ENCODERS = {"Big5" => :big5, "ISO-2022-JP" => :iso_2022_jp, "Shift_JIS" => :shift_jis,
546
+ "EUC-JP" => :euc_jp, "EUC-KR" => :euc_kr, "GBK" => :gbk, "gb18030" => :gb18030}.freeze
547
+ private_constant :ENCODERS
548
+
549
+ def handles?(name) = ENCODERS.key?(name)
550
+
551
+ # `string` encoded in `name` as bytes; a code point the encoding cannot
552
+ # represent is the block's bytes for it.
553
+ def encode(name, string, &on_error)
554
+ __send__(ENCODERS.fetch(name), string, &on_error)
555
+ end
556
+
557
+ # Spec: https://encoding.spec.whatwg.org/#shift_jis-encoder
558
+ def shift_jis(string)
559
+ out = []
560
+ string.each_codepoint do |code_point|
561
+ next out << code_point if code_point <= 0x80
562
+ next out << 0x5C if code_point == 0xA5
563
+ next out << 0x7E if code_point == 0x203E
564
+ next out << code_point - 0xFF61 + 0xA1 if code_point.between?(0xFF61, 0xFF9F)
565
+
566
+ pointer = shift_jis_pointer(code_point == 0x2212 ? 0xFF0D : code_point)
567
+ next out.concat(yield(code_point)) if pointer.nil?
568
+
569
+ leading, trailing = pointer.divmod(188)
570
+ out << leading + (leading < 0x1F ? 0x81 : 0xC1) << trailing + (trailing < 0x3F ? 0x40 : 0x41)
571
+ end
572
+ out
573
+ end
574
+
575
+ # Spec: https://encoding.spec.whatwg.org/#euc-jp-encoder
576
+ def euc_jp(string)
577
+ out = []
578
+ string.each_codepoint do |code_point|
579
+ next out << code_point if code_point <= 0x7F
580
+ next out << 0x5C if code_point == 0xA5
581
+ next out << 0x7E if code_point == 0x203E
582
+ next out << 0x8E << code_point - 0xFF61 + 0xA1 if code_point.between?(0xFF61, 0xFF9F)
583
+
584
+ pointer = jis0208_first_pointer(code_point == 0x2212 ? 0xFF0D : code_point)
585
+ next out.concat(yield(code_point)) if pointer.nil?
586
+
587
+ leading, trailing = pointer.divmod(94)
588
+ out << leading + 0xA1 << trailing + 0xA1
589
+ end
590
+ out
591
+ end
592
+
593
+ # Spec: https://encoding.spec.whatwg.org/#euc-kr-encoder
594
+ def euc_kr(string)
595
+ out = []
596
+ string.each_codepoint do |code_point|
597
+ next out << code_point if code_point <= 0x7F
598
+
599
+ pointer = euc_kr_pointer(code_point)
600
+ next out.concat(yield(code_point)) if pointer.nil?
601
+
602
+ leading, trailing = pointer.divmod(190)
603
+ out << leading + 0x81 << trailing + 0x41
604
+ end
605
+ out
606
+ end
607
+
608
+ def gbk(string, &) = gb18030(string, gbk: true, &)
609
+
610
+ # The private-use code points gb18030 encodes as two bytes ahead of the
611
+ # index: what the index had for those pointers before GB18030-2022.
612
+ GB18030_PRIVATE_USE_PAIRS = {
613
+ 0xE78D => [0xA6, 0xD9], 0xE78E => [0xA6, 0xDA], 0xE78F => [0xA6, 0xDB], 0xE790 => [0xA6, 0xDC],
614
+ 0xE791 => [0xA6, 0xDD], 0xE792 => [0xA6, 0xDE], 0xE793 => [0xA6, 0xDF], 0xE794 => [0xA6, 0xEC],
615
+ 0xE795 => [0xA6, 0xED], 0xE796 => [0xA6, 0xF3], 0xE81E => [0xFE, 0x59], 0xE826 => [0xFE, 0x61],
616
+ 0xE82B => [0xFE, 0x66], 0xE82C => [0xFE, 0x67], 0xE832 => [0xFE, 0x6D], 0xE843 => [0xFE, 0x7E],
617
+ 0xE854 => [0xFE, 0x90], 0xE864 => [0xFE, 0xA0]
618
+ }.freeze
619
+ private_constant :GB18030_PRIVATE_USE_PAIRS
620
+
621
+ # Spec: https://encoding.spec.whatwg.org/#gb18030-encoder
622
+ def gb18030(string, gbk: false)
623
+ out = []
624
+ string.each_codepoint do |code_point|
625
+ next out << code_point if code_point <= 0x7F
626
+ next out.concat(yield(code_point)) if code_point == 0xE5E5
627
+ next out << 0x80 if gbk && code_point == 0x20AC
628
+ next out.concat(GB18030_PRIVATE_USE_PAIRS[code_point]) if GB18030_PRIVATE_USE_PAIRS.key?(code_point)
629
+
630
+ if (pointer = gb18030_pointer(code_point))
631
+ leading, trailing = pointer.divmod(190)
632
+ next out << leading + 0x81 << trailing + (trailing < 0x3F ? 0x40 : 0x41)
633
+ end
634
+ bytes = gbk ? nil : gb18030_four_bytes(code_point)
635
+ next out.concat(yield(code_point)) if bytes.nil?
636
+
637
+ out.concat(bytes)
638
+ end
639
+ out
640
+ end
641
+
642
+ # The index gb18030 pointer: the first pointer for a code point.
643
+ def gb18030_pointer(code_point)
644
+ (@gb18030_pointers ||= first_pointers(:gb18030, 0...LegacyIndexes::POINTER_COUNTS[:gb18030]))[code_point]
645
+ end
646
+
647
+ # The four bytes of a code point's index gb18030 ranges pointer. The
648
+ # ranges are Ruby's GB18030's four-byte sequences (the decoder reads
649
+ # them back the same way), but for U+E7C7, whose pointer is 7457.
650
+ def gb18030_four_bytes(code_point)
651
+ return [0x81, 0x35, 0xF4, 0x37] if code_point == 0xE7C7
652
+
653
+ bytes = code_point.chr(::Encoding::UTF_8).encode(::Encoding::GB18030).bytes
654
+ bytes if bytes.length == 4
655
+ rescue ::EncodingError
656
+ nil
657
+ end
658
+
659
+ def big5(string)
660
+ out = []
661
+ string.each_char do |char|
662
+ code_point = char.ord
663
+ next out << code_point if code_point <= 0x7F
664
+
665
+ pointer = big5_pointer(code_point)
666
+ next out.concat(yield(code_point)) if pointer.nil?
667
+
668
+ leading, trailing = pointer.divmod(157)
669
+ out << leading + 0x81 << trailing + (trailing < 0x3F ? 0x40 : 0x62)
670
+ end
671
+ out
672
+ end
673
+
674
+ # Spec: https://encoding.spec.whatwg.org/#iso-2022-jp-encoder
675
+ def iso_2022_jp(string)
676
+ out = []
677
+ state = :ascii
678
+ queue = string.codepoints
679
+ until queue.empty?
680
+ code_point = queue.shift
681
+ if [0x0E, 0x0F, 0x1B].include?(code_point) && state != :jis0208
682
+ out.concat(yield(0xFFFD))
683
+ next
684
+ end
685
+ if state == :ascii && code_point <= 0x7F
686
+ out << code_point
687
+ next
688
+ end
689
+ if state == :roman && ((code_point <= 0x7F && code_point != 0x5C && code_point != 0x7E) || code_point == 0xA5 || code_point == 0x203E)
690
+ out << (code_point == 0xA5 ? 0x5C : (code_point == 0x203E ? 0x7E : code_point))
691
+ next
692
+ end
693
+ if code_point <= 0x7F
694
+ queue.unshift(code_point)
695
+ state = :ascii
696
+ out << 0x1B << 0x28 << 0x42
697
+ next
698
+ end
699
+ if code_point == 0xA5 || code_point == 0x203E
700
+ queue.unshift(code_point)
701
+ state = :roman
702
+ out << 0x1B << 0x28 << 0x4A
703
+ next
704
+ end
705
+ original = code_point
706
+ code_point = 0xFF0D if code_point == 0x2212
707
+ code_point = katakana(code_point) if code_point.between?(0xFF61, 0xFF9F)
708
+ pointer = jis0208_pointer(code_point)
709
+ if pointer.nil?
710
+ if state == :jis0208
711
+ queue.unshift(original)
712
+ state = :ascii
713
+ out << 0x1B << 0x28 << 0x42
714
+ else
715
+ out.concat(yield(original))
716
+ end
717
+ next
718
+ end
719
+ if state != :jis0208
720
+ queue.unshift(original)
721
+ state = :jis0208
722
+ out << 0x1B << 0x24 << 0x42
723
+ next
724
+ end
725
+ leading, trailing = pointer.divmod(94)
726
+ out << leading + 0x21 << trailing + 0x21
727
+ end
728
+ out << 0x1B << 0x28 << 0x42 unless state == :ascii
729
+ out
730
+ end
731
+
732
+ # index ISO-2022-JP katakana: halfwidth to fullwidth katakana, as NFKC
733
+ # maps them, but with the voiced sound marks as their spacing forms.
734
+ def katakana(code_point)
735
+ return 0x309B if code_point == 0xFF9E
736
+ return 0x309C if code_point == 0xFF9F
737
+
738
+ code_point.chr(::Encoding::UTF_8).unicode_normalize(:nfkc).ord
739
+ end
740
+
741
+ BIG5_LAST_POINTER = [0x2550, 0x255E, 0x2561, 0x256A, 0x5341, 0x5345].freeze
742
+ private_constant :BIG5_LAST_POINTER
743
+
744
+ # The index Big5 pointer: Big5 without the HKSCS extensions below 0xA1,
745
+ # the first pointer for a code point, but the last for six duplicates.
746
+ def big5_pointer(code_point)
747
+ @big5_pointers ||= begin
748
+ first = {}
749
+ last = {}
750
+ ((0xA1 - 0x81) * 157...((0xFE - 0x81 + 1) * 157)).each do |pointer|
751
+ mapped = LegacyIndexes.big5(pointer)
752
+ next unless mapped
753
+
754
+ first[mapped] ||= pointer
755
+ last[mapped] = pointer
756
+ end
757
+ BIG5_LAST_POINTER.each { |cp| first[cp] = last[cp] if last[cp] }
758
+ first
759
+ end
760
+ @big5_pointers[code_point]
761
+ end
762
+
763
+ # The first pointer for each code point of `index` over `pointers`.
764
+ def first_pointers(index, pointers)
765
+ pointers.each_with_object({}) do |pointer, map|
766
+ mapped = LegacyIndexes.public_send(index, pointer)
767
+ map[mapped] ||= pointer if mapped
768
+ end
769
+ end
770
+
771
+ # The index jis0208 pointer over the whole index, as EUC-JP's encoder
772
+ # asks for it.
773
+ def jis0208_first_pointer(code_point)
774
+ (@jis0208_first_pointers ||= first_pointers(:jis0208, 0...LegacyIndexes::POINTER_COUNTS[:jis0208]))[code_point]
775
+ end
776
+
777
+ # The index Shift_JIS pointer: index jis0208 without its pointers 8272
778
+ # to 8835 (the NEC selection of IBM extensions, which the IBM rows
779
+ # repeat), so those characters encode as their IBM bytes.
780
+ def shift_jis_pointer(code_point)
781
+ @shift_jis_pointers ||= first_pointers(:jis0208, (0...LegacyIndexes::POINTER_COUNTS[:jis0208]).reject { |pointer| pointer.between?(8272, 8835) })
782
+ @shift_jis_pointers[code_point]
783
+ end
784
+
785
+ # The index EUC-KR pointer: the first pointer for a code point.
786
+ def euc_kr_pointer(code_point)
787
+ (@euc_kr_pointers ||= first_pointers(:euc_kr, 0...LegacyIndexes::POINTER_COUNTS[:euc_kr]))[code_point]
788
+ end
789
+
790
+ # The index jis0208 pointer: the first pointer for a code point.
791
+ def jis0208_pointer(code_point)
792
+ @jis0208_pointers ||= (0...(94 * 94)).each_with_object({}) do |pointer, map|
793
+ mapped = LegacyIndexes.jis0208(pointer)
794
+ map[mapped] ||= pointer if mapped
795
+ end
796
+ @jis0208_pointers[code_point]
797
+ end
798
+ end
799
+ end
800
+ end