Class ChrLib {
Class Events {
static __New() {
Event.OnEvent("Application", "Initialized", (*) => ChrLib.CountOfUpdate())
return
}
}
static entries := {}
static entriesSource := {}
static entryGroups := Map("Favorites", [])
static entryCategories := Map()
static entryRecipes := Map()
static entryTags := []
static entryIdentifiers := Map()
static dumpPath := App.PATHS.TEMP "\characters_dump.json"
static duplicatesList := Map()
static lastIndexAdded := -1
static countOf := {
entries: 0,
glyphVariations: 0,
userEntries: 0,
recipes: 0,
userRecipes: 0,
uniqueRecipes: 0,
uniqueUserRecipes: 0,
fastKeys: 0,
allKeys: 0,
}
static AddDomain(scriptNames*) {
for each in scriptNames
this.domainsList.Push(each)
return
}
static AddScript(scriptNames*) {
return this.AddDomain(scriptNames*)
; temporary alias
}
static domainsList := JSON.LoadFile(App.PATHS.DATA "\character_domains.json", "UTF-8")
static casesList := JSON.LoadFile(App.PATHS.DATA "\character_cases.json", "UTF-8")
static typesList := JSON.LoadFile(App.PATHS.DATA "\character_types.json", "UTF-8")
static labelNormalizationList := JSON.LoadFile(App.PATHS.DATA "\character_label_normalize.json", "UTF-8")
static GetDecomposition(subMap, toGet, get := "Value", &output := "") {
if get = "Value" {
if this.labelNormalizationList[subMap] is Map && this.labelNormalizationList[subMap].Has(toGet) {
output := this.labelNormalizationList[subMap][toGet]
return this.labelNormalizationList[subMap][toGet]
}
} else if get = "Key" {
if this.labelNormalizationList[subMap] is Map {
for key, value in this.labelNormalizationList[subMap] {
if value = toGet {
output := key
return key
}
}
}
}
return False
}
static __New() {
ChrReg(characters.data)
if ChrLib.duplicatesList.Count > 0 {
TrayTip(Locale.ReadInject("warnings.duplicate_recipe", [ChrLib.duplicatesList.Keys().ToString()]), App.Title("+status+version"), "Icon! Mute")
output := ""
for recipe, names in ChrLib.duplicatesList
output .= recipe "`t" names.ToString() "`n"
Journal("duplicates", output)
}
return Event.Trigger("Character Library", "Default Ready")
}
static RenameEntry(entryName, newName) {
if this.entries.HasOwnProp(entryName) {
local entry := this.GetEntry(entryName)
local scenarios := Map(
"groups", { source: entry["groups"], target: this.entryGroups },
"tags", { source: entry["tags"], target: this.entryTags }
)
for scenarioName, scenario in scenarios {
for item in scenario.source {
if scenario.target.Has(item) && scenario.target.Get(item).HasValue(entryName, &i) {
scenario.target[item][i] := newName
}
}
}
this.entryIdentifiers.Set(entry["index"], newName)
this.entries.DeleteProp(entryName)
this.entries.%newName% := entry
}
return
}
static RemoveEntry(entryName) {
if this.entries.HasOwnProp(entryName) {
local entry := this.GetEntry(entryName)
local scenarios := Map(
"groups", { source: entry["groups"], target: this.entryGroups },
"tags", { source: entry["tags"], target: this.entryTags }
)
for scenarioName, scenario in scenarios {
for item in scenario.source {
if scenario.target.Has(item) && scenario.target.Get(item).HasValue(entryName) {
local intermediateArray := scenario.target.Get(item)
intermediateArray.RemoveValue(entryName)
scenario.target.Delete(item)
scenario.target.Set(item, intermediateArray)
}
}
}
this.entries.DeleteProp(entryName)
}
return
}
static GetReferenceName(&entryName, &entry?) {
if !IsSet(entry)
entry := this.entries.%entryName%
local referencingTo := entry["reference"] is Object ? entry["reference"].Clone() : entry["reference"]
if referencingTo is Object && referencingTo.Has("params") {
local call := referencingTo.Has("call") ? referencingTo["call"] : Cfg.Get.Bind(Cfg, referencingTo["params"]*)
local result := call()
referencingTo := result = referencingTo["if"]
? referencingTo["then"]
: referencingTo["else"]
} else if referencingTo is Object && referencingTo.Has("name")
referencingTo := referencingTo["name"]
return referencingTo
}
static GetEntry(entryName) {
if this.entries.HasOwnProp(entryName) && this.entries.%entryName% is Map {
local entry := this.entries.%entryName%.DeepClone()
local referencingTo := this.GetReferenceName(&entryName, &entry)
if referencingTo != ""
entry := this.ReplaceEntryKeys(entry, this.GetEntry(referencingTo), entry["modifiedKeys"])
return entry
} else {
return False
}
return
}
static GetValue(entryName, value, useRef := False, &output?) {
if useRef {
if InStr(value, ".") {
local split := StrSplit(value, ".")
local current := this.entries.%entryName%
for index, segment in split {
if current.Has(segment)
current := current[segment]
else
return False
}
output := current
return True
} else if this.entries.%entryName%.Has(value) {
output := this.entries.%entryName%[value]
return True
} else {
return False
}
} else {
if InStr(value, ".") {
local split := StrSplit(value, ".")
local current := this.entries.%entryName%
for index, segment in split {
if current.Has(segment)
current := current[segment]
else
return
}
return current
}
return this.entries.%entryName%[value]
}
return
}
static Print() {
local lang := Language.Get(, , 2)
local printPath := App.PATHS.TEMP "\printed_pairs.html"
local tableRows := ""
if FileExist(printPath)
FileDelete(printPath)
local i := 0
for entryName, entry in this.entries.OwnProps() {
i++
local characterContent := (
entry["symbol"]["category"] = "Diacritic Mark" ? DottedCircle : "") (
entry["result"].Length = 1 ? (
UnicodeUtils.GetCodePoint(entry["result"][1], "HTML")) : (entry["sequence"].Length > 0 ? UnicodeUtils.GetBatchSymbols(entry["sequence"], "") : UnicodeUtils.GetSymbol(entry["unicode"])
)
)
local characterAlts := ""
local maxJ := ObjOwnPropCount(entry["alterations"])
if maxJ > 0 {
local j := 0
for key, value in entry["alterations"] {
j++
if !InStr(key, "HTML") {
characterAlts .= (j = 1 ? "`n" : "") '' key ': ' DottedCircle UnicodeUtils.GetSymbol(value) (j < maxJ ? "`n" : "")
}
}
}
local tableRows .= (
'
`n'
' | ' i ' | `n'
' 15 * (InStr(characterContent, "&") ? 8 : 1) ? ' class="small-text"' : entry["symbol"]["category"] = "Spaces" ? ' class="spaces"' : '') '>' characterContent characterAlts ' | `n'
' ' entryName ' | `n'
'
`n'
)
}
local html := (
'`n'
'`n'
' `n'
' `n'
' Printed Pairs`n'
' `n'
' `n'
' `n'
' `n'
' `n'
' `n'
' `n'
'
`n'
' `n'
' | ' (lang = "en" ? "#" : "№") ' | `n'
' ' (lang = "en" ? "Symbol" : "Символ") ' | `n'
' ' (lang = "en" ? "Entry" : "Запись") ' | `n'
'
`n'
tableRows
'
`n'
'
`n'
' `n'
''
)
FileAppend(html, printPath, "UTF-8")
Run(printPath)
return
}
static ReplaceEntryKeys(entryToModify, entrySource, keyNames := []) {
if !entrySource || !entryToModify
return False
local output := entrySource.Clone()
for each in ArrayMerge(keyNames, ["index", "variant", "variantPos"]) {
if entryToModify.Has(each) {
output[each] := entryToModify[each] is Object
? entryToModify[each].Clone()
: entryToModify[each]
}
}
if entryToModify["reference"] is Object
&& entryToModify["reference"].Has("as")
&& entrySource["alterations"].Count > 0
&& entrySource["alterations"].Has(entryToModify["reference"]["as"]) {
output["unicode"] := entrySource["alterations"][entryToModify["reference"]["as"]]
output["symbol"]["set"] := UnicodeUtils.GetSymbol(entrySource["alterations"][entryToModify["reference"]["as"]])
output["sequence"] := []
output["alterations"] := Map()
output["altCode"] := []
output["altCodePages"] := []
output["LaTeX"] := []
output["LaTeXPackage"] := ""
if entryToModify["reference"].Has("include")
for k, v in entryToModify["reference"]["include"]
output["alterations"][k] := entrySource["alterations"][v]
}
local dataPack := output["data"]
for key, value in output["options"]
if ["fastKey", "altLayoutKey", "altSpecialKey"].HasValue(key)
output["options"][key] := ChrReg.SetNotaion(&value, &dataPack)
return output
}
static glyphVariantsFallbacks := Map(
"superscript", "modifier",
"fraktur", "blackletter",
"bold", "heavy",
"sansSerifBold", "sansSerifHeavy",
)
static Get(entryName, extraRules := False, getMode := "Unicode", alt := Scripter.selectedMode.Get("Glyph Variations")) {
if RegExMatch(entryName, "\\(.*?)\/", &multiMatch) {
local output := ""
local split := StrSplit(multiMatch[1], ",")
for each in split {
local name := each ~= "^\+" ? SubStr(each, 2) : RegExReplace(entryName, "\\(.*?)\/", each)
output .= this.Get(name, extraRules, getMode, alt)
}
return output
}
if StrLen(alt) == 0
alt := Scripter.selectedMode.Get("Glyph Variations")
alt := this.ValidateAlt(alt)
local entry := this.GetEntry(entryName)
if !entry
return False
; if alt = "superscript" && !entry["alterations"].Has("superscript")
; alt := "modifier"
; else if alt = "fraktur" && !entry["alterations"].Has("fraktur")
; alt := "blackletter"
; else if alt = "bold" && !entry["alterations"].Has("bold")
; alt := "heavy"
if this.glyphVariantsFallbacks.Has(alt) && !entry["alterations"].Has(alt)
alt := this.glyphVariantsFallbacks.Get(alt)
getMode := StrLen(getMode) ? getMode : "Unicode"
local getChar := entry["result"].Length = 1 ? entry["result"][1] : (entry["sequence"].Length > 0 ? UnicodeUtils.GetBatchSymbols(entry["sequence"], "") : UnicodeUtils.GetSymbol(entry["unicode"]))
if (getMode = "HTML") {
local output := getChar
local entity := entry["entity"]
if (extraRules && StrLen(alt) > 0 && entry["alterations"].Has(alt)) {
output := UnicodeUtils.GetSymbol(entry["alterations"][alt])
if entry["alterations"].Has(alt "Entity") && entry["alterations"][alt "Entity"] != ""
entity := entry["alterations"][alt "Entity"]
}
return StrLen(entity) > 0 ? entity : UnicodeUtils.GetCodePoint(output, Cfg.HTMLMode = "Hexadecimal" ? "XML4" : "HTML")
} else if (getMode = "LaTeX" && entry["LaTeX"].Length > 0) {
return (entry["LaTeX"].Length = 2 && Cfg.LaTeXMode = "Math") ? entry["LaTeX"][2] : entry["LaTeX"][1]
} else {
if (extraRules && StrLen(alt) > 0 && entry["alterations"].Has(alt)) {
return UnicodeUtils.GetSymbol(entry["alterations"][alt])
} else if (extraRules && getMode != "Unicode" && entry["alterations"].Has(getMode)) {
return UnicodeUtils.GetSymbol(entry["alterations"][getMode])
} else {
try {
return getChar
} catch {
MsgBox(Locale.Read("error.critical") "`n`n" Locale.ReadInject("error.entry_not_found", [entryName]), App.Title(), "Iconx")
return
}
}
}
return
}
static GetByPrefix(prefix, extraRules := False, getMode := "Unicode", alt := Scripter.selectedMode.Get("Glyph Variations")) {
local output := ""
for entryName, entyr in this.entries.OwnProps()
if entryName ~= "^(" prefix ")"
output .= this.Get(entryName, extraRules, getMode, alt)
return output
}
static Gets(entryNames*) {
local output := ""
local indexMap := Map()
local charIndex := 0
for i, character in entryNames {
charIndex++
local repeatCount := 1
local characterMatch := character
if RegExMatch(characterMatch, "(.+?)\[(\d+(?:,\d+)*)\]$", &match) {
if RegExMatch(match[1], "(.+?)×(\d+)$", &subMatch) {
character := subMatch[1]
repeatCount := subMatch[2]
} else {
character := match[1]
}
local positions := StrSplit(match[2], ",")
for _, position in positions {
position := Number(position)
if !indexMap.Has(position) {
indexMap[position] := []
}
indexMap[position].Push([character, repeatCount])
}
continue
} else if RegExMatch(characterMatch, "(.+?)×(\d+)$", &match) {
character := match[1]
repeatCount := match[2]
continue
}
if !indexMap.Has(charIndex) {
indexMap[charIndex] := []
}
indexMap[charIndex].Push([character, repeatCount])
}
for indexEntry, value in indexMap {
for _, charData in value {
setSequence(charData[1], charData[2])
}
}
setSequence(entryName, repeatCount) {
Loop repeatCount {
output .= ChrLib.Get(entryName)
}
}
return output
}
static GetIndexedMap() {
local output := Map()
for k, v in this.entries.OwnProps()
output.Set(v["index"], k)
return output
}
static GeneratePermutations(items) {
if items.Length = 1 {
return [items[1]]
}
local permutations := []
for i, item in items {
local remaining := items.Clone()
remaining.RemoveAt(i)
subPermutations := this.GeneratePermutations(remaining)
for subPerm in subPermutations {
permutations.Push(item subPerm)
}
}
return permutations
}
static Count(groupRestrict?, onlyGlyphsVariations := False) {
local count := 0
for entry, value in this.entries.OwnProps() {
if !IsSet(groupRestrict) || (IsSet(groupRestrict) && value["groups"].HasValue(groupRestrict)) {
if !value["options"]["noCalc"] && !onlyGlyphsVariations
count++
for alteration, value in value["alterations"]
if !InStr(alteration, "HTML") && !InStr(alteration, "Entity")
count++
}
}
return count
}
static HotKeysCount(key := "fastKey") {
local count := 0
for entry, value in this.entries.OwnProps() {
if key is Array {
for k in key
if value["options"].Has(k) && value["options"][k] != ""
count++
} else
if value["options"].Has(key) && value["options"][key] != ""
count++
}
return count
}
static CountOfUpdate() {
this.countOf.userEntries := this.Count("Custom Composes")
this.countOf.entries := this.Count() - this.countOf.userEntries
this.countOf.glyphVariations := this.Count(, True)
this.countOf.userRecipes := ChrRecipeHandler.Count("Custom Only")
this.countOf.recipes := ChrRecipeHandler.Count("No Custom")
this.countOf.uniqueRecipes := ChrRecipeHandler.Count("No Custom", True)
this.countOf.uniqueUserRecipes := ChrRecipeHandler.Count("Custom Only", True)
this.countOf.fastKeys := this.HotKeysCount("fastKey")
this.countOf.allKeys := this.countOf.fastKeys + this.HotKeysCount(["altLayoutKey", "altSpecialKey"])
return
}
static ValidateAlt(str) {
static alterationNames := Map(
"modifier", ["модификатор", "мо", "mo"],
"superscript", ["верхний индекс", "ви", "sup"],
"subscript", ["нижний индекс", "ни", "sub"],
"italic", ["курсив", "ку", "it"],
"italicBold", ["курсив полужирный", "куп", "itb"],
"bold", ["полужирный", "п", "b"],
"fraktur", ["фрактур", "ф", "f"],
"frakturBold", ["полужирный фрактур", "пф", "fb"],
"script", ["рукописный", "р", "s"],
"scriptBold", ["полужирный рукописный", "пр", "sb"],
"doubleStruck", ["ds"],
"sansSerif", ["без засечек", "бз", "ss"],
"sansSerifItalic", ["курсив без засечек", "кубз", "ssit"],
"sansSerifItalicBold", ["курсив полужирный без засечек", "купбз", "ssitb"],
"sansSerifBold", ["полужирный без засечек", "пбз", "ssb"],
"monospace", ["моноширинный", "м", "m"],
"smallCapital", ["капитель", "к", "sc"],
"smallCapitalModifier", ["капитель-модификатор", "км", "scm"],
"small", ["маленький", "мал", "sm"],
"combining", ["комбинируемый", "ко", "c"],
"uncombined", ["некомбинируемый", "неко", "uc"],
"fullwidth", ["полноширинный", "пш", "fw"],
"blackletter", ["bl"],
"regionalIndicator", ["региональный индикатор", "ри", "ri"],
"circled", ["в круге", "вк", "ci"],
"negativeCircled", ["в закрашенном круге", "взк", "nci"],
"doubleCircled", ["в двойном круге", "вдк", "dci"],
"parenthesized", ["в скобках", "вск", "pr"],
"squared", ["в квадрате", "вкв", "sq"],
"negativeSquared", ["в закрашенном квадрате", "взкв", "nsq"],
"fullstop", ["с точкой", "стч", "fs"],
"comma", ["с запятой", "сзп", "cm"],
"tag", ["тег", "т", "t"],
"oblique", ["косая", "кя", "ob"],
"segmented", ["сегментированный", "сг", "sg"],
"outlined", ["контурный", "кн", "ol"],
)
for key, value in alterationNames {
for name in value {
if name = str || key = str {
return key
}
}
}
return str
}
static EntryPreview(entryName, indent := 1) {
local entry := this.GetEntry(entryName)
local entryType := entry["groups"].HasValue("Custom Composes") ? "UserRecipes" : "InternalLibrary"
local output := Format('"{}": ', entryName) JSONExt.Dump(entry, True)
Constructor() {
local pGui := Gui()
pGui.title := Format("[ λ://DSLKeyPad.ROOT/{}/Entries/{}.JSON ]", entryType, entryName)
pGui.BackColor := "White"
local w := 650
local h := 900
local xPos := (A_ScreenWidth - w) / 2
local yPos := (A_ScreenHeight - h) / 2
local contentW := w - 20
local contentH := h - 20
local contentX := (w - contentW) / 2
local contentY := (h - contentH) / 2
local content := pGui.AddEdit(Format("vEntryPreviewContent w{} h{} x{} y{} ReadOnly BackgroundWhite +Wrap -HScroll", contentW, contentH, contentX, contentY), output)
content.SetFont("s10", "Courier New")
pGui.Show(Format("w{} h{} x{} y{}", w, h, xPos, yPos))
return pGui
}
pGui := Constructor()
return
}
static FormatEntry(entry, indent := 1) {
; if !(entry is Map)
; return ""
local output := ""
local indentStr := Util.StrRepeat(" ", indent * 8)
for key, value in entry {
if value is Array {
output .= indentStr key ": ["
local subOutput := ""
for subValue in value {
if subValue is Array
subOutput .= indentStr indentStr "[" subValue.ToString(, "'") indentStr "]`n"
else if subValue is Map
subOutput .= this.FormatEntry(subValue, indent + 2)
else if subValue is Object {
local tempMap := Map()
for prop in subValue.OwnProps()
tempMap[prop] := subValue.%prop%
subOutput .= this.FormatEntry(tempMap, indent + 2)
}
else
subOutput .= indentStr indentStr "'" subValue "'`n"
}
if subOutput != ""
output .= "`n" subOutput indentStr
output .= "]`n"
} else if value is Map {
output .= indentStr key ": (`n"
for mapKey, mapValue in value {
if mapValue is Map {
output .= Util.StrRepeat(" ", (indent + 1) * 8) mapKey ": (`n"
output .= this.FormatEntry(mapValue, indent + 2)
output .= Util.StrRepeat(" ", (indent + 1) * 8) ")`n"
} else if mapValue is Array {
output .= Util.StrRepeat(" ", (indent + 1) * 8) mapKey ": ["
local arrayOutput := ""
for arrayValue in mapValue {
if arrayValue is Array
arrayOutput .= Util.StrRepeat(" ", (indent + 2) * 8) "[" arrayValue.ToString(, "'") "]`n"
else if arrayValue is Map
arrayOutput .= this.FormatEntry(arrayValue, indent + 2)
else if arrayValue is Object {
local tempMap := Map()
for prop in arrayValue.OwnProps()
tempMap[prop] := arrayValue.%prop%
arrayOutput .= this.FormatEntry(tempMap, indent + 2)
}
else
arrayOutput .= Util.StrRepeat(" ", (indent + 2) * 8) "'" arrayValue "'`n"
}
if arrayOutput != ""
output .= "`n" arrayOutput Util.StrRepeat(" ", (indent + 1) * 8)
output .= "]`n"
} else if mapValue is Object {
local tempMap := Map()
for prop in mapValue.OwnProps()
tempMap[prop] := mapValue.%prop%
subOutput := this.FormatEntry(tempMap, indent + 2)
subOutput := subOutput != "" ? "`n" subOutput Util.StrRepeat(" ", (indent + 1) * 8) : ""
output .= Util.StrRepeat(" ", (indent + 1) * 8) mapKey ": {" subOutput "}`n"
} else {
output .= mapValue is Number ? Util.StrRepeat(" ", (indent + 1) * 8) mapKey ": " mapValue "`n" : Util.StrRepeat(" ", (indent + 1) * 8) mapKey ": '" StrReplace(mapValue, "`n", " ") "'`n"
}
}
output .= indentStr ")`n"
} else if value is Object {
local tempMap := Map()
for prop in value.OwnProps()
tempMap[prop] := value.%prop%
subOutput := this.FormatEntry(tempMap, indent + 1)
subOutput := subOutput != "" ? "`n" subOutput indentStr : ""
output .= indentStr key ": {" subOutput "}`n"
} else
output .= value is Number ? indentStr key ": " value "`n" : indentStr key ": '" StrReplace(value, "`n", " ") "'`n"
}
return output
}
static PrintRecipesToFile() {
local filePath := A_ScriptDir "\recipes.txt"
local indexedMap := Map()
local output := ""
for recipe, reference in ChrLib.entryRecipes {
indexedMap.Set(reference.index, recipe "`t::`t" reference.name "`n")
}
for key, value in indexedMap {
output .= value
}
FileAppend(output, filePath, "UTF-8")
return
}
}