mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-10 07:57:28 +00:00
chore(windows): Merge branch 'master' into fix/windows/7870/add-is-text-selected-flag-api
# Keyman Conventional Commit suggestions:
#
# - Link to a Sentry issue with git trailer:
# Fixes: _MODULE_-_ID_
# - Give credit to co-authors:
# Co-authored-by: _Name_ <_email_>
# - Use imperative, present tense ('attach' not 'attaches', 'attached' etc)
# - Don't include a period at the end of the title
# - Always include a blank line before trailers
# - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes
This commit is contained in:
commit
1bcafb1a72
256 changed files with 10439 additions and 1076 deletions
130
HISTORY.md
130
HISTORY.md
|
|
@ -1,5 +1,120 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 18.0.175 alpha 2025-01-22
|
||||
|
||||
* feat(windows): Fixes check now to use SM and LastUpdateCheckTime key on first run. (#12922)
|
||||
* chore(developer): validate keyboard and model IDs in kmc-generate (#12958)
|
||||
* fix(developer): use correct terms in kmc-generate lexical model help (#12960)
|
||||
* fix(developer): use 1.0.0 as default for ldml keyboards in kmc-generate (#12962)
|
||||
* chore: adds h1 tags (#12967)
|
||||
* fix(web): prevent warning that occurs when multitapping longpressable modifier keys (#12964)
|
||||
* chore(developer): add test for basic transform to kmc-generate (#12966)
|
||||
* fix(android/engine): Revert how keyboard picker menu launches (#12986)
|
||||
|
||||
## 18.0.174 alpha 2025-01-21
|
||||
|
||||
* chore(developer): validate targets parameter in kmc-generate (#12955)
|
||||
* fix(developer): support `&displayMap` font in web debugger (#12929)
|
||||
|
||||
## 18.0.173 alpha 2025-01-20
|
||||
|
||||
* feat(developer): rewrite font data in .kvk from package metadata (#12949)
|
||||
* docs(ios): document gestures within iOS app help (#12900)
|
||||
* fix(developer): include developer-utils deps in server build (#12951)
|
||||
|
||||
## 18.0.172 alpha 2025-01-19
|
||||
|
||||
* chore(common): Add 17.0.333 to version history (#12926)
|
||||
|
||||
## 18.0.171 alpha 2025-01-18
|
||||
|
||||
* fix(common/web): add StrsItem.isEqual() method (#12868)
|
||||
* fix(common/web): handle invalid order and tertiary arguments to ElementString.fromString() (#12882)
|
||||
|
||||
## 18.0.170 alpha 2025-01-17
|
||||
|
||||
* chore(linux): update copyright year (#12918)
|
||||
* fix(web): patch unit test with execution dependent on autocorrect state (#12940)
|
||||
* test(common/web): unit tests for element-string (#12811)
|
||||
* chore(linux): Update debian changelog (#12917)
|
||||
|
||||
## 18.0.169 alpha 2025-01-17
|
||||
|
||||
* chore(windows): remove `postinstall` state from mermaid diagram (#12923)
|
||||
|
||||
## 18.0.168 alpha 2025-01-16
|
||||
|
||||
* chore: update macOS environment-variable shell script (#12878)
|
||||
* fix(android): use main looper to dispatch key events when OSK is hidden (#12871)
|
||||
* chore(web): integrates predictive-text builds to top level script, reconnects headless tests (#12866)
|
||||
* chore(ios): Update crowdin strings for Khmer (#12910)
|
||||
* chore(windows): merge master epic windows updates (#12904)
|
||||
* fix(developer): detect invalid key ids in touch layout files (#12895)
|
||||
* chore: use GitHub PR titles when writing HISTORY.md (#12907)
|
||||
* fix(developer): filter incorrect fonts out of .keyboard_info (#12909)
|
||||
* change(web): make 'keep' transform pattern match standard suggestion pattern by including the prefix string (#12906)
|
||||
* chore: Add docker images for building the different platforms (#11397)
|
||||
|
||||
## 18.0.167 alpha 2025-01-15
|
||||
|
||||
* chore: changes to use https and removes anchor in docs (#12838)
|
||||
* fix(developer): ensure license parameter is required and add tests (#12879)
|
||||
* fix(web): Improve predictive-text handling of text wordbreaking transitions (#12864)
|
||||
* chore(common): Update crowdin strings for Spanish (#12886)
|
||||
* docs(developer): Clarify square brackets in `U_####[_####]` shortcut (#12876)
|
||||
* feat: add builder tab-completion script (#12296)
|
||||
* docs(android): document longpress up-flick to quick-display shortcut (#12899)
|
||||
* docs(web): fix `adding-keyboards.md` (#12890)
|
||||
|
||||
## 18.0.166 alpha 2025-01-10
|
||||
|
||||
* fix(web): numpad + and - with zoom shortcut use (#12865)
|
||||
|
||||
## 18.0.165 alpha 2025-01-09
|
||||
|
||||
* feat(web): add KeymanWeb API for auto-correct (#12857)
|
||||
* chore(web): disable auto-correct (#12858)
|
||||
* refactor(web): modernize `web/ci.sh` script (#12862)
|
||||
|
||||
## 18.0.164 alpha 2025-01-08
|
||||
|
||||
* fix(windows): add language and close button fit inside pop-window (#12855)
|
||||
* chore(windows): add bcp47 code and tips to diagnostic (#12843)
|
||||
* fix(android/engine): Cleanup KMManager API keys and calls (#12861)
|
||||
* docs(developer,mac): add playable video links to the help documentation (#12828)
|
||||
|
||||
## 18.0.163 alpha 2025-01-06
|
||||
|
||||
* docs: clarify that sections in KMX file might not be aligned (#12856)
|
||||
|
||||
## 18.0.162 alpha 2024-12-19
|
||||
|
||||
* fix(mac): standardize interaction between OSK and physical keyboard (#12836)
|
||||
|
||||
## 18.0.161 alpha 2024-12-17
|
||||
|
||||
* fix(mac): OSK layers displayed consistently for hardware and OSK modifiers (#12829)
|
||||
|
||||
## 18.0.160 alpha 2024-12-16
|
||||
|
||||
* feat(ios): configurable keyboard height (#12571)
|
||||
* chore(common/web): remove .c8rc.json from types and merge exclusions into package.json (#12813)
|
||||
* fix(android/engine): Initialize index when resuming KeyboardPicker (#12832)
|
||||
|
||||
## 18.0.159 alpha 2024-12-13
|
||||
|
||||
* chore(core): remove meson warnings for wasm builds (#12827)
|
||||
* chore(linux): Update debian changelog (#12023)
|
||||
|
||||
## 18.0.158 alpha 2024-12-11
|
||||
|
||||
* fix(linux): pushing of updated changelog branch (#12818)
|
||||
* fix(linux): work around Lintian errors (#12815)
|
||||
|
||||
## 18.0.157 alpha 2024-12-10
|
||||
|
||||
* test(common/web): unit tests for kvk-file-writer (#12734)
|
||||
|
||||
## 18.0.156 alpha 2024-12-09
|
||||
|
||||
* fix(developer): remove platforms from kmc-generate LM readme (#12803)
|
||||
|
|
@ -153,7 +268,7 @@
|
|||
|
||||
## 18.0.134 alpha 2024-11-04
|
||||
|
||||
* (#12606)
|
||||
* fix(developer): ldml don't allow a uset as right-hand-side variable (#12606)
|
||||
|
||||
## 18.0.133 alpha 2024-11-01
|
||||
|
||||
|
|
@ -876,7 +991,7 @@
|
|||
## 18.0.41 alpha 2024-05-22
|
||||
|
||||
* fix(developer): handle `KM_CORE_IT_INVALIDATE_CONTEXT` in debugger (#11488)
|
||||
* chore(linux): Trigger GHA packaging for stable builds :cherries: (#11495)
|
||||
* chore(linux): Trigger GHA packaging for stable builds (#11495)
|
||||
* chore(android,mac,windows): Update crowdin strings for DE (#11497)
|
||||
* feat(web): custom infrastructure for @web/test-runner use (#11403)
|
||||
* chore(web): conversion of lm-worker browser-test for @web/test-runner use (#11404)
|
||||
|
|
@ -1072,6 +1187,17 @@
|
|||
* chore(common): move to 18.0 alpha (#10713)
|
||||
* chore: move to 18.0 alpha
|
||||
|
||||
## 17.0.333 stable 2025-01-16
|
||||
|
||||
* fix(core): permanently disable logging (#12674)
|
||||
* fix(linux): pushing of updated changelog branch (#12819)
|
||||
* fix(linux): work around Lintian errors (#12817)
|
||||
* fix(core): implement ldml_processor::get_key_list() (#12816)
|
||||
* chore(linux): Update debian changelog (#12022)
|
||||
* fix(android): use main looper to dispatch key events when OSK is hidden (#12875)
|
||||
* chore: use GitHub PR titles when writing HISTORY.md (#12908)
|
||||
* fix(developer): filter incorrect fonts out of .keyboard_info (#12913)
|
||||
|
||||
## 17.0.332 stable 2024-11-06
|
||||
|
||||
* fix(developer): create Server config directory before options save (#12609)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
18.0.157
|
||||
18.0.176
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
<!-- Context: Textfield prompt (… is the ellipsis character "...") -->
|
||||
<string name="textview_hint" comment="Prompt to start typing">Escriba aquí…</string>
|
||||
<!-- Context: Splash screen (version/copyright info currently disabled -->
|
||||
<string name="splash_blank" translatable="false" comment="Blank text while version info on splash page is disabled"></string>
|
||||
<!-- Context: Splash screen (version/copyright info currently disabled -->
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="text_size" comment="Current text size (number)">Tamaño del texto: %1$d</string>
|
||||
|
|
@ -34,6 +33,8 @@
|
|||
<string name="ic_text_size_up" comment="Make text bigger">Agrandar texto</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_down" comment="Make text smaller">Achicar texto</string>
|
||||
<!-- Context: Text Size dialog -->
|
||||
<string name="ic_text_size_slider" comment="Adjust text size">Control deslizante de tamaño de texto</string>
|
||||
<!-- Context: Clear Text dialog -->
|
||||
<string name="all_text_will_be_cleared" comment="Erase all the text">\nTodo el texto será borrado\n</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
|
|
@ -52,6 +53,7 @@
|
|||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages"> Para instalar los paquetes de teclado, permita que Keyman acceda al almacenamiento externo.</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted"> Se ha denegado la solicitud de permiso de almacenamiento. La instalación del paquete de teclado podría fallar</string>
|
||||
<string name="storage_permission_denied2" comment="Keyboard package installation failed. Recommend install from local file"> Fallo en la solicitud de permiso de almacenamiento. Pruebe Configuración de Keyman - Instalar desde archivo local</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="keyman_settings" comment="Settings menu title">Configuración</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
|
|
@ -66,6 +68,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Ajustar altura del teclado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Ajustar demora de pulsación larga</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Leyenda del teclado</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">Teclado</string>
|
||||
|
|
@ -85,11 +89,11 @@
|
|||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">No mostrar leyenda en la barra espaciadora</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">Vibrar al escribir</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner" comment="text suggestions banner">Mostrar siempre el banner</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">Próximamente implementado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">Cuando esté desactivado, sólo mostrado cuando el texto predictivo esté habilitado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">Permitir el envío de informes de fallos a través de la red</string>
|
||||
|
|
@ -121,6 +125,14 @@
|
|||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">Rotar el dispositivo para ajustar a modo vertical u horizontal</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">Restablecer valores predeterminados</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Tiempo de retraso: %1$.1f segundos</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">Tiempo de retraso más largo</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Tiempo de retraso más corto</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Deslizador de tiempo de retraso de pulsación larga</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">Buscar o escribir una dirección web</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
|
|||
|
|
@ -241,11 +241,11 @@ function enableSuggestions(model, suggestionType) {
|
|||
[true, true, false], // 2 = SuggestionType.PREDICTIONS_WITH_CORRECTIONS
|
||||
[true, true, true], // 3 = SuggestionType.PREDICTIONS_WITH_AUTO_CORRECT
|
||||
];
|
||||
const t = suggestionSettings[suggestionType]
|
||||
const t = suggestionSettings[suggestionType]
|
||||
? suggestionSettings[suggestionType] : suggestionSettings[0];
|
||||
keyman.core.languageProcessor.mayPredict = t[0];
|
||||
keyman.core.languageProcessor.maySuggest = t[1];
|
||||
// keyman.core.languageProcessor.mayAutoCorrect = t[2];
|
||||
keyman.core.languageProcessor.mayAutoCorrect = t[2];
|
||||
|
||||
registerModel(model);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo;
|
|||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
|
|
@ -70,6 +71,10 @@ final class KMKeyboard extends WebView {
|
|||
protected KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED;
|
||||
protected ArrayList<String> javascriptAfterLoad = new ArrayList<>();
|
||||
|
||||
// .getMainLooper() returns the looper associated with the main UI thread.
|
||||
// https://stackoverflow.com/questions/13974661/runonuithread-vs-looper-getmainlooper-post-in-android
|
||||
private Handler jsQueuer = new Handler(Looper.getMainLooper());
|
||||
|
||||
private static String currentKeyboard = null;
|
||||
|
||||
/**
|
||||
|
|
@ -368,7 +373,7 @@ final class KMKeyboard extends WebView {
|
|||
if(this.javascriptAfterLoad.size() > 0) {
|
||||
// Don't call this WebView method on just ANY thread - run it on the main UI thread.
|
||||
// https://stackoverflow.com/a/22611010
|
||||
this.postDelayed(new Runnable() {
|
||||
jsQueuer.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
StringBuilder allCalls = new StringBuilder();
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ public final class KMManager {
|
|||
|
||||
public final static String predictionPrefSuffix = ".mayPredict";
|
||||
public final static String correctionPrefSuffix = ".mayCorrect";
|
||||
public final static String autoCorrectionPrefSuffix = ".mayAutoCorect";
|
||||
public final static String autoCorrectionPrefSuffix = ".mayAutoCorrect";
|
||||
|
||||
// Special override for when the keyboard may have haptic feedback when typing.
|
||||
// haptic feedback disabled for hardware keystrokes
|
||||
|
|
@ -305,11 +305,6 @@ public final class KMManager {
|
|||
public static final String KMKey_LexicalModelVersion = "lmVersion";
|
||||
public static final String KMKey_LexicalModelPackageFilename = "kmpPackageFilename";
|
||||
|
||||
// DEPRECATED keys
|
||||
public static final String KMKey_CustomKeyboard = "CustomKeyboard";
|
||||
public static final String KMKey_CustomModel = "CustomModel";
|
||||
public static final String KMKey_HelpLink = "helpLink";
|
||||
|
||||
// Keyman internal keys
|
||||
protected static final String KMKey_ShouldShowHelpBubble = "ShouldShowHelpBubble";
|
||||
|
||||
|
|
@ -1714,26 +1709,6 @@ public final class KMManager {
|
|||
return KeyboardPickerActivity.addKeyboard(context, keyboardInfo);
|
||||
}
|
||||
|
||||
// Intend to deprecate in Keyman 15.0
|
||||
public static boolean addKeyboard(Context context, HashMap<String, String> keyboardInfo) {
|
||||
String packageID = keyboardInfo.get(KMManager.KMKey_PackageID);
|
||||
String keyboardID = keyboardInfo.get(KMManager.KMKey_KeyboardID);
|
||||
String keyboardName = keyboardInfo.get(KMManager.KMKey_KeyboardName);
|
||||
String languageID = keyboardInfo.get(KMManager.KMKey_LanguageID);
|
||||
String languageName = keyboardInfo.get(KMManager.KMKey_LanguageName);
|
||||
String version = keyboardInfo.get(KMManager.KMKey_KeyboardVersion);
|
||||
String helpLink = keyboardInfo.get(KMManager.KMKey_CustomHelpLink);
|
||||
String kmpLink = MapCompat.getOrDefault(keyboardInfo, KMManager.KMKey_KMPLink, "");
|
||||
String font = keyboardInfo.get(KMManager.KMKey_Font);
|
||||
String oskFont = keyboardInfo.get(KMManager.KMKey_OskFont);
|
||||
boolean isNewKeyboard = true;
|
||||
|
||||
Keyboard k = new Keyboard(packageID, keyboardID, keyboardName,
|
||||
languageID, languageName, version, helpLink, kmpLink,
|
||||
isNewKeyboard, font, oskFont);
|
||||
return addKeyboard(context, k);
|
||||
}
|
||||
|
||||
public static boolean removeKeyboard(Context context, int position) {
|
||||
return KeyboardPickerActivity.removeKeyboard(context, position);
|
||||
}
|
||||
|
|
@ -2292,6 +2267,7 @@ public final class KMManager {
|
|||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
}
|
||||
|
||||
i.putExtra(KMKey_DisplayKeyboardSwitcher, kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.keyman.engine.util.MapCompat;
|
|||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
|
@ -252,8 +253,17 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
int curKbPos = KeyboardController.getInstance().getKeyboardIndex(KMKeyboard.currentKeyboard());
|
||||
setSelection(curKbPos);
|
||||
|
||||
// Determine the index to the current keyboard position to highlight as the selected keyboard
|
||||
int currentKeyboardIndex;
|
||||
String currentKeyboardKey = KMKeyboard.currentKeyboard();
|
||||
if (currentKeyboardKey == null) {
|
||||
SharedPreferences prefs = this.getSharedPreferences(this.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
currentKeyboardIndex = prefs.getInt(KMManager.KMKey_UserKeyboardIndex, 0);
|
||||
} else {
|
||||
currentKeyboardIndex = KeyboardController.getInstance().getKeyboardIndex(currentKeyboardKey);
|
||||
}
|
||||
setSelection(currentKeyboardIndex);
|
||||
|
||||
imeList = getIMEList(this);
|
||||
BaseAdapter imeAdapter = (BaseAdapter)imeListAdapter;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
<string name="dictionary_download_start_in_background" comment="Notification that a dictionary download has started">La descarga del diccionario inició en segundo plano</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">El diccionario seleccionado ya se está descargando; ¡por favor, inténtalo de nuevo en un momento!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<!-- Context: Background download messages. Removed in Keyman 17-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">La descarga del diccionario ha finalizado.</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">Descargar falló</string>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
<string name="update_check_current" comment="Notification that all resources are up to date">"¡Todos los recursos están actualizados!"</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">¡Uno o más recursos no se han actualizado!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<!-- Context: General Updates. Removed in Keyman 17 -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">¡Recursos actualizados con éxito!</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">Versión del diccionario</string>
|
||||
|
|
@ -133,10 +133,15 @@
|
|||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">Teclado %1$s instalado</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">Teclado eliminado</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">Habilitar correcciones</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">Habilitar predicciones</string>
|
||||
<!-- Context: Language Settings menu strings - radio buttons -->
|
||||
<string name="suggestions_radio_0" comment="Predictions and corrections disabled">Desactivar sugerencias</string>
|
||||
<string name="suggestions_radio_1" comment="Suggestions Enabled: Predictions with no corrections">Sólo predicciones</string>
|
||||
<string name="suggestions_radio_2" comment="Suggestions Enabled: Predictions with corrections">Predicciones con correcciones</string>
|
||||
<string name="suggestions_radio_3" comment="Suggestions Enabled: Predictions with auto-corrections">Predicciones con autocorrecciones</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Diccionario</string>
|
||||
<plurals name="model_count">
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ public class KMManagerTest {
|
|||
HashMap<String, String> kmInfo = new HashMap<String, String>();
|
||||
kmInfo.put(KMManager.KMKey_PackageID, "khmer_angkor");
|
||||
kmInfo.put(KMManager.KMKey_KeyboardID, "khmer_angkor");
|
||||
kmInfo.put(KMManager.KMKey_CustomKeyboard, "Y");
|
||||
kmInfo.put(KMManager.KMKey_LanguageID, "km");
|
||||
kmInfo.put(KMManager.KMKey_KeyboardName, "Khmer Angkor");
|
||||
kmInfo.put(KMManager.KMKey_LanguageName, "Central Khmer");
|
||||
|
|
|
|||
|
|
@ -66,7 +66,21 @@ public class FunctionalTestHelper {
|
|||
|
||||
Assert.assertEquals(installedKbds.size(),1);
|
||||
|
||||
KMManager.addKeyboard(ApplicationProvider.getApplicationContext(),(HashMap<String, String>) installedKbds.get(0));
|
||||
HashMap<String, String> map = (HashMap<String, String>)installedKbds.get(0);
|
||||
Keyboard keyboard = new Keyboard(
|
||||
map.get(KMManager.KMKey_PackageID),
|
||||
map.get(KMManager.KMKey_KeyboardID),
|
||||
map.get(KMManager.KMKey_KeyboardName),
|
||||
map.get(KMManager.KMKey_LanguageID),
|
||||
map.get(KMManager.KMKey_LanguageName),
|
||||
map.get(KMManager.KMKey_KeyboardVersion),
|
||||
map.get(KMManager.KMKey_CustomHelpLink),
|
||||
map.get(KMManager.KMKey_KMPLink),
|
||||
true,
|
||||
null,
|
||||
null
|
||||
);
|
||||
KMManager.addKeyboard(ApplicationProvider.getApplicationContext(), keyboard);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ There are now several gestures available to interact with some keys on the keybo
|
|||

|
||||
|
||||
* **Long press**: Press and hold a key, and after a moment a submenu will appear. Slide the finger to the submenu to select a key. Release the finger, and the highlighted key from the submenu will be output.
|
||||
* **Flick**: Press and hold a key, and then slide the finger in various directions on a key to reach alternate outputs. The key will animate to show the expected output when the finger gets released. A common flick is to slide down to reach numerals on the top row of the keyboard.
|
||||
* **Multitap**: Some keys can be pressed repeatedly and rapidly to reach alternate outputs. Tapping rapidly twice on <kbd>Shift</kbd> will activate <kbd>Caps Lock</kbd> on many keyboards.
|
||||
|
||||
* **Flick**: Press and hold a key, and then slide the finger in various directions on a key to reach alternate outputs. The key will animate to show the expected output when the finger gets released. A common flick is to slide down to reach numerals on the top row of the keyboard.
|
||||
|
||||
An up-flick on keys that support longpresses but do not have a defined up-flick will skip the wait and immediately display their longpress submenu.
|
||||
* **Multitap**: Some keys can be pressed repeatedly and rapidly to reach alternate outputs. Tapping rapidly twice on <kbd>Shift</kbd> will activate <kbd>Caps Lock</kbd> on many keyboards.
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"exclude": [
|
||||
"src/keyboard-object.ts",
|
||||
"src/lexical-model-types.ts",
|
||||
"src/outputTarget.interface.ts",
|
||||
"src/*.d.ts",
|
||||
"src/main.ts",
|
||||
"src/schemas/*",
|
||||
"src/schema-validators.ts",
|
||||
"src/schemas.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ function do_test() {
|
|||
|
||||
readonly C8_THRESHOLD=60
|
||||
|
||||
# Excludes are defined in .c8rc.json
|
||||
# Excludes are defined in package.json
|
||||
c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha ${MOCHA_FLAGS} "${builder_extra_params[@]}"
|
||||
builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal."
|
||||
builder_echo warning "Please increase threshold in build.sh as test coverage improves."
|
||||
|
|
|
|||
|
|
@ -76,7 +76,13 @@
|
|||
"src/keyman-touch-layout/keyman-touch-layout-file-writer.ts",
|
||||
"src/osk/osk.ts",
|
||||
"src/schemas/*",
|
||||
"tests/"
|
||||
"tests/",
|
||||
"src/keyboard-object.ts",
|
||||
"src/outputTarget.interface.ts",
|
||||
"src/*.d.ts",
|
||||
"src/main.ts",
|
||||
"src/schema-validators.ts",
|
||||
"src/schemas.ts"
|
||||
]
|
||||
},
|
||||
"sideEffects": false
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ export class ElemElement {
|
|||
order: number; // -128 to +127; used only by reorder element values
|
||||
tertiary: number; // -128 to +127; used only by reorder element values
|
||||
flags: ElemElementFlags;
|
||||
isEqual(a: ElemElement) {
|
||||
return a.value === this.value &&
|
||||
isEqual(a: ElemElement): boolean {
|
||||
return a.value.isEqual(this.value) &&
|
||||
a.order === this.order &&
|
||||
a.tertiary === this.tertiary &&
|
||||
a.flags === this.flags;
|
||||
|
|
@ -104,8 +104,8 @@ export class ElementString extends Array<ElemElement> {
|
|||
typeFlag |= constants.elem_flags_type_str;
|
||||
}
|
||||
}
|
||||
elem.order = orders.length ? parseInt(orders[i], 10) : 0;
|
||||
elem.tertiary = tertiaries.length ? parseInt(tertiaries[i], 10) : 0;
|
||||
elem.order = orders.length ? this.parseIntOrZero(orders[i]) : 0;
|
||||
elem.tertiary = tertiaries.length ? this.parseIntOrZero(tertiaries[i]) : 0;
|
||||
elem.flags = ElemElementFlags.none |
|
||||
(ElemElementFlags.type & typeFlag) |
|
||||
(tertiary_bases?.[i] == '1' /* TODO-LDML: or 'true'? */ ? ElemElementFlags.tertiary_base : 0) |
|
||||
|
|
@ -125,5 +125,9 @@ export class ElementString extends Array<ElemElement> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
private static parseIntOrZero(str: string): number {
|
||||
const num = parseInt(str, 10);
|
||||
return !Number.isNaN(num) ? num : 0;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
|
|||
|
|
@ -131,6 +131,10 @@ export class StrsItem {
|
|||
get isOneChar() {
|
||||
return this.char !== undefined;
|
||||
}
|
||||
|
||||
isEqual(a: StrsItem): boolean {
|
||||
return a.value === this.value && a.char === this.char;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
498
common/web/types/tests/kmx/kmx-plus/element-string.tests.ts
Normal file
498
common/web/types/tests/kmx/kmx-plus/element-string.tests.ts
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by Dr Mark C. Sinclair on 2024-12-09
|
||||
*
|
||||
* Test code for element-string.ts
|
||||
*/
|
||||
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { ElemElementFlags, ElemElement, ElementString } from '../../../src/kmx/kmx-plus/element-string.js';
|
||||
import { StrsItem, UsetItem, Strs, StrsOptions, DependencySections, CharStrsItem, Uset } from '../../../src/kmx/kmx-plus/kmx-plus.js';
|
||||
import { UnicodeSet, UnicodeSetParser } from '../../../src/ldml-keyboard/unicodeset-parser-api.js';
|
||||
import { ElementParser, ElementSegment, ElementType } from '../../../src/ldml-keyboard/pattern-parser.js';
|
||||
|
||||
const GOTHIC_A = new StrsItem("𐌰", 0x10330);
|
||||
const GOTHIC_B = new StrsItem("𐌱", 0x10331);
|
||||
const GOTHIC_C = new StrsItem("𐌲", 0x10332);
|
||||
const GOTHIC_D = new StrsItem("𐌳", 0x10333);
|
||||
const HI_GOTHIC_A = new StrsItem('\ud800', 0xd800);
|
||||
const LO_GOTHIC_A = new StrsItem('\udf30', 0xdf30);
|
||||
const GOTHIC_PATTERN = "[𐌰-𐍊]";
|
||||
const GOTHIC_STRSITEM = new StrsItem(GOTHIC_PATTERN);
|
||||
const GOTHIC_SET = new UnicodeSet(GOTHIC_PATTERN, [[0x10330,0x1034A]]);
|
||||
const GOTHIC_USETITEM = new UsetItem(GOTHIC_SET, GOTHIC_STRSITEM);
|
||||
const UGARITIC_PATTERN = "[𐎀-𐎟]";
|
||||
const UGARITIC_STRSITEM = new StrsItem(UGARITIC_PATTERN);
|
||||
const UGARITIC_SET = new UnicodeSet(UGARITIC_PATTERN, [[0x10380,0x1039F]]);
|
||||
const UGARITIC_USETITEM = new UsetItem(UGARITIC_SET, UGARITIC_STRSITEM);
|
||||
|
||||
class TestUnicodeSetParser implements UnicodeSetParser {
|
||||
parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => GOTHIC_SET;
|
||||
sizeUnicodeSet = (pattern: string) : number => 1;
|
||||
};
|
||||
|
||||
const origElementParserSegment = ElementParser.segment;
|
||||
let sections: DependencySections = null;
|
||||
|
||||
describe('Test of ElementString file', () => {
|
||||
describe('Test of ElemElement', () => {
|
||||
describe('Test of isEqual()', () => {
|
||||
it('returns true when elems identical', () => {
|
||||
const one = initElemElement();
|
||||
const two = initElemElement();
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
it('returns true when elems are clones', () => {
|
||||
const one = initElemElement(new StrsItem("𐌰", 0x10330));
|
||||
const two = initElemElement(new StrsItem("𐌰", 0x10330));
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
it('returns false when value differs', () => {
|
||||
const one = initElemElement(GOTHIC_A);
|
||||
const two = initElemElement(GOTHIC_B);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when order differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0);
|
||||
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 1);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when tertiary differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0);
|
||||
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 1);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when flags differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0, ElemElementFlags.none);
|
||||
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0, ElemElementFlags.type);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns true even though uset differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM);
|
||||
const two = initElemElement(GOTHIC_A, UGARITIC_USETITEM);
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Test of ElementString class', () => {
|
||||
beforeEach(() => {
|
||||
sections = {
|
||||
strs: new Strs(),
|
||||
uset: new Uset(),
|
||||
usetparser: new TestUnicodeSetParser(),
|
||||
};
|
||||
ElementParser.segment = stubElementParserSegment_CodePoint;
|
||||
});
|
||||
afterEach(() => {
|
||||
ElementParser.segment = origElementParserSegment;
|
||||
});
|
||||
describe('Test of fromStrings()', () => {
|
||||
it('returns an empty ElementString if source is null', () => {
|
||||
const es = ElementString.fromStrings({}, null);
|
||||
assert.deepEqual(es, new ElementString());
|
||||
});
|
||||
it('can create an ElementString from a string array', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(sections, ["𐌰", "𐌱", "𐌲"]);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can create an ElementString from a string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(sections, "𐌰𐌱𐌲");
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply order string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1 2 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single order to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 1),
|
||||
initElemElement(GOTHIC_C, undefined, 1),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle order string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1 2",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 0),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle non-number in order string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1 A 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0),
|
||||
initElemElement(GOTHIC_C, undefined, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply tertiary string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 2 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single tertiary to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 1),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle tertiary string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 2",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle non-number in tertiary string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 A 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply tertiary_base string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
"1 0 1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single tertiary_base to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle tertiary_base string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
"1 0",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.none),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply prebase string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"1 0 1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single prebase to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle prebase string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"1 0",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.none),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can create an ElementString from a uset string', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.strs.allocString = stubStrsAllocString_Str;
|
||||
sections.uset.allocUset = stubUsetAllocUset;
|
||||
const actual = ElementString.fromStrings(sections, "[𐌰-𐍊]");
|
||||
const expected = [
|
||||
initElemElement(
|
||||
new StrsItem(''),
|
||||
GOTHIC_USETITEM,
|
||||
0,
|
||||
0,
|
||||
constants.elem_flags_type_uset,
|
||||
),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('returns null for an invalid unicode set size', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.usetparser.sizeUnicodeSet = (pattern: string) : number => -1;
|
||||
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
|
||||
});
|
||||
it('returns null if it cannot parse the unicode set', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.usetparser.parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => null;
|
||||
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
|
||||
});
|
||||
it('can handle quad strings', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
ElementParser.segment = stubElementParserSegment_Escaped;
|
||||
const actual = ElementString.fromStrings(sections, "\\ud800\\udf30");
|
||||
const expected = [
|
||||
initElemElement(HI_GOTHIC_A),
|
||||
initElemElement(LO_GOTHIC_A),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle ElemElement of string type', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Str;
|
||||
const actual = ElementString.fromStrings(sections, ["𐌰𐌱𐌲",]);
|
||||
const expected = [
|
||||
initElemElement(
|
||||
new StrsItem("𐌰𐌱𐌲"),
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
constants.elem_flags_type_str,
|
||||
),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
describe('Test of isEqual()', () => {
|
||||
it('returns true when ElementStrings are identical', () => {
|
||||
const es = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
assert.isTrue(es.isEqual(es));
|
||||
});
|
||||
it('returns true when ElementStrings are clones', () => {
|
||||
const one = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
const two = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
it('returns false when ElementStrings are different lengths', () => {
|
||||
const one = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
const two = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
]);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when ElementStrings have different ElemElements', () => {
|
||||
const one = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
const two = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_D),
|
||||
]);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
});
|
||||
describe('Test of parseIntOrZero()', () => {
|
||||
it('returns a number for a valid string', () => {
|
||||
const num = ElementString['parseIntOrZero']('1');
|
||||
assert.equal(num, 1);
|
||||
});
|
||||
it('returns zero for an invalid string', () => {
|
||||
const num = ElementString['parseIntOrZero']('A');
|
||||
assert.equal(num, 0);
|
||||
});
|
||||
it('returns zero for undefined', () => {
|
||||
const num = ElementString['parseIntOrZero'](undefined);
|
||||
assert.equal(num, 0);
|
||||
});
|
||||
it('returns zero for a null string', () => {
|
||||
const num = ElementString['parseIntOrZero'](null);
|
||||
assert.equal(num, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function initElemElement(
|
||||
value: StrsItem = GOTHIC_A,
|
||||
uset: UsetItem = undefined,
|
||||
order: number = 0,
|
||||
tertiary: number = 0,
|
||||
flags: ElemElementFlags = ElemElementFlags.none,
|
||||
): ElemElement {
|
||||
const ee = new ElemElement();
|
||||
ee.value = value;
|
||||
ee.uset = uset;
|
||||
ee.order = order;
|
||||
ee.tertiary = tertiary;
|
||||
ee.flags = flags;
|
||||
return ee;
|
||||
};
|
||||
|
||||
function initElementString(elemElements: ElemElement[]): ElementString {
|
||||
const es: ElementString = new ElementString();
|
||||
elemElements.forEach((ee) => {es.push(ee)});
|
||||
return es;
|
||||
};
|
||||
|
||||
function stubStrsAllocString_Char(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
|
||||
return new CharStrsItem(s);
|
||||
};
|
||||
|
||||
function stubStrsAllocString_Str(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
|
||||
return new StrsItem(s);
|
||||
};
|
||||
|
||||
function stubElementParserSegment_CodePoint(str: string): ElementSegment[] {
|
||||
return [...str].map(s => new ElementSegment(s, ElementType.codepoint));
|
||||
};
|
||||
|
||||
function stubElementParserSegment_Uset(str: string): ElementSegment[] {
|
||||
return [new ElementSegment(str, ElementType.uset)];
|
||||
};
|
||||
|
||||
function stubElementParserSegment_Escaped(str: string): ElementSegment[] {
|
||||
const strs = str.match(/\\u[0-9a-fA-F]{4}/g);
|
||||
return strs.map((s) => new ElementSegment(s, ElementType.escaped));
|
||||
};
|
||||
|
||||
function stubUsetAllocUset(set: UnicodeSet, sections: DependencySections) : UsetItem {
|
||||
return new UsetItem(set, new StrsItem(set.pattern));
|
||||
};
|
||||
|
||||
325
common/web/types/tests/kvk/kvk-file-writer.tests.ts
Normal file
325
common/web/types/tests/kvk/kvk-file-writer.tests.ts
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by Dr Mark C. Sinclair on 2024-12-05
|
||||
*
|
||||
* Test code for kvk-file-writer.ts
|
||||
*/
|
||||
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { KvkFileWriter } from '../../src/main.js';
|
||||
import { VisualKeyboard,
|
||||
VisualKeyboardKey,
|
||||
VisualKeyboardFont,
|
||||
DEFAULT_KVK_FONT
|
||||
} from '../../src/kvk/visual-keyboard.js';
|
||||
import { BUILDER_KVK_FILE,
|
||||
BUILDER_KVK_HEADER_FLAGS,
|
||||
BUILDER_KVK_STRING,
|
||||
BUILDER_KVK_HEADER_IDENTIFIER,
|
||||
BUILDER_KVK_HEADER_VERSION,
|
||||
BUILDER_KVK_KEY,
|
||||
BUILDER_KVK_KEY_FLAGS,
|
||||
BUILDER_KVK_SHIFT_STATE,
|
||||
} from '../../src/kvk/kvk-file.js';
|
||||
|
||||
const VISUAL_KEYBOARD_TEXT_COLOR = 0xFF000008;
|
||||
|
||||
describe('Test of KVK-File-Writer', () => {
|
||||
describe('Test of write()', () => {
|
||||
it('can create a visual keyboard', () => {
|
||||
const writer = new KvkFileWriter;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(0),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const file = writer.write(vk);
|
||||
assert.isNotNull(file);
|
||||
});
|
||||
});
|
||||
describe('Test of build()', () => {
|
||||
it('can build a BUILDER_KVK_FILE', () => {
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(0),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it('does not take account of the VisualKeyboard version', () => {
|
||||
const vk_version = (BUILDER_KVK_HEADER_VERSION + 0x0100);
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(0),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
],
|
||||
vk_version,
|
||||
BUILDER_KVK_HEADER_FLAGS.kvkhNone,
|
||||
"associatedKeyboard",
|
||||
DEFAULT_KVK_FONT,
|
||||
DEFAULT_KVK_FONT,
|
||||
undefined,
|
||||
);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it.skip('can handle a null associatedKeyboard', () => {
|
||||
const vk_associatedKeyboard: string = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(0),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
],
|
||||
undefined,
|
||||
BUILDER_KVK_HEADER_FLAGS.kvkhNone,
|
||||
vk_associatedKeyboard,
|
||||
DEFAULT_KVK_FONT,
|
||||
DEFAULT_KVK_FONT,
|
||||
undefined,
|
||||
);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it.skip('can handle a null ansiFont name', () => {
|
||||
const vk_ansiFont_name: string = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(0),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
],
|
||||
undefined,
|
||||
BUILDER_KVK_HEADER_FLAGS.kvkhNone,
|
||||
"associatedKeyboard",
|
||||
{ name: vk_ansiFont_name, size: -12 },
|
||||
DEFAULT_KVK_FONT,
|
||||
undefined,
|
||||
);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it.skip('can handle a null unicodeFont name', () => {
|
||||
const vk_unicodeFont_name: string = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(0),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
],
|
||||
undefined,
|
||||
BUILDER_KVK_HEADER_FLAGS.kvkhNone,
|
||||
"associatedKeyboard",
|
||||
DEFAULT_KVK_FONT,
|
||||
{ name: vk_unicodeFont_name, size: -12 },
|
||||
undefined,
|
||||
);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it('can handle a null vkey', () => {
|
||||
const vkk_vkey: number = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(
|
||||
vkk_vkey,
|
||||
BUILDER_KVK_KEY_FLAGS.kvkkBitmap,
|
||||
BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL,
|
||||
"text",
|
||||
null,
|
||||
),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it('can handle a null key flags', () => {
|
||||
const vkk_flags: BUILDER_KVK_KEY_FLAGS = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(
|
||||
0,
|
||||
vkk_flags,
|
||||
BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL,
|
||||
"text",
|
||||
null,
|
||||
),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it('can handle a null key shift', () => {
|
||||
const vkk_shift: BUILDER_KVK_SHIFT_STATE = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(
|
||||
0,
|
||||
BUILDER_KVK_KEY_FLAGS.kvkkBitmap,
|
||||
vkk_shift,
|
||||
"text",
|
||||
null,
|
||||
),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it('can handle a null key text', () => {
|
||||
const vkk_text: string = null;
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(
|
||||
0,
|
||||
BUILDER_KVK_KEY_FLAGS.kvkkBitmap,
|
||||
BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL,
|
||||
vkk_text,
|
||||
null,
|
||||
),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
it('can handle a non-null key bitmap', () => {
|
||||
const vkk_bitmap: number[] = [0];
|
||||
const vk = initVisualKeyboard([
|
||||
initVisualKeyboardKey(
|
||||
0,
|
||||
BUILDER_KVK_KEY_FLAGS.kvkkBitmap,
|
||||
BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL,
|
||||
"text",
|
||||
vkk_bitmap,
|
||||
),
|
||||
initVisualKeyboardKey(1),
|
||||
initVisualKeyboardKey(2),
|
||||
]);
|
||||
const writer = new KvkFileWriter;
|
||||
const binary: BUILDER_KVK_FILE = writer['build'](vk);
|
||||
checkBuilderKvkFile(binary, vk);
|
||||
});
|
||||
});
|
||||
describe('Test of setString()', () => {
|
||||
it('can set a BUILDER_KVK_STRING', () => {
|
||||
const bks: BUILDER_KVK_STRING = { len: 0, str: null };
|
||||
const writer = new KvkFileWriter;
|
||||
const str = "hello";
|
||||
writer['setString'](bks, str);
|
||||
assert.equal(bks.len, str.length + 1);
|
||||
assert.deepEqual(bks.str, str);
|
||||
});
|
||||
it('can handle an empty string', () => {
|
||||
const bks: BUILDER_KVK_STRING = { len: 0, str: null };
|
||||
const writer = new KvkFileWriter;
|
||||
const str = "";
|
||||
writer['setString'](bks, str);
|
||||
assert.equal(bks.len, str.length + 1);
|
||||
assert.deepEqual(bks.str, str);
|
||||
});
|
||||
it('throws TypeError for a null BUILDER_KVK_STRING', () => {
|
||||
assert.throws(() => {
|
||||
const writer = new KvkFileWriter;
|
||||
writer['setString'](null, "");
|
||||
}, TypeError);
|
||||
});
|
||||
it('throws TypeError for a null value', () => {
|
||||
assert.throws(() => {
|
||||
const bks: BUILDER_KVK_STRING = { len: 0, str: null };
|
||||
const writer = new KvkFileWriter;
|
||||
writer['setString'](bks, null);
|
||||
}, TypeError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function initVisualKeyboard(
|
||||
vkks: VisualKeyboardKey[],
|
||||
version: number = undefined,
|
||||
flags: BUILDER_KVK_HEADER_FLAGS = BUILDER_KVK_HEADER_FLAGS.kvkhNone,
|
||||
associatedKeyboard: string = "associatedKeyboard",
|
||||
ansiFont: VisualKeyboardFont = DEFAULT_KVK_FONT,
|
||||
unicodeFont: VisualKeyboardFont = DEFAULT_KVK_FONT,
|
||||
underlyingLayout: string = undefined,
|
||||
): VisualKeyboard {
|
||||
const vkh = {
|
||||
version: version,
|
||||
flags: flags,
|
||||
associatedKeyboard: associatedKeyboard,
|
||||
ansiFont: ansiFont,
|
||||
unicodeFont: unicodeFont,
|
||||
underlyingLayout: underlyingLayout,
|
||||
};
|
||||
return { header: vkh, keys: vkks };
|
||||
};
|
||||
|
||||
function initVisualKeyboardKey(
|
||||
vkey: number,
|
||||
flags: BUILDER_KVK_KEY_FLAGS = BUILDER_KVK_KEY_FLAGS.kvkkBitmap,
|
||||
shift: BUILDER_KVK_SHIFT_STATE = BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL,
|
||||
text: string = "text",
|
||||
bitmap: number[] = null,
|
||||
): VisualKeyboardKey {
|
||||
const vkk: VisualKeyboardKey = {
|
||||
vkey,
|
||||
flags,
|
||||
shift,
|
||||
text,
|
||||
bitmap: bitmap ? new Uint8Array(bitmap) : null,
|
||||
};
|
||||
return vkk;
|
||||
}
|
||||
|
||||
function checkBuilderKvkFile(binary: BUILDER_KVK_FILE, vk: VisualKeyboard) {
|
||||
assert.equal(binary.header.identifier, BUILDER_KVK_HEADER_IDENTIFIER);
|
||||
assert.equal(binary.header.version, BUILDER_KVK_HEADER_VERSION);
|
||||
if (vk.header.associatedKeyboard != null) {
|
||||
assert.deepEqual(binary.header.associatedKeyboard.str, vk.header.associatedKeyboard);
|
||||
} else {
|
||||
assert.deepEqual(binary.header.associatedKeyboard.str, '');
|
||||
}
|
||||
assert.equal(binary.header.flags, vk.header.flags);
|
||||
assert.equal(binary.header.ansiFont.color, VISUAL_KEYBOARD_TEXT_COLOR);
|
||||
assert.equal(binary.header.ansiFont.size, vk.header.ansiFont.size);
|
||||
if (vk.header.ansiFont.name != null) {
|
||||
assert.deepEqual(binary.header.ansiFont.name.str, vk.header.ansiFont.name);
|
||||
} else {
|
||||
assert.deepEqual(binary.header.ansiFont.name.str, '');
|
||||
}
|
||||
assert.equal(binary.header.unicodeFont.color, VISUAL_KEYBOARD_TEXT_COLOR);
|
||||
assert.equal(binary.header.unicodeFont.size, vk.header.unicodeFont.size);
|
||||
if (vk.header.unicodeFont.name != null) {
|
||||
assert.deepEqual(binary.header.unicodeFont.name.str, vk.header.unicodeFont.name);
|
||||
} else {
|
||||
assert.deepEqual(binary.header.unicodeFont.name.str, '');
|
||||
}
|
||||
for (let idx=0; idx<binary.keys.length; idx++) {
|
||||
checkBuilderKvkKey(binary.keys[idx], vk.keys[idx])
|
||||
}
|
||||
}
|
||||
|
||||
function checkBuilderKvkKey(target: BUILDER_KVK_KEY, source: VisualKeyboardKey) {
|
||||
assert.equal(target.flags, source.flags);
|
||||
assert.equal(target.shift, source.shift);
|
||||
assert.equal(target.vkey, source.vkey);
|
||||
if (source.text != null) {
|
||||
assert.deepEqual(target.text.str, source.text);
|
||||
} else {
|
||||
assert.deepEqual(target.text.str, '');
|
||||
}
|
||||
if (source.bitmap != null) {
|
||||
assert.equal(target.bitmapSize, source.bitmap.byteLength);
|
||||
assert.deepEqual(target.bitmapData, Array.from(source.bitmap));
|
||||
} else {
|
||||
assert.equal(target.bitmapSize, 0);
|
||||
assert.deepEqual(target.bitmapData, []);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
Keyman is copyright (C) SIL Global. MIT License.
|
||||
|
||||
This module provides functionality to track the execution state of the Keyman
|
||||
engine. It uses a global atom to record whether Keyman has started during the
|
||||
current session and checks if it has previously run.
|
||||
}
|
||||
unit Keyman.System.ExecutionHistory;
|
||||
|
||||
|
||||
interface
|
||||
|
||||
const
|
||||
AtomName = 'KeymanSessionFlag';
|
||||
|
||||
function RecordKeymanStarted : Boolean;
|
||||
function HasKeymanRun : Boolean;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
Winapi.Windows,
|
||||
KLog;
|
||||
|
||||
function RecordKeymanStarted : Boolean;
|
||||
var
|
||||
atom: WORD;
|
||||
begin
|
||||
atom := GlobalAddAtom(AtomName);
|
||||
if atom = 0 then
|
||||
begin
|
||||
// TODO-WINDOWS-UPDATES: #10210 log to sentry
|
||||
Result := False;
|
||||
end
|
||||
else
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function HasKeymanRun : Boolean;
|
||||
begin
|
||||
Result := GlobalFindAtom(AtomName) <> 0;
|
||||
if not Result then
|
||||
begin
|
||||
if GetLastError <> ERROR_FILE_NOT_FOUND then
|
||||
begin
|
||||
// TODO-WINDOWS-UPDATES: log to Sentry
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -35,6 +35,7 @@ type
|
|||
|
||||
TUpdateCheckResponse = record
|
||||
private
|
||||
FOriginalData: string;
|
||||
FInstallSize: Int64;
|
||||
FInstallURL: string;
|
||||
FNewVersion: string;
|
||||
|
|
@ -46,9 +47,13 @@ type
|
|||
FFileName: string;
|
||||
function ParseKeyboards(nodes: TJSONObject): Boolean;
|
||||
function ParseLanguages(i: Integer; v: TJSONValue): Boolean;
|
||||
function DoParse(const message, app, currentVersion: string): Boolean;
|
||||
public
|
||||
function Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
|
||||
|
||||
procedure SaveToFile(const Filename: string);
|
||||
function LoadFromFile(const Filename, app, currentVersion: string): Boolean;
|
||||
|
||||
property CurrentVersion: string read FCurrentVersion;
|
||||
property NewVersion: string read FNewVersion;
|
||||
property NewVersionWithTag: string read FNewVersionWithTag;
|
||||
|
|
@ -58,25 +63,32 @@ type
|
|||
property ErrorMessage: string read FErrorMessage;
|
||||
property Status: TUpdateCheckResponseStatus read FStatus;
|
||||
property Packages: TUpdateCheckResponsePackages read FPackages;
|
||||
property OriginalData: string read FOriginalData;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Classes,
|
||||
System.Generics.Collections,
|
||||
versioninfo;
|
||||
|
||||
{ TUpdateCheckResponse }
|
||||
|
||||
function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean;
|
||||
begin
|
||||
Result := DoParse(string(UTF8String(message)), app, currentVersion);
|
||||
end;
|
||||
|
||||
function TUpdateCheckResponse.DoParse(const message, app, currentVersion: string): Boolean;
|
||||
var
|
||||
node, doc: TJSONObject;
|
||||
begin
|
||||
FOriginalData := message;
|
||||
FCurrentVersion := currentVersion;
|
||||
FStatus := ucrsNoUpdate;
|
||||
|
||||
// TODO: test with UTF8 characters in response
|
||||
doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject;
|
||||
doc := TJSONObject.ParseJSONValue(UTF8String(FOriginalData)) as TJSONObject;
|
||||
if doc = nil then
|
||||
begin
|
||||
FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]);
|
||||
|
|
@ -168,4 +180,29 @@ begin
|
|||
Result := True;
|
||||
end;
|
||||
|
||||
function TUpdateCheckResponse.LoadFromFile(const Filename, app, currentVersion: string): Boolean;
|
||||
var
|
||||
ss: TStringStream;
|
||||
begin
|
||||
ss := TStringStream.Create('', TEncoding.UTF8);
|
||||
try
|
||||
ss.LoadFromFile(Filename);
|
||||
Result := DoParse(ss.DataString, app, currentVersion);
|
||||
finally
|
||||
ss.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TUpdateCheckResponse.SaveToFile(const Filename: string);
|
||||
var
|
||||
ss: TStringStream;
|
||||
begin
|
||||
ss := TStringStream.Create(FOriginalData, TEncoding.UTF8);
|
||||
try
|
||||
ss.SaveToFile(Filename);
|
||||
finally
|
||||
ss.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ type
|
|||
const S_CEF_SubProcess = 'kmbrowserhost.exe';
|
||||
const S_CEF_SubProcess_Developer = 'kmdbrowserhost.exe';
|
||||
const S_CustomisationFilename = 'desktop_pro.pxx';
|
||||
|
||||
const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\';
|
||||
public
|
||||
const S_KMShell = 'kmshell.exe';
|
||||
const S_TSysInfoExe = 'tsysinfo.exe';
|
||||
|
|
@ -27,8 +29,10 @@ type
|
|||
const S_FallbackKeyboardPath = 'Keyboards\';
|
||||
const S__Package = '_Package\';
|
||||
const S_MCompileExe = 'mcompile.exe';
|
||||
const S_UpdateCache_Metadata = 'cache.json';
|
||||
class function ErrorLogPath(const app: string = ''): string; static;
|
||||
class function KeymanHelpPath(const HelpFile: string): string; static;
|
||||
class function KeymanUpdateCachePath(const filename: string = ''): string; static;
|
||||
class function KeymanDesktopInstallPath(const filename: string = ''): string; static;
|
||||
class function KeymanEngineInstallPath(const filename: string = ''): string; static;
|
||||
class function KeymanDesktopInstallDir: string; static;
|
||||
|
|
@ -423,6 +427,11 @@ begin
|
|||
Result := '';
|
||||
end;
|
||||
|
||||
class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string;
|
||||
begin
|
||||
Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename;
|
||||
end;
|
||||
|
||||
class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean;
|
||||
begin
|
||||
// On developer machines, if we are running within the source repo, then use
|
||||
|
|
|
|||
|
|
@ -175,8 +175,10 @@ const
|
|||
|
||||
SRegValue_CharMapSourceData = 'charmap source data'; // LM
|
||||
|
||||
SRegValue_AvailableLanguages = 'available languages'; //CU
|
||||
SRegValue_CurrentLanguage = 'current language'; //CU
|
||||
SRegValue_AvailableLanguages = 'available languages'; // CU
|
||||
SRegValue_CurrentLanguage = 'current language'; // CU
|
||||
|
||||
SRegValue_Update_State = 'update state'; // CU
|
||||
|
||||
{ Privacy }
|
||||
|
||||
|
|
@ -312,8 +314,10 @@ const
|
|||
SRegValue_ActiveProject_Filename = 'project filename';
|
||||
SRegValue_ActiveProject_SourcePath = 'source path';
|
||||
|
||||
SRegValue_AutomaticUpdates = 'automatic updates'; //CU
|
||||
SRegValue_CheckForUpdates = 'check for updates'; // CU
|
||||
SRegValue_LastUpdateCheckTime = 'last update check time'; // CU
|
||||
SRegValue_ApplyNow = 'apply now'; // CU Start the install now even though it will require an restart
|
||||
|
||||
SRegValue_UpdateCheck_UseProxy = 'update check use proxy'; // CU
|
||||
SRegValue_UpdateCheck_ProxyHost = 'update check proxy host'; // CU
|
||||
|
|
|
|||
|
|
@ -230,7 +230,8 @@ type
|
|||
ssPlatform = 31, ssBaseLayout = 32, ssLayer = 33, ssVKDictionary = 34, ssLayoutFile = 35, // I3438 // I3483
|
||||
ssKeyboardVersion = 36, ssKMW_EmbedCSS = 37, ssTargets = 38,
|
||||
ssCasedKeys = 39,
|
||||
ssBegin_NewContext = 40, ssBegin_PostKeystroke = 41, ssNewLayer = 42, ssOldLayer = 43); // I4140 // I4368 // I4504
|
||||
ssBegin_NewContext = 40, ssBegin_PostKeystroke = 41, ssNewLayer = 42, ssOldLayer = 43, // I4140 // I4368 // I4504
|
||||
ssDisplayMap = 44);
|
||||
|
||||
const
|
||||
SystemStoreNames: array[TSystemStore] of WideString = (
|
||||
|
|
@ -245,7 +246,8 @@ const
|
|||
'', '', '', '', 'LAYOUTFILE', 'KEYBOARDVERSION', 'KMW_EMBEDCSS',
|
||||
'TARGETS', //9.0 // I3483 // I4140 // I4368 // I4504
|
||||
'CASEDKEYS', //14.0,
|
||||
'', '', 'NEWLAYER', 'OLDLAYER'); //15.0
|
||||
'', '', 'NEWLAYER', 'OLDLAYER', //15.0
|
||||
'DISPLAYMAP'); //17.0
|
||||
|
||||
implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ value.
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_option_scope enum {#km_core_option_scope}
|
||||
# km_core_option_scope enum
|
||||
|
||||
## Description
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ enum km_core_option_scope {
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_option_item struct {#km_core_option_item}
|
||||
# km_core_option_item struct
|
||||
|
||||
## Description
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ struct km_core_option_item {
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_options_list_size() {#km_core_options_list_size}
|
||||
# km_core_options_list_size()
|
||||
|
||||
## Description
|
||||
Return the length of a terminated [km_core_option_item] array (options
|
||||
|
|
@ -150,7 +150,7 @@ km_core_state_option_lookup(km_core_state const *state,
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_state_options_update() {#km_core_state_options_update}
|
||||
# km_core_state_options_update()
|
||||
|
||||
## Description
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ km_core_state_options_update(km_core_state *state,
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# km_core_state_options_to_json() {#km_core_state_options_to_json}
|
||||
# km_core_state_options_to_json()
|
||||
|
||||
## Description
|
||||
|
||||
|
|
|
|||
|
|
@ -56,10 +56,8 @@ TEST_F(KmCoreKeyboardApiTests, LoadFromBlobNull) {
|
|||
// Setup
|
||||
km::core::path kmxfile = "";
|
||||
|
||||
std::unique_ptr<uint8_t> data(new uint8_t[0]);
|
||||
|
||||
// Execute
|
||||
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.get(), 0, &this->keyboard);
|
||||
auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), nullptr, 0, &this->keyboard);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(status, KM_CORE_STATUS_INVALID_ARGUMENT);
|
||||
|
|
|
|||
|
|
@ -16,11 +16,7 @@ binary_tests = [
|
|||
]
|
||||
|
||||
foreach kbd : binary_tests
|
||||
configure_file(
|
||||
input: kbd + '.kmn',
|
||||
output: kbd + '.kmn',
|
||||
copy: true
|
||||
)
|
||||
fs.copyfile(kbd + '.kmn', kbd + '.kmn')
|
||||
|
||||
configure_file(
|
||||
command: hextobin_cmd + ['@INPUT@', '@OUTPUT@'],
|
||||
|
|
|
|||
|
|
@ -139,7 +139,10 @@ foreach kbd : tests
|
|||
cfg.set('CONTENT', content)
|
||||
|
||||
kbd_kmn = configure_file(
|
||||
copy: true,
|
||||
# not really a file that needs to be configured, but the `copy` parameter
|
||||
# is deprecated and `fs.copyfile` doesn't work with files that are not in
|
||||
# the source directory
|
||||
configuration: cfg,
|
||||
input: kbd_src_path,
|
||||
output: kbd + '.kmn'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,7 @@ invalid_tests = [
|
|||
# Build all keyboards in output folder
|
||||
|
||||
foreach kbd : invalid_tests
|
||||
configure_file(
|
||||
input: kbd + '.xml',
|
||||
output: kbd + '.xml',
|
||||
copy: true
|
||||
)
|
||||
fs.copyfile(kbd + '.xml', kbd + '.xml')
|
||||
|
||||
configure_file(
|
||||
command: hextobin_cmd + ['@INPUT@', '@OUTPUT@'],
|
||||
|
|
|
|||
|
|
@ -65,11 +65,7 @@ kmc_cmd = [node, '--enable-source-maps', kmc_root]
|
|||
# Build all keyboards in output folder
|
||||
|
||||
foreach kbd : tests_needing_copy
|
||||
configure_file(
|
||||
copy: true,
|
||||
input: kbd + '.xml',
|
||||
output: kbd + '.xml'
|
||||
)
|
||||
fs.copyfile(kbd + '.xml', kbd + '.xml')
|
||||
|
||||
configure_file(
|
||||
command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'],
|
||||
|
|
@ -86,13 +82,8 @@ foreach kbd : tests_with_testdata
|
|||
)
|
||||
endforeach
|
||||
|
||||
|
||||
foreach kbd : tests_from_cldr
|
||||
configure_file(
|
||||
copy: true,
|
||||
input: ldml_data / kbd + '.xml',
|
||||
output: kbd + '.xml'
|
||||
)
|
||||
fs.copyfile(ldml_data / kbd + '.xml', kbd + '.xml')
|
||||
configure_file(
|
||||
command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'],
|
||||
input: join_paths(ldml_data, kbd + '.xml'),
|
||||
|
|
|
|||
|
|
@ -43,16 +43,8 @@ else
|
|||
endif
|
||||
|
||||
# copy package.json into build dir for test use
|
||||
configure_file(
|
||||
copy: true,
|
||||
input: '../../../../package.json',
|
||||
output: 'package.json',
|
||||
)
|
||||
configure_file(
|
||||
copy: true,
|
||||
input: '../../../../resources/standards-data/unicode-character-database/Blocks.txt',
|
||||
output: 'Blocks.txt',
|
||||
)
|
||||
fs.copyfile('../../../../package.json', 'package.json')
|
||||
fs.copyfile('../../../../resources/standards-data/unicode-character-database/Blocks.txt', 'Blocks.txt')
|
||||
|
||||
configure_file(
|
||||
command: [node, meson.current_source_dir() / 'write_node_versions.js','@OUTPUT@'],
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
strip = ['$EMSCRIPTEN_BASE/emstrip']
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
strip = ['$EMSCRIPTEN_BASE/emstrip']
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
c = ['python.exe', '$EMSCRIPTEN_BASE/emcc.py']
|
||||
cpp = ['python.exe', '$EMSCRIPTEN_BASE/em++.py']
|
||||
ar = ['python.exe', '$EMSCRIPTEN_BASE/emar.py']
|
||||
strip = ['python.exe', '$EMSCRIPTEN_BASE/emstrip.py']
|
||||
|
||||
[properties]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
c = ['emcc']
|
||||
cpp = ['em++']
|
||||
ar = ['emar']
|
||||
strip = ['emstrip']
|
||||
exe_wrapper = 'node'
|
||||
|
||||
[properties]
|
||||
|
|
|
|||
|
|
@ -382,22 +382,24 @@ ID
|
|||
required, and a default code will usually be generated automatically
|
||||
by Keyman Developer.
|
||||
|
||||
- `K_xxxx` is used for a standard Keyman for Windows's key name, e.g.
|
||||
`K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
|
||||
Many of the `K_` ids have overloaded output behaviour, for
|
||||
instance, if no rule is matched for `K_W`, Keyman will output
|
||||
'w' when it is touched. The standard key names are listed in
|
||||
[Virtual Keys and Virtual Character Keys](/developer/language/guide/virtual-keys). Typically,
|
||||
you would use only the "common" virtual ID.
|
||||
- `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`.
|
||||
If you wanted to use it, `T_ENTER` would also be valid. If no
|
||||
rule matches it, the key will have no output behaviour.
|
||||
- `U_####[_####]` is used as a shortcut for a key that
|
||||
will output those Unicode values, if no rule matches it. This is
|
||||
similar to the overloaded behaviour for `K_` ids. Thus `####`
|
||||
must be valid Unicode characters. E.g. `U_0259` would generate a
|
||||
schwa if no rule matches. It is still valid to have a rule such
|
||||
as `+ [U_0259] > ...`
|
||||
- `K_xxxx` is used for a standard Keyman for Windows's key name, e.g.
|
||||
`K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
|
||||
Many of the `K_` ids have overloaded output behaviour, for
|
||||
instance, if no rule is matched for `K_W`, Keyman will output
|
||||
'w' when it is touched. The standard key names are listed in
|
||||
[Virtual Keys and Virtual Character Keys](/developer/language/guide/virtual-keys). Typically,
|
||||
you would use only the "common" virtual ID.
|
||||
- `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`.
|
||||
If you wanted to use it, `T_ENTER` would also be valid. If no
|
||||
rule matches it, the key will have no output behaviour.
|
||||
- `U_####[_####]` is used as a shortcut for a key that
|
||||
will output those Unicode values, if no rule matches it. This is
|
||||
similar to the overloaded behaviour for `K_` ids. Thus `####`
|
||||
must be valid Unicode characters.
|
||||
The square bracket characters `[` and `]` indicate an optional portion of the sequence
|
||||
and are not to be included in the shortcut. E.g. `U_0259` would generate a
|
||||
schwa if no rule matches. It is still valid to have a rule such
|
||||
as `+ [U_0259] > ...`
|
||||
|
||||
Padding Left
|
||||
: Padding to the left of each key can be adjusted, and specified as a
|
||||
|
|
|
|||
|
|
@ -131,7 +131,9 @@ Character Keys"). Typically, you would use only the "common" virtual key
|
|||
|
||||
- `U_####[_####]` is used as a shortcut for a key that will output those
|
||||
Unicode values, if no rule matches it. This is similar to the overloaded
|
||||
behaviour for `K_` ids. Thus `####` must be valid Unicode characters. E.g.
|
||||
behaviour for `K_` ids. Thus `####` must be valid Unicode characters.
|
||||
The square bracket characters `[` and `]` indicate an optional portion of the sequence
|
||||
and are not to be included in the shortcut. E.g.
|
||||
`U_0259` would generate a schwa if no rule matches. It is still valid to
|
||||
have a rule such as `+ [U_0259] > ...`
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ welcome.htm
|
|||
> ### Tip
|
||||
If you want links to your website to open in the user's preferred
|
||||
browser, preface the href link with `link:`, e.g.
|
||||
`<a href="http://keyman.com/">website</a>`
|
||||
`<a href="https://keyman.com/">website</a>`
|
||||
The `link:` sceheme will open the referred file in the default
|
||||
application - that is, a web browser for URLs and links, Notepad for
|
||||
.txt files, Adobe Reader for PDFs. You can use `link:` to open any
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Website
|
|||
|
||||
: Enter the name of the website where you will have information about
|
||||
this keyboard. If you want to host it on keyman.com, you could enter
|
||||
`http://www.keyman.com/`
|
||||
`https://keyman.com/`
|
||||
|
||||
Image file
|
||||
: Select the splash image file that you created in Step 2 from the
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ Linux.
|
|||
There are two core applications included in Keyman Developer: the Keyman
|
||||
Developer IDE (formerly called TIKE), and the command line compiler kmc.
|
||||
|
||||
[Watch a video](https://youtu.be/kwhgx_eX4Es) that highlights some of
|
||||
the Keyman Developer features.
|
||||
Watch a video that highlights some of the Keyman Developer 14.0 features (57:23)
|
||||
|
||||
:<img class="video-thumbnail" data-video="https://www.youtube.com/embed/kwhgx_eX4Es" src="https://img.youtube.com/vi/kwhgx_eX4Es/maxresdefault.jpg" width="600px" alt="Keyman Developer 14.0 Webinar">
|
||||
|
||||
## Keyman Developer IDE
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ recommendations:
|
|||
ZIP archive file!) and you won't save much space by recompressing
|
||||
it.
|
||||
3. Include a link to the Keyman download page:
|
||||
`http://keyman.com/downloads/`
|
||||
`https://keyman.com/downloads/`
|
||||
|
||||
## Distributing a package by email
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@ Character Keys"). Typically, you would use only the "common" virtual key
|
|||
|
||||
- `U_####[_####]` is used as a shortcut for a key that will output those
|
||||
Unicode values, if no rule matches it. This is similar to the overloaded
|
||||
behaviour for `K_` ids. Thus `####` must be valid Unicode characters. E.g.
|
||||
behaviour for `K_` ids. Thus `####` must be valid Unicode characters.
|
||||
The square bracket characters `[` and `]` indicate an optional portion of the sequence
|
||||
and are not to be included in the shortcut. E.g.
|
||||
`U_0259` would generate a schwa if no rule matches. It is still valid to
|
||||
have a rule such as `+ [U_0259] > ...`
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ unit KeyboardFonts;
|
|||
interface
|
||||
|
||||
type
|
||||
TKeyboardFont = (kfontCode, kfontChar, kfontOSK, kfontTouchLayoutPhone, kfontTouchLayoutTablet, kfontTouchLayoutDesktop);
|
||||
TKeyboardFont = (kfontCode, kfontChar, kfontOSK, kfontTouchLayoutPhone, kfontTouchLayoutTablet, kfontTouchLayoutDesktop,
|
||||
kfontDisplayMap);
|
||||
|
||||
TKeyboardFontArray = array[TKeyboardFont] of string; // I4409
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ type
|
|||
|
||||
const
|
||||
KeyboardFontId: array[TKeyboardFont] of string = (
|
||||
'Code', 'Char', 'Osk', 'TouchLayoutPhone', 'TouchLayoutTablet', 'TouchLayoutDesktop'
|
||||
'Code', 'Char', 'Osk', 'TouchLayoutPhone', 'TouchLayoutTablet', 'TouchLayoutDesktop', 'DisplayMap'
|
||||
);
|
||||
implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -24,5 +24,32 @@ export async function getFontFamily(source: Uint8Array) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return font.meta.property.find(prop => prop.name == 'font-family')?.text ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the font-family from an in-memory TTF or WOFF blob in `source`
|
||||
* parameter.
|
||||
*
|
||||
* @param source In-memory TTF or WOFF font blob
|
||||
*
|
||||
* @throws Uncaught exceptions from ttfMeta.promise if the font file is invalid.
|
||||
*
|
||||
* @returns If the file is invalid or cannot be parsed, returns `null`,
|
||||
* otherwise returns the font family as a string.
|
||||
*/
|
||||
export function getFontFamilySync(source: Uint8Array) {
|
||||
/* c8 ignore next 3 */
|
||||
if(!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(source);
|
||||
const font = ttfMeta.ttfInfoSync(buffer);
|
||||
/* c8 ignore next 3 */
|
||||
if(!font) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return font.meta.property.find(prop => prop.name == 'font-family')?.text ?? null;
|
||||
}
|
||||
|
|
@ -68,4 +68,8 @@ export * as SourceFilenamePatterns from './source-filename-patterns.js';
|
|||
export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js';
|
||||
|
||||
export * as GitHubUrls from './github-urls.js';
|
||||
export * as CloudUrls from './cloud-urls.js';
|
||||
export * as CloudUrls from './cloud-urls.js';
|
||||
|
||||
export { getFontFamily, getFontFamilySync } from './font-family.js';
|
||||
|
||||
export * as ValidIds from './valid-ids.js';
|
||||
|
|
|
|||
|
|
@ -31,6 +31,20 @@ export function ttfInfo(pathOrData, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
* @param {string | number | Buffer | URL} pathOrData
|
||||
* @returns {typeof ttf.result}
|
||||
*/
|
||||
export function ttfInfoSync(pathOrData) {
|
||||
if (pathOrData instanceof Buffer) {
|
||||
return ttf.ttfInfoSync(ttf.view(pathOrData));
|
||||
} else {
|
||||
const data = fs.readFileSync(pathOrData);
|
||||
return ttf.ttfInfoSync(ttf.view(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number | Buffer | URL} pathOrData
|
||||
* @returns {Promise<typeof ttf.result>}
|
||||
|
|
@ -48,5 +62,5 @@ export function promise(pathOrData) {
|
|||
}
|
||||
|
||||
/** @namespace */
|
||||
export const ttfMeta = { ttfInfo, promise };
|
||||
export const ttfMeta = { ttfInfo, ttfInfoSync, promise };
|
||||
export default ttfMeta;
|
||||
|
|
@ -300,6 +300,16 @@ export function ttfInfo(data, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
* @param {*} data
|
||||
* @returns {typeof result}
|
||||
*/
|
||||
export function ttfInfoSync(data) {
|
||||
resultTables(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number | Buffer | URL | DataView} pathOrData
|
||||
* @returns {Promise<typeof result>}
|
||||
|
|
@ -66,6 +66,12 @@ export class KeymanSentry {
|
|||
|
||||
static async captureException(e: any): Promise<never> {
|
||||
if(isInit) {
|
||||
// For local development, we don't want to bury the trace; we need the cast to avoid
|
||||
// TS2367 (comparison appears to be unintentional)
|
||||
if((KEYMAN_VERSION.VERSION_ENVIRONMENT as string) == 'local') {
|
||||
throw e;
|
||||
}
|
||||
|
||||
const eventId = Sentry.captureException(e);
|
||||
process.stderr.write(`
|
||||
Fatal error: ${(e??'').toString()}
|
||||
|
|
@ -73,11 +79,6 @@ export class KeymanSentry {
|
|||
this.writeSentryMessage(eventId);
|
||||
await this.close();
|
||||
|
||||
// For local development, we don't want to bury the trace; we need the cast to avoid
|
||||
// TS2367 (comparison appears to be unintentional)
|
||||
if((KEYMAN_VERSION.VERSION_ENVIRONMENT as string) == 'local') {
|
||||
throw e;
|
||||
}
|
||||
process.exit(1);
|
||||
} else {
|
||||
throw e;
|
||||
|
|
@ -86,7 +87,11 @@ export class KeymanSentry {
|
|||
|
||||
static async close() {
|
||||
if(isInit) {
|
||||
await Sentry.close(CLOSE_TIMEOUT);
|
||||
try {
|
||||
await Sentry.close(CLOSE_TIMEOUT);
|
||||
} catch(e) {
|
||||
// ignore any errors here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
developer/src/common/web/utils/src/valid-ids.ts
Normal file
38
developer/src/common/web/utils/src/valid-ids.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that an id passed in is valid as an identifier for a Keyman .kmn
|
||||
* keyboard: starts with alpha or underscore and remainder is alphanumeric
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function isValidKeymanKeyboardId(id: string) {
|
||||
return /^[a-z_][a-z0-9_]*$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an id passed in is valid as an identifier for an LDML xml
|
||||
* keyboard: starts with alpha or underscore and remainder is alphanumeric.
|
||||
* Note that this test is not the same as the CLDR test for valid LDML xml
|
||||
* filename, as this test has a narrower validity policy to match the Keyman
|
||||
* implementation requirements.
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function isValidLdmlKeyboardId(id: string) {
|
||||
return /^[a-z_][a-z0-9_]*$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an id passed in is valid as an identifier for a Keyman
|
||||
* lexical model: starts with alpha or underscore and remainder is alphanumeric,
|
||||
* in three components -- author.bcp47.uniq
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function isValidLexicalModelId(id: string) {
|
||||
return /^[a-z_][a-z0-9_]*\.[a-z][a-z0-9_]+\.[a-z_][a-z0-9_]*$/.test(id);
|
||||
}
|
||||
76
developer/src/common/web/utils/test/valid-ids.tests.ts
Normal file
76
developer/src/common/web/utils/test/valid-ids.tests.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import 'mocha';
|
||||
import { isValidKeymanKeyboardId, isValidLdmlKeyboardId, isValidLexicalModelId } from '../src/valid-ids.js';
|
||||
|
||||
describe('valid-ids', function () {
|
||||
it('should accept a valid Keyman keyboard ID', function() {
|
||||
[
|
||||
'khmer_angkor',
|
||||
'm',
|
||||
'fancypants1_2',
|
||||
'keyboard___',
|
||||
'_100',
|
||||
].forEach(id => assert.isTrue(isValidKeymanKeyboardId(id), `expected '${id}' to be valid`));
|
||||
});
|
||||
|
||||
it('should reject invalid Keyman keyboard IDs', function() {
|
||||
[
|
||||
'khmer_angkor@example.com',
|
||||
'keyboard ',
|
||||
'Khmer',
|
||||
' keyboard',
|
||||
'111',
|
||||
'?',
|
||||
'keyboard.version',
|
||||
].forEach(id => assert.isFalse(isValidKeymanKeyboardId(id), `expected '${id}' to be invalid`));
|
||||
});
|
||||
|
||||
it('should accept a valid LDML keyboard ID', function() {
|
||||
[
|
||||
'khmer_angkor',
|
||||
'm',
|
||||
'fancypants1_2',
|
||||
'keyboard___',
|
||||
'_100',
|
||||
].forEach(id => assert.isTrue(isValidLdmlKeyboardId(id), `expected '${id}' to be valid`));
|
||||
});
|
||||
|
||||
it('should reject invalid LDML keyboard IDs', function() {
|
||||
[
|
||||
'khmer_angkor@example.com',
|
||||
'keyboard ',
|
||||
' keyboard',
|
||||
'Khmer',
|
||||
'111',
|
||||
'?',
|
||||
'keyboard.version',
|
||||
].forEach(id => assert.isFalse(isValidLdmlKeyboardId(id), `expected '${id}' to be invalid`));
|
||||
});
|
||||
|
||||
it('should accept a valid lexical model ID', function() {
|
||||
[
|
||||
'khmer.km.khmer_angkor',
|
||||
'nrc.en.mtnt',
|
||||
].forEach(id => assert.isTrue(isValidLexicalModelId(id), `expected '${id}' to be valid`));
|
||||
});
|
||||
|
||||
it('should reject invalid lexical model IDs', function() {
|
||||
[
|
||||
'khmer',
|
||||
'en-us',
|
||||
'khmer.km-KM.angkor',
|
||||
'Khmer.km_km.angkor',
|
||||
'model ',
|
||||
' model',
|
||||
'111',
|
||||
'?',
|
||||
'lexical.model',
|
||||
'lexical..model',
|
||||
'.en.model',
|
||||
].forEach(id => assert.isFalse(isValidLexicalModelId(id), `expected '${id}' to be invalid`));
|
||||
});
|
||||
});
|
||||
|
|
@ -5,8 +5,12 @@
|
|||
"rootDir": ".",
|
||||
"outDir": "./build/",
|
||||
"baseUrl": ".",
|
||||
|
||||
/** for ttfmeta */
|
||||
"allowJs": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/ttfmeta/lib/*.js",
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@
|
|||
"exclude-after-remap": true,
|
||||
"exclude": [
|
||||
"test/",
|
||||
"src/template/"
|
||||
"src/template/",
|
||||
"src/main.ts"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { KeymanTargets } from "@keymanapp/common-types";
|
||||
import KEYMAN_VERSION from "@keymanapp/keyman-version";
|
||||
import { AbstractGenerator, GeneratorArtifacts } from "./abstract-generator.js";
|
||||
import { GeneratorMessages } from "./generator-messages.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
|
@ -28,7 +29,19 @@ export class BasicGenerator extends AbstractGenerator {
|
|||
this.languageTags = this.options.languageTags.length
|
||||
? this.options.languageTags.map(tag => new Intl.Locale(tag).minimize().toString())
|
||||
: [this.DEFAULT_LOCALE];
|
||||
//TODO-GENERATE: validate targets
|
||||
|
||||
// Verify that targets passed in are valid
|
||||
let failed = false;
|
||||
for(const target of this.options.targets) {
|
||||
if(!KeymanTargets.AllKeymanTargets.includes(target)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidTarget({target}));
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
if(failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.options.targets.includes(KeymanTargets.KeymanTarget.any) || this.options.targets.length == 0) {
|
||||
this.resolvedTargets = KeymanTargets.AllKeymanTargets.filter(target => target != KeymanTargets.KeymanTarget.any);
|
||||
} else {
|
||||
|
|
@ -50,6 +63,8 @@ export class BasicGenerator extends AbstractGenerator {
|
|||
dt.getDate().toString().padStart(2, '0');
|
||||
this.tokenMap['$PACKAGE_LANGUAGES'] = this.generateLanguageListForPackage();
|
||||
this.tokenMap['$PLATFORMS_DOTLIST_README'] = this.getPlatformDotListForReadme();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected generate(artifacts: GeneratorArtifacts): boolean {
|
||||
|
|
@ -89,6 +104,8 @@ export class BasicGenerator extends AbstractGenerator {
|
|||
sourceFile = this.callbacks.path.join(this.templateBasePath, this.templatePath, sourceFile);
|
||||
|
||||
const sourceData = this.callbacks.loadFile(sourceFile);
|
||||
|
||||
/* c8 ignore next 4 */
|
||||
if(sourceData == null) {
|
||||
// internal error -- source file should be readable
|
||||
throw new Error(`source file ${sourceFile} does not exist`);
|
||||
|
|
|
|||
|
|
@ -17,30 +17,65 @@ const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
|||
* @internal
|
||||
*/
|
||||
export class GeneratorMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(
|
||||
this.FATAL_UnexpectedException, null, o.e ?? 'unknown error'
|
||||
);
|
||||
|
||||
static Info_GeneratingProject = (o:{type: string, id: string}) => m(this.INFO_GeneratingProject,
|
||||
`Generating project of type ${def(o.type)} with id ${def(o.id)}`);
|
||||
static INFO_GeneratingProject = SevInfo | 0x0002;
|
||||
static Info_GeneratingProject = (o:{type: string, id: string}) => m(
|
||||
this.INFO_GeneratingProject,
|
||||
`Generating project of type ${def(o.type)} with id ${def(o.id)}`
|
||||
);
|
||||
|
||||
static ERROR_CannotCreateFolder = SevError | 0x0003;
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(this.ERROR_CannotCreateFolder, null,
|
||||
`Unable to create folder ${def(o.folderName)}: ${o.e ?? 'unknown error'}`);
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
|
||||
this.ERROR_CannotCreateFolder, null,
|
||||
`Unable to create folder ${def(o.folderName)}: ${o.e ?? 'unknown error'}`
|
||||
);
|
||||
|
||||
static ERROR_OutputPathAlreadyExists = SevError | 0x0004;
|
||||
static Error_OutputPathAlreadyExists = (o:{outPath:string}) => m(
|
||||
this.ERROR_OutputPathAlreadyExists,
|
||||
`Output path ${def(o.outPath)} already exists, not overwriting`);
|
||||
`Output path ${def(o.outPath)} already exists, not overwriting`
|
||||
);
|
||||
|
||||
static ERROR_CannotWriteOutputFile = SevError | 0x0005;
|
||||
static Error_CannotWriteOutputFile = (o:{filename:string, e: any}) => CompilerMessageSpecWithException(this.ERROR_CannotWriteOutputFile, null,
|
||||
`Unable to write file ${def(o.filename)}: ${o.e ?? 'unknown error'}`);
|
||||
static Error_CannotWriteOutputFile = (o:{filename:string, e: any}) => CompilerMessageSpecWithException(
|
||||
this.ERROR_CannotWriteOutputFile, null,
|
||||
`Unable to write file ${def(o.filename)}: ${o.e ?? 'unknown error'}`
|
||||
);
|
||||
|
||||
// See also PackageCompilerMessages.WARN_PackageNameDoesNotFollowLexicalModelConventions
|
||||
static WARN_ModelIdDoesNotFollowLexicalModelConventions = SevWarn | 0x0006;
|
||||
static Warn_ModelIdDoesNotFollowLexicalModelConventions = (o:{id: string}) => m(this.WARN_ModelIdDoesNotFollowLexicalModelConventions,
|
||||
static Warn_ModelIdDoesNotFollowLexicalModelConventions = (o:{id: string}) => m(
|
||||
this.WARN_ModelIdDoesNotFollowLexicalModelConventions,
|
||||
`The id ${def(o.id)} does not follow the recommended model id conventions. The id should be all lower case, `+
|
||||
`include only alphanumeric characters and underscore (_), not start with a digit, and should have the structure `+
|
||||
`<author>.<bcp47>.<uniq>`);
|
||||
`<author>.<bcp47>.<uniq>`
|
||||
);
|
||||
|
||||
static ERROR_InvalidTarget = SevError | 0x0007;
|
||||
static Error_InvalidTarget = (o:{target:string}) => m(
|
||||
this.ERROR_InvalidTarget,
|
||||
`The specified target platform '${def(o.target)}' is not a valid target.`
|
||||
);
|
||||
|
||||
static ERROR_InvalidKeymanKeyboardId = SevError | 0x0008;
|
||||
static Error_InvalidKeymanKeyboardId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidKeymanKeyboardId,
|
||||
`The specified keyboard id '${def(o.id)}' contains characters that are not permitted for a .kmn keyboard id or filename.`
|
||||
);
|
||||
|
||||
static ERROR_InvalidLdmlKeyboardId = SevError | 0x0009;
|
||||
static Error_InvalidLdmlKeyboardId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidLdmlKeyboardId,
|
||||
`The specified keyboard id '${def(o.id)}' contains characters that are not permitted for an LDML .xml keyboard id or filename.`
|
||||
);
|
||||
|
||||
static ERROR_InvalidLexicalModelId = SevError | 0x000A;
|
||||
static Error_InvalidLexicalModelId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidLexicalModelId,
|
||||
`The specified lexical model id '${def(o.id)}' contains characters that are not permitted or does not match the required pattern of 'author.bcp47.uniq'.`
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { KeymanFileTypes, KeymanTargets } from '@keymanapp/common-types';
|
||||
import { KeymanCompiler, } from '@keymanapp/developer-utils';
|
||||
import { KeymanCompiler, ValidIds, } from '@keymanapp/developer-utils';
|
||||
import { GeneratorArtifacts, GeneratorResult } from './abstract-generator.js';
|
||||
import { BasicGenerator } from './basic-generator.js';
|
||||
import { GeneratorMessages } from './generator-messages.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
@ -39,7 +40,15 @@ export class KeymanKeyboardGenerator extends BasicGenerator implements KeymanCom
|
|||
* @returns Binary artifacts on success, null on failure.
|
||||
*/
|
||||
async run(): Promise<GeneratorResult> {
|
||||
this.preGenerate();
|
||||
if(!ValidIds.isValidKeymanKeyboardId(this.options.id)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidKeymanKeyboardId({id:this.options.id}));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!this.preGenerate()) {
|
||||
// errors will have been reported in preGenerate
|
||||
return null;
|
||||
}
|
||||
|
||||
const artifacts: GeneratorArtifacts = this.defaultArtifacts();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { KeymanCompiler, } from '@keymanapp/developer-utils';
|
||||
import { KeymanCompiler, ValidIds, } from '@keymanapp/developer-utils';
|
||||
import { GeneratorArtifacts, GeneratorResult } from './abstract-generator.js';
|
||||
import { BasicGenerator } from './basic-generator.js';
|
||||
import { GeneratorMessages } from './generator-messages.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
@ -28,7 +29,15 @@ export class LdmlKeyboardGenerator extends BasicGenerator implements KeymanCompi
|
|||
* @returns Binary artifacts on success, null on failure.
|
||||
*/
|
||||
async run(): Promise<GeneratorResult> {
|
||||
this.preGenerate();
|
||||
if(!ValidIds.isValidLdmlKeyboardId(this.options.id)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidLdmlKeyboardId({id:this.options.id}));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!this.preGenerate()) {
|
||||
// errors will have been reported in preGenerate
|
||||
return null;
|
||||
}
|
||||
|
||||
const artifacts: GeneratorArtifacts = this.defaultArtifacts();
|
||||
this.templatePath = 'ldml-keyboard';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { KeymanCompiler, SourceFilenamePatterns } from '@keymanapp/developer-utils';
|
||||
import { KeymanCompiler, SourceFilenamePatterns, ValidIds } from '@keymanapp/developer-utils';
|
||||
import { GeneratorArtifacts, GeneratorResult } from './abstract-generator.js';
|
||||
import { BasicGenerator } from './basic-generator.js';
|
||||
import { GeneratorMessages } from './generator-messages.js';
|
||||
|
|
@ -31,7 +31,15 @@ export class LexicalModelGenerator extends BasicGenerator implements KeymanCompi
|
|||
* @returns Binary artifacts on success, null on failure.
|
||||
*/
|
||||
async run(): Promise<GeneratorResult> {
|
||||
this.preGenerate();
|
||||
if(!ValidIds.isValidLexicalModelId(this.options.id)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidLexicalModelId({id:this.options.id}));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!this.preGenerate()) {
|
||||
// errors will have been reported in preGenerate
|
||||
return null;
|
||||
}
|
||||
|
||||
const artifacts: GeneratorArtifacts = this.defaultArtifacts();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,22 +2,71 @@
|
|||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { BasicGenerator } from '../src/basic-generator.js';
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
import { BasicGenerator } from '../src/basic-generator.js';
|
||||
import { AbstractGenerator, GeneratorArtifacts } from '../src/abstract-generator.js';
|
||||
import { options } from './shared-options.js';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
|
||||
// this class is here to make it possible to access protected members
|
||||
class BasicGeneratorTest extends BasicGenerator {
|
||||
static files() {
|
||||
return [
|
||||
'sample/' + AbstractGenerator.SFile_WelcomeHTM,
|
||||
'sample/' + AbstractGenerator.SFile_ReadmeHTM,
|
||||
'sample/' + AbstractGenerator.SFile_HistoryMD,
|
||||
'sample/' + AbstractGenerator.SFile_LicenseMD,
|
||||
'sample/' + AbstractGenerator.SFile_ReadmeMD,
|
||||
'sample/' + AbstractGenerator.SFile_GitIgnore,
|
||||
];
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.templatePath = 'kmn-keyboard';
|
||||
}
|
||||
}
|
||||
|
||||
describe('BasicGenerator', function () {
|
||||
it('should configure default settings', async function() {
|
||||
const bg = new BasicGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert(await bg.init(callbacks, options));
|
||||
bg.test_preGenerate();
|
||||
assert.isTrue(await bg.init(callbacks, options));
|
||||
assert.isTrue(bg.test_preGenerate());
|
||||
|
||||
assert.equal(bg.test_tokenMap['$NAME'], 'Sample Project');
|
||||
});
|
||||
|
||||
// TODO-GENERATE: test transform
|
||||
it('should successfully transform common project files', async function() {
|
||||
const bg = new BasicGeneratorTest();
|
||||
bg.setup();
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await bg.init(callbacks, options));
|
||||
assert.isTrue(bg.test_preGenerate());
|
||||
|
||||
const artifacts: GeneratorArtifacts = { "kmc-generate:outputPath": { data: null, filename: '' } };
|
||||
assert.isTrue(bg.test_generate(artifacts));
|
||||
|
||||
// standard artifacts are listed in AbstractGenerator
|
||||
const keys = [].concat(
|
||||
'kmc-generate:outputPath',
|
||||
BasicGeneratorTest.files().map(f => f.replaceAll('/', path.sep))
|
||||
);
|
||||
|
||||
assert.hasAllKeys(artifacts, keys);
|
||||
|
||||
// pick one file and compare it
|
||||
const filename = BasicGeneratorTest.files()[0].replaceAll('/', path.sep); // welcome.htm
|
||||
assert.equal(artifacts[filename].filename, filename);
|
||||
const fixturePath = makePathToFixture('basic', 'welcome.htm');
|
||||
const fixture = fs.readFileSync(fixturePath, 'utf-8').replace(/\r\n/g, '\n');
|
||||
const actual = new TextDecoder().decode(artifacts[filename].data);
|
||||
assert.equal(actual, fixture);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
26
developer/src/kmc-generate/test/fixtures/basic/welcome.htm
vendored
Normal file
26
developer/src/kmc-generate/test/fixtures/basic/welcome.htm
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Start Using Sample Project</title>
|
||||
<style type="text/css">
|
||||
p { font: 10pt Tahoma; }
|
||||
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
|
||||
h2 { font: bold 12pt Tahoma; color: #4444cc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Start Using Sample Project</h1>
|
||||
|
||||
<p>
|
||||
# A mighty description
|
||||
</p>
|
||||
|
||||
<h1>Keyboard Layout</h1>
|
||||
|
||||
<!-- Insert Keyboard Layout Images or HTML here -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,15 +2,24 @@
|
|||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { assert } from 'chai';
|
||||
import { GeneratorMessages } from '../src/generator-messages.js';
|
||||
|
||||
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
|
||||
|
||||
import { GeneratorMessages } from '../src/generator-messages.js';
|
||||
import { AbstractGenerator, GeneratorOptions } from '../src/abstract-generator.js';
|
||||
import { BasicGenerator } from '../src/basic-generator.js';
|
||||
import { KeymanKeyboardGenerator } from '../src/keyman-keyboard-generator.js';
|
||||
import { LdmlKeyboardGenerator } from '../src/ldml-keyboard-generator.js';
|
||||
import { LexicalModelGenerator } from '../src/lexical-model-generator.js';
|
||||
|
||||
import { options } from './shared-options.js';
|
||||
|
||||
describe('GeneratorMessages', function () {
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
|
@ -62,4 +71,49 @@ describe('GeneratorMessages', function () {
|
|||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_CannotWriteOutputFile),
|
||||
`messageId ERROR_CannotWriteOutputFile not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidTarget if an invalid target is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
// @ts-ignore ('invalid' could be passed by a user, even if would be a compile error here)
|
||||
targets: ['invalid']
|
||||
};
|
||||
const bg = new BasicGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert(await bg.init(callbacks, testOptions));
|
||||
assert.isFalse(bg.test_preGenerate());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidTarget));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidKeymanKeyboardId if an invalid keyboard id is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
id: '???invalid',
|
||||
};
|
||||
const kkg = new KeymanKeyboardGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await kkg.init(callbacks, testOptions));
|
||||
assert.isNull(await kkg.run());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidKeymanKeyboardId));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidLdmlKeyboardId if an invalid keyboard id is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
id: '???invalid',
|
||||
};
|
||||
const lkg = new LdmlKeyboardGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await lkg.init(callbacks, testOptions));
|
||||
assert.isNull(await lkg.run());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidLdmlKeyboardId));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidLexicalModelId if an invalid lexical model id is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
id: 'example.???.invalid',
|
||||
};
|
||||
const lmg = new LexicalModelGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await lmg.init(callbacks, testOptions));
|
||||
assert.isNull(await lmg.run());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidLexicalModelId));
|
||||
});
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@ import { CompilerCallbacks, KeymanCompiler, CompilerOptions, KeymanCompilerResul
|
|||
import { KmpCompiler } from "@keymanapp/kmc-package";
|
||||
|
||||
import { SchemaValidators } from "@keymanapp/common-types";
|
||||
import { getFontFamily } from "./font-family.js";
|
||||
import { getFontFamily } from "@keymanapp/developer-utils";
|
||||
|
||||
const regionNames = new Intl.DisplayNames(['en'], { type: "region" });
|
||||
const scriptNames = new Intl.DisplayNames(['en'], { type: "script" });
|
||||
|
|
@ -499,9 +499,6 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
|
|||
keyboard_info.languages[language] = {};
|
||||
}
|
||||
|
||||
const fontSource = [].concat(...kmpJsonData.keyboards.map(e => e.displayFont ? [e.displayFont] : []), ...kmpJsonData.keyboards.map(e => e.webDisplayFonts ?? []));
|
||||
const oskFontSource = [].concat(...kmpJsonData.keyboards.map(e => e.oskFont ? [e.oskFont] : []), ...kmpJsonData.keyboards.map(e => e.webOskFonts ?? []));
|
||||
|
||||
let commonScript = null;
|
||||
|
||||
for(const bcp47 of Object.keys(keyboard_info.languages)) {
|
||||
|
|
@ -528,6 +525,20 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
|
|||
// do it right now.
|
||||
//
|
||||
|
||||
// The code below:
|
||||
// 1. Only includes fonts associated with keyboards which support the current bcp47 (filter)
|
||||
// 2. Joins the displayFont and webDisplayFonts data, and removes duplicates (...new Set())
|
||||
|
||||
const supportedKeyboards = kmpJsonData.keyboards.filter(k => k.languages.find(lang => lang.id == bcp47));
|
||||
const fontSource = [...new Set([].concat(
|
||||
...supportedKeyboards.map(e => e.displayFont ? [e.displayFont] : []),
|
||||
...supportedKeyboards.map(e => e.webDisplayFonts ?? [])
|
||||
))];
|
||||
const oskFontSource = [...new Set([].concat(
|
||||
...supportedKeyboards.map(e => e.oskFont ? [e.oskFont] : []),
|
||||
...supportedKeyboards.map(e => e.webOskFonts ?? [])
|
||||
))];
|
||||
|
||||
if(fontSource.length) {
|
||||
language.font = await this.fontSourceToKeyboardInfoFont(kpsFilename, kmpJsonData, fontSource);
|
||||
if(language.font == null) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|||
import { assert } from 'chai';
|
||||
import 'mocha';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { getFontFamily } from '../src/font-family.js';
|
||||
import { getFontFamily } from '@keymanapp/developer-utils';
|
||||
|
||||
const AFGHAN_TURKMEN_DISPLAY_FONT = makePathToFixture('afghan_turkmen', "Lateef-Regular.ttf");
|
||||
const AFGHAN_TURKMEN_OSK_FONT = makePathToFixture('afghan_turkmen', "Lateef-Bold.ttf");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/ttfmeta/lib/*.js",
|
||||
"src/imports/langtags.js",
|
||||
],
|
||||
"references": [
|
||||
|
|
|
|||
|
|
@ -46,6 +46,16 @@ function GetKeyIdUnicodeType(value: string): TKeyIdType {
|
|||
|
||||
function KeyIdType(FId: string): TKeyIdType { // I4142
|
||||
FId = FId.toUpperCase();
|
||||
|
||||
// Validate key id format:
|
||||
// K_xxxx -- predefined virtual key - restricted character set
|
||||
// T_xxxx -- custom 'touch' virtual key - touch key id
|
||||
// U_ABCD_1234 -- Unicode key id (1+ chars)
|
||||
// x00 -- "ISO" key identifier (not currently supported in touch layout files)
|
||||
if(!/^((K_[A-Z0-9_?]+)|(T_\S+)|(U_[0-9A-F_]+))$/.test(FId)) {
|
||||
// note: |[A-Z][0-9][0-9] -- ISO key identifiers not currently supported
|
||||
return TKeyIdType.Key_Invalid;
|
||||
}
|
||||
switch(FId.charAt(0)) {
|
||||
case 'T':
|
||||
return TKeyIdType.Key_Touch;
|
||||
|
|
|
|||
550
developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.keyman-touch-layout
vendored
Normal file
550
developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.keyman-touch-layout
vendored
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
{
|
||||
"tablet": {
|
||||
"displayUnderlying": false,
|
||||
"layer": [
|
||||
{
|
||||
"id": "default",
|
||||
"row": [
|
||||
{
|
||||
"id": 1,
|
||||
"key": [
|
||||
{
|
||||
"id": "U_1E6B[_0307]",
|
||||
"text": "១"
|
||||
},
|
||||
{
|
||||
"id": "K_2",
|
||||
"text": "២"
|
||||
},
|
||||
{
|
||||
"id": "K_3",
|
||||
"text": "៣"
|
||||
},
|
||||
{
|
||||
"id": "K_4",
|
||||
"text": "៤"
|
||||
},
|
||||
{
|
||||
"id": "K_5",
|
||||
"text": "៥"
|
||||
},
|
||||
{
|
||||
"id": "K_6",
|
||||
"text": "៦"
|
||||
},
|
||||
{
|
||||
"id": "K_7",
|
||||
"text": "៧"
|
||||
},
|
||||
{
|
||||
"id": "K_8",
|
||||
"text": "៨"
|
||||
},
|
||||
{
|
||||
"id": "K_9",
|
||||
"text": "៩"
|
||||
},
|
||||
{
|
||||
"id": "K_0",
|
||||
"text": "០"
|
||||
},
|
||||
{
|
||||
"id": "K_HYPHEN",
|
||||
"text": "ឥ"
|
||||
},
|
||||
{
|
||||
"id": "K_EQUAL",
|
||||
"text": "ឲ"
|
||||
},
|
||||
{
|
||||
"id": "K_BKSP",
|
||||
"text": "*BkSp*",
|
||||
"width": "100",
|
||||
"sp": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_Q",
|
||||
"text": "ឆ",
|
||||
"pad": "75"
|
||||
},
|
||||
{
|
||||
"id": "K_W",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_E",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_R",
|
||||
"text": "រ"
|
||||
},
|
||||
{
|
||||
"id": "K_T",
|
||||
"text": "ត"
|
||||
},
|
||||
{
|
||||
"id": "K_Y",
|
||||
"text": "យ"
|
||||
},
|
||||
{
|
||||
"id": "K_U",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_I",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_O",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_P",
|
||||
"text": "ផ"
|
||||
},
|
||||
{
|
||||
"id": "K_LBRKT",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_RBRKT",
|
||||
"text": "ឪ"
|
||||
},
|
||||
{
|
||||
"id": "T_new_138",
|
||||
"text": "",
|
||||
"width": "10",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_BKQUOTE",
|
||||
"text": "«"
|
||||
},
|
||||
{
|
||||
"id": "K_A",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_S",
|
||||
"text": "ស"
|
||||
},
|
||||
{
|
||||
"id": "K_D",
|
||||
"text": "ដ"
|
||||
},
|
||||
{
|
||||
"id": "K_F",
|
||||
"text": "ថ"
|
||||
},
|
||||
{
|
||||
"id": "K_G",
|
||||
"text": "ង"
|
||||
},
|
||||
{
|
||||
"id": "K_H",
|
||||
"text": "ហ"
|
||||
},
|
||||
{
|
||||
"id": "K_J",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_K",
|
||||
"text": "ក"
|
||||
},
|
||||
{
|
||||
"id": "K_L",
|
||||
"text": "ល"
|
||||
},
|
||||
{
|
||||
"id": "K_COLON",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_QUOTE",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_BKSLASH",
|
||||
"text": "ឮ"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_SHIFT",
|
||||
"text": "*Shift*",
|
||||
"width": "160",
|
||||
"sp": "1",
|
||||
"nextlayer": "shift"
|
||||
},
|
||||
{
|
||||
"id": "K_oE2",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_Z",
|
||||
"text": "ឋ"
|
||||
},
|
||||
{
|
||||
"id": "K_X",
|
||||
"text": "ខ"
|
||||
},
|
||||
{
|
||||
"id": "K_C",
|
||||
"text": "ច"
|
||||
},
|
||||
{
|
||||
"id": "K_V",
|
||||
"text": "វ"
|
||||
},
|
||||
{
|
||||
"id": "K_B",
|
||||
"text": "ប"
|
||||
},
|
||||
{
|
||||
"id": "K_N",
|
||||
"text": "ន"
|
||||
},
|
||||
{
|
||||
"id": "K_M",
|
||||
"text": "ម"
|
||||
},
|
||||
{
|
||||
"id": "K_COMMA",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_PERIOD",
|
||||
"text": "។"
|
||||
},
|
||||
{
|
||||
"id": "K_SLASH",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "T_new_164",
|
||||
"text": "",
|
||||
"width": "10",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_LCONTROL",
|
||||
"text": "*AltGr*",
|
||||
"width": "160",
|
||||
"sp": "1"
|
||||
},
|
||||
{
|
||||
"id": "K_LOPT",
|
||||
"text": "*Menu*",
|
||||
"width": "160",
|
||||
"sp": "1"
|
||||
},
|
||||
{
|
||||
"id": "K_SPACE",
|
||||
"text": "",
|
||||
"width": "930"
|
||||
},
|
||||
{
|
||||
"id": "K_ENTER",
|
||||
"text": "*Enter*",
|
||||
"width": "160",
|
||||
"sp": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "shift",
|
||||
"row": [
|
||||
{
|
||||
"id": 1,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_1",
|
||||
"text": "!"
|
||||
},
|
||||
{
|
||||
"id": "K_2",
|
||||
"text": "ៗ"
|
||||
},
|
||||
{
|
||||
"id": "K_3",
|
||||
"text": "\""
|
||||
},
|
||||
{
|
||||
"id": "K_4",
|
||||
"text": "៛"
|
||||
},
|
||||
{
|
||||
"id": "K_5",
|
||||
"text": "%"
|
||||
},
|
||||
{
|
||||
"id": "K_6",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_7",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_8",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_9",
|
||||
"text": "("
|
||||
},
|
||||
{
|
||||
"id": "K_0",
|
||||
"text": ")"
|
||||
},
|
||||
{
|
||||
"id": "K_HYPHEN",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_EQUAL",
|
||||
"text": "="
|
||||
},
|
||||
{
|
||||
"id": "K_BKSP",
|
||||
"text": "*BkSp*",
|
||||
"width": "100",
|
||||
"sp": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_Q",
|
||||
"text": "ឈ",
|
||||
"pad": "75"
|
||||
},
|
||||
{
|
||||
"id": "K_W",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_E",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_R",
|
||||
"text": "ឬ"
|
||||
},
|
||||
{
|
||||
"id": "K_T",
|
||||
"text": "ទ"
|
||||
},
|
||||
{
|
||||
"id": "K_Y",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_U",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_I",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_O",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_P",
|
||||
"text": "ភ"
|
||||
},
|
||||
{
|
||||
"id": "K_LBRKT",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_RBRKT",
|
||||
"text": "ឧ"
|
||||
},
|
||||
{
|
||||
"id": "T_new_364",
|
||||
"text": "",
|
||||
"width": "10",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_BKQUOTE",
|
||||
"text": "»"
|
||||
},
|
||||
{
|
||||
"id": "K_A",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_S",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_D",
|
||||
"text": "ឌ"
|
||||
},
|
||||
{
|
||||
"id": "K_F",
|
||||
"text": "ធ"
|
||||
},
|
||||
{
|
||||
"id": "K_G",
|
||||
"text": "អ"
|
||||
},
|
||||
{
|
||||
"id": "K_H",
|
||||
"text": "ះ"
|
||||
},
|
||||
{
|
||||
"id": "K_J",
|
||||
"text": "ញ"
|
||||
},
|
||||
{
|
||||
"id": "K_K",
|
||||
"text": "គ"
|
||||
},
|
||||
{
|
||||
"id": "K_L",
|
||||
"text": "ឡ"
|
||||
},
|
||||
{
|
||||
"id": "K_COLON",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_QUOTE",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_BKSLASH",
|
||||
"text": "ឭ"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_SHIFT",
|
||||
"text": "*Shift*",
|
||||
"width": "160",
|
||||
"sp": "2",
|
||||
"nextlayer": "default"
|
||||
},
|
||||
{
|
||||
"id": "K_oE2",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_Z",
|
||||
"text": "ឍ"
|
||||
},
|
||||
{
|
||||
"id": "K_X",
|
||||
"text": "ឃ"
|
||||
},
|
||||
{
|
||||
"id": "K_C",
|
||||
"text": "ជ"
|
||||
},
|
||||
{
|
||||
"id": "K_V",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_B",
|
||||
"text": "ព"
|
||||
},
|
||||
{
|
||||
"id": "K_N",
|
||||
"text": "ណ"
|
||||
},
|
||||
{
|
||||
"id": "K_M",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_COMMA",
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"id": "K_PERIOD",
|
||||
"text": "៕"
|
||||
},
|
||||
{
|
||||
"id": "K_SLASH",
|
||||
"text": "?"
|
||||
},
|
||||
{
|
||||
"id": "T_new_390",
|
||||
"text": "",
|
||||
"width": "10",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_LCONTROL",
|
||||
"text": "*AltGr*",
|
||||
"width": "160",
|
||||
"sp": "1"
|
||||
},
|
||||
{
|
||||
"id": "K_LOPT",
|
||||
"text": "*Menu*",
|
||||
"width": "160",
|
||||
"sp": "1"
|
||||
},
|
||||
{
|
||||
"id": "K_SPACE",
|
||||
"text": "",
|
||||
"width": "930"
|
||||
},
|
||||
{
|
||||
"id": "K_ENTER",
|
||||
"text": "*Enter*",
|
||||
"width": "160",
|
||||
"sp": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"font": "Arial"
|
||||
}
|
||||
}
|
||||
10
developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.kmn
vendored
Normal file
10
developer/src/kmc-kmn/test/fixtures/kmw/error_touch_layout_invalid_identifier.kmn
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
store(&VERSION) '15.0'
|
||||
store(&NAME) "error_touch_layout_invalid_identifier"
|
||||
store(©RIGHT) '© 2015-2024 SIL Global'
|
||||
store(&TARGETS) 'any'
|
||||
store(&LAYOUTFILE) 'error_touch_layout_invalid_identifier.keyman-touch-layout'
|
||||
store(&KEYBOARDVERSION) '1.3'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
|
|
@ -36,7 +36,9 @@ describe('KeymanWeb Compiler', function() {
|
|||
});
|
||||
|
||||
this.afterEach(function() {
|
||||
callbacks.printMessages();
|
||||
if(this.currentTest?.isFailed() || debug) {
|
||||
callbacks.printMessages();
|
||||
}
|
||||
callbacks.clear();
|
||||
});
|
||||
|
||||
|
|
@ -203,6 +205,19 @@ describe('KeymanWeb Compiler', function() {
|
|||
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.HINT_TouchLayoutUsesUnsupportedGesturesDownlevel));
|
||||
});
|
||||
|
||||
it('should give error ERROR_TouchLayoutInvalidIdentifier if a virtual key is badly formatted e.g. U_1234[_5678]', async function() {
|
||||
// #12870
|
||||
const filenames = generateTestFilenames('error_touch_layout_invalid_identifier');
|
||||
|
||||
let result = await kmnCompiler.run(filenames.source, null);
|
||||
assert.isNull(result);
|
||||
assert.isFalse(callbacks.hasMessage(KmnCompilerMessages.INFO_MinimumCoreEngineVersion));
|
||||
assert.isFalse(callbacks.hasMessage(KmwCompilerMessages.INFO_MinimumWebEngineVersion));
|
||||
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_TouchLayoutInvalidIdentifier));
|
||||
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_InvalidTouchLayoutFile));
|
||||
assert.lengthOf(callbacks.messages, 2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { PackageKeyboardTargetValidator } from './package-keyboard-target-valida
|
|||
import { PackageMetadataUpdater } from './package-metadata-updater.js';
|
||||
import { markdownToHTML } from './markdown.js';
|
||||
import { PackageValidation } from './package-validation.js';
|
||||
import { getFontFamilySync } from "@keymanapp/developer-utils";
|
||||
import { setKvkFontData } from './kvk-font-facename.js';
|
||||
|
||||
const KMP_JSON_FILENAME = 'kmp.json';
|
||||
const KMP_INF_FILENAME = 'kmp.inf';
|
||||
|
|
@ -483,36 +485,36 @@ export class KmpCompiler implements KeymanCompiler {
|
|||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_AbsolutePath({filename: filename}));
|
||||
}
|
||||
|
||||
let memberFileData = this.getMemberFileData(kpsFilename, filename);
|
||||
if(!memberFileData) {
|
||||
failed = true;
|
||||
return;
|
||||
}
|
||||
filename = this.callbacks.resolveFilename(kpsFilename, filename);
|
||||
const basename = this.callbacks.path.basename(filename);
|
||||
|
||||
if(!this.callbacks.fs.existsSync(filename)) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileDoesNotExist({filename: filename}));
|
||||
failed = true;
|
||||
return;
|
||||
if(KeymanFileTypes.filenameIs(filename, KeymanFileTypes.Binary.VisualKeyboard)) {
|
||||
if(!this.isKvkFileBinary(memberFileData)) {
|
||||
// warn the few users who are still doing this -- non-binary .kvk should not be included in package!
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_FileIsNotABinaryKvkFile({filename: filename}));
|
||||
} else {
|
||||
memberFileData = this.setKvkFontData(kpsFilename, data, filename, memberFileData);
|
||||
}
|
||||
}
|
||||
|
||||
let memberFileData;
|
||||
try {
|
||||
memberFileData = this.callbacks.loadFile(filename);
|
||||
} catch(e) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileCouldNotBeRead({filename: filename, e: e}));
|
||||
failed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.warnIfKvkFileIsNotBinary(filename, memberFileData);
|
||||
|
||||
zip.file(basename, memberFileData);
|
||||
|
||||
// Remove path data from files before JSON save
|
||||
value.name = basename;
|
||||
});
|
||||
|
||||
if(failed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
data.files.forEach((value) => {
|
||||
const basename = this.callbacks.path.basename(value.name);
|
||||
// Remove path data from files before JSON save
|
||||
value.name = basename;
|
||||
});
|
||||
|
||||
// TODO #9477: transform .md to .htm
|
||||
|
||||
// Remove path data from file references in options
|
||||
|
|
@ -557,26 +559,87 @@ export class KmpCompiler implements KeymanCompiler {
|
|||
return transcodeToCP1252(s);
|
||||
}
|
||||
|
||||
private getMemberFileData(kpsFilename: string, filename: string) {
|
||||
filename = this.callbacks.resolveFilename(kpsFilename, filename);
|
||||
|
||||
if(!this.callbacks.fs.existsSync(filename)) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileDoesNotExist({filename: filename}));
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.callbacks.loadFile(filename);
|
||||
} catch(e) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileCouldNotBeRead({filename: filename, e: e}));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private setKvkFontData(kpsFilename: string, data: KmpJsonFile.KmpJsonFile, filename: string, kvk: Uint8Array) {
|
||||
// find the appropriate font to set
|
||||
const kvkId = this.callbacks.path.basename(filename.toLowerCase(), '.kvk');
|
||||
const kbd = data.keyboards.find(k => k.id == kvkId);
|
||||
if(!kbd) {
|
||||
// cannot find a matching keyboard
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotFindMatchingKeyboardForVisualKeyboard({filename}));
|
||||
return kvk;
|
||||
}
|
||||
const fontFilename = kbd.oskFont || kbd.displayFont;
|
||||
if(!fontFilename) {
|
||||
// no font data to set
|
||||
return kvk;
|
||||
}
|
||||
|
||||
// Look up the full font filename
|
||||
const fontFile = data.files.find(file => this.callbacks.path.basename(file.name.toLowerCase()) == fontFilename.toLowerCase());
|
||||
if(!fontFile) {
|
||||
// font cannot be found
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotFindFontForKeyboard({id: kbd.id, fontFilename}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
// the font is a filename, included in the .kps
|
||||
const fontData = this.getMemberFileData(kpsFilename, fontFile.name);
|
||||
if(!fontData) {
|
||||
// cannot find source font
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotFindFontForKeyboard({id: kbd.id, fontFilename: fontFile.name}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
const fontFacename = getFontFamilySync(fontData);
|
||||
if(!fontFacename) {
|
||||
// Font facename could not be extracted from ttf
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotReadFont({fontFilename: fontFile.name}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
const result = setKvkFontData(kvk, fontFacename);
|
||||
if(!result) {
|
||||
// KVK is invalid
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_VisualKeyboardFileIsInvalid({filename}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy .kmp compiler would transform xml-format .kvk files into a binary .kvk file; now
|
||||
* we want that to remain the responsibility of the keyboard compiler, so we'll warn the
|
||||
* few users who are still doing this
|
||||
* we want that to remain the responsibility of the keyboard compiler.
|
||||
*/
|
||||
private warnIfKvkFileIsNotBinary(filename: string, data: Uint8Array) {
|
||||
if(!KeymanFileTypes.filenameIs(filename, KeymanFileTypes.Binary.VisualKeyboard)) {
|
||||
return;
|
||||
}
|
||||
|
||||
private isKvkFileBinary(data: Uint8Array) {
|
||||
if(data.byteLength < 4) {
|
||||
// TODO: Not a valid .kvk file; should we be reporting this?
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must start with 'KVKF'
|
||||
if(data[0] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[0] ||
|
||||
data[1] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[1] ||
|
||||
data[2] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[2] ||
|
||||
data[3] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[3]) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_FileIsNotABinaryKvkFile({filename: filename}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
developer/src/kmc-package/src/compiler/kvk-font-facename.ts
Normal file
16
developer/src/kmc-package/src/compiler/kvk-font-facename.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { KvkFileReader, KvkFileWriter } from "@keymanapp/common-types";
|
||||
|
||||
export function setKvkFontData(fileData: Uint8Array, fontFacename: string) {
|
||||
const reader = new KvkFileReader();
|
||||
const kvk = reader.read(fileData);
|
||||
if(!kvk) {
|
||||
// kvk is invalid
|
||||
return null;
|
||||
}
|
||||
|
||||
kvk.header.ansiFont.name = fontFacename;
|
||||
kvk.header.unicodeFont.name = fontFacename;
|
||||
|
||||
const writer = new KvkFileWriter();
|
||||
return writer.write(kvk);
|
||||
}
|
||||
|
|
@ -22,16 +22,21 @@ export class PackageCompilerMessages {
|
|||
static Warn_AbsolutePath = (o:{filename: string}) => m(this.WARN_AbsolutePath, `File ${def(o.filename)} has an absolute path, which is not portable.`);
|
||||
|
||||
static ERROR_FileDoesNotExist = SevError | 0x0003;
|
||||
static Error_FileDoesNotExist = (o:{filename: string}) => m(this.ERROR_FileDoesNotExist, `File ${def(o.filename)} does not exist.`);
|
||||
static Error_FileDoesNotExist = (o:{filename: string}) => m(
|
||||
this.ERROR_FileDoesNotExist, `File '${def(o.filename)}' does not exist.`
|
||||
);
|
||||
|
||||
static ERROR_FileCouldNotBeRead = SevError | 0x0004;
|
||||
static Error_FileCouldNotBeRead = (o:{filename: string; e: any}) => m(this.ERROR_FileCouldNotBeRead,
|
||||
`File ${def(o.filename)} could not be read: ${(o.e ?? 'unknown error').toString()}.`);
|
||||
static Error_FileCouldNotBeRead = (o:{filename: string; e: any}) => m(
|
||||
this.ERROR_FileCouldNotBeRead,
|
||||
`File '${def(o.filename)}' could not be read: ${(o.e ?? 'unknown error').toString()}.`
|
||||
);
|
||||
|
||||
static WARN_FileIsNotABinaryKvkFile = SevWarn | 0x0005;
|
||||
static Warn_FileIsNotABinaryKvkFile = (o:{filename: string}) => m(this.WARN_FileIsNotABinaryKvkFile,
|
||||
`File ${def(o.filename)} does not appear to be a valid binary .kvk file; this may be an old package that includes an xml-format .kvk file. `+
|
||||
`You must update the package to include the compiled .kvk file in the package.`);
|
||||
`File '${def(o.filename)}' does not appear to be a valid binary .kvk file; this may be an old package that includes an xml-format .kvk file. `+
|
||||
`You must update the package to include the compiled .kvk file in the package.`
|
||||
);
|
||||
|
||||
static ERROR_FollowKeyboardVersionNotAllowedForModelPackages = SevError | 0x0006;
|
||||
static Error_FollowKeyboardVersionNotAllowedForModelPackages = () => m(this.ERROR_FollowKeyboardVersionNotAllowedForModelPackages,
|
||||
|
|
@ -43,15 +48,18 @@ export class PackageCompilerMessages {
|
|||
|
||||
static ERROR_KeyboardContentFileNotFound = SevError | 0x0008;
|
||||
static Error_KeyboardContentFileNotFound = (o:{id:string}) => m(this.ERROR_KeyboardContentFileNotFound,
|
||||
`Keyboard ${def(o.id)} was listed in <Keyboards> but a corresponding .kmx file was not found in <Files>`);
|
||||
`Keyboard '${def(o.id)}' was listed in <Keyboards> but a corresponding .kmx file was not found in <Files>`
|
||||
);
|
||||
|
||||
static ERROR_KeyboardFileNotValid = SevError | 0x0009;
|
||||
static Error_KeyboardFileNotValid = (o:{filename:string, e:any}) => m(this.ERROR_KeyboardFileNotValid,
|
||||
`Keyboard file ${def(o.filename)} is not a valid .kmx file: ${(o.e ?? 'unknown error').toString()}`);
|
||||
`Keyboard file '${def(o.filename)}' is not a valid .kmx file: ${(o.e ?? 'unknown error').toString()}`
|
||||
);
|
||||
|
||||
static INFO_KeyboardFileHasNoKeyboardVersion = SevInfo | 0x000A;
|
||||
static Info_KeyboardFileHasNoKeyboardVersion = (o:{filename:string}) => m(this.INFO_KeyboardFileHasNoKeyboardVersion,
|
||||
`Keyboard file ${def(o.filename)} has no &KeyboardVersion store, using default '0.0'`);
|
||||
`Keyboard file '${def(o.filename)}' has no &KeyboardVersion store, using default '0.0'`
|
||||
);
|
||||
|
||||
static ERROR_PackageCannotContainBothModelsAndKeyboards = SevError | 0x000B;
|
||||
static Error_PackageCannotContainBothModelsAndKeyboards = () => m(this.ERROR_PackageCannotContainBothModelsAndKeyboards,
|
||||
|
|
@ -63,19 +71,22 @@ export class PackageCompilerMessages {
|
|||
|
||||
static WARN_PackageNameDoesNotFollowLexicalModelConventions = SevWarn | 0x000D;
|
||||
static Warn_PackageNameDoesNotFollowLexicalModelConventions = (o:{filename: string}) => m(this.WARN_PackageNameDoesNotFollowLexicalModelConventions,
|
||||
`The package file ${def(o.filename)} does not follow the recommended model filename conventions. The name should be all lower case, `+
|
||||
`The package file '${def(o.filename)}' does not follow the recommended model filename conventions. The name should be all lower case, `+
|
||||
`include only alphanumeric characters and underscore (_), not start with a digit, and should have the structure `+
|
||||
`<author>.<bcp47>.<uniq>.model.kps.`);
|
||||
`<author>.<bcp47>.<uniq>.model.kps.`
|
||||
);
|
||||
|
||||
static WARN_PackageNameDoesNotFollowKeyboardConventions = SevWarn | 0x000E;
|
||||
static Warn_PackageNameDoesNotFollowKeyboardConventions = (o:{filename: string}) => m(this.WARN_PackageNameDoesNotFollowKeyboardConventions,
|
||||
`The package file ${def(o.filename)} does not follow the recommended keyboard filename conventions. The name should be all lower case, `+
|
||||
`include only alphanumeric characters and underscore (_), and not start with a digit.`);
|
||||
`The package file '${def(o.filename)}' does not follow the recommended keyboard filename conventions. The name should be all lower case, `+
|
||||
`include only alphanumeric characters and underscore (_), and not start with a digit.`
|
||||
);
|
||||
|
||||
static WARN_FileInPackageDoesNotFollowFilenameConventions = SevWarn | 0x000F;
|
||||
static Warn_FileInPackageDoesNotFollowFilenameConventions = (o:{filename: string}) => m(this.WARN_FileInPackageDoesNotFollowFilenameConventions,
|
||||
`The file ${def(o.filename)} does not follow the recommended filename conventions. The extension should be all lower case, `+
|
||||
`and the filename should include only alphanumeric characters, -, _, + and .`);
|
||||
`The file '${def(o.filename)}' does not follow the recommended filename conventions. The extension should be all lower case, `+
|
||||
`and the filename should include only alphanumeric characters, -, _, + and .`
|
||||
);
|
||||
|
||||
static ERROR_PackageNameCannotBeBlank = SevError | 0x0010;
|
||||
static Error_PackageNameCannotBeBlank = () => m(this.ERROR_PackageNameCannotBeBlank,
|
||||
|
|
@ -83,7 +94,8 @@ export class PackageCompilerMessages {
|
|||
|
||||
static ERROR_KeyboardFileNotFound = SevError | 0x0011;
|
||||
static Error_KeyboardFileNotFound = (o:{filename:string}) => m(this.ERROR_KeyboardFileNotFound,
|
||||
`Keyboard file ${def(o.filename)} was not found. Has it been compiled?`);
|
||||
`Keyboard file '${def(o.filename)}' was not found. Has it been compiled?`
|
||||
);
|
||||
|
||||
static WARN_KeyboardVersionsDoNotMatch = SevWarn | 0x0012;
|
||||
static Warn_KeyboardVersionsDoNotMatch = (o: {keyboard:string, version:string, firstKeyboard:string, firstVersion:string}) => m(this.WARN_KeyboardVersionsDoNotMatch,
|
||||
|
|
@ -129,7 +141,8 @@ export class PackageCompilerMessages {
|
|||
|
||||
static HINT_PackageContainsSourceFile = SevHint | 0x001D;
|
||||
static Hint_PackageContainsSourceFile = (o:{filename:string}) => m(this.HINT_PackageContainsSourceFile,
|
||||
`The source file ${def(o.filename)} should not be included in the package; instead include the compiled result.`);
|
||||
`The source file '${def(o.filename)}' should not be included in the package; instead include the compiled result.`
|
||||
);
|
||||
|
||||
// 0x001E was ERROR_InvalidPackageFile, now CommonTypesMessages.Error_InvalidPackageFile
|
||||
|
||||
|
|
@ -146,5 +159,30 @@ export class PackageCompilerMessages {
|
|||
this.ERROR_PackageFileHasEmptyVersion,
|
||||
`Package version is not following keyboard version, but the package version field is blank.`
|
||||
);
|
||||
|
||||
static ERROR_RequiredParameterMissing = SevError | 0x0022;
|
||||
static Error_RequiredParameterMissing = (o:{param: string}) => m(
|
||||
this.ERROR_RequiredParameterMissing, `Source parameter '${def(o.param)}' is required.`
|
||||
);
|
||||
|
||||
static WARN_CannotFindMatchingKeyboardForVisualKeyboard = SevWarn | 0x0023;
|
||||
static Warn_CannotFindMatchingKeyboardForVisualKeyboard = (o:{filename: string}) => m(
|
||||
this.WARN_CannotFindMatchingKeyboardForVisualKeyboard, `Cannot find matching keyboard for visual keyboard file '${def(o.filename)}'.`
|
||||
);
|
||||
|
||||
static WARN_CannotFindFontForKeyboard = SevWarn | 0x0024;
|
||||
static Warn_CannotFindFontForKeyboard = (o:{id: string, fontFilename: string}) => m(
|
||||
this.WARN_CannotFindFontForKeyboard, `Cannot find font file '${def(o.fontFilename)}' for keyboard '${def(o.id)}'.`
|
||||
);
|
||||
|
||||
static WARN_CannotReadFont = SevWarn | 0x0025;
|
||||
static Warn_CannotReadFont = (o:{fontFilename: string}) => m(
|
||||
this.WARN_CannotReadFont, `Cannot parse font file '${def(o.fontFilename)}' to get facename.`
|
||||
);
|
||||
|
||||
static WARN_VisualKeyboardFileIsInvalid = SevWarn | 0x0026;
|
||||
static Warn_VisualKeyboardFileIsInvalid = (o:{filename: string}) => m(
|
||||
this.WARN_VisualKeyboardFileIsInvalid, `Visual keyboard file '${def(o.filename)}' is invalid.`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,15 @@ export class WindowsPackageInstallerCompiler implements KeymanCompiler {
|
|||
}
|
||||
|
||||
// Check existence of required files
|
||||
for(const filename of [sources.licenseFilename, sources.msiFilename, sources.setupExeFilename]) {
|
||||
for(const [param,filename] of [
|
||||
['licenseFilename',sources.licenseFilename],
|
||||
['msiFilename',sources.msiFilename],
|
||||
['setupExeFilename',sources.setupExeFilename]
|
||||
]) {
|
||||
if(!filename) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_RequiredParameterMissing({param}));
|
||||
return null;
|
||||
}
|
||||
if(!this.callbacks.fs.existsSync(filename)) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileDoesNotExist({filename}));
|
||||
return null;
|
||||
|
|
@ -186,6 +194,11 @@ export class WindowsPackageInstallerCompiler implements KeymanCompiler {
|
|||
|
||||
private async buildZip(kps: KpsFile.KpsFile, kpsFilename: string, sources: WindowsPackageInstallerSources): Promise<Uint8Array> {
|
||||
const kmpJson: KmpJsonFile.KmpJsonFile = this.kmpCompiler.transformKpsFileToKmpObject(kpsFilename, kps);
|
||||
if(!kmpJson) {
|
||||
// error will have been reported in transformKpsFileToKmpObject
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!kmpJson.info?.name?.description) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_PackageNameCannotBeBlank());
|
||||
return null;
|
||||
|
|
@ -194,6 +207,10 @@ export class WindowsPackageInstallerCompiler implements KeymanCompiler {
|
|||
const kmpFilename = this.callbacks.path.basename(kpsFilename, KeymanFileTypes.Source.Package) + KeymanFileTypes.Binary.Package;
|
||||
const setupInfBuffer = this.buildSetupInf(sources, kmpJson, kmpFilename, kps);
|
||||
const kmpBuffer = await this.kmpCompiler.buildKmpFile(kpsFilename, kmpJson);
|
||||
if(!kmpBuffer) {
|
||||
// error will have been reported in buildKmpFile
|
||||
return null;
|
||||
}
|
||||
|
||||
// Note that this does not technically generate a "valid" sfx according to
|
||||
// the zip spec, because the offsets in the .zip are relative to the start
|
||||
|
|
|
|||
BIN
developer/src/kmc-package/test/fixtures/binary_kvk_file/source/binary_kvk_file.kmx
vendored
Normal file
BIN
developer/src/kmc-package/test/fixtures/binary_kvk_file/source/binary_kvk_file.kmx
vendored
Normal file
Binary file not shown.
|
|
@ -27,7 +27,7 @@
|
|||
<FileType>.kvk</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>../../invalid/basic.kmx</Name>
|
||||
<Name>binary_kvk_file.kmx</Name>
|
||||
<Description>Keyboard Basic</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kmx</FileType>
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
<Keyboards>
|
||||
<Keyboard>
|
||||
<Name>Basic</Name>
|
||||
<ID>basic</ID>
|
||||
<ID>binary_kvk_file</ID>
|
||||
<Version>1.0</Version>
|
||||
<Languages>
|
||||
<Language ID="KM">Khmer</Language>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ function do_build() {
|
|||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
function do_test() {
|
||||
builder_do_typescript_tests 50
|
||||
./test/command-line-tests.sh test
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
function do_api() {
|
||||
rm -rf ./build/messages
|
||||
mkdir -p ./build/messages
|
||||
|
|
@ -99,7 +106,7 @@ function do_bundle() {
|
|||
builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo
|
||||
builder_run_action configure verify_npm_setup
|
||||
builder_run_action build do_build
|
||||
builder_run_action test builder_do_typescript_tests 50
|
||||
builder_run_action test do_test
|
||||
builder_run_action api do_api
|
||||
builder_run_action bundle do_bundle
|
||||
builder_run_action publish builder_publish_npm
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ If no input file is supplied, kmc will build the current folder.`)
|
|||
buildCommand
|
||||
.command('windows-package-installer <infile>')
|
||||
.description('Build an executable installer for Windows for a Keyman package')
|
||||
.option('--msi <msiFilename>', 'Location of keymandesktop.msi')
|
||||
.option('--exe <exeFilename>', 'Location of setup.exe')
|
||||
.option('--license <licenseFilename>', 'Location of license.txt')
|
||||
.requiredOption('--msi <msiFilename>', 'Location of keymandesktop.msi')
|
||||
.requiredOption('--exe <exeFilename>', 'Location of setup.exe')
|
||||
.requiredOption('--license <licenseFilename>', 'Location of license.txt')
|
||||
.option('--title-image [titleImageFilename]', 'Location of title image')
|
||||
.option('--app-name [applicationName]', 'Installer property: name of the application to be installed', 'Keyman')
|
||||
.option('--start-disabled', 'Installer property: do not enable keyboards after installation completes')
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function declareGenerateLdmlKeyboard(command: Command) {
|
|||
.option('-o, --out-path <path>', 'Output path (may exist)')
|
||||
.option('-n, --name <name>', 'Keyboard descriptive name')
|
||||
.option('-c, --copyright <copyright-name>', 'Copyright holder') /* © yyyy <copyright-name> */
|
||||
.option('-v, --version <version-string>', 'Keyboard version', '1.0')
|
||||
.option('-v, --version <version-string>', 'Keyboard version', '1.0.0')
|
||||
.option('-L, --language-tag <bcp-47 tag>', 'BCP-47 language tag',
|
||||
(value, previous) => previous.concat([value]), [])
|
||||
.option('-a, --author <author-name>', 'Name of keyboard author')
|
||||
|
|
@ -59,12 +59,12 @@ function declareGenerateLexicalModel(command: Command) {
|
|||
modelSubCommand
|
||||
.description('Generate a wordlist lexical model project')
|
||||
.option('-o, --out-path <path>', 'Output path (may exist)')
|
||||
.option('-n, --name <name>', 'Keyboard descriptive name')
|
||||
.option('-n, --name <name>', 'Lexical model descriptive name')
|
||||
.option('-c, --copyright <copyright-name>', 'Copyright holder') /* © yyyy <copyright-name> */
|
||||
.option('-v, --version <version-string>', 'Keyboard version', '1.0')
|
||||
.option('-v, --version <version-string>', 'Lexical model version', '1.0')
|
||||
.option('-L, --language-tag <bcp-47 tag>', 'BCP-47 language tag',
|
||||
(value, previous) => previous.concat([value]), [])
|
||||
.option('-a, --author <author-name>', 'Name of keyboard author')
|
||||
.option('-a, --author <author-name>', 'Name of lexical model author')
|
||||
.option('-d, --description <description>', 'Short description of the project, Markdown')
|
||||
.action(generateLexicalModel);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,23 @@ await TestKeymanSentry.runTestIfCLRequested(kmcSentryOptions);
|
|||
if(KeymanSentry.isEnabled()) {
|
||||
KeymanSentry.init(kmcSentryOptions);
|
||||
}
|
||||
try {
|
||||
await run();
|
||||
} catch(e) {
|
||||
KeymanSentry.captureException(e);
|
||||
}
|
||||
|
||||
// Ensure any messages reported to Sentry have had time to be uploaded before we
|
||||
// exit. In most cases, this will be a no-op so should not affect performance.
|
||||
await exitProcess(0);
|
||||
run().then(async () => {
|
||||
// Ensure any messages reported to Sentry have had time to be uploaded before we
|
||||
// exit. In most cases, this will be a no-op so should not affect performance.
|
||||
await exitProcess(0);
|
||||
}, (reason: any) => {
|
||||
KeymanSentry.captureException(reason);
|
||||
// in local environment, captureException will
|
||||
// return so we want to re-throw; note that this is
|
||||
// a little noisy because it comes from an async function
|
||||
console.error('Aborting due to fatal exception. Local development environment detected, so printing trace.');
|
||||
const report: any = process.report?.getReport(reason);
|
||||
console.log(report?.javascriptStack?.message);
|
||||
console.log(report?.javascriptStack?.stack?.map((s: string) => ' '+s).join('\n'));
|
||||
// This cannot be an async function because otherwise the stack trace gets captured
|
||||
exitProcess(1);
|
||||
});
|
||||
|
||||
async function run() {
|
||||
await loadOptions();
|
||||
|
|
@ -62,6 +70,5 @@ async function run() {
|
|||
declarePublish(program);
|
||||
*/
|
||||
|
||||
await program.parseAsync(process.argv)
|
||||
.catch(reason => console.error(reason));
|
||||
await program.parseAsync(process.argv);
|
||||
}
|
||||
147
developer/src/kmc/test/command-line-tests.sh
Executable file
147
developer/src/kmc/test/command-line-tests.sh
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Command line tests for kmc
|
||||
#
|
||||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
# . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
# . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
# . "$KEYMAN_ROOT/developer/src/packages.inc.sh"
|
||||
|
||||
builder_describe "Test Keyman Keyboard Compiler kmc command line e2e" \
|
||||
"test" \
|
||||
"--verbose,-v Verbose logging of output from kmc" \
|
||||
"--filter,-f=FILTER Run tests that match FILTER" \
|
||||
":basic Basic sanity tests" \
|
||||
":analyze Test 'kmc analyze' command" \
|
||||
":build Test 'kmc build' command" \
|
||||
":copy Test 'kmc copy' command'" \
|
||||
":generate Test 'kmc generate' command" \
|
||||
":message Test 'kmc message' command"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
if [[ -z ${KMC+x} ]]; then
|
||||
KMC="node .."
|
||||
fi
|
||||
|
||||
FAILED_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
SKIPPED_TESTS=0
|
||||
|
||||
function run_kmc() {
|
||||
local EXPECTED_EXIT=$1
|
||||
local EXPECTED_MATCH="$2"
|
||||
shift 2
|
||||
local ACTUAL_EXIT=0
|
||||
local MATCH_RESULT=0
|
||||
local DATA
|
||||
|
||||
if builder_has_option --filter; then
|
||||
if [[ ! "$@" =~ "$FILTER" ]]; then
|
||||
builder_echo blue "🟦 SKIP 'kmc $@'"
|
||||
SKIPPED_TESTS=$((SKIPPED_TESTS + 1))
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
DATA=$($KMC "$@" 2>&1) || ACTUAL_EXIT=$?
|
||||
echo "$DATA" | grep -qF "$EXPECTED_MATCH" || MATCH_RESULT=$?
|
||||
|
||||
if [[ $ACTUAL_EXIT != $EXPECTED_EXIT ]]; then
|
||||
builder_echo red "❌ FAIL 'kmc $@': expected exit code $EXPECTED_EXIT != actual exit code $ACTUAL_EXIT"
|
||||
builder_echo grey "Actual output from 'kmc $@':"
|
||||
echo "$DATA"
|
||||
FAILED_TESTS=$(($FAILED_TESTS + 1))
|
||||
elif [[ $MATCH_RESULT != 0 ]]; then
|
||||
builder_echo red "❌ FAIL 'kmc $@': expected string '$EXPECTED_MATCH' not found in output"
|
||||
builder_echo grey "Actual output from 'kmc $@':"
|
||||
echo "$DATA"
|
||||
FAILED_TESTS=$(($FAILED_TESTS + 1))
|
||||
else
|
||||
builder_echo "${COLOR_GREEN}✔${COLOR_RESET} PASS 'kmc $@'"
|
||||
if builder_has_option --verbose; then
|
||||
builder_echo grey "Actual output from 'kmc $@':"
|
||||
echo "$DATA"
|
||||
fi
|
||||
PASSED_TESTS=$(($PASSED_TESTS + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
function do_test_basic() {
|
||||
run_kmc 0 "Keyman Developer Command Line Interface" --help
|
||||
run_kmc 0 "kmc build [options] [command]" build --help
|
||||
run_kmc 1 "error: unknown option '--bad-option'" --bad-option
|
||||
}
|
||||
|
||||
function build_cleanup() {
|
||||
rm -rf ./fixtures/command-line-tests/build/khmer_angkor/build/
|
||||
}
|
||||
|
||||
function do_test_build() {
|
||||
# project/file builds
|
||||
build_cleanup
|
||||
run_kmc 0 "." build ./fixtures/command-line-tests/build/khmer_angkor/khmer_angkor.kpj
|
||||
|
||||
build_cleanup
|
||||
run_kmc 0 "." build file ./fixtures/command-line-tests/build/khmer_angkor/khmer_angkor.kpj
|
||||
|
||||
build_cleanup
|
||||
run_kmc 0 "." build ./fixtures/command-line-tests/build/khmer_angkor/
|
||||
|
||||
# windows-package-installer tests
|
||||
# TODO: windows-package-installer is currently silent but should emit a 'success' message
|
||||
#build_cleanup
|
||||
run_kmc 0 "" build windows-package-installer \
|
||||
./fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kps \
|
||||
--msi ./fixtures/command-line-tests/build/windows-installer/keymandesktop.txt \
|
||||
--exe ./fixtures/command-line-tests/build/windows-installer/setup.txt \
|
||||
--license ./fixtures/command-line-tests/build/windows-installer/license.txt \
|
||||
--out-file ./fixtures/command-line-tests/build/khmer_angkor/build/khmer_angkor.exe
|
||||
|
||||
#build_cleanup
|
||||
run_kmc 1 "" build windows-package-installer \
|
||||
./fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kps \
|
||||
--msi ./fixtures/command-line-tests/build/windows-installer/keymandesktop.txt \
|
||||
--exe ./fixtures/command-line-tests/build/windows-installer/setup.txt \
|
||||
--out-file ./fixtures/command-line-tests/build/khmer_angkor/build/khmer_angkor.exe
|
||||
}
|
||||
|
||||
function do_test_analyze() {
|
||||
builder_heading "kmc analyze"
|
||||
}
|
||||
|
||||
function do_test_copy() {
|
||||
builder_heading "kmc copy"
|
||||
}
|
||||
|
||||
function do_test_generate() {
|
||||
builder_heading "kmc generate"
|
||||
}
|
||||
|
||||
function do_test_message() {
|
||||
run_kmc 1 "Invalid parameter" message zap
|
||||
run_kmc 0 "ERROR_FileDoesNotExist" message 4003
|
||||
}
|
||||
|
||||
builder_run_action test:basic do_test_basic
|
||||
builder_run_action test:analyze do_test_analyze
|
||||
builder_run_action test:build do_test_build
|
||||
builder_run_action test:copy do_test_copy
|
||||
builder_run_action test:generate do_test_generate
|
||||
builder_run_action test:message do_test_message
|
||||
|
||||
if builder_has_action test; then
|
||||
builder_echo grey "$PASSED_TESTS test(s) passed"
|
||||
builder_echo grey "$SKIPPED_TESTS test(s) skipped"
|
||||
if [[ $FAILED_TESTS != 0 ]]; then
|
||||
builder_echo red "$FAILED_TESTS test(s) failed"
|
||||
exit 1
|
||||
else
|
||||
builder_echo "0 test(s) failed"
|
||||
fi
|
||||
fi
|
||||
1
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/.gitignore
vendored
Normal file
1
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build/
|
||||
12
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/khmer_angkor.kpj
vendored
Normal file
12
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/khmer_angkor.kpj
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeymanDeveloperProject>
|
||||
<Options>
|
||||
<BuildPath>$PROJECTPATH\build</BuildPath>
|
||||
<SourcePath>$PROJECTPATH\source</SourcePath>
|
||||
<CompilerWarningsAsErrors>True</CompilerWarningsAsErrors>
|
||||
<WarnDeprecatedCode>True</WarnDeprecatedCode>
|
||||
<CheckFilenameConventions>False</CheckFilenameConventions>
|
||||
<ProjectType>keyboard</ProjectType>
|
||||
<Version>2.0</Version>
|
||||
</Options>
|
||||
</KeymanDeveloperProject>
|
||||
BIN
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.ico
vendored
Normal file
BIN
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load diff
642
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kmn
vendored
Normal file
642
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kmn
vendored
Normal file
|
|
@ -0,0 +1,642 @@
|
|||
store(&VERSION) '10.0'
|
||||
store(&NAME) "Khmer Angkor"
|
||||
store(©RIGHT) '© 2015-2022 SIL International'
|
||||
store(&MESSAGE) "More than just a Khmer Unicode keyboard."
|
||||
store(&TARGETS) 'any'
|
||||
store(&LAYOUTFILE) 'khmer_angkor.keyman-touch-layout'
|
||||
store(&KEYBOARDVERSION) '1.3'
|
||||
store(&BITMAP) 'khmer_angkor.ico'
|
||||
store(&VISUALKEYBOARD) 'khmer_angkor.kvks'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
|
||||
|
||||
c ==============================================STORES==============================================
|
||||
|
||||
|
||||
|
||||
c 33 consonants and two deprecated consonants
|
||||
|
||||
store(c_key) [K_K] [K_X] [SHIFT K_K] [SHIFT K_X] [K_G] \
|
||||
[K_C] [K_Q] [SHIFT K_C] [SHIFT K_Q] [SHIFT K_J] \
|
||||
[K_D] [K_Z] [SHIFT K_D] [SHIFT K_Z] [SHIFT K_N] \
|
||||
[K_T] [K_F] [SHIFT K_T] [SHIFT K_F] [K_N] \
|
||||
[K_B] [K_P] [SHIFT K_B] [SHIFT K_P] [K_M] \
|
||||
[K_Y] [K_R] [K_L] [K_V] [K_S] [K_H] [SHIFT K_L] [SHIFT K_G] \
|
||||
[RALT K_K] [RALT K_B]
|
||||
store(c_out) U+1780 U+1781 U+1782 U+1783 U+1784 \
|
||||
U+1785 U+1786 U+1787 U+1788 U+1789 \
|
||||
U+178A U+178B U+178C U+178D U+178E \
|
||||
U+178F U+1790 U+1791 U+1792 U+1793 \
|
||||
U+1794 U+1795 U+1796 U+1797 U+1798 \
|
||||
U+1799 U+179A U+179B U+179C U+179F U+17A0 U+17A1 U+17A2 \
|
||||
U+179D U+179E c deprecated, but they are used in minority languages
|
||||
|
||||
c All genuine dependent vowels
|
||||
|
||||
store(v_gen_key) [K_A] [K_I] [SHIFT K_I] [K_W] [SHIFT K_W] [K_U] [SHIFT K_U] [SHIFT K_Y] \
|
||||
[K_COLON] [SHIFT K_LBRKT] [K_LBRKT] [K_E] [SHIFT K_E] [SHIFT K_S] [K_O] [SHIFT K_O]
|
||||
store(v_gen) U+17B6 U+17B7 U+17B8 U+17B9 U+17BA U+17BB U+17BC U+17BD \
|
||||
U+17BE U+17BF U+17C0 U+17C1 U+17C2 U+17C3 U+17C4 U+17C5
|
||||
|
||||
c All pseudo dependent vowels
|
||||
|
||||
store(v_pseudo_key) [SHIFT K_M] [SHIFT K_H] [RALT K_QUOTE]
|
||||
store(v_pseudo) U+17C6 U+17C7 U+17C8
|
||||
|
||||
c Both genuine and pseudo vowels
|
||||
|
||||
store(v_key) outs(v_gen_key) outs(v_pseudo_key)
|
||||
store(v_out) outs(v_gen) outs(v_pseudo)
|
||||
store(v_any) outs(v_out)
|
||||
|
||||
c These seven vowels can be concatenated with Reahmuk (U+17C7) to make េះ ោះ ុះ ិះ ីះ ឹះ and ែះ.
|
||||
|
||||
store(v_combo_R) U+17C1 U+17C4 U+17BB U+17B7 U+17B8 U+17B9 U+17C2
|
||||
|
||||
c these two vowels can be concatenated with Nikahit (U+17C6) to make ាំ and ុំ.
|
||||
|
||||
store(v_combo_N) U+17B6 U+17BB
|
||||
store(v_combo) outs(v_combo_R) outs(v_combo_N)
|
||||
|
||||
c Independent vowels
|
||||
|
||||
store(ind_v_key) [K_HYPHEN] [RALT K_I] [SHIFT K_RBRKT] [RALT K_T] [RALT K_LBRKT] [K_RBRKT] [RALT K_R] [SHIFT K_R] \
|
||||
[SHIFT K_BKSLASH] [K_BKSLASH] [RALT K_E] [RALT K_P] [RALT K_O] [K_EQUAL] [RALT K_RBRKT]
|
||||
store(ind_v_out) U+17A5 U+17A6 U+17A7 U+17A8 U+17A9 U+17AA U+17AB U+17AC \
|
||||
U+17AD U+17AE U+17AF U+17B0 U+17B1 U+17B2 U+17B3
|
||||
|
||||
c Diacritics
|
||||
|
||||
store(diacritic_key) [K_QUOTE] [SHIFT K_7] [SHIFT K_HYPHEN] [SHIFT K_8] [SHIFT K_6] [RALT K_QUOTE] \
|
||||
[RALT K_EQUAL] [RALT K_3] [RALT K_W] [RALT K_Q] [K_J]
|
||||
store(diacritic_out) U+17CB U+17D0 U+17CC U+17CF U+17CD U+17C8 \
|
||||
U+17CE U+17D1 U+17DD U+17DC U+17D2
|
||||
|
||||
c Consonant shifter--Muusikatoan(U+17C9) and Triisap(U+17CA)
|
||||
|
||||
store(c_shifter_key) [SHIFT K_QUOTE] [K_SLASH]
|
||||
store(c_shifter) U+17C9 U+17CA
|
||||
|
||||
c punctuations
|
||||
|
||||
store(punct_key) [K_PERIOD] [SHIFT K_PERIOD] [RALT K_COLON] [SHIFT K_2] [RALT K_L] [RALT K_6] [RALT K_7] [RALT K_M]
|
||||
store(punct_out) U+17D4 U+17D5 U+17D6 U+17D7 U+17D8 U+17D9 U+17DA U+17D3
|
||||
|
||||
store(latin_punct_key) [K_BKQUOTE] [SHIFT K_BKQUOTE] [SHIFT K_9] [SHIFT K_0] [SHIFT K_1] [SHIFT K_3] \
|
||||
[SHIFT K_5] [SHIFT K_EQUAL] [SHIFT K_SLASH] [RALT K_9] [RALT K_0] [RALT K_BKSLASH] \
|
||||
[RALT K_2] [RALT K_8] [RALT K_COMMA] [RALT K_D] [RALT K_PERIOD] [RALT K_SLASH] \
|
||||
[RALT K_Y] [RALT K_U] [RALT K_BKQUOTE] [RALT K_1] [RALT K_A] [RALT K_S] \
|
||||
[RALT K_F] [RALT K_G] [RALT K_HYPHEN] [RALT K_H] [RALT K_J] [RALT K_N] \
|
||||
[RALT K_Z] [RALT K_X] [RALT K_C] [RALT K_V]
|
||||
store(latin_punct_out) U+00AB U+00BB U+0028 U+0029 U+0021 U+0022 \
|
||||
U+0025 U+003D U+003F U+007B U+007D U+005C \
|
||||
U+0040 U+002A U+002C U+00D7 U+002E U+002F \
|
||||
U+005B U+005D U+200D U+200C U+002B U+002D \
|
||||
U+00F7 U+003A U+2248 U+2018 U+2019 U+003B \
|
||||
U+003C U+003E U+0023 U+0026
|
||||
c white space, ZWSP, ZWJ
|
||||
store(spaces_key) [K_SPACE] [SHIFT K_SPACE] [RALT K_SPACE]
|
||||
store(spaces_out) U+200B U+0020 U+00A0
|
||||
|
||||
c Currency: Riel, Dollar and Euro
|
||||
|
||||
store(currency_key) [SHIFT K_4] [RALT K_4] [RALT K_5]
|
||||
store(currency_out) U+17DB U+0024 U+20AC
|
||||
|
||||
c Khmer digits 0 through 9
|
||||
|
||||
store(digit_key) [K_0] [K_1] [K_2] [K_3] [K_4] [K_5] [K_6] [K_7] [K_8] [K_9]
|
||||
store(digit_out) U+17E0 U+17E1 U+17E2 U+17E3 U+17E4 U+17E5 U+17E6 U+17E7 U+17E8 U+17E9
|
||||
|
||||
c Khmer Lek Attak (a.k.a. divination lore)
|
||||
store(lek_attak_key) [SHIFT RALT K_0] [SHIFT RALT K_1] [SHIFT RALT K_2] [SHIFT RALT K_3] [SHIFT RALT K_4] \
|
||||
[SHIFT RALT K_5] [SHIFT RALT K_6] [SHIFT RALT K_7] [SHIFT RALT K_8] [SHIFT RALT K_9]
|
||||
store(lek_attak_out) U+17F0 U+17F1 U+17F2 U+17F3 U+17F4 \
|
||||
U+17F5 U+17F6 U+17F7 U+17F8 U+17F9
|
||||
|
||||
c Lunar date format (Khmer style)
|
||||
|
||||
store(lunar_date_key) [SHIFT RALT K_A] [SHIFT RALT K_B] [SHIFT RALT K_C] [SHIFT RALT K_D] [SHIFT RALT K_E] \
|
||||
[SHIFT RALT K_F] [SHIFT RALT K_G] [SHIFT RALT K_H] [SHIFT RALT K_I] [SHIFT RALT K_J] \
|
||||
[SHIFT RALT K_K] [SHIFT RALT K_L] [SHIFT RALT K_M] [SHIFT RALT K_N] [SHIFT RALT K_O] \
|
||||
[SHIFT RALT K_P] [SHIFT RALT K_Q] [SHIFT RALT K_R] [SHIFT RALT K_S] [SHIFT RALT K_T] \
|
||||
[SHIFT RALT K_U] [SHIFT RALT K_V] [SHIFT RALT K_W] [SHIFT RALT K_X] [SHIFT RALT K_Y] \
|
||||
[SHIFT RALT K_Z] [SHIFT RALT K_COLON] [SHIFT RALT K_COMMA] [SHIFT RALT K_PERIOD] [SHIFT RALT K_LBRKT] \
|
||||
[SHIFT RALT K_RBRKT] [SHIFT RALT K_QUOTE]
|
||||
store(lunar_date_out) U+19EC U+19FB U+19F9 U+19EE U+19E2 \
|
||||
U+19EF U+19F0 U+19F1 U+19E7 U+19F2 \
|
||||
U+19F3 U+19F4 U+19FD U+19FC U+19E8 \
|
||||
U+19E9 U+19E0 U+19E3 U+19ED U+19E4 \
|
||||
U+19E6 U+19FA U+19E1 U+19F8 U+19E5 \
|
||||
U+19F7 U+19F5 U+19FE U+19FF U+19EA \
|
||||
U+19EB U+19F6
|
||||
|
||||
c Subscripts for mobile/table layouts
|
||||
|
||||
store(input_subcons) \
|
||||
[T_17D2_1780] [T_17D2_1781] [T_17D2_1782] [T_17D2_1783] [T_17D2_1784] [T_17D2_1785] [T_17D2_1786] [T_17D2_1787] \
|
||||
[T_17D2_1788] [T_17D2_1789] [T_17D2_178A] [T_17D2_178B] [T_17D2_178C] [T_17D2_178D] [T_17D2_178E] [T_17D2_178F] \
|
||||
[T_17D2_1790] [T_17D2_1791] [T_17D2_1792] [T_17D2_1793] [T_17D2_1794] [T_17D2_1795] [T_17D2_1796] [T_17D2_1797] \
|
||||
[T_17D2_1798] [T_17D2_1799] [T_17D2_179A] [T_17D2_179B] [T_17D2_179C] [T_17D2_179D] [T_17D2_179E] [T_17D2_179F] \
|
||||
[T_17D2_17A0] [T_17D2_17A1] [T_17D2_17A2]
|
||||
|
||||
store(subcons) \
|
||||
U+1780 U+1781 U+1782 U+1783 U+1784 U+1785 U+1786 U+1787 \
|
||||
U+1788 U+1789 U+178A U+178B U+178C U+178D U+178E U+178F \
|
||||
U+1790 U+1791 U+1792 U+1793 U+1794 U+1795 U+1796 U+1797 \
|
||||
U+1798 U+1799 U+179A U+179B U+179C U+179D U+179E U+179F \
|
||||
U+17A0 U+17A1 U+17A2
|
||||
|
||||
c Arabic digits 0 through 9
|
||||
|
||||
store(arabic_digit_key) [U_0030] [U_0031] [U_0032] [U_0033] [U_0034] [U_0035] [U_0036] [U_0037] [U_0038] [U_0039]
|
||||
store(arabic_digit_out) U+0030 U+0031 U+0032 U+0033 U+0034 U+0035 U+0036 U+0037 U+0038 U+0039
|
||||
|
||||
c These stores are for consonant (cluster) and consonant shifter configuration
|
||||
|
||||
store(v_above) U+17B7 U+17B8 U+17B9 U+17BA \
|
||||
U+17BE U+17D0 c These vowels are rendered above the base consonant.
|
||||
store(shiftable_c_1st) U+179F U+17A0 U+17A2 c These consonants shift to the 2nd series when adding U+17CA to them.
|
||||
store(shiftable_BA) U+1794 c BA is unique because it can be used with both U+17CA (shift to 2nd) and and U+179C (shift to PA, still in 1st series).
|
||||
store(shiftable_c_2nd) U+1784 U+1789 U+1798 U+1799 \
|
||||
U+179A U+179C U+1793 U+179B c These consonants shift to the 1st series when adding U+17C9 to them.
|
||||
store(shiftable_c_2nd_with_BA) outs(shiftable_c_2nd) outs(shiftable_BA)
|
||||
store(c_2nd_combo_LO) U+1799 U+1798 U+1784 U+1794 \
|
||||
U+179C c These can be combined with U+179B to make a 2nd series consonant cluster.
|
||||
store(c_2nd_combo_MO) U+1799 U+179B U+1784 U+179A c These can be combined with U+1798 to make a 2nd series consonant cluster.
|
||||
store(c_1st_combo_LO) U+1794 U+17A0 U+17A2 c they can be combined with U+179B to make a 1st series consonant cluster.
|
||||
store(c_1st_combo_MO) U+17A0 U+179F U+17A2 c they can be combined with U+1798 to make a 1st series consonant cluster.
|
||||
store(c_combo_SA) U+1794 U+1799 U+179B U+1798 \
|
||||
U+1793 U+1789 U+1784 U+179A \
|
||||
U+179C U+17A2 c they can be combined with U+179F to make a 1st series consonant cluster.
|
||||
store(c_combo_QA) U+1786 U+1788 U+1794 U+1795 \
|
||||
U+178F U+1791 c they can be combined with U+17A2 to make a 1st series consonant cluster.
|
||||
store(c_combo_HA) U+179C U+17A3 c they can be combined with U+17A0 to make a 1st series consonant cluster.
|
||||
|
||||
|
||||
|
||||
c ==============================================BASIC RULES & CONSTRAINTS/ROTATIONS==============================================
|
||||
|
||||
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c Basic rules with RALT key
|
||||
|
||||
+ any(c_key) > index(c_out,1)
|
||||
+ any(v_key) > index(v_out,1)
|
||||
+ any(ind_v_key) > index(ind_v_out,1)
|
||||
+ any(diacritic_key) > index(diacritic_out,1)
|
||||
+ any(c_shifter_key) > index(c_shifter,1)
|
||||
+ any(punct_key) > index(punct_out,1)
|
||||
+ any(latin_punct_key) > index(latin_punct_out,1)
|
||||
+ any(currency_key) > index(currency_out,1)
|
||||
+ any(digit_key) > index(digit_out,1)
|
||||
+ any(lek_attak_key) > index(lek_attak_out,1)
|
||||
+ any(lunar_date_key) > index(lunar_date_out,1)
|
||||
+ any(spaces_key) > index(spaces_out,1)
|
||||
|
||||
c two-unicode-code-point vowels
|
||||
|
||||
+ [SHIFT K_A] > U+17B6 U+17C6
|
||||
+ [SHIFT K_V] > U+17C1 U+17C7
|
||||
+ [SHIFT K_COLON] > U+17C4 U+17C7
|
||||
+ [K_COMMA] > U+17BB U+17C6
|
||||
+ [SHIFT K_COMMA] > U+17BB U+17C7
|
||||
|
||||
c Constraints
|
||||
|
||||
any(v_gen) + [K_QUOTE] > context beep c no Bantoc after any genuine vowel
|
||||
any(v_pseudo) + [K_QUOTE] > context beep c no Bantoc after any pseudo vowel
|
||||
any(c_shifter) + [K_QUOTE] > context beep c no Bantoc after Triisap/Muusikatoan
|
||||
U+17D2 any(c_out) + [K_QUOTE] > context beep c no Bantoc on a subscript
|
||||
any(c_shifter) + any(c_shifter_key) > context beep c no two Triisap/Muusikatoan
|
||||
|
||||
c Rotation
|
||||
|
||||
U+17C7 + [SHIFT K_H] > U+17C8 c U+17C7 is changed to U+17C8 when pressing [SHIFT K_H].
|
||||
U+17C8 + [SHIFT K_H] > U+17C7 c U+17C8 is changed to U+17C7 when K_H is pressed.
|
||||
c any(v_gen) U+17C7 + [SHIFT K_H] > context beep c Don't rotate between U+17C7 and U+17C8.
|
||||
|
||||
c Press Space twice to get a White Space
|
||||
|
||||
U+200B + [K_SPACE] > U+0020
|
||||
|
||||
c One backspace deletes a two-part vowels (i.e. psuedoVowel + U+17C7)
|
||||
|
||||
any(v_combo_N) U+17C6 + [K_BKSP] > nul
|
||||
any(v_combo_R) U+17C7 + [K_BKSP] > nul
|
||||
|
||||
c When pressing [K_U] after ាំ and ុំ, the U+17BB is converted to a respective consonant shifter placing before them (ាំ and ុំ).
|
||||
|
||||
any(shiftable_c_1st) U+17B6 U+17C6 + [K_U] > context(1) U+17CA context(2) context(3) c fix ស ាំ ុ > ស៊ាំ
|
||||
any(shiftable_c_2nd_with_BA) U+17B6 U+17C6 + [K_U] > context(1) U+17C9 context(2) context(3) c fix ម ាំ ុ > ម៉ាំ
|
||||
|
||||
c Consonant shifter constraints (beep when the consonant shifter is used in inappropriate environment)
|
||||
|
||||
any(shiftable_c_1st) + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
any(shiftable_c_2nd_with_BA) + [K_SLASH] > context U+17CA beep
|
||||
|
||||
c 1st series consonant clusters
|
||||
|
||||
any(c_combo_QA) U+17D2 U+17A2 + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
U+179B U+17D2 any(c_1st_combo_LO) + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
U+179F U+17D2 any(c_combo_SA) + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
any(c_combo_HA) U+17D2 U+17A0 + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
U+17A2 U+17D2 U+1784 + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
U+17A2 U+17D2 U+179C + [SHIFT K_QUOTE] > context U+17C9 beep
|
||||
|
||||
c 2nd series consonant clusters
|
||||
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) + [K_SLASH] > context U+17CA beep
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) + [K_SLASH] > context U+17CA beep
|
||||
|
||||
|
||||
c ==============================================MOBILE/TABLET TOUCH LAYOUT==============================================
|
||||
|
||||
|
||||
|
||||
+ any(input_subcons) > U+17D2 index(subcons, 1)
|
||||
|
||||
c delete a subscript (a subscript sign + a consonant) upon a backspace
|
||||
|
||||
platform('touch') U+17D2 any(c_out) + [K_BKSP] > nul c working OK
|
||||
|
||||
+ [K_NPSTAR] > U+002A
|
||||
+ [SHIFT K_NPSTAR] > U+002A
|
||||
+ [K_NPPLUS] > U+002B
|
||||
+ [SHIFT K_NPPLUS] > U+002B
|
||||
+ [K_NPMINUS] > U+002D
|
||||
+ [SHIFT K_NPMINUS] > U+002D
|
||||
+ [K_NPDOT] > U+002E
|
||||
+ [SHIFT K_NPDOT] > U+002E
|
||||
+ [K_NPSLASH] > U+002F
|
||||
+ [SHIFT K_NPSLASH] > U+002F
|
||||
|
||||
|
||||
|
||||
c ==============================================NORMALIZATION==============================================
|
||||
|
||||
|
||||
|
||||
match > use(normalise)
|
||||
|
||||
group(normalise)
|
||||
|
||||
c Illegitimate vowel combinations should be transformed to the legitimate ones (Case #6 and #7).
|
||||
|
||||
U+17C1 U+17B6 > U+17C4 c when type េ and ា, tranform them to ោ
|
||||
U+17B6 U+17C1 > U+17C4 c when type ា and េ, tranform them to ោ
|
||||
U+17C1 U+17B8 > U+17BE c when type េ and ី , tranform them to ើ
|
||||
U+17B8 U+17C1 > U+17BE c when type ី and េ, tranform them to ើ
|
||||
|
||||
c Illegitimate vowel combinations should be transformed to the legitimate ones (Case #5).
|
||||
|
||||
U+17C6 U+17BB > U+17BB U+17C6 c when type ំ and ុ , tranform them to ុំ
|
||||
U+17C6 U+17B6 > U+17B6 U+17C6 c when type ំ and ា, tranform them to ាំ
|
||||
|
||||
|
||||
c Rotate vowels
|
||||
|
||||
any(v_gen) any(v_gen) > context(2) c the last vowel stroke replaces the previous one.
|
||||
|
||||
c Rotation of two-part vowels
|
||||
|
||||
any(v_gen) any(v_pseudo) any(v_gen) > context(3) c a two-part vowel rotates to a genuine vowel.
|
||||
any(v_gen) any(v_pseudo) any(v_pseudo) > context(3) c a two-part vowel rotates to a pseudo vowel.
|
||||
any(v_gen) any(v_gen) any(v_pseudo) > context(2) context(3) c a genuine vowel rotates to a two-part vowel.
|
||||
any(v_pseudo) any(v_gen) any(v_pseudo) > context(2) context(3) c a pseudo vowel roates to a two-part vowel.
|
||||
any(v_gen) any(v_pseudo) any(v_gen) any(v_pseudo) > context(3) context(4) c a two-part vowel rotates to another two-part vowel.
|
||||
any(v_pseudo) any(v_pseudo) > context(2) c a pseudo vowel rotates to another pseudo vowel.
|
||||
c any(v_pseudo) any(v_gen) > context(2) c a pseudo vowel roates to a genuine vowel.
|
||||
|
||||
c Delete extraneous coeng markers
|
||||
|
||||
U+17D2 U+17D2 > context(1) c no two coengs are allowed.
|
||||
U+17D2 any(v_any) > context(2) c The coeng is deleted when a vowel is put after it.
|
||||
U+17D2 any(v_gen) any(v_pseudo) > context(2) context(3) c The coeng is deleted when a pseudo vowel is put after it.
|
||||
|
||||
c Case #1: when a subscript is typed after a vowel, the subscript should be automatically moved to before the vowel.
|
||||
|
||||
any(v_any) U+17D2 any(subcons) > context(2) context(3) context(1) c a vowel has to come after a subscript.
|
||||
any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c these two-part vowels have to come after a subscript.
|
||||
any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c these two-part vowels have to come after a subscript.
|
||||
|
||||
c a subscript placed after a shifter and a vowel (a genuine/pseudo vowel or a two-part vowel) has to move to before them.
|
||||
|
||||
any(c_shifter) any(v_any) U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c after a genuine/pseudo vowel
|
||||
any(c_shifter) any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c after a two-part vowel
|
||||
any(c_shifter) any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c after a two-part vowel
|
||||
|
||||
c Case #2: when [D2+9A] is typed before a subscript, it should be moved to after it.
|
||||
|
||||
U+17D2 U+178A U+17D2 U+179A > context(1) U+178F context(3) context(4) c change subscript ្ដ to ្ត after ្រ
|
||||
U+17D2 U+179A U+17D2 U+178A > context(3) U+178F context(1) context(2) c ្ដ placed after ្រ has to be changed to ្ត and placed before ្រ
|
||||
|
||||
U+17D2 U+178A any(v_any) U+17D2 U+179A > context(1) U+178F context(4) context(5) context(3) c fixed 6e
|
||||
U+17D2 U+178A any(v_combo_N) U+17C6 U+17D2 U+179A > context(1) U+178F context(5) context(6) context(3) context(4)
|
||||
U+17D2 U+178A any(v_combo_R) U+17C7 U+17D2 U+179A > context(1) U+178F context(5) context(6) context(3) context(4)
|
||||
|
||||
U+17D2 U+179A any(v_any) U+17D2 U+178A > context(1) U+178F context(4) context(2) context(3) c fixed 6g
|
||||
U+17D2 U+179A any(v_combo_N) U+17C6 U+17D2 U+178A > context(1) U+178F context(5) context(2) context(3) context(4)
|
||||
U+17D2 U+179A any(v_combo_R) U+17C7 U+17D2 U+178A > context(1) U+178F context(5) context(2) context(3) context(4)
|
||||
|
||||
U+17D2 U+179A any(c_shifter) U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c when a shifter intervenes, move it to the end.
|
||||
U+17D2 U+179A U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c move any subscript placed after [D2+DA] to before them
|
||||
U+17D2 U+179A any(v_any) U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c when a vowel intervenes, move it to the end
|
||||
U+17D2 U+179A any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(5) context(6) context(1) context(2) context(3) context(4) c when a two-part vowel intervenes, move it to the end too.
|
||||
U+17D2 U+179A any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(5) context(6) context(1) context(2) context(3) context(4) c (same as stated right above)
|
||||
|
||||
c when a subscript is placed after a consonant shifter, they should be reversed. (Case #3)
|
||||
|
||||
any(c_shifter) U+17D2 any(subcons) > context(2) context(3) context(1)
|
||||
|
||||
c Reordering - Triisap and Muusikatoan store before a vowel and a vowelcomboR and pseudo vowel.
|
||||
|
||||
any(v_any) any(c_shifter) > context(2) context(1) c place either a enguine or pseudo vowel after a consonant shifter
|
||||
any(v_gen) any(v_pseudo) any(c_shifter) > context(3) context(1) context(2) c place a two-part vowel after a consonant shifter(i.e. មោះ៉ )
|
||||
any(c_shifter) any(v_any) any(c_shifter) > context(3) context(2) c when either a genuine or pseudo vowel is in between two consonant shifters, replace the one on the left with the one on the right.
|
||||
any(c_shifter) any(v_gen) any(v_pseudo) any(c_shifter) > context(2) context(3) context(4) c when a two-part vowel is in between two consonants shifter, replace the one on the left with the one the right.
|
||||
|
||||
c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
c + when [BB] is place either before or after an above vowel (ិ ី ឹ ឺ ើ and ាំ) and a Samyok Sannya (Case #4) +
|
||||
c + the [BB] should transform to a respective consonant shifter placed before the vowel. +
|
||||
c + the consonant shifter is a Trisap if the base consonant is ស ហ អ +
|
||||
c + the consonant shifter is a Muusikatoan if the base consonant is ង ញ ម យ រ វ ប and ន ល +
|
||||
c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
c consonant clusters --1st > 2nd series (with U+17BB)
|
||||
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
any(c_combo_QA) U+17D2 U+17A2 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+179B U+17D2 any(c_1st_combo_LO) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
U+179F U+17D2 U+1794 U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c prevent ស្បុិ from transforming to ស្ប៊ិ, but produce ស្ប៉ិ instead because ប never have Triisap shifted down.
|
||||
U+179F U+17D2 U+1794 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6)
|
||||
U+179F U+17D2 U+1794 any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4)
|
||||
U+179F U+17D2 U+1794 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7)
|
||||
|
||||
U+179F U+17D2 any(c_combo_SA) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+179F U+17D2 any(c_combo_SA) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+179F U+17D2 any(c_combo_SA) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
U+179F U+17D2 any(c_combo_SA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
any(c_combo_HA) U+17D2 U+17A0 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
U+17A2 U+17D2 U+1784 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+17A2 U+17D2 U+1784 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+17A2 U+17D2 U+1784 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
U+17A2 U+17D2 U+1784 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
U+17A2 U+17D2 U+179C U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+17A2 U+17D2 U+179C U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+17A2 U+17D2 U+179C any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4)
|
||||
U+17A2 U+17D2 U+179C U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7)
|
||||
|
||||
U+17A0 U+17D2 U+1794 U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c change U+17BB preceded by HA and coeng BA to U+17C9 before the above vowel
|
||||
U+17A0 U+17D2 U+1794 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(3) context(6) c change U+17BB preceded by HA and coeng BA to U+17C9 before ាំ
|
||||
U+17A0 U+17D2 U+1794 any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) c change U+17BB preceded by HA and coeng BA to U+17C9 before the above vowel
|
||||
U+17A0 U+17D2 U+1794 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) c change U+17BB preceded by HA and coeng BA to U+17C9 before U+17B6 U+17C6
|
||||
|
||||
|
||||
U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before the above vowel
|
||||
U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(3) context(6) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before ាំ
|
||||
U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before the above vowel
|
||||
U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before U+17B6 U+17C6
|
||||
|
||||
c consonant clusters --2nd > 1st series (with U+17BB)
|
||||
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5)
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6)
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4)
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7)
|
||||
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5)
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6)
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4)
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7)
|
||||
|
||||
c single consonants --1st > 2nd series (with U+17BB)
|
||||
|
||||
any(shiftable_c_1st) U+17BB any(v_above) > context(1) U+17CA context(3) c change U+17BB to U+17CA before the above vowel
|
||||
any(shiftable_c_1st) U+17BB U+17B6 U+17C6 > context(1) U+17CA context(3) context(4) c change U+17BB to U+17CA before ាំ
|
||||
any(shiftable_c_1st) any(v_above) U+17BB > context(1) U+17CA context(2) c change U+17BB to U+17CA and place it before the above vowel
|
||||
any(shiftable_c_1st) U+17BB U+17C6 U+17B6 U+17C6 > context(1) U+17CA context(4) context(5) c change U+17BB U+17C6 to U+17CA
|
||||
|
||||
c single consonants --2nd > 1st series (with U+17BB)
|
||||
|
||||
any(shiftable_c_2nd_with_BA) U+17BB any(v_above) > context(1) U+17C9 context(3) c change U+17BB to U+17C9 before the above vowel
|
||||
any(shiftable_c_2nd_with_BA) U+17BB U+17B6 U+17C6 > context(1) U+17C9 context(3) context(4) c change U+17BB to U+17C9 before ាំ
|
||||
any(shiftable_c_2nd_with_BA) any(v_above) U+17BB > context(1) U+17C9 context(2) c change U+17BB to U+17C9 before the above vowel
|
||||
any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) U+17C9 context(4) context(5) c change U+17BB U+17C6 to U+17C9
|
||||
|
||||
c prevent incorrect use of consonant shifter with 2nd series consonant clusters (without U+17BB)
|
||||
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17CA any(v_above) > context(1) context(2) context(3) U+17C9 context(5)
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA any(v_above) > context(1) context(2) context(3) U+17C9 context(5)
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17CA any(v_gen) any(v_pseudo) > context(1) context(2) context(3) U+17C9 context(5) context(6)
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA any(v_gen) any(v_pseudo) > context(1) context(2) context(3) U+17C9 context(5) context(6)
|
||||
|
||||
c single consonants --2nd > 1st series (without U+17BB)
|
||||
|
||||
any(shiftable_c_2nd) U+17CA any(v_above) > context(1) U+17C9 context(3) c change U+17CA to U+17C9 before the above vowel
|
||||
any(shiftable_c_2nd) U+17CA any(v_gen) any(v_pseudo) > context(1) U+17C9 context(3) context(4) c change U+17CA to U+17C9 before ាំ
|
||||
|
||||
U+17D2 any(shiftable_c_2nd) U+17CA any(v_above) > context c stop ស្រ៊ី from becoming ស្រ៉ី
|
||||
U+17D2 any(shiftable_c_2nd) U+17CA any(v_gen) any(v_pseudo) > context
|
||||
|
||||
c SPECIAL CASE -- the following clusters may accept either of the two consonant shifters.
|
||||
|
||||
U+1794 U+17D2 U+1799 any(c_shifter) > context
|
||||
U+179F U+17D2 U+1794 any(c_shifter) > context
|
||||
U+1786 U+17D2 U+1794 any(c_shifter) > context
|
||||
U+1794 U+17D2 U+1799 any(c_shifter) > context
|
||||
U+179F U+17D2 U+1794 any(c_shifter) > context
|
||||
U+1786 U+17D2 U+1794 any(c_shifter) > context
|
||||
|
||||
c single --1st > 2nd series (without U+17BB)
|
||||
|
||||
any(shiftable_c_1st) U+17C9 any(v_above) > context(1) U+17CA context(3) c change U+17C9 to U+17CA before the above vowel
|
||||
any(shiftable_c_1st) U+17C9 any(v_gen) any(v_pseudo) > context(1) U+17CA context(3) context(4) c change U+17C9 to U+17CA before ាំ
|
||||
|
||||
c consonant clusters --1st > 2nd series (without U+17BB)
|
||||
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+179F U+17D2 U+1794 U+17C9 any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c prevent ស្ប៉ី from transforming to ស្ប៊ី
|
||||
U+179F U+17D2 any(c_combo_SA) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+17A2 U+17D2 U+1784 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
U+17A2 U+17D2 U+179C U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5)
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+179F U+17D2 U+1794 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) c prevent ស្ប៉ាំ from transforming to ស្ប៊ាំ
|
||||
U+179F U+17D2 any(c_combo_SA) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+17A2 U+17D2 U+1784 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
U+17A2 U+17D2 U+179C U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6)
|
||||
|
||||
c U+17B6 U+17BB and U+17C6 in that order preceeded by any first series consonant (cluster) are transformed to U+17CA U+17B6 and U+17C6
|
||||
|
||||
any(shiftable_c_1st) U+17B6 U+17BB U+17C6 > context(1) U+17CA context(2) context(4)
|
||||
any(shiftable_c_1st) U+17BB U+17C6 U+17B6 > context(1) U+17CA context(4) context(3)
|
||||
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5)
|
||||
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5)
|
||||
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5)
|
||||
|
||||
U+179F U+17D2 any(c_combo_SA) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
U+179F U+17D2 any(c_combo_SA) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5)
|
||||
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5)
|
||||
|
||||
U+17A2 U+17D2 U+1784 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
U+17A2 U+17D2 U+1784 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5)
|
||||
|
||||
U+17A2 U+17D2 U+179C U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
U+17A2 U+17D2 U+179C U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(4) context(6)
|
||||
|
||||
c U+17B6 U+17BB and U+17C6 in that order preceeded by any second series consonant (cluster) are transformed to U+17C9 U+17B6 and U+17C6
|
||||
|
||||
any(shiftable_c_2nd_with_BA) U+17B6 U+17BB U+17C6 > context(1) U+17C9 context(2) context(4)
|
||||
any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 > context(1) U+17C9 context(4) context(3)
|
||||
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17C9 context(4) context(6)
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17C9 context(6) context(5)
|
||||
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17C9 context(4) context(6)
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17C9 context(6) context(5)
|
||||
|
||||
c េុី/ុេី/៉េី > ៊ើ
|
||||
|
||||
any(shiftable_c_1st) U+17C1 U+17BB U+17B8 > context(1) U+17CA U+17BE c cancel out by vowel rotation
|
||||
any(shiftable_c_1st) U+17BB U+17C1 U+17B8 > context(1) U+17CA U+17BE c cancel out by vowel rotation
|
||||
any(shiftable_c_1st) U+17C9 U+17C1 U+17B8 > context(1) U+17CA U+17BE
|
||||
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
any(c_combo_QA) U+17D2 U+17A2 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+179B U+17D2 any(c_1st_combo_LO) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
U+179F U+17D2 any(c_combo_SA) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+179F U+17D2 any(c_combo_SA) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+179F U+17D2 any(c_combo_SA) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
any(c_combo_HA) U+17D2 U+17A0 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
U+17A2 U+17D2 U+1784 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+17A2 U+17D2 U+1784 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+17A2 U+17D2 U+1784 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
U+17A2 U+17D2 U+179C U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+17A2 U+17D2 U+179C U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation
|
||||
U+17A2 U+17D2 U+179C U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE
|
||||
|
||||
c េុី/ុេី/៊េី > ៉ើ
|
||||
|
||||
any(shiftable_c_2nd) U+17C1 U+17BB U+17B8 > context(1) U+17C9 U+17BE c cancel out by vowel rotation
|
||||
any(shiftable_c_2nd) U+17BB U+17C1 U+17B8 > context(1) U+17C9 U+17BE c cancel out by vowel rotation
|
||||
any(shiftable_c_2nd) U+17CA U+17C1 U+17B8 > context(1) U+17C9 U+17BE
|
||||
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation
|
||||
U+179B U+17D2 any(c_2nd_combo_LO) U+17CA U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE
|
||||
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation
|
||||
U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE
|
||||
|
||||
c place Robat after a base before any vowel
|
||||
|
||||
any(c_out) any(v_gen) U+17CC > context(1) context(3) context(2)
|
||||
|
||||
c Use appropriate identical subscript in two separate contexts. (Case #8)
|
||||
|
||||
U+178E U+17D2 U+178F > context(1) context(2) U+178A
|
||||
U+1793 U+17D2 U+178A > context(1) context(2) U+178F
|
||||
|
||||
c BONUSES(note that these combinations are invalid and they can't be found in the CN dictionary 1967 nor RH 1997, but users think they are)
|
||||
|
||||
U+1791 U+17D2 U+1794 > U+17A1 c ទ្ប transforms to ឡ
|
||||
|
||||
U+1794 U+17D2 U+1789 > U+17AB c ប្ញ transforms to ឫ
|
||||
U+17AB U+17BB > U+17AC c ឫុ transforms to ឬ
|
||||
|
||||
U+17AD U+17B6 > U+1789 c ឭា transforms to ញ
|
||||
U+17AE U+17B6 > U+1789 c ឮា transforms to ញ
|
||||
|
||||
U+1796 U+17D2 U+1789 > U+17AD c ព្ញ transforms to ឭ
|
||||
U+17AD U+17BB > U+17AE c ឭុ transforms to ឮ
|
||||
|
||||
U+1796 U+17D2 U+178B > U+17B0 c ព្ឋ transforms to ឰ
|
||||
|
||||
U+17A7 U+17B7 > U+17B1 c ឧិ transforms to ឱ
|
||||
U+17A7 U+17CC > U+17B1 c ឧ៌ transforms to ឱ
|
||||
U+17A7 U+17CD > U+17B1 c ឧ៍ transforms to ឱ
|
||||
|
||||
c U+17D4 U+179B U+17D4 > U+17D8 c ។ល។ transforms to U+17D8
|
||||
|
||||
U+178A U+17D2 U+1792 > U+178A U+17D2 U+178B c ដ្ធ > ដ្ឋ
|
||||
U+1791 U+17D2 U+178B > U+1791 U+17D2 U+1792 c ទ្ឋ > ទ្ធ
|
||||
|
||||
c additional rules
|
||||
|
||||
U+1796 U+1793 U+17D2 U+178B > context(1) context(2) context(3) U+1792 c ពន្ឋ > ពន្ធ
|
||||
U+1796 U+17D0 U+1793 U+17D2 U+178B > context(1) context(2) context(3) context(4) U+1792 c ព័ន្ឋ > ព័ន្ធ
|
||||
|
||||
|
||||
U+17AA U+17D2 U+1799 > U+17B1 context(2) context(3) c ឪ្យ > ឱ្យ
|
||||
U+17B3 U+17D2 U+1799 > U+17B1 context(2) context(3) c ឳ្យ > ឱ្យ
|
||||
|
||||
U+1789 U+17D2 U+179C > U+1796 context(2) context(3) U+17B6 c ញ្វ (as in សញ្វវុធ) > ព្វា as in សញ្វវុធ
|
||||
|
||||
U+17D2 U+1799 U+17C1 U+17BA > U+17BF c េ្យឺ > ឿ
|
||||
U+17D2 U+1799 U+17C1 U+17B9 > U+17BF c េ្យឹ > ឿ
|
||||
U+17D2 U+1799 U+17C1 U+17B8 > U+17BF c េ្យី > ឿ
|
||||
|
||||
U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17BA > context(3) context(4) U+17BF c គ្រេ្យឺង > គ្រឿង
|
||||
U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17B9 > context(3) context(4) U+17BF c គ្រេ្យឹង > គ្រឿង
|
||||
U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17B8 > context(3) context(4) U+17BF c គ្រេ្យីង > គ្រឿង
|
||||
|
||||
c In everyday practice, both 'regular space' and 'no-break space' are not used before punctuation, i.e. ។ ! etc.
|
||||
c An annoying issue of having line broken up at the end of the line dropping the punctuation (។, ! and also ៗ) to the next line.
|
||||
c It may be helpful to include a rule where a 'Zero Width No-Break Space' should be output together with those punct.
|
||||
c ref: https://www.unicode.org/L2/L2020/20008-core-text.pdf
|
||||
c ref: https://www.unicode.org/versions/Unicode12.0.0/ch23.pdf#G12985
|
||||
c ref: https://www.compart.com/en/unicode/U+FEFF
|
||||
|
||||
c EOF
|
||||
58
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kps
vendored
Normal file
58
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kps
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package>
|
||||
<System>
|
||||
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
|
||||
<FileVersion>7.0</FileVersion>
|
||||
</System>
|
||||
<Options>
|
||||
<ExecuteProgram></ExecuteProgram>
|
||||
<ReadMeFile>readme.htm</ReadMeFile>
|
||||
<GraphicFile>splash.gif</GraphicFile>
|
||||
<MSIFileName></MSIFileName>
|
||||
<MSIOptions></MSIOptions>
|
||||
<FollowKeyboardVersion/>
|
||||
</Options>
|
||||
<StartMenu>
|
||||
<Folder></Folder>
|
||||
<Items/>
|
||||
</StartMenu>
|
||||
<Info>
|
||||
<Name URL="">Khmer Angkor</Name>
|
||||
<Copyright URL="">© 2015-2022 SIL International</Copyright>
|
||||
<Author URL="mailto:makara_sok@sil.org">Makara Sok</Author>
|
||||
<Version URL=""></Version>
|
||||
<WebSite URL="https://keyman.com/keyboards/khmer_angkor">https://keyman.com/keyboards/khmer_angkor</WebSite>
|
||||
<Description>Khmer Angkor</Description>
|
||||
</Info>
|
||||
<Files>
|
||||
<File>
|
||||
<Name>..\build\khmer_angkor.js</Name>
|
||||
<Description>File khmer_angkor.js</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.js</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>..\build\khmer_angkor.kvk</Name>
|
||||
<Description>File khmer_angkor.kvk</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kvk</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>..\build\khmer_angkor.kmx</Name>
|
||||
<Description>Keyboard Khmer Angkor</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kmx</FileType>
|
||||
</File>
|
||||
</Files>
|
||||
<Keyboards>
|
||||
<Keyboard>
|
||||
<Name>Khmer Angkor</Name>
|
||||
<ID>khmer_angkor</ID>
|
||||
<Version>1.3</Version>
|
||||
<Languages>
|
||||
<Language ID="km">Central Khmer (Khmer, Cambodia)</Language>
|
||||
</Languages>
|
||||
</Keyboard>
|
||||
</Keyboards>
|
||||
<Strings/>
|
||||
</Package>
|
||||
206
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kvks
vendored
Normal file
206
developer/src/kmc/test/fixtures/command-line-tests/build/khmer_angkor/source/khmer_angkor.kvks
vendored
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<visualkeyboard>
|
||||
<header>
|
||||
<version>10.0</version>
|
||||
<kbdname>khmer_angkor</kbdname>
|
||||
<flags>
|
||||
<usealtgr/>
|
||||
</flags>
|
||||
</header>
|
||||
<encoding name="unicode" fontname="Khmer Busra Kbd" fontsize="16">
|
||||
<layer shift="RA">
|
||||
<key vkey="K_B">ឞ</key>
|
||||
<key vkey="K_K">ឝ</key>
|
||||
<key vkey="K_QUOTE">ៈ</key>
|
||||
<key vkey="K_RBRKT">ឳ</key>
|
||||
<key vkey="K_T">ឨ</key>
|
||||
<key vkey="K_LBRKT">ឩ</key>
|
||||
<key vkey="K_P">ឰ</key>
|
||||
<key vkey="K_R">ឫ</key>
|
||||
<key vkey="K_I">ឦ</key>
|
||||
<key vkey="K_O">ឱ</key>
|
||||
<key vkey="K_E">ឯ</key>
|
||||
<key vkey="K_3"></key>
|
||||
<key vkey="K_W"></key>
|
||||
<key vkey="K_Q">ៜ</key>
|
||||
<key vkey="K_EQUAL"></key>
|
||||
<key vkey="K_COLON">៖</key>
|
||||
<key vkey="K_6">៙</key>
|
||||
<key vkey="K_7">៚</key>
|
||||
<key vkey="K_M"></key>
|
||||
<key vkey="K_L">៘</key>
|
||||
<key vkey="K_1"></key>
|
||||
<key vkey="K_BKQUOTE"></key>
|
||||
<key vkey="K_U">]</key>
|
||||
<key vkey="K_Y">[</key>
|
||||
<key vkey="K_SLASH">/</key>
|
||||
<key vkey="K_PERIOD">.</key>
|
||||
<key vkey="K_H">‘</key>
|
||||
<key vkey="K_A">+</key>
|
||||
<key vkey="K_V">&</key>
|
||||
<key vkey="K_J">’</key>
|
||||
<key vkey="K_8">*</key>
|
||||
<key vkey="K_2">@</key>
|
||||
<key vkey="K_BKSLASH">\</key>
|
||||
<key vkey="K_0">}</key>
|
||||
<key vkey="K_9">{</key>
|
||||
<key vkey="K_S">-</key>
|
||||
<key vkey="K_F">÷</key>
|
||||
<key vkey="K_G">:</key>
|
||||
<key vkey="K_COMMA">,</key>
|
||||
<key vkey="K_HYPHEN">≈</key>
|
||||
<key vkey="K_N">;</key>
|
||||
<key vkey="K_Z"><</key>
|
||||
<key vkey="K_C">#</key>
|
||||
<key vkey="K_X">></key>
|
||||
<key vkey="K_D">×</key>
|
||||
<key vkey="K_4">$</key>
|
||||
<key vkey="K_5">€</key>
|
||||
<key vkey="K_SPACE"> </key>
|
||||
</layer>
|
||||
<layer shift="SRA">
|
||||
<key vkey="K_8">៸</key>
|
||||
<key vkey="K_0">៰</key>
|
||||
<key vkey="K_1">៱</key>
|
||||
<key vkey="K_2">៲</key>
|
||||
<key vkey="K_3">៳</key>
|
||||
<key vkey="K_4">៴</key>
|
||||
<key vkey="K_5">៵</key>
|
||||
<key vkey="K_6">៶</key>
|
||||
<key vkey="K_7">៷</key>
|
||||
<key vkey="K_9">៹</key>
|
||||
<key vkey="K_PERIOD">᧿</key>
|
||||
<key vkey="K_COMMA">᧾</key>
|
||||
<key vkey="K_LBRKT">᧪</key>
|
||||
<key vkey="K_RBRKT">᧫</key>
|
||||
<key vkey="K_QUOTE">᧶</key>
|
||||
<key vkey="K_COLON">᧵</key>
|
||||
<key vkey="K_A">᧬</key>
|
||||
<key vkey="K_Z">᧷</key>
|
||||
<key vkey="K_Y">᧥</key>
|
||||
<key vkey="K_X">᧸</key>
|
||||
<key vkey="K_W">᧡</key>
|
||||
<key vkey="K_B">᧻</key>
|
||||
<key vkey="K_C">᧹</key>
|
||||
<key vkey="K_D">᧮</key>
|
||||
<key vkey="K_E">᧢</key>
|
||||
<key vkey="K_F">᧯</key>
|
||||
<key vkey="K_G">᧰</key>
|
||||
<key vkey="K_U">᧦</key>
|
||||
<key vkey="K_H">᧱</key>
|
||||
<key vkey="K_T">᧤</key>
|
||||
<key vkey="K_S">᧭</key>
|
||||
<key vkey="K_R">᧣</key>
|
||||
<key vkey="K_I">᧧</key>
|
||||
<key vkey="K_Q">᧠</key>
|
||||
<key vkey="K_V">᧺</key>
|
||||
<key vkey="K_J">᧲</key>
|
||||
<key vkey="K_K">᧳</key>
|
||||
<key vkey="K_L">᧴</key>
|
||||
<key vkey="K_P">᧩</key>
|
||||
<key vkey="K_O">᧨</key>
|
||||
<key vkey="K_N">᧼</key>
|
||||
<key vkey="K_M">᧽</key>
|
||||
</layer>
|
||||
<layer shift="">
|
||||
<key vkey="K_SPACE"></key>
|
||||
<key vkey="K_QUOTE"></key>
|
||||
<key vkey="K_COMMA"></key>
|
||||
<key vkey="K_HYPHEN">ឥ</key>
|
||||
<key vkey="K_PERIOD">។</key>
|
||||
<key vkey="K_SLASH"></key>
|
||||
<key vkey="K_0">០</key>
|
||||
<key vkey="K_1">១</key>
|
||||
<key vkey="K_2">២</key>
|
||||
<key vkey="K_3">៣</key>
|
||||
<key vkey="K_4">៤</key>
|
||||
<key vkey="K_5">៥</key>
|
||||
<key vkey="K_6">៦</key>
|
||||
<key vkey="K_7">៧</key>
|
||||
<key vkey="K_8">៨</key>
|
||||
<key vkey="K_9">៩</key>
|
||||
<key vkey="K_COLON"></key>
|
||||
<key vkey="K_EQUAL">ឲ</key>
|
||||
<key vkey="K_LBRKT"></key>
|
||||
<key vkey="K_BKSLASH">ឮ</key>
|
||||
<key vkey="K_RBRKT">ឪ</key>
|
||||
<key vkey="K_BKQUOTE">«</key>
|
||||
<key vkey="K_A"></key>
|
||||
<key vkey="K_B">ប</key>
|
||||
<key vkey="K_C">ច</key>
|
||||
<key vkey="K_D">ដ</key>
|
||||
<key vkey="K_E"></key>
|
||||
<key vkey="K_F">ថ</key>
|
||||
<key vkey="K_G">ង</key>
|
||||
<key vkey="K_H">ហ</key>
|
||||
<key vkey="K_I"></key>
|
||||
<key vkey="K_J"></key>
|
||||
<key vkey="K_K">ក</key>
|
||||
<key vkey="K_L">ល</key>
|
||||
<key vkey="K_M">ម</key>
|
||||
<key vkey="K_N">ន</key>
|
||||
<key vkey="K_O"></key>
|
||||
<key vkey="K_P">ផ</key>
|
||||
<key vkey="K_Q">ឆ</key>
|
||||
<key vkey="K_R">រ</key>
|
||||
<key vkey="K_S">ស</key>
|
||||
<key vkey="K_T">ត</key>
|
||||
<key vkey="K_U"></key>
|
||||
<key vkey="K_V">វ</key>
|
||||
<key vkey="K_W"></key>
|
||||
<key vkey="K_X">ខ</key>
|
||||
<key vkey="K_Y">យ</key>
|
||||
<key vkey="K_Z">ឋ</key>
|
||||
</layer>
|
||||
<layer shift="S">
|
||||
<key vkey="K_SPACE"></key>
|
||||
<key vkey="K_1">!</key>
|
||||
<key vkey="K_QUOTE"></key>
|
||||
<key vkey="K_3">"</key>
|
||||
<key vkey="K_4">៛</key>
|
||||
<key vkey="K_5">%</key>
|
||||
<key vkey="K_7"></key>
|
||||
<key vkey="K_9">(</key>
|
||||
<key vkey="K_0">)</key>
|
||||
<key vkey="K_8"></key>
|
||||
<key vkey="K_EQUAL">=</key>
|
||||
<key vkey="K_COLON"></key>
|
||||
<key vkey="K_PERIOD">៕</key>
|
||||
<key vkey="K_SLASH">?</key>
|
||||
<key vkey="K_2">ៗ</key>
|
||||
<key vkey="K_A"></key>
|
||||
<key vkey="K_B">ព</key>
|
||||
<key vkey="K_C">ជ</key>
|
||||
<key vkey="K_D">ឌ</key>
|
||||
<key vkey="K_E"></key>
|
||||
<key vkey="K_F">ធ</key>
|
||||
<key vkey="K_G">អ</key>
|
||||
<key vkey="K_H">ះ</key>
|
||||
<key vkey="K_I"></key>
|
||||
<key vkey="K_J">ញ</key>
|
||||
<key vkey="K_K">គ</key>
|
||||
<key vkey="K_L">ឡ</key>
|
||||
<key vkey="K_M"></key>
|
||||
<key vkey="K_N">ណ</key>
|
||||
<key vkey="K_O"></key>
|
||||
<key vkey="K_P">ភ</key>
|
||||
<key vkey="K_Q">ឈ</key>
|
||||
<key vkey="K_R">ឬ</key>
|
||||
<key vkey="K_S"></key>
|
||||
<key vkey="K_T">ទ</key>
|
||||
<key vkey="K_U"></key>
|
||||
<key vkey="K_V"></key>
|
||||
<key vkey="K_W"></key>
|
||||
<key vkey="K_X">ឃ</key>
|
||||
<key vkey="K_Y"></key>
|
||||
<key vkey="K_Z">ឍ</key>
|
||||
<key vkey="K_6"></key>
|
||||
<key vkey="K_HYPHEN"></key>
|
||||
<key vkey="K_LBRKT"></key>
|
||||
<key vkey="K_BKSLASH">ឭ</key>
|
||||
<key vkey="K_RBRKT">ឧ</key>
|
||||
<key vkey="K_BKQUOTE">»</key>
|
||||
<key vkey="K_COMMA"></key>
|
||||
</layer>
|
||||
</encoding>
|
||||
</visualkeyboard>
|
||||
3
developer/src/kmc/test/fixtures/command-line-tests/build/windows-installer/keymandesktop.txt
vendored
Normal file
3
developer/src/kmc/test/fixtures/command-line-tests/build/windows-installer/keymandesktop.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
This placeholder file represents keymandesktop.msi, the Windows Installer package for Keyman for Windows.
|
||||
|
||||
(Not including the real file to reduce file size in repo, as it is not necessary in order to complete the unit test)
|
||||
3
developer/src/kmc/test/fixtures/command-line-tests/build/windows-installer/license.txt
vendored
Normal file
3
developer/src/kmc/test/fixtures/command-line-tests/build/windows-installer/license.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
This placeholder file represents license.txt.
|
||||
|
||||
(Not including the real file to reduce file size in repo, as it is not necessary in order to complete the unit test)
|
||||
3
developer/src/kmc/test/fixtures/command-line-tests/build/windows-installer/setup.txt
vendored
Normal file
3
developer/src/kmc/test/fixtures/command-line-tests/build/windows-installer/setup.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
This placeholder file represents setup-redist.exe, the sfx loader.
|
||||
|
||||
(Not including the real file to reduce file size in repo, as it is not necessary in order to complete the unit test)
|
||||
|
|
@ -162,7 +162,7 @@ gtestcompilertest = executable('gtest-compiler-tests', 'gtest-compiler.tests.cpp
|
|||
include_directories: inc,
|
||||
name_suffix: name_suffix,
|
||||
link_args: links + tests_links,
|
||||
objects: lib.extract_all_objects(),
|
||||
objects: lib.extract_all_objects(recursive: false),
|
||||
dependencies: [ icuuc_dep, gtest_dep, gmock_dep ],
|
||||
)
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ gtest_km_u16_test = executable('gtest-km_u16-tests', 'gtest-km_u16.tests.cpp',
|
|||
include_directories: inc,
|
||||
name_suffix: name_suffix,
|
||||
link_args: links + tests_links,
|
||||
objects: lib.extract_all_objects(),
|
||||
objects: lib.extract_all_objects(recursive: false),
|
||||
dependencies: [ icuuc_dep, gtest_dep, gmock_dep ],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
strip = ['$EMSCRIPTEN_BASE/emstrip']
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
strip = ['$EMSCRIPTEN_BASE/emstrip']
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
c = ['python.exe', '$EMSCRIPTEN_BASE/emcc.py']
|
||||
cpp = ['python.exe', '$EMSCRIPTEN_BASE/em++.py']
|
||||
ar = ['python.exe', '$EMSCRIPTEN_BASE/emar.py']
|
||||
strip = ['python.exe', '$EMSCRIPTEN_BASE/emstrip.py']
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
[binaries]
|
||||
c = ['emcc.py']
|
||||
cpp = ['em++.py']
|
||||
ar = ['emar.py']
|
||||
c = ['emcc']
|
||||
cpp = ['em++']
|
||||
ar = ['emar']
|
||||
strip = ['emstrip']
|
||||
exe_wrapper = 'node'
|
||||
|
||||
[properties]
|
||||
|
|
|
|||
|
|
@ -113,7 +113,17 @@ function installer_server() {
|
|||
# monorepo context
|
||||
cat package.json | "$JQ" \
|
||||
'. | del(.devDependencies."@keymanapp/resources-gosh") | del(.devDependencies."@keymanapp/keyman-version") | del(.dependencies."@keymanapp/developer-utils")' \
|
||||
> "$PRODBUILDTEMP/package0.json"
|
||||
|
||||
# #12950: Merge dependencies from @keymanapp/developer-utils and Server; this
|
||||
# is a bit messy but will mean we don't lose the needed dependencies.
|
||||
"$JQ" -s '.[0] + { dependencies: (.[1].dependencies * .[0].dependencies) }' "$PRODBUILDTEMP/package0.json" \
|
||||
"$KEYMAN_ROOT/developer/src/common/web/utils/package.json" > "$PRODBUILDTEMP/package1.json"
|
||||
cat $PRODBUILDTEMP/package1.json | "$JQ" \
|
||||
'. | del(.dependencies."@keymanapp/common-types")' \
|
||||
> "$PRODBUILDTEMP/package.json"
|
||||
rm "$PRODBUILDTEMP/package0.json"
|
||||
rm "$PRODBUILDTEMP/package1.json"
|
||||
|
||||
pushd "$PRODBUILDTEMP"
|
||||
npm install --omit=dev --omit=optional
|
||||
|
|
|
|||
|
|
@ -369,6 +369,8 @@ type
|
|||
FLayoutSetup: Boolean;
|
||||
FCheckboxGridHelper: TCheckboxGridHelper;
|
||||
|
||||
FDisplayMapFont: TFont;
|
||||
|
||||
function GetCurrentRule: TKeyboardParser_LayoutRule;
|
||||
|
||||
procedure EnableControls;
|
||||
|
|
@ -475,6 +477,7 @@ type
|
|||
procedure OrderDetailsPanels;
|
||||
function ValidateFileHasNoUnicodeCharacters(sw: WideString): Boolean;
|
||||
procedure UpdateQRCode;
|
||||
procedure SetDisplayMapFont(const Value: TFont);
|
||||
|
||||
protected
|
||||
function GetHelpTopic: string; override;
|
||||
|
|
@ -495,6 +498,8 @@ type
|
|||
|
||||
function ShouldRememberFocus(Control: TWinControl): Boolean; override; // I4679
|
||||
|
||||
property DisplayMapFont: TFont read FDisplayMapFont write SetDisplayMapFont;
|
||||
|
||||
public
|
||||
procedure StartDebugging(FStartTest: Boolean = False);
|
||||
procedure StopDebugging;
|
||||
|
|
@ -630,6 +635,8 @@ procedure TfrmKeymanWizard.FormCreate(Sender: TObject);
|
|||
begin
|
||||
FLoading := True;
|
||||
|
||||
FDisplayMapFont := TFont.Create;
|
||||
|
||||
//SetWindowTheme(pages.Handle, 'Explorer', '');
|
||||
|
||||
cmdAddToProject.Caption := '&Add to Project';
|
||||
|
|
@ -681,6 +688,7 @@ begin
|
|||
FreeAndNil(FKeyboardParser); // I4557
|
||||
FreeAndNil(FDebugForm);
|
||||
FreeAndNil(FCheckboxGridHelper);
|
||||
FreeAndNil(FDisplayMapFont);
|
||||
UninitSystemKeyboard;
|
||||
end;
|
||||
|
||||
|
|
@ -1825,6 +1833,15 @@ begin
|
|||
Delete(Result.Size, Length(Result.Size)-1, 2);
|
||||
end;
|
||||
end;
|
||||
kfontDisplayMap:
|
||||
begin
|
||||
Result.Enabled := FKeyboardParser.GetSystemStoreValue(ssDisplayMap) <> '';
|
||||
if Result.Enabled then
|
||||
begin
|
||||
Result.Name := DisplayMapFont.Name;
|
||||
Result.Size := IntToStr(TFontUtils.FontSizeInPoints(DisplayMapFont.Name, DisplayMapFont.Size)); // I4872
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -2535,6 +2552,8 @@ begin
|
|||
FFont.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
SetFontFromString(DisplayMapFont, ProjectFile.IDEState['DisplayMapFont']);
|
||||
end;
|
||||
|
||||
procedure TfrmKeymanWizard.SaveSettings(SaveProject: Boolean);
|
||||
|
|
@ -2548,6 +2567,7 @@ begin
|
|||
else ProjectFile.IDEState['DebugDefaultFont'] := '0'; // I4702
|
||||
|
||||
ProjectFile.IDEState['DebugFont'] := FontAsString(FDebugForm.memo.Font); // I4702
|
||||
ProjectFile.IDEState['DisplayMapFont'] := FontAsString(DisplayMapFont);
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
|
@ -2781,6 +2801,13 @@ begin
|
|||
TUtilExecute.URL(lbDebugHosts.Items[lbDebugHosts.ItemIndex]);
|
||||
end;
|
||||
|
||||
procedure TfrmKeymanWizard.SetDisplayMapFont(const Value: TFont);
|
||||
begin
|
||||
FDisplayMapFont.Assign(Value);
|
||||
if ProjectFile <> nil then
|
||||
ProjectFile.IDEState['DisplayMapFont'] := FontAsString(DisplayMapFont);
|
||||
end;
|
||||
|
||||
procedure TfrmKeymanWizard.SetFontInfo(Index: TKeyboardFont; const Value: TKeyboardFontInfo); // I4057
|
||||
var
|
||||
f: TFont;
|
||||
|
|
@ -2814,6 +2841,11 @@ begin
|
|||
CharFont.Name := Value.Name;
|
||||
CharFont.Size := StrToIntDef(Value.Size, 12);
|
||||
end;
|
||||
kfontDisplayMap:
|
||||
begin
|
||||
DisplayMapFont.Name := Value.Name;
|
||||
DisplayMapFont.Size := StrToIntDef(Value.Size, 12);
|
||||
end;
|
||||
kfontOSK:
|
||||
begin
|
||||
if FKeyboardParser.Features.ContainsKey(kfOSK) then // I4258
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ inherited frmKeyboardFonts: TfrmKeyboardFonts
|
|||
BorderIcons = [biSystemMenu]
|
||||
BorderStyle = bsDialog
|
||||
Caption = 'Keyboard Fonts'
|
||||
ClientHeight = 291
|
||||
ClientHeight = 322
|
||||
ClientWidth = 512
|
||||
Position = poScreenCenter
|
||||
ExplicitWidth = 518
|
||||
ExplicitHeight = 320
|
||||
ExplicitHeight = 351
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object lblDevEnv: TLabel
|
||||
|
|
@ -147,6 +147,20 @@ inherited frmKeyboardFonts: TfrmKeyboardFonts
|
|||
Height = 13
|
||||
Caption = 'em'
|
||||
end
|
||||
object lblDisplayMap: TLabel
|
||||
Left = 32
|
||||
Top = 248
|
||||
Width = 109
|
||||
Height = 13
|
||||
Caption = '&displayMap target font'
|
||||
end
|
||||
object Label4: TLabel
|
||||
Left = 463
|
||||
Top = 248
|
||||
Width = 14
|
||||
Height = 13
|
||||
Caption = 'em'
|
||||
end
|
||||
object fcbCode: TscFontComboBox
|
||||
Left = 176
|
||||
Top = 41
|
||||
|
|
@ -260,23 +274,23 @@ inherited frmKeyboardFonts: TfrmKeyboardFonts
|
|||
end
|
||||
object cmdOK: TButton
|
||||
Left = 162
|
||||
Top = 248
|
||||
Top = 280
|
||||
Width = 91
|
||||
Height = 25
|
||||
Caption = 'OK'
|
||||
Default = True
|
||||
TabOrder = 12
|
||||
TabOrder = 14
|
||||
OnClick = cmdOKClick
|
||||
end
|
||||
object cmdCancel: TButton
|
||||
Left = 259
|
||||
Top = 248
|
||||
Top = 280
|
||||
Width = 90
|
||||
Height = 25
|
||||
Cancel = True
|
||||
Caption = 'Cancel'
|
||||
ModalResult = 2
|
||||
TabOrder = 13
|
||||
TabOrder = 15
|
||||
end
|
||||
object fcbTouchLayoutPhone: TscFontComboBox
|
||||
Left = 176
|
||||
|
|
@ -347,4 +361,34 @@ inherited frmKeyboardFonts: TfrmKeyboardFonts
|
|||
ShowPreviewFontName = False
|
||||
ShowPreviewInList = False
|
||||
end
|
||||
object editDisplayMapSize: TEdit
|
||||
Left = 391
|
||||
Top = 245
|
||||
Width = 66
|
||||
Height = 21
|
||||
TabOrder = 13
|
||||
end
|
||||
object fcbDisplayMap: TscFontComboBox
|
||||
Left = 176
|
||||
Top = 245
|
||||
Width = 209
|
||||
Height = 24
|
||||
DropDownCount = 12
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -13
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
ItemHeight = 18
|
||||
ParentFont = False
|
||||
TabOrder = 12
|
||||
MoveUsedToTop = False
|
||||
ColorUsed = clWindow
|
||||
PreviewWidth = 240
|
||||
PreviewHeight = 60
|
||||
FontTypes = [ftTrueTypeAnsi, ftTrueTypeSymbol]
|
||||
ShowPreview = False
|
||||
ShowPreviewFontName = False
|
||||
ShowPreviewInList = False
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue