| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // The following segment is not only used in the generating script, but also | |
| // copied to the generated package. | |
| /*@@@ SHARED SEGMENT START @@@*/ | |
| /// Used in the final mapping indicating the logical key should be derived from | |
| /// KeyboardEvent.keyCode. | |
| /// | |
| /// This value is chosen because it's a printable character within EASCII that | |
| /// will never be mapped to (checked in the marshalling algorithm). | |
| const int kUseKeyCode = 0xFF; | |
| /// Used in the final mapping indicating the event key is 'Dead', the dead key. | |
| final String _kUseDead = String.fromCharCode(0xFE); | |
| /// The KeyboardEvent.key for a dead key. | |
| const String _kEventKeyDead = 'Dead'; | |
| /// A map of all goals from the scan codes to their mapped value in US layout. | |
| const Map<String, String> kLayoutGoals = <String, String>{ | |
| 'KeyA': 'a', | |
| 'KeyB': 'b', | |
| 'KeyC': 'c', | |
| 'KeyD': 'd', | |
| 'KeyE': 'e', | |
| 'KeyF': 'f', | |
| 'KeyG': 'g', | |
| 'KeyH': 'h', | |
| 'KeyI': 'i', | |
| 'KeyJ': 'j', | |
| 'KeyK': 'k', | |
| 'KeyL': 'l', | |
| 'KeyM': 'm', | |
| 'KeyN': 'n', | |
| 'KeyO': 'o', | |
| 'KeyP': 'p', | |
| 'KeyQ': 'q', | |
| 'KeyR': 'r', | |
| 'KeyS': 's', | |
| 'KeyT': 't', | |
| 'KeyU': 'u', | |
| 'KeyV': 'v', | |
| 'KeyW': 'w', | |
| 'KeyX': 'x', | |
| 'KeyY': 'y', | |
| 'KeyZ': 'z', | |
| 'Digit1': '1', | |
| 'Digit2': '2', | |
| 'Digit3': '3', | |
| 'Digit4': '4', | |
| 'Digit5': '5', | |
| 'Digit6': '6', | |
| 'Digit7': '7', | |
| 'Digit8': '8', | |
| 'Digit9': '9', | |
| 'Digit0': '0', | |
| 'Minus': '-', | |
| 'Equal': '=', | |
| 'BracketLeft': '[', | |
| 'BracketRight': ']', | |
| 'Backslash': r'\', | |
| 'Semicolon': ';', | |
| 'Quote': "'", | |
| 'Backquote': '`', | |
| 'Comma': ',', | |
| 'Period': '.', | |
| 'Slash': '/', | |
| }; | |
| final int _kLowerA = 'a'.codeUnitAt(0); | |
| final int _kUpperA = 'A'.codeUnitAt(0); | |
| final int _kLowerZ = 'z'.codeUnitAt(0); | |
| final int _kUpperZ = 'Z'.codeUnitAt(0); | |
| bool _isAscii(int charCode) { | |
| // 0x20 is the first printable character in ASCII. | |
| return charCode >= 0x20 && charCode <= 0x7F; | |
| } | |
| /// Returns whether the `char` is a single character of a letter or a digit. | |
| bool isLetter(int charCode) { | |
| return (charCode >= _kLowerA && charCode <= _kLowerZ) | |
| || (charCode >= _kUpperA && charCode <= _kUpperZ); | |
| } | |
| /// A set of rules that can derive a large number of logical keys simply from | |
| /// the event's code and key. | |
| /// | |
| /// This greatly reduces the entries needed in the final mapping. | |
| int? heuristicMapper(String code, String key) { | |
| // Digit code: return the digit by event code. | |
| if (code.startsWith('Digit')) { | |
| assert(code.length == 6); | |
| return code.codeUnitAt(5); // The character immediately after 'Digit' | |
| } | |
| final int charCode = key.codeUnitAt(0); | |
| // Non-ascii: return the goal (i.e. US mapping by event code). | |
| if (key.length > 1 || !_isAscii(charCode)) { | |
| return kLayoutGoals[code]?.codeUnitAt(0); | |
| } | |
| // Letter key: return the event key letter. | |
| if (isLetter(charCode)) { | |
| return key.toLowerCase().codeUnitAt(0); | |
| } | |
| return null; | |
| } | |
| // Maps an integer to a printable EASCII character by adding it to this value. | |
| // | |
| // We could've chosen 0x20, the first printable character, for a slightly bigger | |
| // range, but it's prettier this way and sufficient. | |
| final int _kMarshallIntBase = '0'.codeUnitAt(0); | |
| class _StringStream { | |
| _StringStream(this._data) : _offset = 0; | |
| final String _data; | |
| final Map<int, String> _goalToEventCode = Map<int, String>.fromEntries( | |
| kLayoutGoals | |
| .entries | |
| .map((MapEntry<String, String> beforeEntry) => | |
| MapEntry<int, String>(beforeEntry.value.codeUnitAt(0), beforeEntry.key)) | |
| ); | |
| int get offest => _offset; | |
| int _offset; | |
| int readIntAsVerbatim() { | |
| final int result = _data.codeUnitAt(_offset); | |
| _offset += 1; | |
| assert(result >= _kMarshallIntBase); | |
| return result - _kMarshallIntBase; | |
| } | |
| int readIntAsChar() { | |
| final int result = _data.codeUnitAt(_offset); | |
| _offset += 1; | |
| return result; | |
| } | |
| String readEventKey() { | |
| final String char = String.fromCharCode(readIntAsChar()); | |
| if (char == _kUseDead) { | |
| return _kEventKeyDead; | |
| } else { | |
| return char; | |
| } | |
| } | |
| String readEventCode() { | |
| final int charCode = _data.codeUnitAt(_offset); | |
| _offset += 1; | |
| return _goalToEventCode[charCode]!; | |
| } | |
| } | |
| Map<String, int> _unmarshallCodeMap(_StringStream stream) { | |
| final int entryNum = stream.readIntAsVerbatim(); | |
| return <String, int>{ | |
| for (int i = 0; i < entryNum; i++) | |
| stream.readEventKey(): stream.readIntAsChar(), | |
| }; | |
| } | |
| /// Decode a key mapping data out of the string. | |
| Map<String, Map<String, int>> unmarshallMappingData(String compressed) { | |
| final _StringStream stream = _StringStream(compressed); | |
| final int eventCodeNum = stream.readIntAsVerbatim(); | |
| return <String, Map<String, int>>{ | |
| for (int i = 0; i < eventCodeNum; i++) | |
| stream.readEventCode() : _unmarshallCodeMap(stream), | |
| }; | |
| } | |
| /*@@@ SHARED SEGMENT END @@@*/ | |
| /// Whether the given charCode is a ASCII letter. | |
| bool isLetterChar(int charCode) { | |
| return (charCode >= _kLowerA && charCode <= _kLowerZ) | |
| || (charCode >= _kUpperA && charCode <= _kUpperZ); | |
| } | |
| bool _isPrintableEascii(int charCode) { | |
| return charCode >= 0x20 && charCode <= 0xFF; | |
| } | |
| typedef _ForEachAction<V> = void Function(String key, V value); | |
| void _sortedForEach<V>(Map<String, V> map, _ForEachAction<V> action) { | |
| map | |
| .entries | |
| .toList() | |
| ..sort((MapEntry<String, V> a, MapEntry<String, V> b) => a.key.compareTo(b.key)) | |
| ..forEach((MapEntry<String, V> entry) { | |
| action(entry.key, entry.value); | |
| }); | |
| } | |
| // Encode a small integer as a character by its value. | |
| // | |
| // For example, 0x48 is encoded as '0'. This means that values within 0x0 - 0x19 | |
| // or greater than 0xFF are forbidden. | |
| void _marshallIntAsChar(StringBuffer builder, int value) { | |
| assert(_isPrintableEascii(value), '$value'); | |
| builder.writeCharCode(value); | |
| } | |
| const int _kMarshallIntEnd = 0xFF; // The last printable EASCII. | |
| // Encode a small integer as a character based on a certain printable codepoint. | |
| // | |
| // For example, 0x0 is encoded as '0', and 0x1 is encoded as '1'. This function | |
| // allows smaller values than _marshallIntAsChar. | |
| void _marshallIntAsVerbatim(StringBuffer builder, int value) { | |
| final int result = value + _kMarshallIntBase; | |
| assert(result <= _kMarshallIntEnd); | |
| builder.writeCharCode(result); | |
| } | |
| void _marshallEventCode(StringBuffer builder, String value) { | |
| // Instead of recording the entire eventCode, since the eventCode is mapped | |
| // 1-to-1 to a character in kLayoutGoals, we record the goal instead. | |
| final String char = kLayoutGoals[value]!; | |
| builder.write(char); | |
| } | |
| void _marshallEventKey(StringBuffer builder, String value) { | |
| if (value == _kEventKeyDead) { | |
| builder.write(_kUseDead); | |
| } else { | |
| assert(value.length == 1, value); | |
| assert(value != _kUseDead); | |
| builder.write(value); | |
| } | |
| } | |
| /// Encode a key mapping data into a list of strings. | |
| /// | |
| /// The list of strings should be used concatenated, but is returned this way | |
| /// for aesthetic purposes (one entry per line). | |
| /// | |
| /// The algorithm aims at encoding the map directly into a printable string | |
| /// (instead of a binary stream converted by base64). Some characters in the | |
| /// string can be multi-byte, which means the decoder should parse the string | |
| /// using substr instead of as a binary stream. | |
| List<String> marshallMappingData(Map<String, Map<String, int>> mappingData) { | |
| final StringBuffer builder = StringBuffer(); | |
| _marshallIntAsVerbatim(builder, mappingData.length); | |
| _sortedForEach(mappingData, (String eventCode, Map<String, int> codeMap) { | |
| builder.write('\n'); | |
| _marshallEventCode(builder, eventCode); | |
| _marshallIntAsVerbatim(builder, codeMap.length); | |
| _sortedForEach(codeMap, (String eventKey, int logicalKey) { | |
| _marshallEventKey(builder, eventKey); | |
| _marshallIntAsChar(builder, logicalKey); | |
| }); | |
| }); | |
| return builder.toString().split('\n'); | |
| } |