diff --git a/README.md b/README.md index cc7d44c..a41905b 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,25 @@ ├── hosts/ │ # configuration système │ ├── nixos-home/ -│ │ # mon host personnel (spécifique à ma machine) -│ └── nixos/ -│ ├── configuration.nix # entrée des fichiers de configuration -│ ├── hardware-configuration.nix # fichier hardware généré à l'installation -│ └── config/ # tous fichiers config -│ └── ... +│ │ # mon host de la maison +│ │ └── nixos/ +│ │ ├── configuration.nix # entrée des fichiers de configuration +│ │ ├── hardware-configuration.nix # fichier hardware généré à l'installation +│ │ └── config/ # tous fichiers config +│ │ └── ... +│ └── nixos-laptop/ +│ # mon host du travail +│ └── nixos/ +│ ├── configuration.nix # entrée des fichiers de configuration +│ ├── hardware-configuration.nix # fichier hardware généré à l'installation +│ └── config/ # tous fichiers config +│ └── ... ├── home/ │ # configuration user │ └── afoucaultc/ │ # mon user personnel -│ ├── home.nix # point d'entrée home-manager +│ ├── home.nix # point d'entrée home-manager classique +│ ├── home_laptop.nix # point d'entrée home-manager pour machine light │ ├── dotfiles_apps.nix # liens vers mes dossier de configuration │ └── packages/ # lien vers les packages installés sur mon user └── ... @@ -43,6 +51,8 @@ nix flake update sudo nixos-rebuild switch --flake ~/nixos-config#nixos-home ``` +Ou utiliser `update.sh` + ### Pour trouver un nouveau package : ```nix nix search nixpkgs nom_du_package @@ -56,3 +66,5 @@ nix search nixpkgs nom_du_package ### Pour gérer la version 1. version trop ancienne : référencer `pkgs-unstable.xxx` 2. Package pas encore dans nixpkgs : chercher un flake upstream officiel (packages..degault), ajouter comme input et le référencer +### Machine ancienne +Bien utiliser la version `laptop` qui prend la plupart des paquets par cachix (pour éviter la compilation en locale qui dépasse les capacités de la RAM) diff --git a/dotfiles/hypr/%hyprland.conf b/dotfiles/hypr/%hyprland.conf deleted file mode 100644 index e5f4322..0000000 --- a/dotfiles/hypr/%hyprland.conf +++ /dev/null @@ -1,4 +0,0 @@ -source = ~/.config/hypr/hyprland_config/variables.conf -source = ~/.config/hypr/hyprland_config/* - - diff --git a/dotfiles/hypr/%hyprpaper.conf b/dotfiles/hypr/%hyprpaper.conf deleted file mode 100644 index 5e5f71f..0000000 --- a/dotfiles/hypr/%hyprpaper.conf +++ /dev/null @@ -1,11 +0,0 @@ -wallpaper { -monitor = eDP-1 - path = /home/Antoine/.config/hypr/wallpaper/wallpaper_nightmode.jpg - fit_mode = cover -} -wallpaper { -monitor = HDMI-A-1 - path = /home/Antoine/.config/hypr/wallpaper/vertical.jpg - fit_mode = cover -} -splash = false diff --git a/dotfiles/hypr/hyprland.lua b/dotfiles/hypr/hyprland.lua index 570d4ac..59d9010 100644 --- a/dotfiles/hypr/hyprland.lua +++ b/dotfiles/hypr/hyprland.lua @@ -7,9 +7,3 @@ require("conf.startup") require("conf.style") require("conf.windowrules") require("conf.workspace") - --- For Noctalia Color templates -require("noctalia") - --- This loads Noctalia-generated Hyprland colors. -dofile("/home/afoucaultc/.config/hypr/noctalia/noctalia-colors.lua") diff --git a/dotfiles/hypr/hyprpaper.conf b/dotfiles/hypr/hyprpaper.conf new file mode 100644 index 0000000..21b703c --- /dev/null +++ b/dotfiles/hypr/hyprpaper.conf @@ -0,0 +1,11 @@ +wallpaper { +monitor = eDP-1 + path = /home/afoucaultc/Pictures/Wallpapers/nix-wallpaper.png + fit_mode = cover +} +wallpaper { +monitor = HDMI-A-1 + path = /home/afoucaultc/Pictures/Wallpapers/nix-wallpaper.png + fit_mode = cover +} +splash = false diff --git a/dotfiles/hypr/hyprtoolkit.conf b/dotfiles/hypr/hyprtoolkit.conf deleted file mode 100644 index b933e34..0000000 --- a/dotfiles/hypr/hyprtoolkit.conf +++ /dev/null @@ -1,7 +0,0 @@ -background = rgba(010409ff) -base = rgba(010409ff) -text = rgba(c9d1d9ff) -alternate_base = rgba(161b22ff) -bright_text = rgba(010409ff) -accent = rgba(58a6ffff) -accent_secondary = rgba(bc8cffff) \ No newline at end of file diff --git a/dotfiles/hypr/noctalia.lua b/dotfiles/hypr/noctalia.lua deleted file mode 100644 index 43754cf..0000000 --- a/dotfiles/hypr/noctalia.lua +++ /dev/null @@ -1,33 +0,0 @@ --- Generated by Noctalia - -local primary = "rgb(7a88cf)" -local surface = "rgb(1f2335)" -local secondary = "rgb(d7729f)" -local error = "rgb(f7768e)" - -hl.config({ - general = { - col = { - active_border = primary, - inactive_border = surface, - }, - }, - - group = { - col = { - border_active = secondary, - border_inactive = surface, - border_locked_active = error, - border_locked_inactive = surface, - }, - - groupbar = { - col = { - active = secondary, - inactive = surface, - locked_active = error, - locked_inactive = surface, - }, - }, - }, -}) diff --git a/dotfiles/hypr/noctalia/noctalia-colors.conf b/dotfiles/hypr/noctalia/noctalia-colors.conf deleted file mode 100644 index bee4c80..0000000 --- a/dotfiles/hypr/noctalia/noctalia-colors.conf +++ /dev/null @@ -1,25 +0,0 @@ -$primary = rgb(58a6ff) -$surface = rgb(010409) -$secondary = rgb(bc8cff) -$error = rgb(f85149) -$tertiary = rgb(bc8cff) -$surface_lowest = rgb(05080e) - -general { - col.active_border = $primary - col.inactive_border = $surface -} - -group { - col.border_active = $secondary - col.border_inactive = $surface - col.border_locked_active = $error - col.border_locked_inactive = $surface - - groupbar { - col.active = $secondary - col.inactive = $surface - col.locked_active = $error - col.locked_inactive = $surface - } -} diff --git a/dotfiles/hypr/noctalia/noctalia-colors.lua b/dotfiles/hypr/noctalia/noctalia-colors.lua deleted file mode 100644 index 509c7b9..0000000 --- a/dotfiles/hypr/noctalia/noctalia-colors.lua +++ /dev/null @@ -1,35 +0,0 @@ --- Colors template generated by Noctalia --- Source command is: --- dofile(os.getenv("HOME") .. "/.config/hypr/noctalia/noctalia-colors.lua") - -local primary = "rgb(58a6ff)" -local surface = "rgb(010409)" -local secondary = "rgb(bc8cff)" -local error = "rgb(f85149)" - -hl.config({ - general = { - col = { - active_border = primary, - inactive_border = surface, - }, - }, - - group = { - col = { - border_active = secondary, - border_inactive = surface, - border_locked_active = error, - border_locked_inactive = surface, - }, - - groupbar = { - col = { - active = secondary, - inactive = surface, - locked_active = error, - locked_inactive = surface, - }, - }, - }, -}) diff --git a/dotfiles/inkscape/cphistory.xml b/dotfiles/inkscape/cphistory.xml new file mode 100644 index 0000000..cd91a84 --- /dev/null +++ b/dotfiles/inkscape/cphistory.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/dotfiles/inkscape/dialogs-state-ex.ini b/dotfiles/inkscape/dialogs-state-ex.ini new file mode 100644 index 0000000..296e8a7 --- /dev/null +++ b/dotfiles/inkscape/dialogs-state-ex.ini @@ -0,0 +1,37 @@ +[Windows] +Count=1 + +[Window0Column0] +Notebook0Dialogs=ObjectProperties;FillStroke;Export;AlignDistribute;Text; +Notebook0Height=609 +Notebook0ActiveTab=2 +ColumnWidth=641 +NotebookCount=1 +BeforeCanvas=false + +[Window0] +ColumnCount=1 +Floating=false + +[transient] +state1= +dialogs1=CloneTiler; +state2=[Windows]\nCount=1\n\n[Window0Column0]\nNotebook0Dialogs=DocumentProperties;\nNotebookCount=1\n\n[Window0]\nColumnCount=1\nPosition=true\nx=0\ny=0\nwidth=836\nheight=673\n +dialogs2=DocumentProperties; +state3=[Windows]\nCount=1\n\n[Window0Column0]\nNotebook0Dialogs=DocumentResources;\nNotebookCount=1\n\n[Window0]\nColumnCount=1\nPosition=true\nx=0\ny=0\nwidth=613\nheight=678\n +dialogs3=DocumentResources; +state4=[Windows]\nCount=1\n\n[Window0Column0]\nNotebook0Dialogs=ExtensionsGallery;\nNotebookCount=1\n\n[Window0]\nColumnCount=1\nPosition=true\nx=0\ny=0\nwidth=1186\nheight=777\n +dialogs4=ExtensionsGallery; +state5= +dialogs5=FilterEffects; +state6= +dialogs6=FilterGallery; +state7= +dialogs7=FontCollections; +state8=[Windows]\nCount=1\n\n[Window0Column0]\nNotebook0Dialogs=Objects;\nNotebookCount=1\n\n[Window0]\nColumnCount=1\nPosition=true\nx=0\ny=0\nwidth=360\nheight=520\n +dialogs8=Objects; +state9=[Windows]\nCount=1\n\n[Window0Column0]\nNotebook0Dialogs=Preferences;\nNotebookCount=1\n\n[Window0]\nColumnCount=1\nPosition=true\nx=0\ny=0\nwidth=1447\nheight=672\n +dialogs9=Preferences; +state10= +dialogs10=XMLEditor; +count=10 diff --git a/dotfiles/inkscape/extension-errors.log b/dotfiles/inkscape/extension-errors.log new file mode 100644 index 0000000..51a8772 --- /dev/null +++ b/dotfiles/inkscape/extension-errors.log @@ -0,0 +1,20 @@ +L'extension « Optimized PNG » n'a pas été chargée, car une dépendance est manquante. Dépendance: + type: executable + emplacement: chemin + chaîne: optipng + +L'extension « Export to PDF via Scribus » n'a pas été chargée, car une dépendance est manquante. Dépendance: + type: executable + emplacement: chemin + chaîne: scribus + +L'extension « Formula (typst) » n'a pas été chargée, car une dépendance est manquante. Dépendance: + type: executable + emplacement: chemin + chaîne: typst + +L'extension « XFIG Input » n'a pas été chargée, car une dépendance est manquante. Dépendance: + type: executable + emplacement: chemin + chaîne: fig2dev + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/info.json b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/info.json new file mode 100644 index 0000000..50e4978 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/info.json @@ -0,0 +1 @@ +{"id": "org.inkscape.extension.30306", "author": "burghoff", "name": "Scientific Inkscape", "type": "application/zip", "license": "GPLv3", "icon": "https://media.inkscape.org/media/resources/thumb/Thumbnail.png", "summary": "A set of extensions for efficiently importing, editing, and resizing scientific figures.\r", "links": {"html": "https://inkscape.org/gallery/item/30306/", "json": "https://inkscape.org/gallery/item/30306/json/", "file": "https://media.inkscape.org/media/resources/file/scientific_inkscape_ZeJG1fk.zip"}, "verified": true, "stats": {"liked": 18, "downloaded": 1154, "revisions": 12, "extra": 7}, "dates": {"created": "2021-11-21T17:57:19+00:00", "edited": "2025-05-18T22:59:59.756843+00:00"}, "Inkscape Version": ["1.0", "1.1", "1.2", "1.3", "1.4"], "Extension Type": ["export", "import"], "Platform": ["linux", "os x", "windows"], "previous": {"Inkscape Version": ["1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.0", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1", "1.1.2", "1.1.2", "1.1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.2", "1.3", "1.3", "1.3", "1.3", "1.4", "1.4", "1.4"], "Extension Type": ["export", "export", "export", "export", "export", "export", "export", "export", "import", "import", "import", "import", "import", "import", "import", "import", "text", "text", "text", "text", "text"], "Platform": ["linux", "linux", "linux", "linux", "linux", "linux", "linux", "linux", "linux", "linux", "linux", "linux", "os x", "os x", "os x", "os x", "os x", "os x", "os x", "os x", "os x", "os x", "os x", "os x", "windows", "windows", "windows", "windows", "windows", "windows", "windows", "windows", "windows", "windows", "windows", "windows"]}, "files": ["org.inkscape.extension.30306/scientific_inkscape/applytransform_mod.py", "org.inkscape.extension.30306/scientific_inkscape/autoexporter.inx", "org.inkscape.extension.30306/scientific_inkscape/autoexporter.py", "org.inkscape.extension.30306/scientific_inkscape/autoexporter_script.py", "org.inkscape.extension.30306/scientific_inkscape/combine_by_color.inx", "org.inkscape.extension.30306/scientific_inkscape/combine_by_color.py", "org.inkscape.extension.30306/scientific_inkscape/dhelpers.py", "org.inkscape.extension.30306/scientific_inkscape/favorite_markers.inx", "org.inkscape.extension.30306/scientific_inkscape/favorite_markers.py", "org.inkscape.extension.30306/scientific_inkscape/flatten_plots.inx", "org.inkscape.extension.30306/scientific_inkscape/flatten_plots.py", "org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.inx", "org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.py", "org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_script.py", "org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_template.html", "org.inkscape.extension.30306/scientific_inkscape/homogenizer.inx", "org.inkscape.extension.30306/scientific_inkscape/homogenizer.py", "org.inkscape.extension.30306/scientific_inkscape/image_helpers.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/base.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/bezier.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/colors.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/command.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/css.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/deprecatedeffect.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/main.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/meta.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/bezmisc.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cspsubdiv.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cubicsuperpath.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/ffgeom.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/README.rst", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/run_command.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplepath.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplestyle.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simpletransform.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_base.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_filters.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_groups.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_image.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_meta.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_polygons.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_selected.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_svg.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_text.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_use.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_utils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/extensions.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/app.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/asyncme.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/listview.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/pixmap.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/README.md", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/tester.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/window.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/IElement.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/inx.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/localization.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/paths.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/ports.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/properties.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/styles.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/decorators.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/filters.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.rng", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.schema", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inx.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/mock.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/svg.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/test_inx_file.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/word.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/xmldiff.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/cache.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/font_properties.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/fontconfig-2.0.typelib", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/freetype2-2.0.typelib", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFc-1.0.typelib", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFT2-1.0.typelib", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/utils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/transforms.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/turtle.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tween.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/units.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/utils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5freq.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5prober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/chardistribution.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetgroupprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/chardetect.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachine.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachinedict.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cp949prober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/enums.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escsm.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/eucjpprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrfreq.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwfreq.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312freq.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312prober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/hebrewprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jisfreq.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabfreq.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jpcntx.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langbulgarianmodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langgreekmodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhebrewmodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhungarianmodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langrussianmodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langthaimodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langturkishmodel.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/latin1prober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/macromanprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcharsetprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcsgroupprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcssm.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/languages.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/py.typed", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/resultdict.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcharsetprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcsgroupprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sjisprober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/universaldetector.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf1632prober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf8prober.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/version.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/py.typed", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/xpath.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/afmLib.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/agl.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/specializer.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/width.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/builder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/errors.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/geometry.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/table_builder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/unbuilder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/config/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/config/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/benchmark.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cli.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cu2qu.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/errors.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/ufo.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/split.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/statNames.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/types.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/encodings/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/encodings/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/encodings/codecs.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/encodings/MacRoman.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/encodings/StandardEncoding.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/ast.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/builder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/error.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/lexer.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/location.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/lookupDebugInfo.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/feaLib/variableScalar.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/fontBuilder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/help.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/base.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/cmap.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/layout.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/options.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/tables.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/unicode.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/merge/util.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/arrayTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/bezierTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/classifyTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/cliTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/configTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/cython.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/dictTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/eexec.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/encodingTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/etree.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/filenames.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/fixedTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/intTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/loggingTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/macCreatorType.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/macRes.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/plistlib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/plistlib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/plistlib/py.typed", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/psCharStrings.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/psLib.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/psOperators.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/py23.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/roundTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/sstruct.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/symfont.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/testTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/textTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/timeTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/transform.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/treeTools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/vector.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/visitor.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/xmlReader.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/misc/xmlWriter.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/mtiLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/mtiLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/mtiLib/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/builder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/builder.py.sketch", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/error.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/maxContextCalc.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/optimize/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/optimize/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/optimize/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/otlLib/optimize/gpos.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/areaPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/basePen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/boundsPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/cairoPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/cocoaPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/cu2quPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/explicitClosingLinePen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/filterPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/freetypePen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/hashPointPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/momentsPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/perimeterPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/pointInsidePen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/pointPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/qtPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/qu2cuPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/quartzPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/recordingPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/reportLabPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/reverseContourPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/roundingPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/statisticsPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/svgPathPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/t2CharStringPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/teePen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/transformPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/ttGlyphPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/pens/wxPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/qu2cu/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/qu2cu/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/qu2cu/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/qu2cu/benchmark.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/qu2cu/cli.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/qu2cu/qu2cu.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/subset/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/subset/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/subset/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/subset/cff.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/subset/svg.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/subset/util.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/path/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/path/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/path/arc.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/path/parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/svgLib/path/shapes.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/t1Lib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/t1Lib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/tfmLib.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/macUtils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/removeOverlaps.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/scaleUpem.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/sfnt.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/standardGlyphOrder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_a_n_k_r.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_a_v_a_r.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_b_s_l_n.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_c_i_d_g.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_c_m_a_p.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_c_v_a_r.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_c_v_t.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_f_e_a_t.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_f_p_g_m.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_f_v_a_r.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_g_a_s_p.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_g_c_i_d.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_g_l_y_f.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_g_v_a_r.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_h_d_m_x.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_h_e_a_d.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_h_h_e_a.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_h_m_t_x.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_k_e_r_n.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_l_c_a_r.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_l_o_c_a.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_l_t_a_g.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_m_a_x_p.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_m_e_t_a.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_m_o_r_t.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_m_o_r_x.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_n_a_m_e.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_o_p_b_d.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_p_o_s_t.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_p_r_e_p.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_p_r_o_p.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_s_b_i_x.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_t_r_a_k.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_v_h_e_a.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/_v_m_t_x.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/asciiTable.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/B_A_S_E_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/BitmapGlyphMetrics.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/C_B_D_T_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/C_B_L_C_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/C_F_F_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/C_F_F__2.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/C_O_L_R_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/C_P_A_L_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/D__e_b_g.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/D_S_I_G_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/DefaultTable.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/E_B_D_T_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/E_B_L_C_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/F__e_a_t.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/F_F_T_M_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G__l_a_t.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G__l_o_c.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G_D_E_F_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G_M_A_P_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G_P_K_G_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G_P_O_S_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/G_S_U_B_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/grUtils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/H_V_A_R_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/J_S_T_F_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/L_T_S_H_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/M_A_T_H_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/M_E_T_A_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/M_V_A_R_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/O_S_2f_2.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/otBase.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/otConverters.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/otData.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/otTables.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/otTraverse.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/S__i_l_f.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/S__i_l_l.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/S_I_N_G_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/S_T_A_T_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/S_V_G_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/sbixGlyph.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/sbixStrike.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I__0.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I__1.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I__2.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I__3.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I__5.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_B_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_C_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_D_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_J_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_P_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_S_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_S_I_V_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/T_T_F_A_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/table_API_readme.txt", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/ttProgram.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/TupleVariation.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/V_D_M_X_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/V_O_R_G_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/tables/V_V_A_R_.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/ttCollection.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/ttFont.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/ttGlyphSet.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/ttVisitor.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttLib/woff2.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ttx.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/converters.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/errors.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/etree.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/filenames.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/glifLib.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/kerning.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/plistlib.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/pointPen.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/utils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/ufoLib/validators.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicode.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicodedata/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicodedata/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicodedata/Blocks.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicodedata/OTTags.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicodedata/ScriptExtensions.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/unicodedata/Scripts.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/builder.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/cff.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/errors.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/featureVars.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/instancer/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/instancer/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/instancer/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/instancer/featureVars.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/instancer/names.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/instancer/solver.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/interpolatable.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/interpolate_layout.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/iup.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/merger.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/models.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/mutator.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/mvar.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/plot.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/stat.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/varLib/varStore.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/voltLib/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/voltLib/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/voltLib/ast.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/voltLib/error.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/voltLib/lexer.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/voltLib/parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/.gitignore", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/COPYING", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/fontconfig.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/README", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/setup.cfg", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/python_fontconfig/setup_renamed_for_inkman.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/README.md", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/bezier.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/document.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/misctools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/parser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/path.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/paths2svg.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/polytools.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/smoothing.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/svg_io_sax.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgpathtools/svg_to_paths.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/animate.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/base.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/container.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/colors.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/full11.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/pattern.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/svgparser.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/tiny12.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/typechecker.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/data/types.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/drawing.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/elementfactory.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/etree.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/extensions/", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/extensions/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/extensions/inkscape.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/extensions/shapes.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/filters.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/gradients.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/image.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/masking.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/mixins.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/params.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/path.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/pattern.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/shapes.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/solidcolor.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/text.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/utils.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/validator2.py", "org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/svgwrite/version.py", "org.inkscape.extension.30306/scientific_inkscape/packages/", "org.inkscape.extension.30306/scientific_inkscape/packages/click/", "org.inkscape.extension.30306/scientific_inkscape/packages/click/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/_compat.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/_termui_impl.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/_textwrap.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/_unicodefun.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/_winconsole.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/core.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/decorators.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/exceptions.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/formatting.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/globals.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/parser.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/click/shell_completion.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/termui.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/testing.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/types.py", "org.inkscape.extension.30306/scientific_inkscape/packages/click/utils.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/__main__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/_compat.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/app.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/blueprints.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/cli.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/config.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/ctx.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/debughelpers.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/dependency_links.txt", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/entry_points.txt", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/PKG-INFO", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/requires.txt", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/SOURCES.txt", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/Flask.egg-info/top_level.txt", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/globals.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/helpers.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/json/", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/json/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/json/tag.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/logging.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/pngs/", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/pngs/scale_options.png", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/scaffold.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/sessions.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/signals.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/templating.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/testing.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/typing.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/views.py", "org.inkscape.extension.30306/scientific_inkscape/packages/flask/wrappers.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/_json.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/encoding.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/exc.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/jws.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/serializer.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/signer.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/timed.py", "org.inkscape.extension.30306/scientific_inkscape/packages/itsdangerous/url_safe.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/_compat.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/_identifier.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/async_utils.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/asyncfilters.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/asyncsupport.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/bccache.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/compiler.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/constants.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/debug.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/defaults.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/environment.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/exceptions.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/ext.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/filters.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/idtracking.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/lexer.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/loaders.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/meta.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/nativetypes.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/nodes.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/optimizer.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/parser.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/runtime.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/tests.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/utils.py", "org.inkscape.extension.30306/scientific_inkscape/packages/jinja2/visitor.py", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/_compat.py", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/_constants.py", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/_native.py", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/_speedups.c", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/_speedups.cp39-win_amd64.pyd", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/_speedups.pyi", "org.inkscape.extension.30306/scientific_inkscape/packages/markupsafe/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/onenoteextractor/", "org.inkscape.extension.30306/scientific_inkscape/packages/onenoteextractor/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/onenoteextractor/cli.py", "org.inkscape.extension.30306/scientific_inkscape/packages/onenoteextractor/enc.py", "org.inkscape.extension.30306/scientific_inkscape/packages/onenoteextractor/one.py", "org.inkscape.extension.30306/scientific_inkscape/packages/onenoteextractor/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/pyaes/", "org.inkscape.extension.30306/scientific_inkscape/packages/pyaes/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/pyaes/aes.py", "org.inkscape.extension.30306/scientific_inkscape/packages/pyaes/blockfeeder.py", "org.inkscape.extension.30306/scientific_inkscape/packages/pyaes/util.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/events.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/api.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/fsevents.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/fsevents2.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/inotify.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/inotify_buffer.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/inotify_c.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/kqueue.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/polling.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/read_directory_changes.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/observers/winapi.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/tricks/", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/tricks/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/bricks.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/delayed_queue.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/dirsnapshot.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/echo.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/patterns.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/utils/platform.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/version.py", "org.inkscape.extension.30306/scientific_inkscape/packages/watchdog/watchmedo.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/_internal.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/_reloader.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/datastructures.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/datastructures.pyi", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/console.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/repr.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/console.png", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/debugger.js", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/FONT_LICENSE", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/ICON_LICENSE.md", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/less.png", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/more.png", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/source.png", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/style.css", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/shared/ubuntu.ttf", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/debug/tbtools.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/exceptions.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/filesystem.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/formparser.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/http.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/local.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/dispatcher.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/http_proxy.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/lint.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/profiler.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/proxy_fix.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/middleware/shared_data.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/py.typed", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/converters.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/exceptions.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/map.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/matcher.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/routing/rules.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/http.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/multipart.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/request.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/response.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/sansio/utils.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/security.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/serving.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/test.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/testapp.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/urls.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/user_agent.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/useragents.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/utils.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/__init__.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/accept.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/auth.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/base_request.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/base_response.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/common_descriptors.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/cors.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/etag.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/json.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/request.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/response.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wrappers/user_agent.py", "org.inkscape.extension.30306/scientific_inkscape/packages/werkzeug/wsgi.py", "org.inkscape.extension.30306/scientific_inkscape/pngs/", "org.inkscape.extension.30306/scientific_inkscape/pngs/converting_emf.svg", "org.inkscape.extension.30306/scientific_inkscape/pngs/converting_wmf.svg", "org.inkscape.extension.30306/scientific_inkscape/pngs/missing_svg.svg", "org.inkscape.extension.30306/scientific_inkscape/pngs/scale_options.png", "org.inkscape.extension.30306/scientific_inkscape/remove_kerning.py", "org.inkscape.extension.30306/scientific_inkscape/scale_plots.inx", "org.inkscape.extension.30306/scientific_inkscape/scale_plots.py", "org.inkscape.extension.30306/scientific_inkscape/speedups.py", "org.inkscape.extension.30306/scientific_inkscape/styles0.py", "org.inkscape.extension.30306/scientific_inkscape/text_ghoster.inx", "org.inkscape.extension.30306/scientific_inkscape/text_ghoster.py"]} \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/applytransform_mod.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/applytransform_mod.py new file mode 100644 index 0000000..13e3e9b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/applytransform_mod.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +# +# License: GPL2 +# Copyright Mark "Klowner" Riedesel +# https://github.com/Klowner/inkscape-applytransforms +# Modified by David Burghoff + +import inkex +from inkex.paths import CubicSuperPath, Path +from inkex.transforms import Transform +from inkex import Rectangle, Ellipse, Circle + +import math +import dhelpers as dh +from inkex.text.cache import BaseElementCache + +Itr = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) + + +# @staticmethod +def remove_attrs(el): + if el.tag == inkex.addNS("g", "svg"): + return el + if el.tag == inkex.addNS("path", "svg") or el.tag == "path": + for attName in el.attrib.keys(): + if ( + (("sodipodi" in attName) or ("inkscape" in attName)) + and "inkscape-academic" not in attName + and "inkscape-scientific" not in attName + ): + del el.attrib[attName] + return el + return el + + +# Scale stroke width and dashes +def applyToStrokes(el, tf): + if "style" in el.attrib: + style = el.cstyle + update = False + if "stroke-width" in style: + try: + stroke_width = dh.ipx(style.get("stroke-width")) + stroke_width *= math.sqrt(abs(tf.a * tf.d - tf.b * tf.c)) + style["stroke-width"] = str(stroke_width) + update = True + except AttributeError: + pass + if "stroke-dasharray" in style: + try: + strokedasharray = style.get("stroke-dasharray") + if strokedasharray.lower() != "none": + strokedasharray = dh.listsplit(style.get("stroke-dasharray")) + strokedasharray = [ + sdv * math.sqrt(abs(tf.a * tf.d - tf.b * tf.c)) + for sdv in strokedasharray + ] + style["stroke-dasharray"] = ( + str(strokedasharray).strip("[").strip("]") + ) + update = True + except AttributeError: + pass + if update: + el.cstyle = style + + +def transform_clipmask(el, mask=False): + if not (mask): + cm = "clip-path" + else: + cm = "mask" + clippathurl = el.get(cm) + if clippathurl is not None and el.ctransform is not None: + svg = el.croot + clippath = svg.getElementById(clippathurl[5:-1]) + if clippath is not None: + d = clippath.duplicate() + clippathurl = "url(#" + d.get_id() + ")" + el.set(cm, clippathurl) + dh.fix_css_clipmask(el, mask=mask) + for k in d.getchildren(): + if k.ctransform is not None: + tr = el.ctransform @ k.ctransform + else: + tr = el.ctransform + k.ctransform = tr + + +poly_tags = {inkex.Polygon.ctag, inkex.Polyline.ctag} +round_tags = {inkex.Ellipse.ctag, inkex.Circle.ctag} +line_tag = inkex.Line.ctag +rect_tag = inkex.Rectangle.ctag + + +def fuseTransform(el, transf=Itr, irange=None, trange=None, applytostroke=True): + # Fuses an object's transform to its path, adding the additional transformation transf + # When applytostroke enabled, transform goes onto stroke/dashes, keeping it looking the same + # Without it, it is applied to the points only + + if el.tag in BaseElementCache.otp_support_tags: # supported types + # Since transforms apply to an object's clips, before applying the transform + # we will need to duplicate the clip path and transform it + transform_clipmask(el, mask=False) + transform_clipmask(el, mask=True) + + transf = Transform(transf) @ el.ctransform + el.ctransform = None + el = remove_attrs(el) + + if not (transf.b == 0 and transf.c == 0) and isinstance( + el, (Rectangle, Ellipse, Circle) + ): + # Rectangles, Ellipses, and Circles need to be converted to paths if there is shear/rotation + el.object_to_path() + + if not (transf == Itr and irange is None and trange is None): + # Don't do anything if there is effectively no transform applied + if el.tag in poly_tags: + points = [] + for p in el.cpath.end_points: + p = transf.apply_to_point([p[0],p[1]]) + points.append(f"{p[0]},{p[1]}") + points = " ".join(points) + el.set("points", points) + elif el.tag in round_tags: + + def isequal(a, b): + return abs(a - b) <= transf.absolute_tolerance + + if el.tag == inkex.addNS( + "ellipse", "svg" + ): # "{http://www.w3.org/2000/svg}ellipse": + rx = dh.ipx(el.get("rx")) + ry = dh.ipx(el.get("ry")) + else: + rx = dh.ipx(el.get("r")) + ry = rx + cx = dh.ipx(el.get("cx")) + cy = dh.ipx(el.get("cy")) + sqxy1 = (cx - rx, cy - ry) + sqxy2 = (cx + rx, cy - ry) + sqxy3 = (cx + rx, cy + ry) + newxy1 = transf.apply_to_point(sqxy1) + newxy2 = transf.apply_to_point(sqxy2) + newxy3 = transf.apply_to_point(sqxy3) + el.set("cx", (newxy1[0] + newxy3[0]) / 2) + el.set("cy", (newxy1[1] + newxy3[1]) / 2) + edgex = math.sqrt( + abs(newxy1[0] - newxy2[0]) ** 2 + abs(newxy1[1] - newxy2[1]) ** 2 + ) + edgey = math.sqrt( + abs(newxy2[0] - newxy3[0]) ** 2 + abs(newxy2[1] - newxy3[1]) ** 2 + ) + + if isequal(edgex, edgey): + el.tag = inkex.addNS("circle", "svg") + el.set("rx", None) + el.set("ry", None) + el.set("r", edgex / 2) + else: + el.tag = inkex.addNS("ellipse", "svg") + el.set("rx", edgex / 2) + el.set("ry", edgey / 2) + el.set("r", None) + elif el.tag in line_tag: + x1 = dh.ipx(el.get("x1")) + x2 = dh.ipx(el.get("x2")) + y1 = dh.ipx(el.get("y1")) + y2 = dh.ipx(el.get("y2")) + p1 = transf.apply_to_point([x1, y1]) + p2 = transf.apply_to_point([x2, y2]) + el.set("x1", str(p1[0])) + el.set("y1", str(p1[1])) + el.set("x2", str(p2[0])) + el.set("y2", str(p2[1])) + + elif el.tag in rect_tag: + x = dh.ipx(el.get("x")) + y = dh.ipx(el.get("y")) + w = dh.ipx(el.get("width")) + h = dh.ipx(el.get("height")) + pts = [[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]] + xs = [] + ys = [] + for p in pts: + p = transf.apply_to_point(p) + xs.append(p.x) + ys.append(p.y) + el.set("x", str(min(xs))) + el.set("y", str(min(ys))) + el.set("width", str(max(xs) - min(xs))) + el.set("height", str(max(ys) - min(ys))) + else: + if "d" in el.attrib: + # inkex.utils.debug(el.get('d')) + d = el.get("d") + try: + p = CubicSuperPath(d) + except ZeroDivisionError: + p = Path(d) + if irange is None: + p = Path(p).to_absolute().transform(transf, True) + if irange is not None: + p = Path(p).to_absolute() + pnew = [] + for ii in range(len(irange)): + xf = ( + trange[ii] @ el.ctransform + ) # Transform(el.get("transform", None)) + pnew += Path(p[irange[ii][0] : irange[ii][1]]).transform( + xf, True + ) + p = pnew + + try: + p2 = str(Path(CubicSuperPath(p).to_path())) + except ZeroDivisionError: + p2 = str(Path(p)) + el.set("d", p2) + + el.cpath = None + + if applytostroke: + applyToStrokes(el, transf) + + # Duplicate any gradient and apply the transform + for sf in ["fill", "stroke"]: + sfel = el.cstyle.get_link(sf, svg=el.croot) + if sfel is not None and "gradient" in sfel.tag.lower(): + d = sfel.duplicate() + el.cstyle[sf] = "url(#{0})".format(d.get_id()) + gt = d.get("gradientTransform") + gt = Transform(gt) if gt is not None else Itr + d.set("gradientTransform", str(transf @ gt)) + + for child in list(el): + fuseTransform(child, transf) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter.inx b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter.inx new file mode 100644 index 0000000..4ffeeb5 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter.inx @@ -0,0 +1,98 @@ + + + Autoexporter + burghoff.autoexporter + + + + + + true + false + false + false + false + + + + + + + + + + + + + + + + + + + + + 600 + + + + true + + + false + true + false + true + 0.3 + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter.py new file mode 100644 index 0000000..936196e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter.py @@ -0,0 +1,1949 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# pylint: disable=import-error +""" +AutoExporter for Inkscape + +This module provides an extension for Inkscape to automate the export of SVG files +into multiple formats. It supports watching directories, exporting immediately, +and offers various configuration options for exporting to PDF, PNG, EMF, EPS, +and plain SVG. The module handles preprocessing steps to ensure compatibility +and quality of exported files, such as embedding linked images, fixing markers, +scaling text, and more. + +Main Features: +- Watches specified directories for changes and exports files automatically. +- Supports exporting to multiple formats with various options. +- Handles preprocessing of SVG files to fix common issues and improve compatibility. +- Provides postprocessing to clean up and adjust SVGs for better rendering in + different environments. +- Includes utility functions for handling subprocesses, file manipulations, + and SVG operations. +""" + +import os +import sys +import time +import re +import copy +import subprocess +import warnings +import pickle +import math +import shutil +import tempfile +import hashlib +import lxml +import random +import threading + +import dhelpers as dh +import inkex +from inkex import TextElement, Transform, Vector2d +from inkex.text.utils import default_style_atts, unique +from inkex.text.cache import BaseElementCache +from inkex.text.parser import ParsedText, xyset + +import numpy as np +import image_helpers as ih + +otp_support_tags = BaseElementCache.otp_support_tags +peltag = inkex.PathElement.ctag + +USE_TERMINAL = False +DEBUGGING = False +DISPPROFILE = False + + +MAXATTEMPTS = 2 +MAX_THREADS = 10; +sema1 = threading.Semaphore(MAX_THREADS) +sema2 = threading.Semaphore(MAX_THREADS) + +class AutoExporter(inkex.EffectExtension): + """Automates exporting of SVG files in multiple formats.""" + + def add_arguments(self, pars): # pylint: disable=no-self-use + """Add arguments to the parser.""" + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument("--watchdir", help="Watch directory") + pars.add_argument("--writedir", help="Write directory") + pars.add_argument( + "--usepdf", type=inkex.Boolean, default=False, help="Export PDF?" + ) + pars.add_argument( + "--usepng", type=inkex.Boolean, default=False, help="Export PNG?" + ) + pars.add_argument( + "--useemf", type=inkex.Boolean, default=False, help="Export EMF?" + ) + pars.add_argument( + "--useeps", type=inkex.Boolean, default=False, help="Export EPS?" + ) + pars.add_argument( + "--usepsvg", type=inkex.Boolean, default=False, help="Export plain SVG?" + ) + pars.add_argument("--dpi", default=600, help="Rasterization DPI") + # pars.add_argument("--dpi_im", default=300, help="Resampling DPI") + pars.add_argument( + "--imagemode2", + type=inkex.Boolean, + default=True, + help="Embedded image handling", + ) + pars.add_argument( + "--thinline", + type=inkex.Boolean, + default=True, + help="Prevent thin line enhancement", + ) + pars.add_argument( + "--texttopath", type=inkex.Boolean, default=False, help="Text to paths?" + ) + pars.add_argument( + "--backingrect", + type=inkex.Boolean, + default=True, + help="Add backing rectangle?", + ) + pars.add_argument( + "--stroketopath", type=inkex.Boolean, default=False, help="Stroke to paths?" + ) + pars.add_argument( + "--latexpdf", type=inkex.Boolean, default=False, help="Make LaTeX PDF?" + ) + pars.add_argument( + "--testmode", type=inkex.Boolean, default=False, help="Test mode?" + ) + pars.add_argument("--testpage", type=int, default=1, help="Test mode page") + pars.add_argument("--v", type=str, default="1.2", help="Version for debugging") + pars.add_argument( + "--rasterizermode", type=int, default=1, help="Mark for rasterization" + ) + pars.add_argument( + "--margin", type=float, default=0.5, help="Document margin (mm)" + ) + pars.add_argument( + "--exportwhat", type=int, default=1, help="Export what?" + ) + + def effect(self): + """Start the Autoexporter or initiate export now.""" + if self.options.tab == "rasterizer": + sel = [self.svg.selection[i] for i in range(len(self.svg.selection))] + for elem in sel: + if self.options.rasterizermode == 1: + elem.set("autoexporter_rasterize", "png") + elif self.options.rasterizermode == 2: + elem.set("autoexporter_rasterize", "jpg") + elif self.options.rasterizermode == 3: + elem.set("autoexporter_rasterize", "topath") + else: + elem.set("autoexporter_rasterize", None) + return + self.options.exportnow = self.options.exportwhat==3 + self.options.watchhere = self.options.exportwhat==2 + + # self.options.testmode = True; + if self.options.testmode: + self.options.usepsvg = True + self.options.thinline = True + self.options.imagemode2 = True + self.options.texttopath = True + self.options.stroketopath = True + self.options.exportnow = True + self.options.margin = 0.5 + self.options.latexpdf = False + + if DISPPROFILE: + # pylint: disable=import-outside-toplevel + import cProfile + import pstats + import io + from pstats import SortKey + # pylint: enable=import-outside-toplevel + + prf = cProfile.Profile() + prf.enable() + + formats = [ + self.options.usepdf, + self.options.usepng, + self.options.useemf, + self.options.useeps, + self.options.usepsvg, + ] + formats = [ + ["pdf", "png", "emf", "eps", "psvg"][i] + for i in range(len(formats)) + if formats[i] + ] + + # Make an options copy we can pass to the external program + optcopy = copy.copy(self.options) + delattr(optcopy, "output") + delattr(optcopy, "input_file") + optcopy.reduce_images = self.options.imagemode2 + + bfn = inkex.inkscape_system_info.binary_location + pyloc, pybin = os.path.split(sys.executable) + + if not (self.options.exportnow): + aepy = os.path.abspath( + os.path.join(dh.get_script_path(), "autoexporter_script.py") + ) + + if self.options.watchhere: + pth = dh.Get_Current_File(self, "To watch this document's location, ") + optcopy.watchdir = os.path.dirname(pth) + optcopy.writedir = os.path.dirname(pth) + + if not os.path.exists(optcopy.watchdir): + dh.idebug( + "Watch directory could not be found. Please be sure the " + "selected location is valid.\n" + ) + dh.idebug( + "Note: Linux AppImage and Snap installations of Inkscape " + "cannot see locations outside the installation directory." + ) + sys.exit() + + # Pass settings using a config file. Include the current path so + # Inkex can be called if needed. + optcopy.inkscape_bfn = bfn + optcopy.formats = formats + optcopy.syspath = sys.path + + try: + with warnings.catch_warnings(): + # Ignore ImportWarning for Gtk/Pango + warnings.simplefilter("ignore") + # Prevent Gtk-Message: Failed to load module "xapp-gtk3-module" + os.environ["GTK_MODULES"] = "" + # pylint: disable=import-outside-toplevel, unused-import + import gi + + gi.require_version("Gtk", "3.0") + from gi.repository import Gtk # noqa + # pylint: enable=import-outside-toplevel, unused-import + guitype = "gtk" + except ImportError: + guitype = "terminal" + if USE_TERMINAL: + guitype = "terminal" + optcopy.guitype = guitype + + aes = os.path.join( + os.path.abspath(tempfile.gettempdir()), "si_ae_settings.p" + ) + with open(aes, "wb") as file: + pickle.dump(optcopy, file) + warnings.simplefilter("ignore", ResourceWarning) + # prevent warning that process is open + + if guitype == "gtk": + AutoExporter._gtk_call(pybin, aepy) + else: + AutoExporter._terminal_call(pybin, aepy, pyloc) + + else: + if not (self.options.testmode): + pth = dh.Get_Current_File(self, "To do a direct export, ") + else: + pth = self.options.input_file + optcopy.original_file = pth + optcopy.debug = DEBUGGING + optcopy.prints = False + optcopy.linked_locations = ih.get_linked_locations(self) + # needed to find linked images in relative directories + optcopy.formats = formats + optcopy.outtemplate = pth + optcopy.bfn = bfn + + Exporter(self.options.input_file, optcopy).export_all() + + if DISPPROFILE: + prf.disable() + sio = io.StringIO() + sortby = SortKey.CUMULATIVE + pst = pstats.Stats(prf, stream=sio).sort_stats(sortby) + pst.print_stats() + dh.debug(sio.getvalue()) + + if self.options.testmode: + nfn = os.path.abspath(pth[0:-4] + "_plain.svg") + stream = self.options.output + + if isinstance(stream, str): + # Copy the new file + shutil.copyfile(nfn, self.options.output) + else: + # Write to the output stream + svg2 = get_svg(nfn) + newdoc = lxml.etree.tostring(svg2, pretty_print=True) + try: + stream.write(newdoc) + except TypeError: + # we hope that this happens only when document needs to be encoded + stream.write(newdoc.encode("utf-8")) # type: ignore + self.options.output = None + + os.remove(nfn) + + # Runs a Python script using a Python binary in a working directory + # It detaches from Inkscape, allowing it to continue running after the + # extension has finished + @staticmethod + def _gtk_call(python_bin, python_script): + """Run a Python script using GTK terminal.""" + devnull_location = dh.shared_temp(filename="si_ae_output.txt") + with open(devnull_location, "w") as devnull: + subprocess.Popen( + [python_bin, python_script], stdout=devnull, stderr=devnull + ) + + @staticmethod + def _terminal_call(python_bin, python_script, python_wd): + """Run a Python script using a terminal.""" + + def escp(x): + return x.replace(" ", "\\\\ ") + + if sys.platform == "darwin": + # https://stackoverflow.com/questions/39840632/launch-python-script-in-new-terminal + os.system( + 'osascript -e \'tell application "Terminal" to do script "' + + escp(sys.executable) + + " " + + escp(python_script) + + "\"' >/dev/null" + ) + elif sys.platform == "win32": + if "pythonw.exe" in python_bin: + python_bin = python_bin.replace("pythonw.exe", "python.exe") + subprocess.Popen([python_bin, python_script], shell=False, cwd=python_wd) + + # if 'pythonw.exe' in python_bin: + # python_bin = python_bin.replace('pythonw.exe', 'python.exe') + # DETACHED_PROCESS = 0x08000000 + # subprocess.Popen([python_bin, python_script, 'standalone'], + # creationflags=DETACHED_PROCESS) + else: + if sys.executable[0:4] == "/tmp": + inkex.utils.errormsg( + "This appears to be an AppImage of Inkscape, which the " + "Autoexporter cannot support since AppImages are sandboxed." + ) + return + if sys.executable[0:5] == "/snap": + inkex.utils.errormsg( + "This appears to be an Snap installation of Inkscape, which " + "the Autoexporter cannot support since Snap installations " + "are sandboxed." + ) + return + terminals = [ + "x-terminal-emulator", + "mate-terminal", + "gnome-terminal", + "terminator", + "xfce4-terminal", + "urxvt", + "rxvt", + "termit", + "Eterm", + "aterm", + "uxterm", + "xterm", + "roxterm", + "termite", + "lxterminal", + "terminology", + "st", + "qterminal", + "lilyterm", + "tilix", + "terminix", + "konsole", + "kitty", + "guake", + "tilda", + "alacritty", + "hyper", + "terminal", + "iTerm", + "mintty", + "xiterm", + "terminal.app", + "Terminal.app", + "terminal-w", + "terminal.js", + "Terminal.js", + "conemu", + "cmder", + "powercmd", + "terminus", + "termina", + "terminal-plus", + "iterm2", + "terminus-terminal", + "terminal-tabs", + ] + terms = [] + for terminal in terminals: + result = subprocess.run( + ["which", terminal], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if result.returncode == 0: + terms.append(terminal) + + for t in reversed(terms): + if t == "x-terminal-emulator": + linux_terminal_call = "x-terminal-emulator -e bash -c '%CMD'" + elif t == "gnome-terminal": + linux_terminal_call = 'gnome-terminal -- bash -c "%CMD; exec bash"' + elif t == "konsole": + linux_terminal_call = "konsole -e bash -c '%CMD'" + os.system( + linux_terminal_call.replace( + "%CMD", + escp(sys.executable) + " " + escp(python_script) + " >/dev/null", + ) + ) + +class Exporter(): + def __init__(self,fin,opts): + self.filein = fin + self.__dict__.update(vars(opts)) + + sema_temp = threading.Semaphore(1) + def export_all(self): + """Export all files in specified formats.""" + # Make a temp directory + self.tempdir, self.temphead = dh.shared_temp('ae') + self.tempbase = joinmod(self.tempdir,self.temphead) + + + if self.debug: + if self.prints: + self.prints("\n " + joinmod(self.tempdir, "")) + + # Make sure output directory exists + outdir = os.path.dirname(self.outtemplate) + if not os.path.exists(outdir): + os.makedirs(outdir) + + # Add a document margin + cfile = self.filein # current file we're working on + if self.margin != 0: + svg = get_svg(cfile) + tmp = self.tempbase + "_marg.svg" + Exporter.add_margin(svg, self.margin, self.testmode) + dh.overwrite_svg(svg, tmp) + cfile = copy.copy(tmp) + + # Do png before any preprocessing + if "png" in self.formats: + finished, myo = self.export_file(cfile, "png") + + # Do preprocessing + if any(fmt in self.formats for fmt in ["pdf", "emf", "eps", "psvg"]): + cfile = self.preprocessing(cfile) + + # Do vector outputs + newfiles = [] + for fmt in self.formats: + if fmt != "png": + finished, myo = self.export_file(cfile, fmt) + if finished: + newfiles.append(myo) + + # Remove temporary outputs and directory + failed_to_delete = None + if not (self.debug): + self.clear_temp() + else: + warnings.simplefilter("ignore", ResourceWarning) + # prevent warning that process is open + subprocess.Popen(f'explorer "{os.path.realpath(self.tempdir)}"') + return failed_to_delete + + def clear_temp(self): + """ Clear any temporary files """ + if not (self.debug): + tmps = [] + for t in os.listdir(self.tempdir): + tmp = joinmod(self.tempdir, t) + try: + one_day_ago = time.time() - 24 * 60 * 60 + if os.path.getmtime(tmp) < one_day_ago: + tmps.append(joinmod(self.tempdir, t)) + except FileNotFoundError: # already deleted + pass + if tmp.startswith(self.tempbase): + tmps.append(joinmod(self.tempdir, t)) + for tmp in tmps: + if os.path.exists(tmp): + deleted = False + nattempts = 0 + while not (deleted) and nattempts < MAXATTEMPTS: + try: + os.remove(tmp) + deleted = True + except PermissionError: + time.sleep(1) + nattempts += 1 + + def preprocessing(self, fin): + """Modifications that are done prior to conversion to any vector output""" + if self.prints: + fname = os.path.split(self.filein)[1] + try: + offset = round(os.get_terminal_size().columns / 2) + except OSError: + offset = 40 + fname = fname + " " * max(0, offset - len(fname)) + self.prints(fname + ": Preprocessing vector output", flush=True) + timestart = time.time() + + tempdir = self.tempdir + temphead = self.temphead + + # SVG modifications that should be done prior to any binary calls + cfile = fin + svg = get_svg(cfile) + + # Prune hidden items and remove language switching + stag = inkex.addNS("switch", "svg") + todelete, todelang, switches = [], [], [] + for elem in dh.visible_descendants(svg): + if elem.cspecified_style.get("display") == "none": + todelete.append(elem) + if elem.get("systemLanguage") is not None: + lang = inkex.inkscape_system_info.language + if elem.get("systemLanguage") == lang: + todelang.append(elem) + # Remove other languages from switches + if elem.getparent().tag == stag: + todelete.extend( + [ + k + for k in elem.getparent() + if k.get("systemLanguage") != lang + ] + ) + else: + # Remove non-matching languages + todelete.append(elem) + if elem.tag == stag: + switches.append(elem) + for elem in unique(todelete): + elem.delete() + for elem in todelang: + elem.set("systemLanguage", None) + for elem in switches: + if len(elem)==1: + dh.ungroup(elem) + + # Embed linked images into the SVG. This should be done prior to clone unlinking + # since some images may be cloned + if self.exportnow: + lls = self.linked_locations + else: + lls = ih.get_linked_locations_file(cfile, svg) + for k in lls: + elem = svg.getElementById(k) + ih.embed_external_image(elem, lls[k]) + + vds = dh.visible_descendants(svg) + raster_ids, image_ids, jpgs = [], [], [] + for elem in vds: + # Unlink any clones for the PDF image and marker fixes + if isinstance(elem, (inkex.Use)) and not (isinstance(elem, (inkex.Symbol))): + newel = dh.unlink2(elem) + myi = vds.index(elem) + vds[myi] = newel + for ddv in newel.descendants2()[1:]: + vds.append(ddv) + + # Remove trivial groups inside masks/transparent objects or clipped groups + if isinstance(elem, (inkex.Group)): + ancs = elem.ancestors2(includeme=True) + masked = any( + anc.get_link("mask", svg) is not None + or ( + elem.ccascaded_style.get("opacity") is not None + and float(elem.ccascaded_style.get("opacity")) < 1 + ) + for anc in ancs + ) + clipped = any( + anc.get_link("clip-path", svg) is not None for anc in ancs + ) + if ( + len(list(elem)) == 1 + and (masked or clipped) + and elem.get("autoexporter_rasterize") not in ["png", "jpg", "True"] + ): + dh.ungroup(elem) + + # Remove groups inside clips + cpth = elem.get_link("clip-path", svg) + if cpth is not None: + while any(isinstance(v, inkex.Group) for v in list(cpth)): + for grp in list(cpth): + if isinstance(grp, inkex.Group): + dh.ungroup(grp) + + stps = [] + ttps = [] + for elem in vds: + elid = elem.get_id() + + # Fix marker export bug for PDFs + mkrs = Exporter.marker_fix(elem) + # Convert markers to path for Office + if len(mkrs) > 0 and self.usepsvg: + stps.append(elid) + + # Fix opacity bug for Office PDF saving + if len(mkrs) == 0: # works poorly with markers + Exporter.opacity_fix(elem) + + # Disable connectors + elem.set("inkscape:connector-type", None) + + # Find out which objects need to be rasterized + sty = elem.cstyle + # only want the object with the filter + if sty.get_link("filter", svg) is not None: + raster_ids.append(elid) # filtered objects (always rasterized at PDF) + if (sty.get("fill") is not None and "url" in sty.get("fill")) or ( + sty.get("stroke") is not None and "url" in sty.get("stroke") + ): + raster_ids.append(elid) # gradient objects + if elem.get_link("mask") is not None: + raster_ids.append(elid) # masked objects (always rasterized at PDF) + if elem.get("autoexporter_rasterize") in ["png", "jpg", "True"]: + raster_ids.append(elid) + # rasterizer-marked + if elem.get("autoexporter_rasterize") == "jpg": + jpgs.append(elid) + if elem.get("autoexporter_rasterize")=='topath': + ttps.append(elid) + if isinstance(elem, (inkex.Image)): + image_ids.append(elid) + # Inkscape inappropriately clips non-'optimizeQuality' images + # when generating PDFs and calculating bounding boxes + elem.cstyle["image-rendering"] = "optimizeQuality" + # Set image x,y,width,height to standard values to prevent Office + # from mishandling clips + Exporter.standardize_image(elem) + + # Remove style attributes with invalid URLs + # (Office doesn't display the element while Inkscape ignores it) + for satt in list(sty.keys()): + if ( + satt in dh.urlatts + and sty.get(satt).startswith("url") + and sty.get_link(satt, svg) is None + ): + elem.cstyle[satt] = None + + # Prune single-point paths, which Inkscape doesn't show + if elem.tag in otp_support_tags: + pth = elem.cpath + firstpt = None + trivial = True + for pnt in pth.end_points: + if firstpt is None: + firstpt = (pnt.x, pnt.y) + elif (pnt.x, pnt.y) != firstpt: + trivial = False + break + if trivial: + elem.delete(deleteup=True) + + # Fix Avenir/Whitney + tetag = inkex.TextElement.ctag + frtag = inkex.FlowRoot.ctag + tels = [elem for elem in vds if elem.tag in {tetag, frtag}] + + dh.character_fixer(tels) + + # Strip all sodipodi:role lines from document + # Conversion to plain SVG does this automatically but poorly + excludetxtids = [] + self.duplicatelabels = dict() + # if self.usepsvg: + if len(tels) > 0: + svg.make_char_table() + self.ctable = svg.char_table + # store for later + + nels = [] + for elem in reversed(tels): + if elem.parsed_text.isflow: + nels += elem.parsed_text.flow_to_text() + tels.remove(elem) + tels += nels + + for elem in tels: + elem.parsed_text.strip_text_baseline_shift() + elem.parsed_text.strip_sodipodi_role_line() + elem.parsed_text.fuse_fonts() + Exporter.subsuper_fix(elem) + + # Preserve duplicate of text to be converted to paths + if self.texttopath and elem.get("display") != "none" and elem.croot is not None: + dup = elem.duplicate() + excludetxtids.append(dup.get_id()) + grp = dh.group([dup]) + grp.set("display", "none") + + # tel = svg.new_element(inkex.TextElement, svg) + tel = inkex.TextElement() + grp.append(tel) # Remember the original ID + tel.text = "{0}: {1}".format(DUP_KEY, elem.get_id()) + tel.set("display", "none") + excludetxtids.append(tel.get_id()) + self.duplicatelabels[tel.get_id()] = elem.get_id() + + # Make sure nested tspans have fill specified (STP bug) + for dsd in elem.descendants2()[1:]: + if ( + "fill" in dsd.cspecified_style + and dsd.cspecified_style["fill"] != "#000000" + ): + dsd.cstyle["fill"] = dsd.cspecified_style["fill"] + + tmp = self.tempbase + "_mod.svg" + dh.overwrite_svg(svg, tmp) + cfile = tmp + self.excludetxtids = excludetxtids + + do_rasterizations = len(raster_ids + image_ids) > 0 + do_stroketopaths = self.texttopath or self.stroketopath or len(stps) > 0 or len(ttps)>0 + + dpi = self.dpi + class Act(): + ''' + Represents a single binary call Action. + type == 'stp' creates an action that stroke-to-path's a group of objects + type == 'imgt' exports a PNG copy of an image with a transparent background + type == 'imgo' exports a PNG copy of an image with an opaque background, and objects above hidden + ''' + def __init__(self,typ,els,fname=None,overlaps=None): + self.type = typ + if isinstance(els,list): + self.els = els + else: + self.els = [els] + if fname is None: + elid = self.els[0].get_id() + if typ=='imgt': + fname = temphead + "_im_" + elid + "." + imgtype + else: + fname = temphead + "_imbg_" + elid + "." + imgtype + self.fname = fname; + self.overlaps = overlaps + + if ( + (self.stroketopath or len(stps) > 0) + and inkex.installed_ivp[0] >= 1 + and inkex.installed_ivp[1] > 0 + ): + stpact = "object-stroke-to-path" + else: + stpact = "object-to-path" + + def __str__(self): + if self.type == 'stp': + return "select:{0}; {1}; export-filename:{2}; export-do; unselect:{0}; ".format( + ",".join(self.els), Act.stpact, self.fname + ) + elif self.type == 'imgt': + fmt1 = ( + "export-id:{0}; export-id-only; export-dpi:{1}; " + "export-filename:{2}; export-background-opacity:0.0; " + "export-do; " + ) + return fmt1.format(self.els[0].get_id(), int(dpi), self.fname) + elif self.type == 'imgo': + el = self.els[0] + fmt2 = ( + "export-id:{0}; export-dpi:{1}; " + "export-filename:{2}; export-background-opacity:1.0; " + "export-do; " + ) + actv = fmt2.format(el.get_id(), int(dpi), self.fname) + + # For export all, hide objects on top + displays = {el: el.cstyle.get('display') for el in overlaps[el]} + hides = ['select:{0}; object-set-property:display,none; unselect:{0}; '.format(el.get_id()) for el in overlaps[el]] + unhides = ['select:{0}; object-set-property:display,{1}; unselect:{0}; '.format(el.get_id(), \ + displays[el] if displays[el] is not None else '') for el in overlaps[el]] + return ''.join(hides) + actv + ''.join(unhides) + + def split(self,intermediate_fn): + ''' Splits a STP act into two sub-acts ''' + spl = math.ceil(len(self.els) / 2) + act1 = Act('stp',self.els[:spl],intermediate_fn) + act2 = Act('stp',self.els[spl:],self.fname) + return act1, act2 + + + allacts = [] + if do_stroketopaths: + svg = get_svg(cfile) + vdd = dh.visible_descendants(svg) + + updatefile = False + tels = [] + if self.texttopath or len(ttps)>0: + for elem in vdd: + if (elem.get_id() in ttps or self.texttopath) and (elem.tag == tetag and elem.get_id() not in excludetxtids): + tels.append(elem.get_id()) + for dsd in elem.descendants2(): + # Fuse fill and stroke to account for STP bugs + if "fill" in dsd.cspecified_style: + dsd.cstyle["fill"] = dsd.cspecified_style["fill"] + updatefile = True + if "stroke" in dsd.cspecified_style: + dsd.cstyle["stroke"] = dsd.cspecified_style["stroke"] + updatefile = True + + pels, dgroups = [], [] + if self.stroketopath or len(stps) > 0: + # Stroke to Path has a number of bugs, try to fix them + if self.stroketopath: + stpels = vdd + elif len(stps) > 0: + stpels = [el for el in vdd if el.get_id() in stps] + stpels = [el for el in stpels if el.get_id() not in raster_ids] + pels, dgroups = Exporter.stroke_to_path_fixes(stpels) + updatefile = True + + if updatefile: + dh.overwrite_svg(svg, cfile) + + tmpstp = self.tempbase + "_stp.svg" + allacts += [Act('stp',tels + pels,tmpstp)] + + + # Rasterizations + actts, actos = [], [] + if do_rasterizations: + svg = get_svg(cfile) + + vds = dh.visible_descendants(svg) + els = [el for el in vds if el.get_id() in list(set(raster_ids + image_ids))] + if len(els) > 0: + imgtype = "png" + + overlaps = dh.overlapping_els(svg,els) + for elem in els: + elid = elem.get_id() + actts.append(Act('imgt',elem)) + actos.append(Act('imgo',elem,overlaps=overlaps)) + + allacts += (actos + actts) if ih.hasPIL else actts + # export-id-onlys need to go last + + imgs_trnp = {act.fname:act.els[0].get_id() for act in actts} + imgs_opqe = {act.fname:act.els[0].get_id() for act in actos} + + # To reduce the number of binary calls, we collect the stroke-to-path and + # rasterization actions into a single call that also gets the Bounding Boxes. + # Skip binary call during testing + if len(allacts) > 0 and not self.testmode: + # use relative paths to reduce arg length + + bbs = self.split_acts(fnm=cfile, acts=allacts) + + imgs = imgs_opqe | imgs_trnp + missing_images = [t for t in imgs if not os.path.exists(os.path.join(tempdir, t)) and imgs[t] in bbs] + if missing_images: + raise TimeoutError( + "\nThe Inkscape binary could not generate the temporary images " + + ", ".join(missing_images) + ' in ' + tempdir + '.\n\n' + + "This may be a temporary issue; try running the extension again." + ) + if do_stroketopaths: + cfile = tmpstp + + if do_rasterizations: + svg = get_svg(cfile) + vds = dh.visible_descendants(svg) + els = [el for el in vds if el.get_id() in list(set(raster_ids + image_ids))] + if len(els) > 0: + jimgs_trnp = [os.path.join(tempdir, t) for t in imgs_trnp] + jimgs_opqe = [os.path.join(tempdir, t) for t in imgs_opqe] + + for i, elem in enumerate(els): + img_trnp = jimgs_trnp[i] + img_opqe = jimgs_opqe[i] + + if os.path.exists(img_trnp): + anyalpha0 = False + if ih.hasPIL: + bbox = ih.crop_images([img_trnp, img_opqe]) + anyalpha0 = ih.Set_Alpha0_RGB(img_trnp, img_opqe) + if elem.get_id() in jpgs: + tmpjpg = img_trnp.replace(".png", ".jpg") + ih.to_jpeg(img_opqe, tmpjpg) + img_trnp = copy.copy(tmpjpg) + else: + bbox = None + + # Compare size of old and new images + osz = ih.embedded_size(elem) + if osz is None: + osz = float("inf") + nsz = os.path.getsize(img_trnp) + hasmaskclip = ( + elem.get_link("mask") is not None + or elem.get_link("clip-path") is not None + ) # clipping and masking + embedimg = (nsz < osz) or (anyalpha0 or hasmaskclip) + if embedimg: + Exporter.replace_with_raster( + elem, img_trnp, bbs[elem.get_id()], bbox + ) + + tmp = self.tempbase + "_eimg.svg" + dh.overwrite_svg(svg, tmp) + cfile = tmp + + if do_stroketopaths: + svg = get_svg(cfile) + # Remove temporary groups + if self.stroketopath: + for elid in dgroups: + elem = svg.getElementById(elid) + if elem is not None: + dh.ungroup(elem) + + tmp = self.tempbase + "_poststp.svg" + dh.overwrite_svg(svg, tmp) + cfile = tmp + + if self.prints: + self.prints( + fname + + ": Preprocessing done (" + + str(round(1000 * (time.time() - timestart)) / 1000) + + " s)" + ) + return cfile + + def check(self,func, *args, **kwargs): + """Wraps a binary call with a check if thread has been stopped; if so, clear the temp file and exit""" + with sema1 if func==dh.wrapped_binary else sema2: + if hasattr(self, "aeThread") and self.aeThread.stopped is True: + self.clear_temp() + sys.exit() + ret = func(*args, **kwargs) + if hasattr(self, "aeThread") and self.aeThread.stopped is True: + self.clear_temp() + sys.exit() + return ret + + def split_acts(self, fnm, acts, reserved=None, get_bbs=True): + """Split actions and run.""" + if reserved is None: + reserved = set() + + eargs = ["--actions", "".join([str(a) for a in acts])] + bbs = None + try: + bbs = self.check(dh.wrapped_binary,filename=fnm, inkscape_binary=self.bfn, extra_args=eargs, get_bbs = get_bbs, cwd=self.tempdir) + fnf_err = False + except FileNotFoundError: + fnf_err = True + + + missing_exports = [] + missing_fns = [] + found_fns = [] + for i, act in enumerate(acts): + actfile = act.fname + if actfile is not None and not os.path.exists(joinmod(self.tempdir,actfile)): + missing_exports.append(act) + missing_fns.append(actfile) + if os.path.exists(joinmod(self.tempdir,actfile)): + found_fns.append(actfile) + + # dh.idebug('Found '+str(found_fns)) + # dh.idebug('FNF: '+str(fnf_err)) + # dh.idebug('Missing ' + str(missing_fns) +'\n') + + if fnf_err: + if len(acts)==1: + if missing_exports[0].type=='stp': + bbs = self.split_stp( + fnm, missing_exports, 0, reserved=reserved, get_bbs = get_bbs + ) + else: + # Already simplified call as much as we can... + raise Exception('FileNotFoundError and cannot split:\n'+str(acts)) + elif len(missing_exports)>0: + acts1 = missing_exports[: math.ceil(len(missing_exports) / 2)] + acts2 = missing_exports[math.ceil(len(missing_exports) / 2) :] + bbs = self.split_acts( + fnm=fnm, acts=acts1, reserved=reserved, get_bbs = get_bbs + ) + if len(acts2)>0: + self.split_acts( + fnm=fnm, acts=acts2, reserved=reserved, get_bbs = False + ) + elif any(act.type=='stp' for act in missing_exports): + stp_idx = [i for i, act in enumerate(missing_exports) if act.type=='stp'][0] + bbs = self.split_stp( + fnm, missing_exports, stp_idx, reserved=reserved, get_bbs = get_bbs + ) + return bbs + + + def split_stp(self,fnm, acts, selii, reserved, get_bbs=True, cwd=None): + """ + Windows cannot handle arguments that are too long. + If needed, this can split a stroke-to-path operation into two. + """ + act = acts[selii] + actfn = act.fname + + reserved.update({fnm, actfn}) + isreserved = True + cnt = 0 + while isreserved: + actfna = actfn.strip(".svg") + str(cnt) + ".svg" + isreserved = actfna in reserved + cnt += 1 + reserved.update({actfna}) + + if len(act.els) == 1: + # cannot split, fact that we failed means there is a STP crash + shutil.copy(fnm, actfn) + return self.split_acts( + fnm=fnm, + acts=acts[:selii] + acts[selii + 1 :], + reserved=reserved + ) + act1, act2 = act.split(actfna) + acts1 = acts[:selii] + [act1] if len(act1.els) > 0 else acts[:selii] + acts2 = ( + [act2] + acts[selii + 1 :] if len(act2.els) > 0 else acts[selii + 1 :] + ) + + bbs = self.split_acts( + fnm=fnm, acts=acts1, reserved=reserved, get_bbs=get_bbs + ) + bbs = self.split_acts( + fnm=actfna, acts=acts2, reserved=reserved, get_bbs=get_bbs + ) + return bbs + + def export_file(self, fin, fformat): + """Use the Inkscape binary to export the file""" + myoutput = self.outtemplate[0:-4] + "." + fformat + if self.prints: + fname = os.path.split(self.filein)[1] + try: + offset = round(os.get_terminal_size().columns / 2) + except OSError: + offset = 40 + fname = fname + " " * max(0, offset - len(fname)) + self.prints(fname + ": Converting to " + fformat, flush=True) + timestart = time.time() + + ispsvg = fformat == "psvg" + notpng = not (fformat == "png") + + cfile = fin + if self.thinline and notpng: + svg = get_svg(cfile) + if fformat in ["pdf", "eps"]: + Exporter.thinline_dehancement(svg, "bezier") + else: + Exporter.thinline_dehancement(svg, "split") + tmp = self.tempbase + "_tld" + fformat[0] + ".svg" + dh.overwrite_svg(svg, tmp) + cfile = copy.copy(tmp) + + if fformat == "psvg": + myoutput = myoutput.replace(".psvg", "_plain.svg") + + def overwrite_output(filein, fileout): + if os.path.exists(fileout): + os.remove(fileout) + args = [ + self.bfn, + "--export-background", + "#ffffff", + "--export-background-opacity", + "1.0", + "--export-dpi", + str(self.dpi), + "--export-filename", + fileout, + filein, + ] + if fileout.endswith(".pdf") and self.latexpdf: + if os.path.exists(fileout + "_tex"): + os.remove(fileout + "_tex") + args = args[0:5] + ["--export-latex"] + args[5:] + if fileout.endswith(".svg"): + args = ( + [args[0]] + + ["--vacuum-defs"] + + args[1:5] + + ["--export-plain-svg"] + + args[5:] + ) + self.check(dh.subprocess_repeat,args) + + def make_output(filein, fileout): + if fileout.endswith(".svg"): + if not (self.testmode): + overwrite_output(filein, fileout) + else: + shutil.copy(filein, fileout) # skip conversion + self.made_outputs = [fileout] + + osvg = get_svg(filein) + + pgs = osvg.cdocsize.pgs + haspgs = inkex.installed_haspages + if (haspgs or self.testmode) and len(pgs) > 0: + # bbs = dh.BB2(type("DummyClass", (), {"svg": osvg})) + bbs = dh.BB2(osvg) + dlbl = self.duplicatelabels + outputs = [] + pgiis = ( + range(len(pgs)) if not (self.testmode) else [self.testpage - 1] + ) + for i in pgiis: + # match the viewbox to each page and delete them + psvg = get_svg(fileout) + # plain SVG has no pages + pgs2 = psvg.cdocsize.pgs + + Exporter.change_viewbox_to_page(psvg, pgs[i]) + # Only need to delete other Pages in testmode since + # plain SVGs have none + if self.testmode: + for j in reversed(range(len(pgs2))): + pgs2[j].delete() + + # Delete content not on current page + pgbb = dh.bbox(psvg.cdocsize.effvb) + for k, bbx in bbs.items(): + removeme = not (dh.bbox(bbx).intersect(pgbb)) + if k in dlbl: + removeme = not ( + dlbl[k] in bbs + and dh.bbox(bbs[dlbl[k]]).intersect(pgbb) + ) + if removeme and psvg.getElementById(k) is not None: + psvg.getElementById(k).delete() + + pname = pgs[i].get("inkscape:label") + pname = str(i + 1) if pname is None else pname + addendum = ( + "_page_" + pname + if not (self.testmode or len(pgs) == 1) + else "" + ) + outparts = fileout.split(".") + pgout = ".".join(outparts[:-1]) + addendum + "." + outparts[-1] + dh.overwrite_svg(psvg, pgout) + outputs.append(pgout) + self.made_outputs = outputs + else: + svg = get_svg(filein) + pgs = svg.cdocsize.pgs + + haspgs = inkex.installed_haspages + if (haspgs or self.testmode) and len(pgs) > 1: + outputs = [] + pgiis = ( + range(len(pgs)) if not (self.testmode) else [self.testpage - 1] + ) + for i in pgiis: + # match the viewbox to each page and delete them + svgpg = get_svg(filein) + pgs2 = svgpg.cdocsize.pgs + Exporter.change_viewbox_to_page(svgpg, pgs[i]) + for j in reversed(range(len(pgs2))): + pgs2[j].delete() + + pname = pgs[i].get("inkscape:label") + pname = str(i + 1) if pname is None else pname + addendum = "_page_" + pname if not (self.testmode) else "" + svgpgfn = self.tempbase + addendum + ".svg" + dh.overwrite_svg(svgpg, svgpgfn) + + outparts = fileout.split(".") + pgout = ".".join(outparts[:-1]) + addendum + "." + outparts[-1] + overwrite_output(svgpgfn, pgout) + outputs.append(pgout) + self.made_outputs = outputs + else: + overwrite_output(filein, fileout) + self.made_outputs = [fileout] + + if not (ispsvg): + make_output(cfile, myoutput) + finalnames = self.made_outputs + else: + tmp = self.tempbase + "_tmp_small.svg" + make_output(cfile, tmp) + + moutputs = self.made_outputs + finalnames = [] + for mout in moutputs: + svg = get_svg(mout) + self.postprocessing(svg) + finalname = myoutput + if len(moutputs) > 1: + pnum, _ = os.path.splitext(mout.split("_page_")[-1]) + finalname = myoutput.replace( + "_plain.svg", "_page_" + pnum + "_plain.svg" + ) + dh.overwrite_svg(svg, finalname) + finalnames.append(finalname) + + # Remove any previous outputs that we did not just make + directory, file_name = os.path.split(myoutput) + base_name, extension = os.path.splitext(file_name) + if extension == ".svg" and base_name.endswith("_plain"): + base_name = base_name[:-6] # Remove "_plain" from the base name + pattern = re.compile( + rf"{re.escape(base_name)}(_page_.*)?_plain{re.escape(extension)}$" + ) + else: + pattern = re.compile( + rf"{re.escape(base_name)}(_page_.*)?{re.escape(extension)}$" + ) + matching_files = [] + for file in os.listdir(directory): + if pattern.match(file): + matching_files.append(os.path.join(directory, file)) + for file in matching_files: + if file not in finalnames: + try: + os.remove(file) + except PermissionError: + pass + + if self.prints: + toc = time.time() - timestart + self.prints( + fname + + ": Conversion to " + + fformat + + " done (" + + str(round(1000 * toc) / 1000) + + " s)" + ) + return True, myoutput + + def postprocessing(self, svg): + """Postprocessing of SVGs, mainly for overcoming bugs in Office products""" + vds = dh.visible_descendants(svg) + + # Shift viewbox corner to (0,0) by translating top-level elements + evb = svg.cdocsize.effvb + if not (evb[0] == 0 and evb[1] == 0): + newtr = Transform("translate(" + str(-evb[0]) + ", " + str(-evb[1]) + ")") + svg.set_viewbox([0, 0, evb[2], evb[3]]) + ndefs = [el for el in list(svg) if not (el.tag in dh.unungroupable)] + for elem in ndefs: + elem.ctransform = newtr @ elem.ctransform + + for dsd in vds: + if ( + isinstance(dsd, (TextElement)) + and dsd.get_id() not in self.excludetxtids + ): + scaleto = 100 if not self.testmode else 10 + # Make 10 px in test mode so that errors are not unnecessarily large + Exporter.scale_text(dsd, scaleto) + elif isinstance(dsd, (inkex.Image)): + if ih.hasPIL: + Exporter.merge_mask(dsd) + # while dsd.getparent()!=dsd.croot and dsd.getparent() is not None + # and dsd.croot is not None: + # # iteratively remove groups containing images + # # I don't think it's necessary? + # dh.ungroup(dsd.getparent()); + elif isinstance(dsd, (inkex.Group)): + if len(dsd) == 0: + dsd.delete(deleteup=True) + + if self.backingrect: + r = inkex.Rectangle() + tlvl = [el for el in list(svg) if not (el.tag in dh.unungroupable)] + if len(tlvl) > 0: + svg.insert(svg.index(tlvl[0]), r) + else: + svg.append(r) + r.cstyle["fill"] = "#ffffff" + r.cstyle["fill-opacity"] = ".00001" + r.cstyle["stroke"] = "none" + vbx = svg.cdocsize.effvb + r.set("x", vbx[0]) + r.set("y", vbx[1]) + r.set("width", vbx[2]) + r.set("height", vbx[3]) + + # if self.thinline: + # for dsd in vds: + # self.Bezier_to_Split(dsd) # moved to deprecated + + # dh.idebug(self.svgtopdf_vbs) + # embed_original = False + # if embed_original: + # def main_contents(svg): + # ret = list(svg) + # for k in reversed(ret): + # if k.tag in [inkex.addNS('metadata','svg'), + # inkex.addNS('namedview','sodipodi')]: + # ret.remove(k); + # return ret + + # grp = dh.group(main_contents(svg)) + # svgo = get_svg(original_file) + # go = dh.group(main_contents(svgo)) + # svg.append(go) + # go.set('style','display:none'); + # go.set('inkscape:label','SI original'); + # go.set('inkscape:groupmode','layer'); + + # # Conversion to PDF changes the viewbox to pixels. Convert back to the + # # original viewbox by applying a transform + # dds = self.svgtopdf_dss[i] + # tfmt = 'matrix({0},{1},{2},{3},{4},{5})'; + # T =inkex.Transform(tfmt.format(dds.uuw,0,0,dds.uuh, + # -dds.effvb[0]*dds.uuw, + # -dds.effvb[1]*dds.uuh)) + # grp.set('transform',str(-T)) + # svg.set_viewbox(dds.effvb) + + tel = None + for elem in list(svg): + if ( + isinstance(elem, (inkex.TextElement,)) + and elem.text is not None + and ORIG_KEY in elem.text + ): + tel = elem + if tel is None: + # tel = svg.new_element(inkex.TextElement, svg) + tel = inkex.TextElement() + svg.append(tel) + tel.text = ORIG_KEY + ": {0}, hash: {1}".format( + self.original_file, hash_file(self.original_file) + ) + tel.set("style", "display:none") + dh.clean_up_document(svg) # Clean up + + PTH_COMMANDS = list("MLHVCSQTAZmlhvcsqtaz") + + @staticmethod + def thinline_dehancement(svg, mode="split"): + """ + Prevents thin-line enhancement in certain bad PDF renderers + 'bezier' mode converts h,v,l path commands to trivial Beziers + 'split' mode splits h,v,l path commands into two path commands + The Office PDF renderer removes trivial Beziers, as does conversion to EMF + The Inkscape PDF/EPS renderer removes split commands + Split is more general, so apply it to everything but PDFs/EPS + """ + + command_chars = {"h", "v", "l", "H", "V", "L"} + split = mode == "split" + for elem in svg.descendants2(): + if elem.tag in otp_support_tags and not elem.tag == peltag: + elem.object_to_path() + + pthd = elem.get("d") + if pthd and any(char in pthd for char in command_chars): + if any(char in pthd for char in {"H", "V", "L"}): + pthd = str(inkex.Path(pthd).to_relative()) + dds = [v for v in pthd.replace(",", " ").split(" ") if v] + current_command = None + i = 0 + while i < len(dds): + token = dds[i] + if token in {"v", "h", "l"}: + current_command = token + dds[i] = "" + elif token in Exporter.PTH_COMMANDS: + current_command = None + else: + if current_command == "h": + hval = float(token) + dds[i] = ( + f"h {hval / 2} {hval / 2}" + if split + else f"c {hval},0 {hval},0 {hval},0" + ) + elif current_command == "v": + vval = float(token) + dds[i] = ( + f"v {vval / 2} {vval / 2}" + if split + else f"c 0,{vval} 0,{vval} 0,{vval}" + ) + elif current_command == "l": + lxv = float(token) + lyv = float(dds[i + 1]) + dds[i] = ( + f"l {lxv / 2},{lyv / 2} {lxv / 2},{lyv / 2}" + if split + else f"c {lxv},{lyv} {lxv},{lyv} {lxv},{lyv}" + ) + dds[i + 1] = "" + i += 1 + i += 1 + + newd = " ".join(v for v in dds if v).replace(" ", " ") + elem.set("d", newd) + + @staticmethod + def marker_fix(elem): + """Fixes the marker bug that occurs with context-stroke and context-fill""" + mkrs = Exporter.get_markers(elem) + sty = elem.cspecified_style + for mtyp, mkrel in mkrs.items(): + dh.get_strokefill(elem) + mkrds = mkrel.descendants2() + anycontext = any( + a in ("stroke", "fill") and "context" in v + for d in mkrds + for a, v in d.cspecified_style.items() + ) + if anycontext: + handled = True + dup = mkrel.duplicate() + dupds = dup.descendants2() + for dsd in dupds: + dsty = dsd.cspecified_style + for att, val in dsty.items(): + if att in ("stroke", "fill") and "context" in val: + if val == "context-stroke": + dsd.cstyle[att] = sty.get("stroke", "none") + elif val == "context-fill": + dsd.cstyle[att] = sty.get("fill", "none") + else: # I don't know what this is + handled = False + if handled: + elem.cstyle[mtyp] = dup.get_id(as_url=2) + return mkrs + + @staticmethod + def opacity_fix(elem): + """ + Fuse opacity onto fill and stroke for path-like elements + Helps prevent rasterization-at-PDF for Office products + """ + sty = elem.cspecified_style + if ( + sty.get("opacity") is not None + and float(sty.get("opacity", 1.0)) != 1.0 + and elem.tag in otp_support_tags + ): + strf = dh.get_strokefill(elem) # fuses opacity and + # stroke-opacity/fill-opacity + if strf.stroke is not None and strf.fill is None: + elem.cstyle["stroke-opacity"] = strf.stroke.alpha + elem.cstyle["opacity"] = 1 + elif strf.fill is not None and strf.stroke is None: + elem.cstyle["fill-opacity"] = strf.fill.alpha + elem.cstyle["opacity"] = 1 + + @staticmethod + def subsuper_fix(elem): + """ + Replace super and sub with numerical values + Collapse Tspans with 0% baseline-shift, which Office displays incorrectly + """ + for dsd in elem.descendants2(): + bsh = dsd.ccascaded_style.get("baseline-shift") + if bsh in ["super", "sub"]: + sty = dsd.ccascaded_style + sty["baseline-shift"] = "40%" if bsh == "super" else "-20%" + dsd.cstyle = sty + + for dsd in reversed(elem.descendants2()): # all Tspans + bsh = dsd.ccascaded_style.get("baseline-shift") + if bsh is not None and bsh.replace(" ", "") == "0%": + # see if any ancestors with non-zero shift + anysubsuper = False + for anc in dsd.ancestors2(stopafter=elem): + bsa = anc.ccascaded_style.get("baseline-shift") + if bsa is not None and "%" in bsa: + anysubsuper = float(bsa.replace(" ", "").strip("%")) != 0 + + # split parent + myp = dsd.getparent() + if anysubsuper and myp is not None: + dds = dh.split_text(myp) + for dsd2 in reversed(dds): + if ( + len(list(dsd2)) == 1 + and list(dsd2)[0].ccascaded_style.get("baseline-shift") + == "0%" + ): + sel = list(dsd2)[0] + mys = sel.ccascaded_style + if mys.get("baseline-shift") == "0%": + mys["baseline-shift"] = dsd2.ccascaded_style.get( + "baseline-shift" + ) + fsz, scf, _ = dh.composed_width(sel, "font-size") + mys["font-size"] = str(fsz / scf) + dsd2.addprevious(sel) + sel.cstyle = mys + sel.tail = dsd2.tail + dsd2.delete() + Exporter.subsuper_fix( + elem + ) # parent is now gone, so start over + return + + @staticmethod + def scale_text(elem, scaleto): + """ + Sets all font-sizes to 100 px by moving size into transform + Office rounds every fonts to the nearest px and then transforms it, + so this makes text sizes more accurate + """ + svg = elem.croot + szs = [] + for dsd in elem.descendants2(): # all Tspan sizes + fsz = dsd.ccascaded_style.get("font-size") + fsz = {"small": "10px", "medium": "12px", "large": "14px"}.get(fsz, fsz) + if fsz is not None and "%" not in fsz: + szs.append(dh.ipx(fsz)) + if len(szs) == 0: + maxsz = default_style_atts["font-size"] + maxsz = {"small": 10, "medium": 12, "large": 14}.get(maxsz, maxsz) + else: + maxsz = max(szs) + + scv = 1 / maxsz * scaleto + + # Make a dummy group so we can properly compose the transform + grp = dh.group([elem], moveTCM=True) + + for dsd in reversed(elem.descendants2()): + x = ParsedText.get_xy(dsd, "x") + y = ParsedText.get_xy(dsd, "y") + dxv = ParsedText.get_xy(dsd, "dx") + dyv = ParsedText.get_xy(dsd, "dy") + + if x[0] is not None: + xyset(dsd, "x", [v * scv for v in x]) + if y[0] is not None: + xyset(dsd, "y", [v * scv for v in y]) + if dxv[0] is not None: + xyset(dsd, "dx", [v * scv for v in dxv]) + if dyv[0] is not None: + xyset(dsd, "dy", [v * scv for v in dyv]) + + fsz, scf, _ = dh.composed_width(dsd, "font-size") + if scf==0: + continue + dsd.cstyle["font-size"] = "{:.3f}".format(fsz / scf * scv) + + otherpx = [ + "letter-spacing", + "inline-size", + "stroke-width", + "stroke-dasharray", + ] + for oth in otherpx: + othv = dsd.ccascaded_style.get(oth) + if ( + othv is None + and oth == "stroke-width" + and "stroke" in dsd.ccascaded_style + ): + othv = default_style_atts[oth] + if othv is not None: + if "," not in othv: + if 'em' not in othv: # scaling not needed for em sizes + dsd.cstyle[oth] = str((dh.ipx(othv) or 0) * scv) + else: + dsd.cstyle[oth] = ",".join( + [str((dh.ipx(v) or 0) * scv) for v in othv.split(",")] + ) + + shape = dsd.ccascaded_style.get_link("shape-inside", svg) + if shape is not None: + dup = shape.duplicate() + svg.cdefs.append(dup) + dup.ctransform = ( + inkex.Transform((scv, 0, 0, scv, 0, 0)) @ dup.ctransform + ) + dsd.cstyle["shape-inside"] = dup.get_id(as_url=2) + + elem.ctransform = inkex.Transform((1 / scv, 0, 0, 1 / scv, 0, 0)) + dh.ungroup(grp) + + @staticmethod + def merge_mask(elem): + """ + Office will poorly rasterize masked elements at PDF-time + Revert alpha masks made by PDFication back to alpha + """ + mymask = elem.get_link("mask") + if mymask is not None and len(list(mymask)) == 1: + # only if one object in mask + mask = list(mymask)[0] + if isinstance(mask, inkex.Image): + if mask.get("height") == "1" and mask.get("width") == "1": + im1 = ih.str_to_ImagePIL(elem.get("xlink:href")).convert("RGBA") + im2 = ih.str_to_ImagePIL(mask.get("xlink:href")).convert("RGBA") + # only if mask the same size + if im1.size == im2.size: + d1a = np.asarray(im1) + d2a = np.asarray(im2) + # only if image is opaque + if np.where(d1a[:, :, 3] == 255, True, False).all(): + nda = np.stack( + ( + d1a[:, :, 0], + d1a[:, :, 1], + d1a[:, :, 2], + d2a[:, :, 0], + ), + 2, + ) + # pylint: disable=import-outside-toplevel + from PIL import Image as ImagePIL + # pylint: enable=import-outside-toplevel + + newstr = ih.ImagePIL_to_str(ImagePIL.fromarray(nda)) + elem.set("xlink:href", newstr) + mask.delete() + elem.set("mask", None) + + @staticmethod + def standardize_image(elem): + """Office has difficulty with clipped images with a nonzero x or y.""" + xv = dh.ipx(elem.get('x','0')) + yv = dh.ipx(elem.get('y','0')) + if xv!=0 or yv!=0: + grp = dh.group([elem], moveTCM=True) + elem.set('x','0') + elem.set('y','0') + elem.ctransform = inkex.Transform(f'translate({xv},{yv})') + dh.ungroup(grp) + + @staticmethod + def replace_with_raster(elem, imgloc, bbx, imgbbox): + """Replace vector elements with raster images.""" + svg = elem.croot + if svg is None: # in case we were already rasterized within ancestor + return + ih.embed_external_image(elem, imgloc) + + # The exported image has a different size and shape than the original + # Correct by putting transform/clip/mask on a new parent group, then + # fix location, then ungroup + grp = dh.group([elem], moveTCM=True) + grp.set("clip-path", None) + # conversion to bitmap already includes clips + grp.set("mask", None) # conversion to bitmap already includes masks + + # Calculate what transform is needed to preserve the image's location + ctf = elem.ccomposed_transform + pbb = [ + Vector2d(bbx[0], bbx[1]), + Vector2d(bbx[0] + bbx[2], bbx[1]), + Vector2d(bbx[0] + bbx[2], bbx[1] + bbx[3]), + Vector2d(bbx[0], bbx[1] + bbx[3]), + ] + # top-left,tr,br,bl + put = [ + (-ctf).apply_to_point(p) for p in pbb + ] # untransformed bbox (where the image needs to go) + + newel = inkex.Image() + dh.replace_element(elem, newel) + newel.set("x", 0) + myx = 0 + newel.set("y", 0) + myy = 0 + newel.set("width", 1) + myw = dh.ipx(newel.get("width")) + newel.set("height", 1) + myh = dh.ipx(newel.get("height")) + newel.set("xlink:href", elem.get("xlink:href")) + + # Inkscape inappropriately clips non-'optimizeQuality' images + # when generating PDFs + sty = "image-rendering:optimizeQuality" + elem = newel + + pgo = [ + Vector2d(myx, myy), + Vector2d(myx + myw, myy), + Vector2d(myx + myw, myy + myh), + Vector2d(myx, myy + myh), + ] # where the image is + elem.set("preserveAspectRatio", "none") # prevents aspect ratio snapping + elem.set("style", sty) # override existing styles + + # pylint: disable=invalid-name + a = np.array( + [ + [pgo[0].x, 0, pgo[0].y, 0, 1, 0], + [0, pgo[0].x, 0, pgo[0].y, 0, 1], + [pgo[1].x, 0, pgo[1].y, 0, 1, 0], + [0, pgo[1].x, 0, pgo[1].y, 0, 1], + [pgo[2].x, 0, pgo[2].y, 0, 1, 0], + [0, pgo[2].x, 0, pgo[2].y, 0, 1], + ] + ) + b = np.array( + [ + [ + put[0].x, + put[0].y, + put[1].x, + put[1].y, + put[2].x, + put[2].y, + ] + ] + ).T + T = np.linalg.solve(a, b) + T = "matrix(" + ",".join([str(v[0]) for v in T]) + ")" + elem.set("transform", T) + # pylint: enable=invalid-name + + # If we cropped, need to modify location according to bbox + if ih.hasPIL and imgbbox is not None: + elem.set("x", str(myx + imgbbox[0] * myw)) + elem.set("y", str(myy + imgbbox[1] * myh)) + elem.set("width", str((imgbbox[2] - imgbbox[0]) * myw)) + elem.set("height", str((imgbbox[3] - imgbbox[1]) * myh)) + dh.ungroup(grp) + + @staticmethod + def add_margin(svg, amt_mm, testmode): + """Add margin to the document.""" + mrgn = inkex.units.convert_unit(str(amt_mm) + "mm", "px") + uuw, uuh = svg.cdocsize.uuw, svg.cdocsize.uuh + # pgs = self.Get_Pages(svg) + + haspgs = inkex.installed_haspages or testmode + if haspgs and len(svg.cdocsize.pgs) > 0: + # Has Pages + for page in svg.cdocsize.pgs: + newbbuu = svg.cdocsize.pxtouupgs( + [ + page.bbpx[0] - mrgn, + page.bbpx[1] - mrgn, + page.bbpx[2] + 2 * mrgn, + page.bbpx[3] + 2 * mrgn, + ] + ) + page.set("x", str(newbbuu[0])) + page.set("y", str(newbbuu[1])) + page.set("width", str(newbbuu[2])) + page.set("height", str(newbbuu[3])) + svg.cdocsize = None + + else: + # If an old version of Inkscape or has no Pages, defined by viewbox + vbx = svg.cdocsize.effvb + nvb = [ + vbx[0] - mrgn / uuw, + vbx[1] - mrgn / uuh, + vbx[2] + 2 * mrgn / uuw, + vbx[3] + 2 * mrgn / uuh, + ] + svg.set_viewbox(nvb) + + @staticmethod + def change_viewbox_to_page(svg, page): + """Change viewbox to match specified page.""" + newvb = page.croot.cdocsize.pxtouu(page.bbpx) + svg.set_viewbox(newvb) + + @staticmethod + def get_markers(elem): + """Returns valid marker keys and corresponding elements""" + mkrd = dict() + for mtyp in ["marker", "marker-start", "marker-mid", "marker-end"]: + mkrel = elem.cstyle.get_link(mtyp, elem.croot) + if mkrel is not None: + mkrd[mtyp] = mkrel + return mkrd + + @staticmethod + def stroke_to_path_fixes(els): + """ + Stroke to Path has tons of bugs. Try to preempt them. + 1. STP does not properly handle clips, so move clips and + masks to a temp parent group. + 2. Densely-spaced nodes can be converted incorrectly, so scale paths up + to be size 1000 and position corner at (0,0) + 3. Starting markers can be flipped. + 4. Markers on groups cause crashes + 5. Markers are given the wrong size + """ + dummy_groups = [] + path_els = [] + stroked_els = [] + if len(els) > 0: + svg = els[0].croot + for elem in els: + mkrs = Exporter.get_markers(elem) + if isinstance(elem, inkex.Group) and len(mkrs) > 0: + dh.ungroup(elem) + elif elem.tag in otp_support_tags: + sty = elem.cspecified_style + if "stroke" in sty and sty["stroke"] != "none": + stroked_els.append((elem, sty, mkrs)) + + for elem, sty, mkrs in stroked_els: + swd = sty.get("stroke-width") + if dh.ipx(swd) == 0: + # Fix bug on zero-stroke paths + sty["stroke"] = "none" + elem.cstyle = sty + elif swd is None: + sty["stroke-width"] = default_style_atts['stroke-width'] + else: + # Clip and mask issues solved by moving to a dummy parent group + gndp = dh.group([elem], moveTCM=True) + # Do it again so we can add transform later without + # messing up clip + grp = dh.group([elem], moveTCM=True) + + path_els.append(elem.get_id()) + dummy_groups.extend([gndp.get_id(), grp.get_id()]) + + # Scale up most paths to be size 1000 and position corner + # at (0,0) + if inkex.addNS("type", "sodipodi") not in elem.attrib: + # Object to path doesn't currently support + # Inkscape-specific objects + elem.object_to_path() + bbx = elem.bounding_box2( + dotransform=False, includestroke=False, roughpath=False + ) + if len(mkrs) == 0: + # scaleby = 1000 + maxsz = max(bbx.w, bbx.h) + scaleby = 1000 / maxsz if maxsz > 0 else 1000 + else: + # For paths with markers, scale to make stroke-width=1 + # Prevents incorrect marker size + # https://gitlab.com/inkscape/inbox/-/issues/10506#note_1931910230 + scaleby = 1 / dh.ipx(swd) if swd is not None else 1 + + tfm = Transform("scale({0})".format(scaleby)) @ Transform( + "translate({0},{1})".format(-bbx.x1, -bbx.y1) + ) + # relative paths seem to have some bugs + pth2 = str(elem.cpath.to_absolute().transform(tfm)) + elem.set("d", pth2) + + # Put transform on parent group since STP cannot convert + # transformed paths correctly if they have dashes + # See https://gitlab.com/inkscape/inbox/-/issues/7844 + grp.ctransform = -tfm + + csty = elem.cspecified_style + if "stroke-width" in csty: + swd = dh.ipx(csty["stroke-width"]) + elem.cstyle["stroke-width"] = str(swd * scaleby) + if "stroke-dasharray" in csty: + sda = dh.listsplit(csty["stroke-dasharray"]) + elem.cstyle["stroke-dasharray"] = ( + str([(sdv or 0) * scaleby for sdv in sda]) + .strip("[") + .strip("]") + ) + + # Fix bug on start markers where auto-start-reverse + # oriented markers are inverted by STP + sty = elem.cspecified_style + mstrt = sty.get_link("marker-start", svg) + if mstrt is not None: + if mstrt.get("orient") == "auto-start-reverse": + dup = mstrt.duplicate() + dup.set("orient", "auto") + for dkid in list(dup): + dkid.ctransform = Transform("scale(-1)") @ dkid.ctransform + sty["marker-start"] = dup.get_id(as_url=2) + elem.cstyle = sty + return path_els, dummy_groups + + +# Convenience functions +def joinmod(dirc, fname): + """Join directory and file name with absolute path.""" + return os.path.join(os.path.abspath(dirc), fname) + +def get_svg(fin): + """Load an SVG file and return the root svg element.""" + try: + svg = inkex.load_svg(fin).getroot() + except lxml.etree.XMLSyntaxError: + # Try removing problematic bytes + with open(fin, "rb") as file: + bytes_content = file.read() + cleaned_content = bytes_content.decode("utf-8", errors="ignore") + nfin = dh.shared_temp(filename="cleaned.svg") + with open(nfin, "w", encoding="utf-8") as file: + file.write(cleaned_content) + svg = inkex.load_svg(nfin).getroot() + return svg + + +ORIG_KEY = "si_ae_original_filename" +DUP_KEY = "si_ae_original_duplicate" + + +def hash_file(filename): + """Calculate hash of a file.""" + hashv = hashlib.sha256() + with open(filename, "rb") as file: + chunk = 0 + while chunk != b"": + chunk = file.read(1024) + hashv.update(chunk) + return hashv.hexdigest() + + +if __name__ == "__main__": + dh.Run_SI_Extension(AutoExporter(), "Autoexporter") \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter_script.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter_script.py new file mode 100644 index 0000000..ecccb7d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/autoexporter_script.py @@ -0,0 +1,911 @@ +# Inkscape Auto-Exporter, by David Burghoff +# Service that checks a folder for changes in svg files, and then exports them +# automatically to another folder in multiple formats. + +DEBUG = False +WHILESLEEP = 0.5 +MAXTHREADS = 1000 + +import sys, platform, os, threading, time, copy, pickle + +import tempfile + +systmpdir = os.path.abspath(tempfile.gettempdir()) +aes = os.path.join(systmpdir, "si_ae_settings.p") + +with open(aes, "rb") as f: + input_options = pickle.load(f) +os.remove(aes) +bfn = input_options.inkscape_bfn +sys.path += input_options.syspath +guitype = input_options.guitype + +import dhelpers as dh # noqa +import inkex +import inkex.text.parser # needed to prevent GTK crashing + +import autoexporter +from autoexporter import Exporter + + +def mprint(*args, **kwargs): + if guitype == "gtk": + global win + GLib.idle_add(win.print_text, args[0]) + else: + print(*args, **kwargs) + + +# Get svg files in directory +def get_files(dirin): + import re + from datetime import datetime + + def ends_with_date(s): + pattern = r"\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d{1,6})\.svg$" + match = re.search(pattern, s) + if match: + date_string = match.group(1) + custom_format = "%Y_%m_%d_%H_%M_%S.%f" + try: + datetime.strptime(date_string, custom_format) + return True + except ValueError: + return False + return False + + fs = [] + try: + for f in os.scandir(dirin): + excludes = ["_portable.svg", "_plain.svg"] + if ( + f.name.endswith(".svg") + and all([not (f.name.endswith(ex)) for ex in excludes]) + and not (ends_with_date(f.name)) + ): + fs.append(os.path.join(os.path.abspath(dirin), f.name)) + return fs + except: # (FileNotFoundError, OSError): + return None # directory missing (cloud drive error?) + + +class Watcher: + """Class that watches a folder for changes to SVGs""" + + def __init__(self, directory_to_watch, createfcn=None, modfcn=None, deletefcn=None): + import re, sys + from threading import Timer + import warnings + + warnings.filterwarnings( + "ignore", message="Failed to import fsevents. Fall back to kqueue" + ) + mydir = os.path.dirname(os.path.abspath(__file__)) + packages = os.path.join(mydir, "packages") + if packages not in sys.path: + sys.path.append(packages) + from watchdog.observers import Observer + from watchdog.events import FileSystemEventHandler + + class Handler(FileSystemEventHandler): + def __init__(self, createfcn=None, modfcn=None, deletefcn=None): + # Dictionary to store the last event and debounce timers for each file + self.debounce_timers = {} + self.last_event = {} + self.file_mod_times = {} + self.createfcn = createfcn + self.modfcn = modfcn + self.deletefcn = deletefcn + # Initialize file modification times + self.initialize_mod_times(directory_to_watch) + + @staticmethod + def is_target_file(file_name): + excludes = ["_portable.svg", "_plain.svg"] + pattern = r"\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d{1,6})\.svg$" + if any(file_name.endswith(ex) for ex in excludes): + return False + if re.search(pattern, file_name): + return False + return file_name.endswith(".svg") + + @staticmethod + def get_mod_time(file_path): + try: + return os.path.getmtime(file_path) + except FileNotFoundError: + return None + + def initialize_mod_times(self, directory): + for file in os.listdir(directory): + file_path = os.path.join(directory, file) + if os.path.isfile(file_path) and self.is_target_file(file_path): + mod_time = self.get_mod_time(file_path) + if mod_time: + self.file_mod_times[file_path] = mod_time + + def debounce(self, event): + # Process the last event + if event.event_type == "created": + if self.createfcn is not None: + self.createfcn(event.src_path) + elif event.event_type == "modified": + if self.modfcn is not None: + self.modfcn(event.src_path) + elif event.event_type == "deleted": + if self.deletefcn is not None: + self.deletefcn(event.src_path) + + def handle_event(self, event): + if event.is_directory: + return None + if self.is_target_file(event.src_path): + # Cancel existing timer if present + if event.src_path in self.debounce_timers: + self.debounce_timers[event.src_path].cancel() + # Store the event + self.last_event[event.src_path] = event + # Set a new timer + self.debounce_timers[event.src_path] = Timer( + 1.0, self.debounce, [event] + ) + self.debounce_timers[event.src_path].start() + + def on_created(self, event): + self.handle_event(event) + + def on_modified(self, event): + if event.is_directory: + return None + + if self.is_target_file(event.src_path): + new_mod_time = self.get_mod_time(event.src_path) + old_mod_time = self.file_mod_times.get(event.src_path) + + # Only handle the event if the file modification time has changed + if new_mod_time and new_mod_time != old_mod_time: + self.file_mod_times[event.src_path] = new_mod_time + self.handle_event(event) + + def on_deleted(self, event): + if event.src_path in self.file_mod_times: + del self.file_mod_times[event.src_path] + self.handle_event(event) + + self.Handler = Handler # Make Handler an attribute of Watcher + self.observer = Observer() + self.directory_to_watch = directory_to_watch + self.createfcn = createfcn + self.modfcn = modfcn + self.deletefcn = deletefcn + self.start() + + def start(self): + event_handler = self.Handler( + createfcn=self.createfcn, modfcn=self.modfcn, deletefcn=self.deletefcn + ) + self.observer.schedule(event_handler, self.directory_to_watch, recursive=False) + self.observer.start() + + def stop(self): + self.observer.stop() + self.observer.join() + + +# Threading class +class FileCheckerThread(threading.Thread): + def __init__(self, watchdir, writedir): + threading.Thread.__init__(self) + self.stopped = False + self.nf = True # new folder + self.ea = False # export all + self.es = False # export selected + self.dm = False # debug mode + self.promptpending = True + self.watcher = None + self.watchdir = input_options.watchdir + self.writedir = input_options.writedir + self.thread_queue = [] + self.running_threads = [] + self.finished_threads = [] + + def queue_thread(self, f): + for t in self.thread_queue + self.running_threads: + if t.file == f: + t.stopped = True + fthr = AutoExporterThread() + fthr.file = f + fthr.outtemplate = autoexporter.joinmod(self.writedir, os.path.split(f)[1]) + self.thread_queue.append(fthr) + + def start_watcher(self): + if self.watcher is not None: # Stop existing watcher + self.watcher.stop() + mfcn = lambda x: self.queue_thread(os.path.abspath(x)) + self.watcher = Watcher(self.watchdir, createfcn=mfcn, modfcn=mfcn) + + def run(self): + self.start_watcher() + while not self.stopped: + self.checkongoing = True + if self.nf: + if guitype == "terminal": + mprint( + "Export formats: " + + ", ".join([v.upper() for v in input_options.formats]) + ) + mprint("Rasterization DPI: " + str(input_options.dpi)) + mprint("Watch directory: " + self.watchdir) + mprint("Write directory: " + self.writedir) + self.nf = False + + if self.watcher.directory_to_watch != self.watchdir: + self.start_watcher() + + if self.ea: # export all + self.ea = False + updatefiles = get_files(self.watchdir) + if updatefiles is None: + mprint("Cannot access watch directory.") + updatefiles = [] + elif self.es: + self.es = False + updatefiles = [self.selectedfile] + else: + updatefiles = [] + + loopme = True + while loopme: + for f in sorted(updatefiles): + self.queue_thread(f) + + while ( + len(self.thread_queue) > 0 + and len(self.running_threads) < MAXTHREADS + and not self.stopped + ): + self.thread_queue[0].start() + self.running_threads.append(self.thread_queue[0]) + self.thread_queue.remove(self.thread_queue[0]) + time.sleep(WHILESLEEP) + + for thr in reversed(self.running_threads): + if not thr.is_alive(): + self.running_threads.remove(thr) + self.finished_threads.append(thr) + self.promptpending = True + + while self.dm and any([thr.is_alive() for thr in self.running_threads]): + time.sleep(WHILESLEEP) + loopme = self.dm + + if self.promptpending and len(self.running_threads) + len(self.thread_queue) == 0: + if guitype == "terminal": + mprint(promptstring) + self.promptpending = False + time.sleep(WHILESLEEP) + + self.watcher.stop() + for t in self.running_threads: + t.stopped = True + while any([thr.is_alive() for thr in self.running_threads]): + time.sleep(WHILESLEEP) + for thr in reversed(self.running_threads): + self.running_threads.remove(thr) + self.finished_threads.append(thr) + +class PromptThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.ui = None # user input + + def run(self): + self.ui = input("") + + +class AutoExporterThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.file = None + self.outtemplate = None + self.stopped = False + + def run(self): + fname = os.path.split(self.file)[1] + try: + offset = round(os.get_terminal_size().columns / 2) + except: + offset = 40 + fname = fname + " " * max(0, offset - len(fname)) + mprint(fname + ": Beginning export") + opts = copy.copy(input_options) + opts.debug = DEBUG + opts.prints = mprint + opts.aeThread = self + opts.original_file = self.file + opts.formats = [ + fmt + for fmt, use in zip( + ["pdf", "png", "emf", "eps", "psvg"], + [opts.usepdf, opts.usepng, opts.useemf, opts.useeps, opts.usepsvg], + ) + if use + ] + opts.outtemplate = self.outtemplate + opts.bfn = bfn + try: + Exporter(self.file, opts).export_all() + except SystemExit: + pass + except: + import traceback + error_message = f"Exception in {fname}\n" + error_message += traceback.format_exc() + mprint(error_message) + +if guitype == "gtk": + import warnings + + with warnings.catch_warnings(): + # Ignore ImportWarning for Gtk + warnings.simplefilter("ignore") + import gi + + gi.require_version("Gtk", "3.0") + from gi.repository import Gtk, GLib, Gdk + + class AutoexporterWindow(Gtk.Window): + def __init__(self, ct): + Gtk.Window.__init__(self, title="Autoexporter") + WINDOW_WIDTH = 450 + MARGIN = 10 + self.set_default_size(WINDOW_WIDTH, -1) + self.set_position(Gtk.WindowPosition.CENTER) + + self.notebook = Gtk.Notebook() + self.notebook.set_margin_start(MARGIN) + self.notebook.set_margin_end(MARGIN) + self.notebook.set_margin_top(MARGIN) + self.notebook.set_margin_bottom(MARGIN) + self.add(self.notebook) + + # Tab 1: Controls + tab1_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + self.notebook.append_page(tab1_box, Gtk.Label(label="Controls")) + + # Top message window + self.selected_file_label = Gtk.TextView() + self.selected_file_label.set_editable(False) + self.selected_file_label.set_wrap_mode(Gtk.WrapMode.CHAR) + self.selected_file_label.get_buffer().set_text("No file selected.") + self.message_scrolled_window = Gtk.ScrolledWindow() # Renamed for clarity + self.message_scrolled_window.set_size_request(WINDOW_WIDTH-2*MARGIN, 150) + self.message_scrolled_window.set_hexpand(True) + self.message_scrolled_window.set_vexpand(True) + self.message_scrolled_window.add(self.selected_file_label) + tab1_box.pack_start(self.message_scrolled_window, True, True, 0) + + # Bottom file log window + self.filelogstore = Gtk.ListStore(str, str) + self.treeview = Gtk.TreeView(model=self.filelogstore) + renderer_text = Gtk.CellRendererText() + column_text = Gtk.TreeViewColumn("Filename", renderer_text, text=0) + column_text.set_fixed_width((WINDOW_WIDTH-2*MARGIN)*0.49) # Set your desired fixed width + self.treeview.append_column(column_text) + renderer_text = Gtk.CellRendererText() + column_text = Gtk.TreeViewColumn("Message", renderer_text, text=1) + self.treeview.append_column(column_text) + self.file_log_scrolled_window = Gtk.ScrolledWindow() # Renamed for clarity + self.file_log_scrolled_window.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + self.file_log_scrolled_window.set_size_request(WINDOW_WIDTH-2*MARGIN, 350) + self.file_log_scrolled_window.set_vexpand(True) + self.file_log_scrolled_window.add(self.treeview) + self.filelogstore.connect("row-inserted", self.on_row_inserted) + self.maxlsrows = 100 + self.treeview.connect("size-allocate", self.on_treeview_size_allocate) + tab1_box.pack_start(self.file_log_scrolled_window, True, True, 0) + + # Separator + separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) + tab1_box.pack_start(separator, False, True, 0) + + # Watch directory + LABEL_WIDTH = 16 + watch_file_chooser_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + watch_file_chooser_label = Gtk.Label(label="Watch directory", xalign=0.5) # Center text + watch_file_chooser_label.set_width_chars(LABEL_WIDTH) # Set fixed width + self.watch_file_chooser_button = Gtk.FileChooserButton(title="Choose watch directory", action=Gtk.FileChooserAction.SELECT_FOLDER) + self.watch_file_chooser_button.set_filename(input_options.watchdir) # Set the initial folder here + self.watch_file_chooser_button.connect("file-set", self.watch_folder_button_clicked) + self.watch_file_chooser_button.set_hexpand(True) + self.watch_file_chooser_button.set_halign(Gtk.Align.FILL) + watch_file_chooser_box.pack_start(watch_file_chooser_label, False, False, 0) + watch_file_chooser_box.pack_start(self.watch_file_chooser_button, True, True, 0) + tab1_box.pack_start(watch_file_chooser_box, False, False, 0) + + # Write directory + write_file_chooser_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + write_file_chooser_label = Gtk.Label(label="Write directory", xalign=0.5) # Center text + write_file_chooser_label.set_width_chars(LABEL_WIDTH) # Set fixed width + self.write_file_chooser_button = Gtk.FileChooserButton(title="Choose write directory", action=Gtk.FileChooserAction.SELECT_FOLDER) + self.write_file_chooser_button.set_filename(input_options.writedir) # Set the initial folder here + self.write_file_chooser_button.connect("file-set", self.write_folder_button_clicked) + self.write_file_chooser_button.set_hexpand(True) + self.write_file_chooser_button.set_halign(Gtk.Align.FILL) + write_file_chooser_box.pack_start(write_file_chooser_label, False, False, 0) + write_file_chooser_box.pack_start(self.write_file_chooser_button, True, True, 0) + tab1_box.pack_start(write_file_chooser_box, False, False, 0) + + # Export all button + self.ea_button = Gtk.Button(label="Export all") + self.ea_button.connect("clicked", self.export_all_clicked) + tab1_box.pack_start(self.ea_button, False, False, 0) + + # Export file button + self.ef_button = Gtk.Button(label="Export file") + self.ef_button.connect("clicked", self.export_file_clicked) + tab1_box.pack_start(self.ef_button, False, False, 0) + + # Exit button + self.exit_button = Gtk.Button(label="Exit") + self.exit_button.connect("clicked", self.exit_clicked) + tab1_box.pack_start(self.exit_button, False, False, 0) + + + + # Tab 2: Options + tab2_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + tab2_box.set_margin_start(10) + tab2_box.set_margin_end(10) + tab2_box.set_margin_top(10) + tab2_box.set_margin_bottom(10) + self.notebook.append_page(tab2_box, Gtk.Label(label="Options")) + + css_provider = Gtk.CssProvider() + css_provider.load_from_data(b".label-bold { font-weight: bold; }") + Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + + # Formats to export + export_label = Gtk.Label(label="Formats to export", xalign=0) + export_label.get_style_context().add_class("label-bold") + tab2_box.pack_start(export_label, False, False, 5) + export_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + tab2_box.pack_start(export_box, False, False, 10) + vbox_formats = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + export_box.pack_start(vbox_formats, True, True, 0) + self.pdf_check = Gtk.CheckButton(label="PDF") + self.png_check = Gtk.CheckButton(label="PNG") + self.emf_check = Gtk.CheckButton(label="EMF") + self.eps_check = Gtk.CheckButton(label="EPS") + self.svg_check = Gtk.CheckButton(label="Plain SVG") + vbox_formats.pack_start(self.pdf_check, False, False, 0) + vbox_formats.pack_start(self.png_check, False, False, 0) + vbox_formats.pack_start(self.emf_check, False, False, 0) + vbox_formats.pack_start(self.eps_check, False, False, 0) + vbox_formats.pack_start(self.svg_check, False, False, 0) + + # Rasterization DPI + dpi_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + export_box.pack_start(dpi_box, False, False, 0) + dpi_label = Gtk.Label(label="Rasterization DPI") + adj = Gtk.Adjustment(value=1.0, lower=1, upper=10000, step_increment=1, + page_increment=10, page_size=0) + self.dpi_spin = Gtk.SpinButton(adjustment=adj, digits=0) + dpi_box.pack_start(dpi_label, False, False, 0) + dpi_box.pack_start(self.dpi_spin, False, False, 0) + + # Embedded image handling options + image_handling_label = Gtk.Label(label="Embedded image handling", xalign=0) + image_handling_label.get_style_context().add_class("label-bold") + tab2_box.pack_start(image_handling_label, False, False, 5) + self.crop_check = Gtk.CheckButton(label="Crop and resample images?") + tab2_box.pack_start(self.crop_check, False, False, 5) + + # Other options + other_options_label = Gtk.Label(label="Other options", xalign=0) + other_options_label.get_style_context().add_class("label-bold") + tab2_box.pack_start(other_options_label, False, False, 5) + other_options_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=10 + ) + tab2_box.pack_start(other_options_box, False, False, 10) + vbox_other_options = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + other_options_box.pack_start(vbox_other_options, True, True, 0) + self.convert_text_check = Gtk.CheckButton(label="Convert text to paths") + self.prevent_thin_lines_check = Gtk.CheckButton( + label="Prevent thin line enhancement" + ) + self.convert_strokes_check = Gtk.CheckButton( + label="Convert all strokes to paths" + ) + self.transparent_back_check = Gtk.CheckButton( + label="Add transparent backing rectangle" + ) + vbox_other_options.pack_start(self.convert_text_check, False, False, 0) + vbox_other_options.pack_start( + self.prevent_thin_lines_check, False, False, 0 + ) + vbox_other_options.pack_start(self.convert_strokes_check, False, False, 0) + vbox_other_options.pack_start(self.transparent_back_check, False, False, 0) + extra_margin_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + other_options_box.pack_start(extra_margin_box, False, False, 0) + self.extra_margin_label = Gtk.Label(label="Extra margin (mm)") + adj = Gtk.Adjustment(value=1.0, lower=0, upper=10, step_increment=0.1, page_increment=1, page_size=0) + self.extra_margin_spin = Gtk.SpinButton(adjustment=adj, digits=1) + extra_margin_box.pack_start(self.extra_margin_label, False, False, 0) + extra_margin_box.pack_start(self.extra_margin_spin, False, False, 0) + + # PDF options + pdf_options_label = Gtk.Label(label="PDF options", xalign=0) + pdf_options_label.get_style_context().add_class("label-bold") + tab2_box.pack_start(pdf_options_label, False, False, 5) + self.omit_text_check = Gtk.CheckButton( + label="Omit text in PDF and create LaTeX file" + ) + tab2_box.pack_start(self.omit_text_check, False, False, 5) + + # Initialize from input_options + self.pdf_check.set_active(input_options.usepdf) + self.png_check.set_active(input_options.usepng) + self.emf_check.set_active(input_options.useemf) + self.eps_check.set_active(input_options.useeps) + self.svg_check.set_active(input_options.usepsvg) + self.dpi_spin.set_value(float(input_options.dpi)) + + self.crop_check.set_active(input_options.imagemode2) + self.convert_text_check.set_active(input_options.texttopath) + self.prevent_thin_lines_check.set_active(input_options.thinline) + self.convert_strokes_check.set_active(input_options.stroketopath) + self.transparent_back_check.set_active(input_options.backingrect) + self.extra_margin_spin.set_value(float(input_options.margin)) + self.omit_text_check.set_active(input_options.latexpdf) + + # Connect options buttons to callbacks + self.pdf_check.connect("toggled", self.on_pdf_toggled) + self.png_check.connect("toggled", self.on_png_toggled) + self.emf_check.connect("toggled", self.on_emf_toggled) + self.eps_check.connect("toggled", self.on_eps_toggled) + self.svg_check.connect("toggled", self.on_svg_toggled) + self.dpi_spin.connect("value-changed", self.on_dpi_changed) + self.crop_check.connect("toggled", self.on_crop_toggled) + self.convert_text_check.connect("toggled", self.on_convert_text_toggled) + self.prevent_thin_lines_check.connect( + "toggled", self.on_prevent_thin_lines_toggled + ) + self.convert_strokes_check.connect( + "toggled", self.on_convert_strokes_toggled + ) + self.transparent_back_check.connect( + "toggled", self.on_transparent_back_toggled + ) + self.extra_margin_spin.connect("value-changed", self.on_margin_changed) + self.omit_text_check.connect("toggled", self.on_omit_text_toggled) + + self.ct = ct + + + def on_treeview_size_allocate(self, widget, allocation): + ''' + Scroll to end after file inserted to File Log window + Seems to sometimes cause GTK to crash + ''' + if len(self.filelogstore)>0: + path = Gtk.TreePath.new_from_string(str(len(self.filelogstore)-1)) + column = None + self.treeview.scroll_to_cell(path, column, False, 0.0, 1.0) + + # def on_text_buffer_insert_text(self, buffer, iter, text, length): + # # Scroll to end after text printed + # mark = buffer.get_insert() + # self.selected_file_label.scroll_to_mark(mark, 0.0, True, 0.0, 1.0) + + def on_row_inserted(self, store, path, iter): + # Count the number of rows currently in the store + num_rows = len(self.filelogstore) + + # If the number of rows exceeds 100, remove the oldest rows + if num_rows > self.maxlsrows: + for i in range(num_rows - self.maxlsrows): + self.filelogstore.remove(self.filelogstore.get_iter_first()) + + def print_text(self, text): + lns = text.split("\n") + tor = [] + exception = text.startswith("Exception") + for ln in lns: + if "Export formats: " in ln or "Rasterization DPI: " in ln or exception: + continue + if ( + ":" in ln + and len(ln.split(":")) == 2 + and "Inkscape binary" not in ln + and "Python interpreter" not in ln + ): + ln2 = [v.strip(" ") for v in ln.split(":")] + self.filelogstore.append(ln2) + tor.append(ln) + lns = [item for item in lns if item not in tor] + text = "\n".join(lns) + + if len(text) > 0: + buffer = self.selected_file_label.get_buffer() + self.selected_file_label.get_buffer() + start, end = buffer.get_bounds() + if buffer.get_text(start, end, False) == "No file selected.": + buffer.set_text("") + buffer.insert(buffer.get_end_iter(), text + "\n") + + def exit_clicked(self, widget): + self.destroy() + + def watch_folder_button_clicked(self, widget): + selected_file = self.watch_file_chooser_button.get_filename() + # Check if selected_file is None, which indicates the user clicked 'Cancel' + if selected_file is not None: + self.ct.watchdir = selected_file + self.ct.nf = True + + def write_folder_button_clicked(self, widget): + selected_file = self.write_file_chooser_button.get_filename() + # Check if selected_file is None, which indicates the user clicked 'Cancel' + if selected_file is not None: + self.ct.writedir = selected_file + self.ct.nf = True + + def export_all_clicked(self, widget): + self.ct.ea = True + # self.ct.dm = True + + def export_file_clicked(self, widget): + native = Gtk.FileChooserNative.new( + "Please choose a file", self, Gtk.FileChooserAction.OPEN, None, None + ) + filter_ppt = Gtk.FileFilter() + filter_ppt.set_name("SVG files") + filter_ppt.add_pattern("*.svg") + native.add_filter(filter_ppt) + response = native.run() + if response == Gtk.ResponseType.ACCEPT: + selected_file = native.get_filename() + self.ct.selectedfile = selected_file + self.ct.es = True + native.destroy() + + def on_pdf_toggled(self, widget): + input_options.usepdf = widget.get_active() + print("PDF option toggled:", widget.get_active()) + + def on_png_toggled(self, widget): + input_options.usepng = widget.get_active() + print("PNG option toggled:", widget.get_active()) + + def on_emf_toggled(self, widget): + input_options.useemf = widget.get_active() + print("EMF option toggled:", widget.get_active()) + + def on_eps_toggled(self, widget): + input_options.useeps = widget.get_active() + print("EPS option toggled:", widget.get_active()) + + def on_svg_toggled(self, widget): + input_options.usepsvg = widget.get_active() + print("SVG option toggled:", widget.get_active()) + + def on_dpi_changed(self, widget): + input_options.dpi = widget.get_value_as_int() + print("Rasterization DPI changed:", widget.get_value_as_int()) + + def on_crop_toggled(self, widget): + input_options.imagemode2 = widget.get_active() + print("Crop and resample images option toggled:", widget.get_active()) + + def on_convert_text_toggled(self, widget): + input_options.texttopath = widget.get_active() + print("Convert text to paths option toggled:", widget.get_active()) + + def on_prevent_thin_lines_toggled(self, widget): + input_options.thinline = widget.get_active() + print("Prevent thin line enhancement option toggled:", widget.get_active()) + + def on_convert_strokes_toggled(self, widget): + input_options.stroketopath = widget.get_active() + print("Convert all strokes to paths option toggled:", widget.get_active()) + + def on_transparent_back_toggled(self, widget): + input_options.backingrect = widget.get_active() + print( + "Add transparent backing rectangle option toggled:", widget.get_active() + ) + + def on_margin_changed(self, widget): + input_options.margin = widget.get_value() + print("Extra margin changed:", widget.get_value()) + + def on_omit_text_toggled(self, widget): + input_options.latexpdf = widget.get_active() + print( + "Omit text in PDF and create LaTeX file option toggled:", + widget.get_active(), + ) + + fc = FileCheckerThread(input_options.watchdir,input_options.writedir) + global win + win = AutoexporterWindow(fc) + win.set_keep_above(True) + + mprint("Scientific Inkscape Autoexporter") + mprint("\nPython interpreter: " + sys.executable) + mprint("Inkscape binary: " + bfn + "") + + import image_helpers as ih + + mprint("Python does not have PIL, images will not be cropped" + " or converted to JPG\n" if not ih.hasPIL else "Python has PIL\n") + fc.start() + + def quit_and_close(self): + fc.stopped = True + Gtk.main_quit() + + win.connect("destroy", quit_and_close) + win.show_all() + win.set_keep_above(False) + Gtk.main() +else: + try: + import tkinter + + promptstring = ("\nEnter D to change directories, R to change DPI, F to" + " export a file, A to export all now, and Q to quit: ") + hastkinter = True + except: + promptstring = "\nEnter A to export all now, R to change DPI, and Q to quit: " + hastkinter = False + + if platform.system().lower() == "darwin": + mprint(" ") + elif platform.system().lower() == "windows": + # Disable QuickEdit, which I think causes the occasional freezes + # From https://stackoverflow.com/questions/37500076/ + # how-to-enable-windows-console-quickedit-mode-from-python + def quickedit(enabled=1): + import ctypes + + """ + Enable or disable quick edit mode to prevent system hangs, + sometimes when using remote desktop + Param (Enabled) + enabled = 1(default), enable quick edit mode in python console + enabled = 0, disable quick edit mode in python console + """ + # -10 is input handle => STD_INPUT_HANDLE (DWORD) -10 | + # https://docs.microsoft.com/en-us/windows/console/getstdhandle + # default = (0x4|0x80|0x20|0x2|0x10|0x1|0x40|0x200) + # 0x40 is quick edit, #0x20 is insert mode + # 0x8 is disabled by default + # https://docs.microsoft.com/en-us/windows/console/setconsolemode + kernel32 = ctypes.windll.kernel32 + if enabled: + kernel32.SetConsoleMode( + kernel32.GetStdHandle(-10), + (0x4 | 0x80 | 0x20 | 0x2 | 0x10 | 0x1 | 0x40 | 0x100), + ) + # mprint("Console Quick Edit Enabled") + else: + kernel32.SetConsoleMode( + kernel32.GetStdHandle(-10), + (0x4 | 0x80 | 0x20 | 0x2 | 0x10 | 0x1 | 0x00 | 0x100), + ) + # mprint("Console Quick Edit Disabled") + + quickedit(0) # Disable quick edit in terminal + + mprint("Scientific Inkscape Autoexporter") + mprint("\nPython interpreter: " + sys.executable) + mprint("Inkscape binary: " + bfn + "") + + import image_helpers as ih + + mprint("Python does not have PIL, images will not be cropped" + " or converted to JPG\n" if not ih.hasPIL else "Python has PIL\n") + + def Get_Directories(): + root = tkinter.Tk() + root.geometry("1x1") + root.lift() + root.overrideredirect(1) + mprint("Select a directory to watch") + watchdir = tkinter.filedialog.askdirectory(title="Select a directory to watch") + root.destroy() + if watchdir == "": + raise + root = tkinter.Tk() + root.geometry("1x1") + root.lift() + root.overrideredirect(1) + mprint("Select a directory to write to") + writedir = tkinter.filedialog.askdirectory( + title="Select a directory to write to" + ) + root.destroy() + if writedir == "": + raise + return watchdir, writedir + + def Get_File(initdir): + root = tkinter.Tk() + root.geometry("1x1") + root.lift() + root.overrideredirect(1) + mprint("Select a file to export") + selectedfile = tkinter.filedialog.askopenfile(title="Select a file") + root.destroy() + selectedfile.close() + return selectedfile.name + + def get_defs(svg): + for k in list(svg): + if isinstance(k, (inkex.Defs)): + return k + d = inkex.Defs() + # no Defs, make one + svg.insert(len(list(svg)), d) + return d + + # Main loop + fc = FileCheckerThread(input_options.watchdir,input_options.writedir) + if fc.watchdir is None or fc.writedir is None: + fc.watchdir, fc.writedir = Get_Directories() + + fc.start() + while fc.nf: # wait until it's done initializing + pass + t2 = PromptThread() + t2.start() + keeprunning = True + while keeprunning: + if not (t2.is_alive()): + if t2.ui in ["Q", "q"]: + fc.stopped = True + keeprunning = False + elif t2.ui in ["D", "d"]: + if hastkinter: + try: + fc.watchdir, fc.writedir = Get_Directories() + fc.nf = True + except: + pass + elif t2.ui in ["R", "r"]: + input_options.dpi = int(input("Enter new rasterization DPI: ")) + elif t2.ui in ["A", "a"]: + fc.ea = True + elif t2.ui in ["F", "f"]: + if hastkinter: + try: + fc.selectedfile = Get_File(fc.watchdir) + fc.es = True + except: + pass + elif t2.ui in ["#"]: + fc.dm = True + # entering # starts an infinite export loop in the current dir + fc.ea = True + else: + mprint("Invalid input!") + if keeprunning: + t2 = PromptThread() + t2.start() + fc.promptpending = True + time.sleep(WHILESLEEP) + + # On macOS close the terminal we opened + # https://superuser.com/questions/158375/ + # how-do-i-close-the-terminal-in-osx-from-the-command-line + if platform.system().lower() == "darwin": + os.system( + "osascript -e 'tell application \"Terminal\" to close first window' & exit" + ) \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/combine_by_color.inx b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/combine_by_color.inx new file mode 100644 index 0000000..7318c7e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/combine_by_color.inx @@ -0,0 +1,34 @@ + + + Combine by color + burghoff.combinebycolor + + + + + + + + + + + + 15 + + + + + + + + + text + + + + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/combine_by_color.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/combine_by_color.py new file mode 100644 index 0000000..2291e78 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/combine_by_color.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2021 David Burghoff, dburghoff@nd.edu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import dhelpers as dh +import inkex +from inkex import NamedView, Defs, Metadata, ForeignObject, Group, MissingGlyph +import math + +class CombineByColor(inkex.EffectExtension): + # def document_path(self): + # return 'test' + + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument( + "--lightnessth", type=float, default=15, help="Lightness threshold" + ) + + def effect(self): + lightness_threshold = self.options.lightnessth / 100 + + sel = [self.svg.selection[ii] for ii in range(len(self.svg.selection))] + # should work with both v1.0 and v1.1 + sel = [v for el in sel for v in el.descendants2()] + + allel = [v for v in self.svg.descendants2()] + elord = [allel.index(v) for v in sel] + # order of selected elements in svg + + els = [ + el + for el in sel + if not ( + isinstance( + el, (NamedView, Defs, Metadata, ForeignObject, Group, MissingGlyph) + ) + ) + and ( + el.get("d") is not None + or el.get("points") is not None + or el.get("x1") is not None + ) + ] + + merged = [False for el in els] + # stys = [(el.cspecified_style) for el in els] + sfs = [dh.get_strokefill(els[ii]) for ii in range(len(els))] + # dh.debug(lightness_threshold) + for ii in reversed(range(len(els))): # reversed so that order is preserved + sf1 = sfs[ii] + # if ( + # sf1.stroke is not None + # and sf1.stroke.efflightness >= lightness_threshold + # ): + if ( + sf1.stroke is None or sf1.stroke.efflightness >= lightness_threshold + ) and (sf1.fill is None or sf1.fill.efflightness >= lightness_threshold): + merges = [ii] + merged[ii] = True + for jj in range(ii): + if not (merged[jj]): + sf2 = sfs[jj] + samesw = ( + sf1.strokewidth is None and sf2.strokewidth is None + ) or ( + sf1.strokewidth is not None + and sf2.strokewidth is not None + and abs(sf1.strokewidth - sf2.strokewidth) < 0.001 + ) + samestrk = (sf1.stroke is None and sf2.stroke is None) or ( + sf1.stroke is not None + and sf2.stroke is not None + and sf1.stroke.red == sf2.stroke.red + and sf1.stroke.blue == sf2.stroke.blue + and sf1.stroke.green == sf2.stroke.green + and abs(sf1.stroke.alpha - sf2.stroke.alpha) < 0.001 + ) + # RGB are 0-255, alpha are 0-1 + samefill = (sf1.fill is None and sf2.fill is None) or ( + sf1.fill is not None + and sf2.fill is not None + and sf1.fill.red == sf2.fill.red + and sf1.fill.blue == sf2.fill.blue + and sf1.fill.green == sf2.fill.green + and abs(sf1.fill.alpha - sf2.fill.alpha) < 0.001 + ) + if ( + samestrk + and samefill + and samesw + and sf1.strokedasharray == sf2.strokedasharray + and sf1.markerstart == sf2.markerstart + and sf1.markermid == sf2.markermid + and sf1.markerend == sf2.markerend + ): + merges.append(jj) + merged[jj] = True + if len(merges) > 1: + ords = [elord[kk] for kk in merges] + ords.sort() + medord = ords[math.floor((len(ords) - 1) / 2)] + topord = ords[-1] + mergeii = [ + kk for kk in range(len(merges)) if elord[merges[kk]] == topord + ][ + 0 + ] # use the median + dh.combine_paths([els[kk] for kk in merges], mergeii) + # dh.flush_stylesheet_entries(self.svg) # since we removed clips + + +if __name__ == "__main__": + dh.Run_SI_Extension(CombineByColor(), "Combine by color") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/dhelpers.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/dhelpers.py new file mode 100644 index 0000000..539791d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/dhelpers.py @@ -0,0 +1,1963 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# Functions modified from Inkex were made by +# Martin Owens +# Sergei Izmailov +# Thomas Holder +# Jonathan Neuhauser +# Functions modified from Deep_Ungroup made by Nikita Kitaev +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# Locate the installed Inkex so we can assess the version. Do not import! +import pkgutil +installed_inkex = None +for finder in pkgutil.iter_importers(): + if hasattr(finder, "find_spec"): + try: + spec = finder.find_spec("inkex") + if spec and spec.origin: + installed_inkex = spec.origin + except TypeError: + continue + +# Import the packaged version of Inkex (currently v1.3.0) +import sys, os + +inkex_to_use = "inkex1_3_0" +si_dir = os.path.dirname(os.path.realpath(__file__)) # my install location +sys.path.insert(0, os.path.join(si_dir, inkex_to_use)) +sys.path.insert(1, os.path.join(si_dir, inkex_to_use, "site-packages")) +import inkex + +# For SI we override Inkex's Style with a modified version, Style0 +# To do this we import Style0, then replace inkex.Style with it +import styles0 + +inkex.Style = styles0.Style0 + +# Next we make sure we have the text submodule +sys.path.append( + os.path.join(si_dir, inkex_to_use, "site-packages", "python_fontconfig") +) +import inkex.text # noqa +import speedups # noqa + +from inkex import Style +from inkex.text.cache import BaseElementCache +from inkex.text.parser import TextTree, TYP_TEXT + +from inkex.text.utils import ( + composed_width, + unique, + isrectangle, + subprocess_repeat, + tags, + bbox, + ipx, +) + + +from inkex import Tspan, Transform, Path, PathElement, BaseElement +from applytransform_mod import fuseTransform +import lxml, math, re, os, random, sys +from functools import lru_cache + +# Parsed Inkex version, with extension back to v0.92.4 +if not hasattr(inkex, "__version__"): + try: + tmp = BaseElement.unittouu # introduced in 1.1 + inkex.__version__ = "1.1.0" + except: + try: + from inkex.paths import Path, CubicSuperPath # noqa + + inkex.__version__ = "1.0.0" + except: + inkex.__version__ = "0.92.4" +inkex.vparse = lambda x: [int(v) for v in x.split(".")] # type: ignore +inkex.ivp = inkex.vparse(inkex.__version__) # type: ignore + + +# Returns non-comment children +def list2(el): + return [k for k in list(el) if not (k.tag == ctag)] + + +EBget = lxml.etree.ElementBase.get +EBset = lxml.etree.ElementBase.set + +import inspect + + +def count_callers(): + caller_frame = inspect.stack()[2] + filename = caller_frame.filename + line_number = caller_frame.lineno + lstr = f"{filename} at line {line_number}" + global callinfo + try: + callinfo + except: + callinfo = dict() + if lstr in callinfo: + callinfo[lstr] += 1 + else: + callinfo[lstr] = 1 + + +# Discover the version of Inkex installed, NOT the version packaged with SI +if installed_inkex is not None: + with open(installed_inkex, "r") as file: + content = file.read() + match = re.search(r'__version__\s*=\s*"(.*?)"', content) + vstr = match.group(1) if match else "1.0.0" + if vstr=='1.2.0': # includes 1.2.0, 1.2.1, 1.2.2 + extpy = os.path.join(os.path.dirname(installed_inkex),'extensions.py') + with open(extpy, "r") as file: + content = file.read() + match = re.search(r'Pattern', content) # appeared in 1.2.2 + if match: + vstr='1.2.2' +else: + # No installed Inkex, probably not called by Inkscape + vstr = inkex.__version__ + +inkex.installed_ivp = inkex.vparse(vstr) # type: ignore +inkex.installed_haspages = inkex.installed_ivp[0] >= 1 and inkex.installed_ivp[1] >= 2 + +# On v1.1-1.2.1 gi produces an error for some reason that is actually fine +import platform + +if platform.system().lower() == "windows" and inkex.installed_ivp[0:2] == [1, 1] or (inkex.installed_ivp[0:2] == [1, 2] and inkex.installed_ivp[2]<2): + if inkex.text.font_properties.HASPANGOFT2: + from gi.repository import GLib + + def custom_log_writer(log_domain, log_level, message, user_data): + return GLib.LogWriterOutput.UNHANDLED + + GLib.log_set_writer_func(custom_log_writer, None) + + +# Replace an element with another one +# Puts it in the same location, update the cache dicts +def replace_element(el1, el2): + # replace el1 with el2 + myp = el1.getparent() + myi = list(myp).index(el1) + myp.insert(myi + 1, el2) + + newid = el1.get_id() + oldid = el2.get_id() + + el1.delete() + el2.set_id(newid) + el2.croot.iddict.add(el2) + el2.croot.cssdict.dupe_entry(oldid, newid) + + +# For style components that are a list (stroke-dasharray), calculate +# the true size reported by Inkscape, inheriting any styles/transforms +def listsplit(x): + # split list on commas or spaces + return [ipx(v) for v in re.split("[ ,]", x) if v] + + +def composed_list(el, comp): + cs = el.cspecified_style + ct = el.ccomposed_transform + sc = cs.get(comp) + if sc == "none": + return "none", None + elif sc is not None: + sv = listsplit(sc) + sf = math.sqrt(abs(ct.a * ct.d - ct.b * ct.c)) + sv = [x * sf for x in sv] + return sv, sf + else: + return None, None + + +# Unit parser and renderer +def uparse(str): + if str is not None: + uv = inkex.units.parse_unit(str, default_unit=None) + return uv[0], uv[1] + else: + return None, None + + +def urender(v, u): + if v is not None: + if u is not None: + return inkex.units.render_unit(v, u) + else: + return str(v) + else: + return None + + +# Get points of a path, element, or rectangle in the global coordinate system +def get_points(el, irange=None): + pth = el.cpath.to_absolute() + if irange is not None: + pnew = Path() + for ii in range(irange[0], irange[1]): + pnew.append(pth[ii]) + pth = pnew + pts = list(pth.end_points) + + ct = el.ccomposed_transform + + xs = [] + ys = [] + for p in pts: + p = ct.apply_to_point(p) + xs.append(p.x) + ys.append(p.y) + return xs, ys + + +# Unlinks clones and composes transform/clips/etc, along with descendants +def unlink2(el): + if el.tag == usetag: + useel = el.get_link("xlink:href") + if useel is not None: + d = useel.duplicate() + + # xy translation treated as a transform (applied first, then clip/mask, then full xform) + tx = EBget(el, "x") + ty = EBget(el, "y") + if tx is None: + tx = 0 + if ty is None: + ty = 0 + # order: x,y translation, then clip/mask, then transform + compose_all( + d, + None, + None, + Transform("translate(" + str(tx) + "," + str(ty) + ")"), + None, + ) + compose_all( + d, + el.get_link("clip-path", llget=True), + el.get_link("mask", llget=True), + el.ctransform, + el.ccascaded_style, + ) + replace_element(el, d) + d.set("unlinked_clone", True) + for k in d.descendants2()[1:]: + unlink2(k) + + # To match Unlink Clone behavior, convert Symbol to Group + if isinstance(d, (inkex.Symbol)): + g = group(list(d)) + ungroup(d) + d = g + return d + else: + return el + else: + return el + + +# unungroupable = (NamedView, Defs, Metadata, ForeignObject, lxml.etree._Comment) +unungroupable = tags((inkex.NamedView, inkex.Defs, inkex.Metadata, inkex.ForeignObject)) +ctag = lxml.etree.Comment("").tag +unungroupable.add(ctag) + + +def ungroup(g, removetextclip=False): + # Ungroup a group, preserving style, clipping, and masking + # Remove any comments + + if g.croot is not None: + gparent = g.getparent() + gindex = gparent.index(g) # group's location in parent + + gtransform = g.ctransform + gclip = g.get_link("clip-path", llget=True) + gmask = g.get_link("mask", llget=True) + gstyle = g.ccascaded_style + + for el in reversed(list(g)): + if el.tag == ctag: # remove comments + g.remove(el) + if el.tag not in unungroupable: + clippedout = compose_all( + el, gclip, gmask, gtransform, gstyle, removetextclip=removetextclip + ) + if clippedout: + el.delete() + else: + gparent.insert(gindex + 1, el) # places above + if len(g) == 0: + g.delete() + + +# Group a list of elements, placing the group in the location of the first element +def group(el_list, moveTCM=False): + g = inkex.Group() + myi = list(el_list[0].getparent()).index(el_list[0]) + el_list[0].getparent().insert(myi + 1, g) + for el in el_list: + g.append(el) + + # If moveTCM is set and are grouping one element, move transform/clip/mask to group + # Handy for adding and properly composing transforms/clips/masks + if moveTCM and len(el_list) == 1: + g.ctransform = el.ctransform + el.ctransform = None + g.set("clip-path", el.get("clip-path")) + el.set("clip-path", None) + g.set("mask", el.get("mask")) + el.set("mask", None) + return g + + +# For composing a group's properties onto its children (also group-like objects like Uses) +Itmat = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0)) + + +def compose_all(el, clip, mask, transform, style, removetextclip=False): + if style is not None: # style must go first since we may change it with CSS + mysty = el.ccascaded_style + compsty = style + mysty + compsty["opacity"] = str( + float(mysty.get("opacity", "1")) * float(style.get("opacity", "1")) + ) # opacity accumulates at each layer + el.cstyle = compsty + + if removetextclip and el.tag in ttags: + el.set("clip-path", None) + el.set("mask", None) + cout = False + else: + if clip is not None: + cout = merge_clipmask(el, clip) # clip applied before transform, fix first + if mask is not None: + merge_clipmask(el, mask, mask=True) + if clip is not None: + fix_css_clipmask(el) + if mask is not None: + fix_css_clipmask(el, mask=True) + + if transform is not None and transform.matrix != Itmat: + if el.ctransform is None or el.ctransform.matrix == Itmat: + el.ctransform = transform + else: + el.ctransform = transform @ el.ctransform + + if clip is None: + return False + else: + return cout + + +# If an element has clipping/masking specified in a stylesheet, this will override any attributes +# I think this is an Inkscape bug +# Fix by creating a style specific to my id that includes the new clipping/masking +def fix_css_clipmask(el, mask=False): + cm = "clip-path" if not mask else "mask" + svg = el.croot + if svg is not None: + mycss = svg.cssdict.get(el.get_id()) + if ( + mycss is not None + and mycss.get(cm) is not None + and mycss.get(cm) != el.get(cm) + ): + sty = el.croot.crootsty + sty.text = (sty.text or "") + "\n#{0}{{{1}:{2}}}".format( + el.get_id(), cm, el.get(cm) + ) + mycss[cm] = el.get(cm) + if el.cstyle.get(cm) is not None: # also clear local style + el.cstyle[cm] = None + + +def intersect_paths(ptha, pthb): + # Intersect two rectangular paths. Could be generalized later + ptsa = list(ptha.end_points) + ptsb = list(pthb.end_points) + x1c = max(min([p.x for p in ptsa]), min([p.x for p in ptsb])) + x2c = min(max([p.x for p in ptsa]), max([p.x for p in ptsb])) + y1c = max(min([p.y for p in ptsa]), min([p.y for p in ptsb])) + y2c = min(max([p.y for p in ptsa]), max([p.y for p in ptsb])) + w = x2c - x1c + h = y2c - y1c + if w > 0 and h > 0: + return Path("M {},{} h {} v {} h {} Z".format(x1c, y1c, w, h, -w)) + else: + return Path("") + + +usetag = inkex.Use.ctag + + +def merge_clipmask(node, newclip, mask=False): + # Modified from Deep Ungroup + def compose_clips(el, ptha, pthb): + newpath = intersect_paths(ptha, pthb) + isempty = str(newpath) == "" + + if not (isempty): + p = PathElement() + el.getparent().append(p) + p.set("d", newpath) + el.delete() + return isempty # if clipped out, safe to delete element + + if newclip is not None: + svg = node.croot + cmstr = "mask" if mask else "clip-path" + + if node.ctransform is not None: + # Clip-paths on nodes with a transform have the transform + # applied to the clipPath as well, which we don't want. + # Duplicate the new clip and apply node's inverse transform to its children. + if newclip is not None: + d = newclip.duplicate() + if not (hasattr(svg, "newclips")): + svg.newclips = [] + svg.newclips.append(d) # for later cleanup + for k in list(d): + compose_all(k, None, None, -node.ctransform, None) + # newclipurl = d.get_id(2) + newclip = d + + if newclip is not None: + for k in list(newclip): + if k.tag == usetag: + k = unlink2(k) + oldclip = node.get_link(cmstr, llget=True) + if oldclip is not None: + # Existing clip is replaced by a duplicate, then apply new clip to children of duplicate + for k in list(oldclip): + if k.tag == usetag: + k = unlink2(k) + + d = oldclip.duplicate() + if not (hasattr(svg, "newclips")): + svg.newclips = [] + svg.newclips.append(d) # for later cleanup + node.set(cmstr, d.get_id(2)) + + newclipisrect = False + if newclip is not None and len(newclip) == 1: + newclipisrect = isrectangle(list(newclip)[0]) + + couts = [] + for k in reversed(list(d)): # may be deleting, so reverse + oldclipisrect = isrectangle(k) + if newclipisrect and oldclipisrect and mask == False: + # For rectangular clips, we can compose them easily + # Since most clips are rectangles this semi-fixes the PDF clip export bug + newclippth = list(newclip)[0].cpath.transform( + list(newclip)[0].ctransform + ) + oldclippth = k.cpath.transform(k.ctransform) + cout = compose_clips(k, newclippth, oldclippth) + else: + cout = merge_clipmask(k, newclip, mask) + couts.append(cout) + cout = all(couts) + + if oldclip is None: + node.set(cmstr, newclip.get_id(2)) + cout = False + + return cout + + +# A cached list of all descendants of an svg in order +# Currently only handles deletions appropriately +class dtree: + def __init__(self, svg): + ds, pts = svg.descendants2(True) + self.ds = ds + self.iids = {d: ii for ii, d in enumerate(ds)} # desc. index by el + iipts = { + ptv: (ii, jj) for ii, pt in enumerate(pts) for jj, ptv in enumerate(pt) + } + self.range = [(ii, iipts[d][0]) for ii, d in enumerate(ds)] + + def iterel(self, el): + try: + eli = self.iids[el] + for ii in range(self.range[eli][0], self.range[eli][1]): + yield self.ds[ii] + except: + pass + + def delel(self, el): + try: + eli = self.iids[el] + strt = self.range[eli][0] + stop = self.range[eli][1] + self.ds = self.ds[:strt] + self.ds[stop:] + self.range = self.range[:strt] + self.range[stop:] + N = stop - strt + self.range = [ + (x - N if x > strt else x, y - N if y > strt else y) + for x, y in self.range + ] + self.iids = {d: ii for ii, d in enumerate(self.ds)} # desc. index by el + except: + pass + + +def get_cd2(svg): + if not (hasattr(svg, "_cd2")): + svg._cd2 = dtree(svg) + return svg._cd2 + + +def set_cd2(svg, sv): + if sv is None and hasattr(svg, "_cd2"): + delattr(svg, "_cd2") + + +inkex.SvgDocumentElement.cdescendants2 = property(get_cd2, set_cd2) + + +masktag = inkex.addNS("mask", "svg") +svgtag = inkex.SvgDocumentElement.ctag + +unrendered = tags( + ( + inkex.NamedView, + inkex.Defs, + inkex.Metadata, + inkex.ForeignObject, + inkex.Guide, + inkex.ClipPath, + inkex.StyleElement, + Tspan, + inkex.FlowRegion, + inkex.FlowPara, + ) +) +unrendered.update( + { + masktag, + inkex.addNS("RDF", "rdf"), + inkex.addNS("Work", "cc"), + inkex.addNS("format", "dc"), + inkex.addNS("type", "dc"), + } +) + + + +def wrapped_binary(filename, inkscape_binary=None, extra_args=None, svg=None, get_bbs=True,cwd=None): + """ + Retrieves all of a document's bounding boxes using a call to the Inkscape binary. + + Parameters: + filename (str): The path to the SVG file. + inkscape_binary (str): The path to the Inkscape binary. If not provided, + it will attempt to find it. + extra_args (list): Additional arguments to pass to the Inkscape command. + svg: An optional svg to use instead of loading from file. + + Returns: + dict: A dictionary where keys are element IDs and values are bounding + boxes in user units. + """ + if inkscape_binary is None: + inkscape_binary = inkex.inkscape_system_info.binary_location + extra_args = [] if extra_args is None else extra_args + + if get_bbs: + arg2 = [inkscape_binary, "--query-all"] + extra_args + [filename] + proc = subprocess_repeat(arg2,cwd=cwd) + else: + arg2 = [inkscape_binary] + extra_args + [filename] + proc = subprocess_repeat(arg2,cwd=cwd) + return None + tfstr = proc.stdout + + # Parse the output + tbbli = tfstr.splitlines() + bbs = dict() + for line in tbbli: + keyv = str(line).split(",", maxsplit=1)[0] + if keyv[0:2] == "b'": # pre version 1.1 + keyv = keyv[2:] + if str(line)[2:52] == "WARNING: Requested update while update in progress": + continue + # skip warnings (version 1.0 only?) + data = [float(x.strip("'")) for x in str(line).split(",")[1:]] + if keyv != "'": # sometimes happens in v1.3 + bbs[keyv] = data + + # Inkscape always reports a bounding box in pixels, relative to the viewbox + # Convert to user units for the output + if svg is None: + # If SVG not supplied, load from file from load_svg + svg = load_svg(filename).getroot() + + dsz = svg.cdocsize + for k in bbs: + bbs[k] = dsz.pxtouu(bbs[k]) + return bbs + + +# Determine if object has a bbox +@lru_cache(maxsize=None) +def hasbbox(el): + myp = el.getparent() + if myp is None: + return el.tag == svgtag + else: + return el.tag not in unrendered if hasbbox(myp) else False + + +# Determine if object itself is drawn +@lru_cache(maxsize=None) +def isdrawn(el): + return ( + el.tag not in grouplike_tags + and hasbbox(el) + and el.cspecified_style.get("display") != "none" + ) + + +# A wrapper that replaces get_bounding_boxes with Pythonic calls only if possible +def BB2(svg, els=None, forceupdate=False, roughpath=False, parsed=False): + if els is None: + els = svg.descendants2() + if all([d.tag in bb2_support_tags or not (hasbbox(d)) for d in els]): + # All descendants of all els in the list + allds = set() + for el in els: + if el not in allds: # so we're not re-descendants2ing + allds.update(el.descendants2()) + tels = [ + d + for d in unique(allds) + if isinstance(d, (inkex.TextElement, inkex.FlowRoot)) + ] + + if len(tels) > 0: + if forceupdate: + svg.char_table = None + for d in els: + d.cbbox = None + d.parsed_text = None + if not hasattr(svg, "_char_table"): + from inkex.text import parser # noqa + + svg.make_char_table(els=tels) + # pts = [el.parsed_text for el in tels] + ptl = parser.ParsedTextList(tels) + ptl.precalcs() + ret = dict() + for d in els: + if d.tag in bb2_support_tags and hasbbox(d): + mbbox = bounding_box2(d, roughpath=roughpath, parsed=parsed) + if not (mbbox.isnull): + ret[d.get_id()] = mbbox.sbb + else: + import tempfile + + # with tempfile.TemporaryFile() as temp: + # idebug(temp.name) + # tname = os.path.abspath(temp.name) + # overwrite_svg(svg, tname) + # ret = wrapped_binary(filename=tname, svg=svg) + + with tempfile.NamedTemporaryFile(delete=False) as temp: + tname = os.path.abspath(temp.name) + try: + overwrite_svg(svg, tname) + ret = wrapped_binary(filename=tname, svg=svg) + finally: + if os.path.exists(tname): + os.remove(tname) + + return ret + + +# For diagnosing BB2 +def Check_BB2(svg): + bb2 = BB2(svg) + HIGHLIGHT_STYLE = "fill:#007575;fill-opacity:0.4675" # mimic selection + for el in svg.descendants2(): + if el.get_id() in bb2 and not el.tag in grouplike_tags: + bb = bbox(bb2[el.get_id()]) + # bb = bbox(bb2[el.get_id()])*(1/slf.svg.cscale); + r = inkex.Rectangle() + r.set("mysource", el.get_id()) + r.set("x", bb.x1) + r.set("y", bb.y1) + r.set("height", bb.h) + r.set("width", bb.w) + r.set("style", HIGHLIGHT_STYLE) + el.croot.append(r) + + +# Vectorized calculation of bbox intersection bools +# Returns a matrix sized len(bbs) x len(bb2s) +def bb_intersects(bbs, bb2s=None): + if bb2s is None: + bb2s = bbs + import numpy as np + + if len(bbs) == 0 or len(bb2s) == 0: + return np.zeros((len(bbs), len(bb2s)), dtype=bool) + else: + xc1, yc1, wd1, ht1 = np.array([ + (bb.xc, bb.yc, bb.w, bb.h) if not bb.isnull else (np.nan, np.nan, np.nan, np.nan) + for bb in bbs + ]).T + + xc2, yc2, wd2, ht2 = np.array([ + (bb.xc, bb.yc, bb.w, bb.h) if not bb.isnull else (np.nan, np.nan, np.nan, np.nan) + for bb in bb2s + ]).T + return np.logical_and( + np.nan_to_num(abs(xc1.reshape(-1, 1) - xc2) * 2 < (wd1.reshape(-1, 1) + wd2), nan=False), + np.nan_to_num(abs(yc1.reshape(-1, 1) - yc2) * 2 < (ht1.reshape(-1, 1) + ht2), nan=False), + ) + +# Return list of objects on top of other objects +def overlapping_els(svg,tocheck): + els = [el for el in svg.descendants2() if isdrawn(el)] + bbs = BB2(svg, els, roughpath=True, parsed=True) + bbs = [bbox(bbs.get(el.get_id())) for el in els] + + chki = [i for i,el in enumerate(els) if el in tocheck] + bbs_check = [bbs[i] for i in chki] + intrscts = bb_intersects(bbs, bbs_check) + + ret = {el: [] for el in tocheck} + for j,ci in enumerate(chki): + elj = els[ci] + for i in range(ci+1,len(els)): + eli = els[i] + ds = elj.descendants2() + if intrscts[i,j] and eli not in ds: + ret[elj].append(eli) + + # for k,v in ret.items(): + # dh.idebug(k.get_id()+': '+str([v2.get_id() for v2 in v])) + return ret + +# Get SVG from file +from inkex import load_svg + + +def svg_from_file(fin): + svg = load_svg(fin).getroot() + return svg + + +def el_from_string(strin): + prefix = """ + + """ + svgtxt = prefix + strin + "" + nsvg = svg_from_file(svgtxt) + return list(nsvg)[0] + + +# Write to disk, removing any existing file +def overwrite_svg(svg, fileout): + try: + os.remove(fileout) + except: + pass + # idebug(inkex) + import inkex.command # needed for a weird bug in v1.1 + + inkex.command.write_svg(svg, fileout) + + +global debugs +debugs = "" + + +def debug(x): + # inkex.utils.debug(x); + global debugs + if debugs != "": + debugs += "\n" + debugs += str(x) + + +def write_debug(): + global debugs + if debugs != "": + debugname = "Debug.txt" + f = open(debugname, "w", encoding="utf-8") + f.write(debugs) + f.close() + + +def idebug(x, printids=True): + def is_nested_list_of_base_elements(x): + if not isinstance(x, (list, tuple)): + return False + for element in x: + if isinstance(element, (list, tuple)): + if not is_nested_list_of_base_elements(element): + return False + elif not isinstance(element, BaseElement): + return False + return True + + if printids and is_nested_list_of_base_elements(x): + + def process_nested_list(input_list): + if isinstance(input_list, list): + return [process_nested_list(e) for e in input_list] + if isinstance(input_list, tuple): + return tuple([process_nested_list(e) for e in input_list]) + elif isinstance(input_list, BaseElement): + return input_list.get_id() + + pv = process_nested_list(x) + else: + pv = x + inkex.utils.debug(pv) + + +import time + +global lasttic + + +def tic(): + global lasttic + lasttic = time.time() + + +def toc(): + global lasttic + idebug(time.time() - lasttic) + + +def benchmark_functions(func1, func2): + import numpy as np + + differences = [] + time1s = [] + + MINIBATCH_TIME = 0.01 + TOTAL_TIME = 10 + + strt = time.time() + f1cnt = 0 + while time.time() - strt < MINIBATCH_TIME: + func1() + f1cnt += 1 + strt = time.time() + f2cnt = 0 + while time.time() - strt < MINIBATCH_TIME: + func2() + f2cnt += 1 + + for Nr in range(5): + strt = time.time() + for ii in range(f1cnt): + func1() + f1act = time.time() - strt + strt = time.time() + for ii in range(f2cnt): + func2() + f2act = time.time() - strt + f1cnt = int(f1cnt * MINIBATCH_TIME / f1act) + f2cnt = int(f2cnt * MINIBATCH_TIME / f2act) + + M = int(TOTAL_TIME / MINIBATCH_TIME / 2) + N1 = f1cnt + N2 = f2cnt + + for _ in range(M): + # Timing function 1 + start_time = time.time() + for _ in range(N1): + func1() + end_time = time.time() + time_func1 = (end_time - start_time) / N1 + time1s.append(time_func1) + + # Timing function 2 + start_time = time.time() + for _ in range(N2): + func2() + end_time = time.time() + time_func2 = (end_time - start_time) / N2 + + # Calculate the difference in times + time_difference = time_func2 - time_func1 + differences.append(time_difference) + + # Calculating mean and standard deviation of time differences + mean_difference = np.mean(differences) + std_deviation = np.std(differences) + + return np.mean(time1s), mean_difference, std_deviation / np.sqrt(M) + + +# style atts that could have urls +urlatts = [ + "fill", + "stroke", + "clip-path", + "mask", + "filter", + "marker-start", + "marker-mid", + "marker-end", + "marker", +] + + +# An efficient Pythonic version of Clean Up Document +def clean_up_document(svg): + # defs types that do nothing unless they are referenced + prune = [ + "clipPath", + "mask", + "linearGradient", + "radialGradient", + "pattern", + "symbol", + "marker", + "filter", + "animate", + "animateTransform", + "animateMotion", + "textPath", + "font", + "font-face", + ] + prune = [inkex.addNS(v, "svg") for v in prune] + + # defs types that don't need to be referenced + exclude = [inkex.addNS(v, "svg") for v in ["style", "glyph"]] + + def should_prune(el): + return el.tag in prune or ( + el.getparent() == svg.cdefs and el.tag not in exclude + ) + + xlink = [inkex.addNS("href", "xlink"), "href"] + attids = {sa: dict() for sa in urlatts} + xlinks = dict() + + def miterdescendants(el): + yield el + for d in el.iterdescendants(): + yield d + + # Make dicts of all url-containing style atts and xlinks + for d in svg.cdescendants2.ds: + for attName in d.attrib.keys(): + if attName in urlatts: + if d.attrib[attName].startswith("url"): + attids[attName][d.get_id()] = d.attrib[attName][5:-1] + elif attName in xlink: + if d.attrib[attName].startswith("#"): + xlinks[d.get_id()] = d.attrib[attName][1:] + elif attName == "style": + if "url" in d.attrib[attName]: + sty = Style(d.attrib[attName]) + for an2 in sty.keys(): + if an2 in urlatts: + if sty[an2].startswith("url"): + attids[an2][d.get_id()] = sty[an2][5:-1] + + deletedsome = True + while deletedsome: + allurls = set( + [v for sa in urlatts for v in attids[sa].values()] + list(xlinks.values()) + ) + # sets much faster than lists for membership testing + deletedsome = False + for el in svg.cdescendants2.ds: + if should_prune(el): + eldids = [dv.get_id() for dv in svg.cdescendants2.iterel(el)] + if not (any([idv in allurls for idv in eldids])): + el.delete() + deletedsome = True + for did in eldids: + for anm in urlatts: + if did in attids[anm]: + del attids[anm][did] + if did in xlinks: + del xlinks[did] + + +def global_transform(el, trnsfrm, irange=None, trange=None, preserveStroke=True): + # Transforms an object and fuses it to any paths + # If preserveStroke is set the stroke width will be unchanged, otherwise + # will also be scaled + + # If parent layer is transformed, need to rotate out of its coordinate system + myp = el.getparent() + if myp is None: + prt = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) + else: + prt = myp.ccomposed_transform + + myt = el.ctransform + if myt == None: + newtr = (-prt) @ trnsfrm @ prt + if trange is not None: + for ii in range(len(trange)): + trange[ii] = (-prt) @ trange[ii] @ prt + else: + newtr = (-prt) @ trnsfrm @ prt @ Transform(myt) + if trange is not None: + for ii in range(len(trange)): + trange[ii] = (-prt) @ trange[ii] @ prt @ Transform(myt) + + sw, _, _ = composed_width(el, "stroke-width") + sd, _ = composed_list(el, "stroke-dasharray") + + el.ctransform = newtr # Add the new transform + fuseTransform(el, irange=irange, trange=trange) + + if preserveStroke: + if sw is not None: + neww, sf, _ = composed_width(el, "stroke-width") + if sf != 0: + el.cstyle["stroke-width"] = str(sw / sf) + # fix width + if not (sd in [None, "none"]): + nd, sf = composed_list(el, "stroke-dasharray") + if sf != 0: + el.cstyle["stroke-dasharray"] = ( + str([sdv / sf for sdv in sd]).strip("[").strip("]") + ) + # fix dash + + +# Combines a group of path-like elements +def combine_paths(els, mergeii=0): + pnew = Path() + si = [] + # start indices + for el in els: + pth = el.cpath.to_absolute().transform(el.ccomposed_transform) + if el.get("inkscape-scientific-combined-by-color") is None: + si.append(len(pnew)) + else: + cbc = el.get( + "inkscape-scientific-combined-by-color" + ) # take existing ones and weld them + cbc = [int(v) for v in cbc.split()] + si += [v + len(pnew) for v in cbc[0:-1]] + for p in pth: + pnew.append(p) + si.append(len(pnew)) + + # Set the path on the mergeiith element + mel = els[mergeii] + if mel.get("d") is None: # Polylines and lines have to be converted to a path + mel.object_to_path() + mel.set("d", str(pnew.transform(-mel.ccomposed_transform))) + + # Release clips/masks + mel.set("clip-path", "none") + # release any clips + mel.set("mask", "none") + # release any masks + fix_css_clipmask(mel, mask=False) # fix CSS bug + fix_css_clipmask(mel, mask=True) + + mel.set("inkscape-scientific-combined-by-color", " ".join([str(v) for v in si])) + for s in range(len(els)): + if s != mergeii: + # deleteup(els[s]) + els[s].delete(deleteup=True) + + +# Gets all of the stroke and fill properties from a style +# Alpha is its effective alpha including opacity +# Note to self: inkex.Color inherits from list +from inkex.text.utils import default_style_atts as dsa + + +def get_strokefill(el): + sty = el.cspecified_style + strk = sty.get("stroke", dsa.get("stroke")) + fill = sty.get("fill", dsa.get("fill")) + op = float(sty.get("opacity", 1.0)) + nones = [None, "none"] + strk_isurl, fill_isurl = False, False + if not (strk in nones): + try: + strk = inkex.Color(strk).to_rgb() + strkl = strk.lightness + strkop = float(sty.get("stroke-opacity", 1.0)) + strk.alpha = strkop * op + strkl = strk.alpha * strkl / 255 + (1 - strk.alpha) * 1 + # effective lightness frac with a white bg + strk.efflightness = strkl + except: # inkex.colors.ColorIdError: + if "url(#" in strk: + strk = sty.get_link("stroke", el.croot) + strk_isurl = True + else: + strk = None + else: + strk = None + if not (fill in nones): + try: + fill = inkex.Color(fill).to_rgb() + filll = fill.lightness + fillop = float(sty.get("fill-opacity", 1.0)) + fill.alpha = fillop * op + filll = fill.alpha * filll / 255 + (1 - fill.alpha) * 1 + # effective lightness frac with a white bg + fill.efflightness = filll + except: # inkex.colors.ColorIdError: + if "url(#" in fill: + fill = sty.get_link("fill", el.croot) + fill_isurl = True + else: + fill = None + else: + fill = None + + sw, _, _ = composed_width(el, "stroke-width") + sd, _ = composed_list(el, "stroke-dasharray") + if sd in nones: + sd = None + if sw in nones or sw == 0 or strk is None: + sw = None + strk = None + sd = None + + ms = sty.get("marker-start", None) + mm = sty.get("marker-mid", None) + me = sty.get("marker-end", None) + + class StrokeFill: + def __init__(self, *args): + ( + self.stroke, + self.fill, + self.strokewidth, + self.strokedasharray, + self.markerstart, + self.markermid, + self.markerend, + self.strk_isurl, + self.fill_isurl, + ) = args + + return StrokeFill(strk, fill, sw, sd, ms, mm, me, strk_isurl, fill_isurl) + + +# Gets the caller's location +def get_script_path(): + return os.path.dirname(os.path.realpath(sys.argv[0])) + + +# Return a document's visible descendants not in Defs/Metadata/etc +def visible_descendants(svg): + ndefs = [el for el in list(svg) if not (el.tag in unungroupable)] + return [v for el in ndefs for v in el.descendants2()] + + +# Get document location or prompt +def Get_Current_File(ext, msgstr): + tooearly = inkex.installed_ivp[0] <= 1 and inkex.installed_ivp[1] < 1 + if not (tooearly): + myfile = ext.document_path() + else: + myfile = None + + if myfile is None or myfile == "": + if tooearly: + msg = msgstr + "Inkscape must be version 1.1.0 or higher." + else: + msg = ( + msgstr + + "the SVG must first be saved. Please retry after you have done so." + ) + inkex.utils.errormsg(msg) + quit() + return None + else: + return myfile + + +import threading +sema_temp = threading.Semaphore(1) + +def shared_temp(headprefix = None, filename=None): + """ + Generate a temporary file in the system temp folder or SI's location + (tempfile does not always work with Linux Snap distributions) + + Can also generate a unique temp_head that can be used to prefix all temp files. + Since the Inkscape binary cannot handle multiple cwd arguments when multithreading, + (and might switch dirs unexpectedly), any exports with multiple cwd's and relative + paths must be done here. + + """ + if sys.executable[0:4] == "/tmp" or sys.executable[0:5] == "/snap": + si_dir = os.path.dirname( + os.path.realpath(__file__) + ) # in case si_dir is not loaded + system_temp = si_dir + else: + import tempfile + system_temp = tempfile.gettempdir() + if not os.path.exists(system_temp): + os.mkdir(system_temp) + + tempdir = os.path.join(os.path.abspath(system_temp), 'si_temp') + if not os.path.exists(tempdir): + os.mkdir(tempdir) + + if headprefix is not None: + with sema_temp: + pnum = random.randint(1, 100000) + while any(t.startswith(f"{headprefix}{pnum:05d}") for t in os.listdir(tempdir)): + pnum = random.randint(1, 100000) + temphead = f"{headprefix}{pnum:05d}" + tempbase = os.path.join(tempdir, temphead) + open(tempbase+'.lock', 'w').close() + return tempdir, temphead + if filename is not None: + return os.path.join(tempdir,filename) + return tempdir + + + +ttags = tags((inkex.TextElement, inkex.FlowRoot)) +line_tag = inkex.Line.ctag +cpath_support_tags = tags(BaseElementCache.cpath_support) +mask_tag = inkex.addNS("mask", "svg") +grouplike_tags = tags( + ( + inkex.SvgDocumentElement, + inkex.Group, + inkex.Layer, + inkex.ClipPath, + inkex.Symbol, + ) +) +grouplike_tags.add(mask_tag) + + +def bounding_box2( + self, dotransform=True, includestroke=True, roughpath=False, parsed=False, includeclipmask=True +): + """ + Cached bounding box that requires no command call + Uses extents for text + dotransform: whether or not we want the element's bbox or its true + transformed bbox + includestroke: whether or not to add the stroke to the calculation + roughpath: use control points for a path's bbox, which is faster and an + upper bound for the true bbox + """ + if not (hasattr(self, "_cbbox")): + self._cbbox = dict() + inputs = (dotransform, includestroke, roughpath, parsed, includeclipmask) + if inputs not in self._cbbox: + try: + ret = bbox(None) + if self.tag in ttags: + ret = self.parsed_text.get_full_extent(parsed=parsed) + elif self.tag in cpath_support_tags: + pth = self.cpath + if len(pth) > 0: + swd = ipx(self.cspecified_style.get("stroke-width", "0px")) + if self.cspecified_style.get("stroke") is None or not ( + includestroke + ): + swd = 0 + + if self.tag == line_tag: + x = [ipx(self.get("x1", "0")), ipx(self.get("x2", "0"))] + y = [ipx(self.get("y1", "0")), ipx(self.get("y2", "0"))] + ret = bbox( + [ + min(x) - swd / 2, + min(y) - swd / 2, + max(x) - min(x) + swd, + max(y) - min(y) + swd, + ] + ) + elif not roughpath: + bbx = pth.bounding_box() + ret = bbox( + [ + bbx.left - swd / 2, + bbx.top - swd / 2, + bbx.width + swd, + bbx.height + swd, + ] + ) + else: + anyarc = any(s.letter in ["a", "A"] for s in pth) + pth = inkex.Path(inkex.CubicSuperPath(pth)) if anyarc else pth + pts = list(pth.control_points) + x = [p.x for p in pts] + y = [p.y for p in pts] + ret = bbox( + [ + min(x) - swd / 2, + min(y) - swd / 2, + max(x) - min(x) + swd, + max(y) - min(y) + swd, + ] + ) + + elif self.tag in grouplike_tags: + for kid in list2(self): + dbb = bounding_box2( + kid, + dotransform=False, + includestroke=includestroke, + roughpath=roughpath, + parsed=parsed, + ) + if not (dbb.isnull): + ret = ret.union(dbb.transform(kid.ctransform)) + elif isinstance(self, (inkex.Image)): + ret = bbox( + [ipx(self.get(v, "0")) for v in ["x", "y", "width", "height"]] + ) + elif isinstance(self, (inkex.Use,)): + lel = self.get_link("xlink:href") + if lel is not None: + ret = bounding_box2( + lel, dotransform=False, roughpath=roughpath, parsed=parsed + ) + + # clones have the transform of the link, followed by any + # xy transform + xyt = inkex.Transform( + "translate({0},{1})".format( + ipx(self.get("x", "0")), ipx(self.get("y", "0")) + ) + ) + ret = ret.transform(xyt @ lel.ctransform) + + if not (ret.isnull): + if includeclipmask: + for cmv in ["clip-path", "mask"]: + clip = self.get_link(cmv, llget=True) + if clip is not None: + cbb = bounding_box2( + clip, + dotransform=False, + includestroke=False, + roughpath=roughpath, + parsed=parsed, + ) + if not (cbb.isnull): + ret = ret.intersection(cbb) + else: + ret = bbox(None) + + if dotransform: + if not (ret.isnull): + ret = ret.transform(self.ccomposed_transform) + except: + # For some reason errors are occurring silently + import traceback + + inkex.utils.debug(traceback.format_exc()) + self._cbbox[inputs] = ret + return self._cbbox[inputs] + + +def set_cbbox(self, val): + """Invalidates the cached bounding box.""" + if val is None and hasattr(self, "_cbbox"): + delattr(self, "_cbbox") + + +inkex.BaseElement.cbbox = property(bounding_box2, set_cbbox) +inkex.BaseElement.bounding_box2 = bounding_box2 + +bb2_support = ( + inkex.TextElement, + inkex.FlowRoot, + inkex.Image, + inkex.Use, + inkex.SvgDocumentElement, + inkex.Group, + inkex.Layer, +) + BaseElementCache.cpath_support +bb2_support_tags = tags(bb2_support) + + +masktag = inkex.addNS("mask", "svg") + + +def isMask(el): + return el.tag == masktag + + +# cprofile tic and toc +def ctic(): + import cProfile + + global pr + pr = cProfile.Profile() + pr.enable() + + +def ctoc(): + import io, pstats + + global pr + pr.disable() + s = io.StringIO() + sortby = pstats.SortKey.CUMULATIVE + profiledir = os.path.dirname(os.path.abspath(__file__)) + try: + pr.dump_stats(os.path.abspath(os.path.join(profiledir, "cprofile.prof"))) + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + ppath = os.path.abspath(os.path.join(profiledir, "cprofile.csv")) + + result = s.getvalue() + prefix = result.split("ncalls")[0] + # chop the string into a csv-like buffer + result = "ncalls" + result.split("ncalls")[-1] + result = "\n".join( + [",".join(line.rstrip().split(None, 5)) for line in result.split("\n")] + ) + result = prefix + "\n" + result + with open(ppath, "w") as f: + f.write(result) + except OSError: # occasional irreproducible error, doesn't matter + return + + +def Run_SI_Extension(effext, name): + Version_Check(name) + + def run_and_cleanup(): + effext.run() + # flush_stylesheet_entries(effext.svg) + + alreadyran = False + lprofile = os.getenv("LINEPROFILE") == "True" + batexists = os.path.exists( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "cprofile open.bat") + ) + cprofile = batexists if not lprofile else False + if cprofile or lprofile: + profiledir = get_script_path() + if cprofile: + ctic() + if lprofile: + try: + from line_profiler import LineProfiler + + lp = LineProfiler() + from inkex.text import parser + from inkex.text import font_properties + from inkex.text import speedups + from inkex.text import cache + import remove_kerning + from inspect import getmembers, isfunction, isclass, getmodule + + fns = [] + for m in [ + sys.modules[__name__], + parser, + remove_kerning, + Style, + font_properties, + inkex.transforms, + getmodule(effext), + speedups, + cache, + ]: + fns += [v[1] for v in getmembers(m, isfunction)] + for c in getmembers(m, isclass): + if getmodule(c[1]) is m: + fns += [v[1] for v in getmembers(c[1], isfunction)] + for p in getmembers( + c[1], lambda o: isinstance(o, property) + ): + if p[1].fget is not None: + fns += [p[1].fget] + if p[1].fset is not None: + fns += [p[1].fset] + for fn in fns: + lp.add_function(fn) + lp.add_function(ipx.__wrapped__) + lp.add_function(font_properties.true_style.__wrapped__) + lp.add_function(speedups.transform_to_matrix.__wrapped__) + lp.add_function(Style.parse_str.__wrapped__) + + lp(run_and_cleanup)() + import io + + stdouttrap = io.StringIO() + lp.dump_stats( + os.path.abspath(os.path.join(profiledir, "lprofile.prof")) + ) + lp.print_stats(stdouttrap) + + ppath = os.path.abspath(os.path.join(profiledir, "lprofile.csv")) + result = stdouttrap.getvalue() + with open(ppath, "w", encoding="utf-8") as f: + f.write(result) + + # Copy lprofile.csv to the profiles subdirectory + profiles_dir = os.path.join(os.path.dirname(ppath), "profiles") + if not os.path.exists(profiles_dir): + os.makedirs(profiles_dir) + from datetime import datetime + import shutil + + timestamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S") + new_filename = f"lprofile_{timestamp}.csv" + dst_path = os.path.join(profiles_dir, new_filename) + shutil.copy2(ppath, dst_path) + + alreadyran = True + except ImportError: + pass + + if not (alreadyran): + try: + run_and_cleanup() + except lxml.etree.XMLSyntaxError: + try: + # Try getting Inkscape to write a new clean copy + s = effext + s.parse_arguments(sys.argv[1:]) + if s.options.input_file is None: + s.options.input_file = sys.stdin + elif "DOCUMENT_PATH" not in os.environ: + os.environ["DOCUMENT_PATH"] = s.options.input_file + + def overwrite_output(filein, fileout): + try: + os.remove(fileout) + except: + pass + arg2 = [ + inkex.inkscape_system_info.binary_location, + "--export-filename", + fileout, + filein, + ] + subprocess_repeat(arg2) + + tmpname = s.options.input_file.strip(".svg") + "_tmp.svg" + overwrite_output(s.options.input_file, tmpname) + os.remove(s.options.input_file) + os.rename(tmpname, s.options.input_file) + try: + run_and_cleanup() + except lxml.etree.XMLSyntaxError: + # Try removing problematic bytes + with open(s.options.input_file, "rb") as f: + bytes_content = f.read() + cleaned_content = bytes_content.decode("utf-8", errors="ignore") + nfin = s.options.input_file.strip(".svg") + "_tmp.svg" + with open(nfin, "w", encoding="utf-8") as f: + f.write(cleaned_content) + os.remove(s.options.input_file) + os.rename(tmpname, s.options.input_file) + run_and_cleanup() + except: + inkex.utils.errormsg( + "Error reading file! Extensions can only run on SVG files.\n\nIf this is a file imported from another format, try saving as an SVG and restarting Inkscape. Alternatively, try pasting the contents into a new document." + ) + if cprofile: + ctoc() + write_debug() + + # Display accumulated caller info if any + # from inkex.transforms import callinfo + global callinfo + try: + callinfo + except: + callinfo = dict() + sorted_items = sorted(callinfo.items(), key=lambda x: x[1], reverse=True) + for key, value in sorted_items: + idebug(f"{key}: {value}") + + +# Give early versions of Style a .to_xpath function +def to_xpath_func(sty): + # pre-1.2: use v1.1 version of to_xpath from inkex.Style + import re + + step_to_xpath = [ + ( + re.compile(r"\[(\w+)\^=([^\]]+)\]"), + r"[starts-with(@\1,\2)]", + ), # Starts With + (re.compile(r"\[(\w+)\$=([^\]]+)\]"), r"[ends-with(@\1,\2)]"), # Ends With + (re.compile(r"\[(\w+)\*=([^\]]+)\]"), r"[contains(@\1,\2)]"), # Contains + (re.compile(r"\[([^@\(\)\]]+)\]"), r"[@\1]"), # Attribute (start) + (re.compile(r"#(\w+)"), r"[@id='\1']"), # Id Match + (re.compile(r"\s*>\s*([^\s>~\+]+)"), r"/\1"), # Direct child match + # (re.compile(r'\s*~\s*([^\s>~\+]+)'), r'/following-sibling::\1'), + # (re.compile(r'\s*\+\s*([^\s>~\+]+)'), r'/following-sibling::\1[1]'), + (re.compile(r"\s*([^\s>~\+]+)"), r"//\1"), # Decendant match + ( + re.compile(r"\.([-\w]+)"), + r"[contains(concat(' ', normalize-space(@class), ' '), ' \1 ')]", + ), + (re.compile(r"//\["), r"//*["), # Attribute only match + (re.compile(r"//(\w+)"), r"//svg:\1"), # SVG namespace addition + ] + + def style_to_xpath(styin): + return "|".join([rule_to_xpath(rule) for rule in styin.rules]) + + def rule_to_xpath(rulein): + ret = rulein.rule + for matcher, replacer in step_to_xpath: + ret = matcher.sub(replacer, ret) + return ret + + return style_to_xpath(sty) + + +if not hasattr(Style, "to_xpath"): + Style.to_xpath = to_xpath_func +if not hasattr(inkex.Style, "to_xpath"): + inkex.Style.to_xpath = to_xpath_func + +# Patch Style string conversion to restore single-quote strings +# FQUOTE = r'^[^\'"]*\"' +# def swap_quotes(s): +# return s.translate(str.maketrans({"'": '"', '"': "'"})) +# def to_str(self, sep=";"): +# return sep.join( +# [f"{key}:{value}" if not re.search(FQUOTE, str(value)) else f"{key}:{swap_quotes(value)}" for key, value in self.items()] +# ) +# def __str__(self): +# return ";".join( +# [f"{key}:{value}" if not re.search(FQUOTE, str(value)) else f"{key}:{swap_quotes(value)}" for key, value in self.items()] +# ) +# inkex.Style.to_str = to_str +# inkex.Style.__str__ = __str__ + + +def nonascii(c): + """Returns True if the character is non-ASCII.""" + return ord(c) >= 128 + + +def nonletter(c): + """Returns True if the character is not a letter.""" + return not ((ord(c) >= 65 and ord(c) <= 90) or (ord(c) >= 97 and ord(c) <= 122)) + + +fixwith = { + "Avenir": (nonletter, "'Avenir Next', 'Arial'"), + "Whitney": (nonascii, "'Avenir Next', 'Arial'"), + "Whitney Book": (nonascii, "'Avenir Next', 'Arial'"), +} +fw2 = {k.lower(): val for k, val in fixwith.items()} + + +def shouldfixfont(ffam): + """Checks if the font needs fixing based on non-ASCII or non-letter characters.""" + shouldfix = ( + ffam is not None + and ffam.split(",")[0].strip("'").strip('"').lower() in fw2.keys() + ) + fixw = ( + None if not shouldfix else fw2[ffam.split(",")[0].strip("'").strip('"').lower()] + ) + return shouldfix, fixw + + +def character_fixer(els): + """Fixes characters in a list of elements based on their text style.""" + for elem in els: + tree = TextTree(elem) + for _, typ, tel, sel, txt in tree.dgenerator(): + if txt is not None and len(txt) > 0: + sty = sel.cspecified_style + shouldfix, fixw = shouldfixfont(sty.get("font-family")) + if shouldfix: + # replace_non_ascii_font(sel, fixw) + elem.set("xml:space", "preserve") # so spaces don't vanish + fixcondition, fixw = fixw + + if all(fixcondition(c) for c in txt) and typ == TYP_TEXT: + sel.cstyle["font-family"] = fixw + else: + prev_nonascii = False + for j, c in enumerate(reversed(txt)): + i = len(txt) - 1 - j + if fixcondition(c): + if not prev_nonascii: + t = Tspan() + t.text = c + if typ == TYP_TEXT: + tbefore = tel.text[0:i] + tafter = tel.text[i + 1 :] + tel.text = tbefore + tel.insert(0, t) + t.tail = tafter + else: + tbefore = tel.tail[0:i] + tafter = tel.tail[i + 1 :] + tel.tail = tbefore + grp = tel.getparent() + # parent is a Tspan, so insert it into + # the grandparent + grp.insert(grp.index(tel) + 1, t) + # after the parent + t.tail = tafter + t.cstyle = Style( + "font-family:" + fixw + ";baseline-shift:0%" + ) + else: + t.text = c + t.text + if typ == TYP_TEXT: + tel.text = tel.text[0:i] + else: + tel.tail = tel.tail[0:i] + if tel.text is not None and tel.text == "": + tel.text = None + if tel.tail is not None and tel.tail == "": + tel.tail = None + prev_nonascii = nonascii(c) + + +spantags = tags((Tspan, inkex.FlowPara, inkex.FlowSpan)) +TEtag = inkex.TextElement.ctag + + +def replace_non_ascii_font(elem, newfont, *args): + """Replaces non-ASCII characters in an element with a specified font.""" + + def alltext(elem): + astr = elem.text + if astr is None: + astr = "" + for k in list(elem): + if k.tag in spantags: + astr += alltext(k) + tlv = k.tail + if tlv is None: + tlv = "" + astr += tlv + return astr + + forcereplace = len(args) > 0 and args[0] + if forcereplace or any(nonascii(c) for c in alltext(elem)): + alltxt = [elem.text] + elem.text = "" + for k in list(elem): + if k.tag in spantags: + dupe = k.duplicate() + alltxt.append(dupe) + alltxt.append(k.tail) + k.tail = "" + k.delete() + lstspan = None + for t in alltxt: + if t is None: + pass + elif isinstance(t, str): + chks = [] + sind = 0 + for i in range(1, len(t)): + # split into chunks based on whether unicode or not + if nonletter(t[i - 1]) != nonletter(t[i]): + chks.append(t[sind:i]) + sind = i + chks.append(t[sind:]) + sty = "baseline-shift:0%;" + for chk in chks: + if any(nonletter(c) for c in chk): + chk = chk.replace(" ", "\u00a0") + # spaces can disappear, replace with NBSP + if elem.croot is not None: + nts = Tspan() + elem.append(nts) + nts.text = chk + nts.cstyle = Style(sty + "font-family:" + newfont) + nts.cspecified_style = None + nts.ccomposed_transform = None + lstspan = nts + else: + if lstspan is None: + elem.text = chk + else: + lstspan.tail = chk + elif t.tag in spantags: + replace_non_ascii_font(t, newfont, True) + elem.append(t) + t.cspecified_style = None + t.ccomposed_transform = None + lstspan = t + + # Inkscape automatically prunes empty text/tails + # Do the same so future parsing is not affected + if elem.tag == TEtag: + for ddv in elem.descendants2(): + if ddv.text is not None and ddv.text == "": + ddv.text = None + if ddv.tail is not None and ddv.tail == "": + ddv.tail = None + + +def split_text(elem): + """ + Splits a text or tspan into its constituent blocks of text + (i.e., each text and each tail in separate hierarchies) + """ + dups = [] + dds = elem.descendants2() + for dgen in reversed(list(TextTree(elem).dgenerator())): + _, _, _, sel, txt = dgen + if txt is not None: + # For each block of text, spin off a copy of the structure + # that only has this block and only the needed ancestors. + dup = elem.duplicate() + d2s = dup.descendants2() + mydup = d2s[dds.index(sel)] + ancs = mydup.ancestors2(includeme=True) + for dd2 in d2s: + dd2.text = None + dd2.tail = None + if dd2 not in ancs: + dd2.delete() + mydup.text = txt + dups = [dup] + dups + if len(dups) > 0 and elem.tail is not None: + dups[-1].tail = elem.tail + elem.delete() + return dups + + +def Version_Check(caller): + siv = "v1.4.23" # Scientific Inkscape version + maxsupport = "1.4.2" + minsupport = "1.1.0" + + logname = "Log.txt" + NFORM = 200 + + maxsupp = inkex.vparse(maxsupport) + minsupp = inkex.vparse(minsupport) + + try: + f = open(logname, "r") + d = f.readlines() + f.close() + except: + d = [] + + displayedform = False + if len(d) > 0: + displayedform = d[-1] == "Displayed form screen" + if displayedform: + d = d[: len(d) - 1] + + # idebug(ivp) + prevvp = [inkex.vparse(dv[-6:]) for dv in d] + if (inkex.ivp[0] < minsupp[0] or inkex.ivp[1] < minsupp[1]) and not ( + inkex.ivp in prevvp + ): + msg = ( + "For best results, Scientific Inkscape requires Inkscape version " + + minsupport + + " or higher. " + + "You are running an older version—all features may not work as expected.\n\nThis is a one-time message.\n\n" + ) + inkex.utils.errormsg(msg) + if (inkex.ivp[0] > maxsupp[0] or inkex.ivp[1] > maxsupp[1]) and not ( + inkex.ivp in prevvp + ): + msg = ( + "For best results, Scientific Inkscape requires Inkscape version " + + maxsupport + + " or lower. " + + "You are running a newer version—you must be from the future!\n\n" + + "It might work, it might not. Check if there is a more recent version of Scientific Inkscape available. \n\nThis is a one-time message.\n\n" + ) + inkex.utils.errormsg(msg) + + from datetime import datetime + + dt = datetime.now().strftime("%Y.%m.%d, %H:%M:%S") + d.append( + dt + + " Running " + + caller + + " " + + siv + + ", Inkscape v" + + inkex.__version__ + + "\n" + ) + + if len(d) > NFORM: + d = d[-NFORM:] + if not (displayedform): + sif3 = "dt9mt3Br6" + sif1 = "https://forms.gle/" + sif2 = "RS6HythP" + msg = ( + f"You have run Scientific Inkscape extensions over {NFORM} times! Thank you for being such a dedicated user!" + "\n\nBuilding and maintaining Scientific Inkscape is a time-consuming job," + " and I have no real way of tracking the number of active users. For reporting purposes, I would greatly " + "appreciate it if you could sign my guestbook to indicate that you use Scientific Inkscape. " + f"It is located at\n\n{sif1}{sif2}{sif3}" + "\n\nPlease note that this is a one-time message. " + "You will never get this message again, so please copy the URL before you click OK.\n\n" + ) + inkex.utils.errormsg(msg) + d.append("Displayed form screen") + + try: + f = open(logname, "w") + f.write("".join(d)) + f.close() + except: + err_msg = ( + "Error: You do not have write access to the directory where the Scientific Inkscape " + "extensions are installed. You may have not installed them in the correct location. " + "\n\nMake sure you install them in the User Extensions directory, not the Inkscape Extensions " + "directory." + ) + inkex.utils.errormsg(err_msg) + quit() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/favorite_markers.inx b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/favorite_markers.inx new file mode 100644 index 0000000..05d6f1b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/favorite_markers.inx @@ -0,0 +1,49 @@ + + + Favorite markers + burghoff.favoritemarkers + + + + + + + + + + + false + false + true + + + 100 + + + + + + + + + false + + false + + + + + + + + + text + + + + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/favorite_markers.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/favorite_markers.py new file mode 100644 index 0000000..9be741a --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/favorite_markers.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2021 David Burghoff, dburghoff@nd.edu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# Note that this function modifies its own .inx file by editing the options in the "template" +# parameter and the "template_rem" parameter. + +# dflt = {'Arrow': [[{'style': 'overflow:visible', 'id': 'Arrow2Lstart', 'refX': '0.0', 'refY': '0.0', 'orient': 'auto', '{http://www.inkscape.org/namespaces/inkscape}stockid': 'Arrow2Lstart', '{http://www.inkscape.org/namespaces/inkscape}isstock': 'true'}, [{'transform': 'matrix(1.1 0 0 1.1 1.1 0)', 'd': 'M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z ', 'style': 'stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round', 'id': 'path1068'}]], [{'style': 'overflow:visible', 'id': 'Arrow2Lend', 'refX': '0.0', 'refY': '0.0', 'orient': 'auto', '{http://www.inkscape.org/namespaces/inkscape}stockid': 'Arrow2Lend', '{http://www.inkscape.org/namespaces/inkscape}isstock': 'true'}, [{'transform': 'matrix(-1.1 1.34707e-16 -1.34707e-16 -1.1 -1.1 1.34707e-16)', 'd': 'M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z ', 'style': 'stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round', 'id': 'path1071'}]], [{'style': 'overflow:visible', 'id': 'marker1328', 'refX': '0.0', 'refY': '0.0', 'orient': 'auto', '{http://www.inkscape.org/namespaces/inkscape}stockid': 'Arrow2Lend', '{http://www.inkscape.org/namespaces/inkscape}isstock': 'true'}, [{'transform': 'matrix(-1.1 1.34707e-16 -1.34707e-16 -1.1 -1.1 1.34707e-16)', 'd': 'M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z ', 'style': 'stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round', 'id': 'path1326'}]]], 'Triangle': [[{'style': 'overflow:visible', 'id': 'TriangleInL', 'refX': '0.0', 'refY': '0.0', 'orient': 'auto', '{http://www.inkscape.org/namespaces/inkscape}stockid': 'TriangleInL', '{http://www.inkscape.org/namespaces/inkscape}isstock': 'true'}, [{'transform': 'scale(-0.8, -0.8)', 'style': 'fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt', 'd': 'M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z ', 'id': 'path1183'}]], [{'style': 'overflow:visible', 'id': 'marker1395', 'refX': '0.0', 'refY': '0.0', 'orient': 'auto', '{http://www.inkscape.org/namespaces/inkscape}stockid': 'TriangleOutL', '{http://www.inkscape.org/namespaces/inkscape}isstock': 'true'}, [{'transform': 'scale(0.8, 0.8)', 'style': 'fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt', 'd': 'M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z ', 'id': 'path1393'}]], [{'style': 'overflow:visible', 'id': 'TriangleOutL', 'refX': '0.0', 'refY': '0.0', 'orient': 'auto', '{http://www.inkscape.org/namespaces/inkscape}stockid': 'TriangleOutL', '{http://www.inkscape.org/namespaces/inkscape}isstock': 'true'}, [{'transform': 'scale(0.8, 0.8)', 'style': 'fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt', 'd': 'M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z ', 'id': 'path1192'}]]]} +dflt = { + "Arrow": [ + [ + { + "style": "overflow:visible", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "Arrow2Mstart", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(0.6, 0.6)", + "d": "M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z ", + "style": "stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round", + "id": "path961", + } + ], + ], + [ + { + "style": "overflow:visible", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "Arrow2Mend", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(-0.6, -0.6)", + "d": "M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z ", + "style": "stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round", + "id": "path964", + } + ], + ], + [ + { + "style": "overflow:visible", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "Arrow2Mend", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(-0.6, -0.6)", + "d": "M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z ", + "style": "stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round", + "id": "path1354", + } + ], + ], + ], + "Triangle": [ + [ + { + "style": "overflow:visible", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "TriangleInM", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(-0.4, -0.4)", + "style": "fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt", + "d": "M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z ", + "id": "path1073", + } + ], + ], + [ + { + "style": "overflow:visible", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "TriangleOutM", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(0.4, 0.4)", + "style": "fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt", + "d": "M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z ", + "id": "path1082", + } + ], + ], + [ + { + "style": "overflow:visible", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "TriangleOutM", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(0.4, 0.4)", + "style": "fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt", + "d": "M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z ", + "id": "path1213", + } + ], + ], + ], + "Distance": [ + [ + { + "{http://www.inkscape.org/namespaces/inkscape}stockid": "DistanceStart", + "orient": "auto", + "refY": "0.0", + "refX": "0.0", + "id": "DistanceStart", + "style": "overflow:visible", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "id": "path2306", + "d": "M 0,0 L 2,0", + "style": "fill:none;stroke:context-fill;stroke-width:1.15;stroke-linecap:square", + }, + { + "id": "path2302", + "d": "M 0,0 L 13,4 L 9,0 13,-4 L 0,0 z ", + "style": "fill:context-stroke;fill-rule:evenodd;stroke:none", + }, + { + "id": "path2304", + "d": "M 0,-4 L 0,40", + "style": "fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:square", + }, + ], + ], + [ + { + "style": "overflow:visible", + "id": "StopL", + "refX": "0.0", + "refY": "0.0", + "orient": "auto", + "{http://www.inkscape.org/namespaces/inkscape}stockid": "StopL", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "transform": "scale(0.8, 0.8)", + "style": "fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:context-stroke;stroke-width:1.0pt", + "d": "M 0.0,5.65 L 0.0,-5.65", + "id": "path2127", + } + ], + ], + [ + { + "{http://www.inkscape.org/namespaces/inkscape}stockid": "DistanceEnd", + "orient": "auto", + "refY": "0.0", + "refX": "0.0", + "id": "DistanceEnd", + "style": "overflow:visible", + "{http://www.inkscape.org/namespaces/inkscape}isstock": "true", + }, + [ + { + "id": "path2316", + "d": "M 0,0 L -2,0", + "style": "fill:none;stroke:context-fill;stroke-width:1.15;stroke-linecap:square", + }, + { + "id": "path2312", + "d": "M 0,0 L -13,4 L -9,0 -13,-4 L 0,0 z ", + "style": "fill:context-stroke;fill-rule:evenodd;stroke:none", + }, + { + "id": "path2314", + "d": "M 0,-4 L 0,40", + "style": "fill:none;stroke:context-stroke;stroke-width:1;stroke-linecap:square", + }, + ], + ], + ], +} + +import dhelpers as dh +import inkex +from inkex import ( + TextElement, + FlowRoot, + FlowPara, + Tspan, + TextPath, + Rectangle, + addNS, + Transform, + PathElement, + Line, + Rectangle, + Path, + Vector2d, + Use, + NamedView, + Defs, + Metadata, + ForeignObject, + Group, + FontFace, + StyleElement, + StyleSheets, + SvgDocumentElement, + ShapeElement, + BaseElement, + FlowSpan, + Ellipse, + Circle, +) + +import os, sys + +def get_script_path(): + return os.path.dirname(os.path.realpath(sys.argv[0])) + + +dispprofile = False + + +class FavoriteMarkers(inkex.EffectExtension): + # def document_path(self): + # return 'test' + + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument("--template", type=int, default=1, help="Template") + + pars.add_argument( + "--smarker", type=inkex.Boolean, default=False, help="Start marker?" + ) + pars.add_argument( + "--mmarker", type=inkex.Boolean, default=False, help="Mid marker?" + ) + pars.add_argument( + "--emarker", type=inkex.Boolean, default=False, help="End marker?" + ) + pars.add_argument("--size", type=float, default=100, help="Size (%)") + + pars.add_argument( + "--addt", type=inkex.Boolean, default=True, help="Add template?" + ) + pars.add_argument( + "--template_name", type=str, default="untitled", help="New template name" + ) + pars.add_argument( + "--remt", type=inkex.Boolean, default=True, help="Remove template?" + ) + pars.add_argument( + "--template_rem", type=int, default=1, help="Template to remove" + ) + + def get_marker_props(self, murl): + if murl is not None and murl != "" and murl != "none": + mkr = self.svg.getElementById(murl.strip("#url(").strip(")")) + mkratt = dict() + for att in mkr.attrib: + if att != "id": + mkratt[att] = mkr.get(att) + patts = [] + if len(mkr.getchildren()) > 0: + if isinstance(mkr.getchildren()[0], (Group)): + pparent = mkr.getchildren()[0] + else: + pparent = mkr + for k in pparent.getchildren(): + if isinstance(k, (PathElement)): + patt = dict() + for att in k.attrib: + patt[att] = k.get(att) + patts.append(patt) + return [mkratt, patts] + else: + return None + else: + return None + + def set_marker_props(self, el, mkrname, mtype, mkrdat): + if mkrdat is not None: + newname = (mkrname + mtype).translate({ord(c): None for c in " \n\t\r"}) + # strip white space + mysize = self.options.size / 100 + existing = [ + v for v in self.svg.defs.descendants2() if newname in v.get_id() + ] + previousmkr = None + for eel in existing: + if len(eel.getchildren()) > 0 and isinstance( + eel.getchildren()[0], (Group) + ): + trn = Transform(eel.getchildren()[0].get("transform")) + if abs(trn.a - mysize) < 0.01 and abs(trn.d - mysize) < 0.01: + previousmkr = eel + + if previousmkr is None: + mkratt = mkrdat[0] + patt = mkrdat[1] + m = inkex.elements._groups.Marker() + + for att in mkratt.keys(): + if att != "id": + m.set(att, mkratt[att]) + g = Group() + m.append(g) + g.set("transform", "scale(" + str(mysize) + ")") + for ii in range(len(patt)): + p = PathElement() + for att in patt[ii].keys(): + if att != "id": + p.set(att, patt[ii][att]) + g.append(p) + self.svg.defs.append(m) + m.set_random_id(prefix=newname) + else: + m = previousmkr + el.cstyle["marker-" + mtype] = "url(#" + m.get_id() + ")" + else: + el.cstyle["marker-" + mtype] = None + + # dh.debug(mkrname+mtype) + # dh.debug(mkrdat) + + def effect(self): + if dispprofile: + import cProfile, pstats, io + from pstats import SortKey + + pr = cProfile.Profile() + pr.enable() + + sel = [self.svg.selection[ii] for ii in range(len(self.svg.selection))] + # should work with both v1.0 and v1.1 + sel = [v for el in sel for v in el.descendants2()] + import pickle, os + + fmsettings = os.path.abspath( + os.path.join(get_script_path(), "favorite_markers.settings") + ) + try: + f = open(fmsettings, "rb") + except: + f = open(fmsettings, "wb") + pickle.dump(dflt, f) + f.close() + f = open(fmsettings, "rb") + s = pickle.load(f) + f.close() + + # dh.debug(s) + if self.options.tab == "addremove": + if self.options.addt: + sel = [ + x + for x in sel + if isinstance( + x, + ( + inkex.PathElement, + inkex.Line, + inkex.Polyline, + inkex.Rectangle, + inkex.Circle, + inkex.Ellipse, + ), + ) + ] + sty = sel[0].cspecified_style + + ms = self.get_marker_props(sty.get("marker-start")) + mm = self.get_marker_props(sty.get("marker-mid")) + me = self.get_marker_props(sty.get("marker-end")) + s[self.options.template_name] = [ms, mm, me] + if self.options.remt: + templates = list(s.keys()) + if self.options.template_rem < len(templates): + del s[templates[self.options.template_rem]] + f = open(fmsettings, "wb") + pickle.dump(s, f) + f.close() + + fminx = os.path.abspath( + os.path.join(get_script_path(), "favorite_markers.inx") + ) + f = open(fminx) + inxd = f.read() + f.close() + + opts = "" + ts = list(s.keys()) + for ii in range(len(ts)): + opts += '\n" + ss1 = '' + ss1loc = inxd.find(ss1) + ss1srt = ss1loc + len(ss1) + ss1end = inxd[ss1loc:].find("") + ss1loc + ss2 = '' + ss2loc = inxd.find(ss2) + ss2srt = ss2loc + len(ss2) + ss2end = inxd[ss2loc:].find("") + ss2loc + newinx = inxd[0:ss1srt] + opts + inxd[ss1end:ss2srt] + opts + inxd[ss2end:] + f = open(fminx, "w") + inxd = f.write(newinx) + f.close() + + dh.idebug( + "Templates successfully updated! Update will take effect when Inkscape is restarted." + ) + else: + for el in sel: + if isinstance( + el, + ( + inkex.PathElement, + inkex.Line, + inkex.Polyline, + inkex.Rectangle, + inkex.Circle, + inkex.Ellipse, + ), + ): + ts = list(s.keys()) + tname = ts[self.options.template] + tval = s[tname] + if self.options.smarker: + self.set_marker_props(el, "FM" + tname, "start", tval[0]) + else: + self.set_marker_props(el, "FM" + tname, "start", None) + if self.options.mmarker: + self.set_marker_props(el, "FM" + tname, "mid", tval[1]) + else: + self.set_marker_props(el, "FM" + tname, "mid", None) + if self.options.emarker: + self.set_marker_props(el, "FM" + tname, "end", tval[2]) + else: + self.set_marker_props(el, "FM" + tname, "end", None) + + # pickle.dump(s,open(os.path.join(get_script_path(),'ae_settings.p'),'wb')); + + # for el in sela: + # if + # dh.debug(el.get_id()) + # dh.debug(bb[el.get_id()]); + + if dispprofile: + pr.disable() + s = io.StringIO() + sortby = SortKey.CUMULATIVE + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + dh.debug(s.getvalue()) + + +if __name__ == "__main__": + dh.Run_SI_Extension(FavoriteMarkers(), "Favorite markers") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/flatten_plots.inx b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/flatten_plots.inx new file mode 100644 index 0000000..c702bea --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/flatten_plots.inx @@ -0,0 +1,81 @@ + + + Flattener + burghoff.flattenplots1 + + + + true + true + true + true + + + true + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + false + Arial + + + + + + + + + + + + + + + + + + + + + text + + + + + + + \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/flatten_plots.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/flatten_plots.py new file mode 100644 index 0000000..6b842e7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/flatten_plots.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import dhelpers as dh +import inkex + +from inkex import ( + TextElement, + FlowRoot, + FlowPara, + FlowRegion, + FlowSpan, + Tspan, + TextPath, + Rectangle, + PathElement, + Line, + StyleElement, + NamedView, + Defs, + Metadata, + ForeignObject, + Group, +) + +from inkex.text.utils import isrectangle +import lxml +from remove_kerning import remove_kerning + + +class FlattenPlots(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument( + "--deepungroup", type=inkex.Boolean, default=True, help="Deep ungroup" + ) + pars.add_argument( + "--fixtext", type=inkex.Boolean, default=True, help="Text fixes" + ) + pars.add_argument( + "--removerectw", + type=inkex.Boolean, + default=True, + help="Remove white rectangles", + ) + pars.add_argument( + "--splitdistant", + type=inkex.Boolean, + default=True, + help="Split distant text", + ) + pars.add_argument( + "--mergenearby", type=inkex.Boolean, default=True, help="Merge nearby text" + ) + pars.add_argument( + "--removemanualkerning", + type=inkex.Boolean, + default=True, + help="Fix text shattering", + ) + pars.add_argument( + "--mergesubsuper", + type=inkex.Boolean, + default=True, + help="Import superscripts and subscripts", + ) + pars.add_argument( + "--setreplacement", + type=inkex.Boolean, + default=False, + help="Replace missing fonts", + ) + pars.add_argument( + "--reversions", + type=inkex.Boolean, + default=True, + help="Revert known paths to text", + ) + pars.add_argument( + "--revertpaths", + type=inkex.Boolean, + default=True, + help="Revert certain paths to strokes?", + ) + pars.add_argument( + "--removeduppaths", + type=inkex.Boolean, + default=True, + help="Delete overlapping duplicate paths?", + ) + pars.add_argument( + "--removetextclips", + type=inkex.Boolean, + default=True, + help="Remove clips and masks from text?", + ) + pars.add_argument( + "--replacement", type=str, default="Arial", help="Missing font replacement" + ) + pars.add_argument( + "--justification", type=int, default=1, help="Text justification" + ) + pars.add_argument("--markexc", type=int, default=1, help="Exclude objects") + pars.add_argument( + "--testmode", type=inkex.Boolean, default=False, help="Test mode" + ) + pars.add_argument("--v", type=str, default="1.2", help="Version for debugging") + pars.add_argument( + "--debugparser", + type=inkex.Boolean, + default=False, + help="Use parser debugger?", + ) + + def duplicate_layer1(self): + # For testing, duplicate selection and flatten its elements + sel = [self.svg.selection[ii] for ii in range(len(self.svg.selection))] + # should work with both v1.0 and v1.1 + for el in sel: + d = el.duplicate() + el.getparent().insert(list(el.getparent()).index(el), d) + if d.get("inkscape:label") is not None: + el.set("inkscape:label", el.get("inkscape:label") + " flat") + d.set("inkscape:label", d.get("inkscape:label") + " original") + d.set("sodipodi:insensitive", "true") + # lock original + d.set("opacity", 0.3) + sel = [list(el) for el in sel] + import itertools + + sel = list(itertools.chain.from_iterable(sel)) + return sel + + def effect(self): + if self.options.testmode: + if not hasattr(self.options, "enabled_profile"): + self.options.enabled_profile = True + self.options.lyr1 = self.duplicate_layer1() + dh.ctic() + self.effect() + dh.ctoc() + return + else: + sel = self.options.lyr1 + self.options.deepungroup = True + self.options.fixtext = True + self.options.removerectw = True + self.options.revertpaths = True + self.options.splitdistant = True + self.options.mergenearby = True + self.options.removemanualkerning = True + self.options.mergesubsuper = True + self.options.setreplacement = True + self.options.reversions = True + self.options.removetextclips = True + self.options.replacement = "sans-serif" + self.options.justification = 1 + else: + sel = [self.svg.selection[ii] for ii in range(len(self.svg.selection))] + + splitdistant = self.options.splitdistant and self.options.fixtext + removemanualkerning = self.options.removemanualkerning and self.options.fixtext + mergesubsuper = self.options.mergesubsuper and self.options.fixtext + mergenearby = self.options.mergenearby and self.options.fixtext + setreplacement = self.options.setreplacement and self.options.fixtext + reversions = self.options.reversions and self.options.fixtext + removetextclips = self.options.removetextclips and self.options.fixtext + + sel = [el for el in self.svg.descendants2() if el in sel] # doc order + if self.options.tab == "Exclusions": + self.options.markexc = {1: True, 2: False}[self.options.markexc] + for el in sel: + if self.options.markexc: + el.set("inkscape-scientific-flattenexclude", self.options.markexc) + else: + el.set("inkscape-scientific-flattenexclude", None) + return + for el in sel: + if el.get("inkscape-scientific-flattenexclude"): + sel.remove(el) + seld = [v for el in sel for v in el.descendants2()] + for el in seld: + if el.get("inkscape-scientific-flattenexclude"): + seld.remove(el) + + # Move selected defs/clips/mask into global defs + defstag = inkex.Defs.ctag + clipmask = {inkex.addNS("mask", "svg"), inkex.ClipPath.ctag} + if self.options.deepungroup: + seldefs = [el for el in seld if el.tag == defstag] + for el in seldefs: + self.svg.cdefs.append(el) + for d in el.descendants2(): + if d in seld: + seld.remove(d) # no longer selected + selcm = [el for el in seld if el.tag in clipmask] + for el in selcm: + self.svg.cdefs.append(el) + for d in el.descendants2(): + if d in seld: + seld.remove(d) # no longer selected + + gtag = inkex.Group.ctag + gigtags = dh.tags((NamedView, Defs, Metadata, ForeignObject) + (Group,)) + + gs = [el for el in seld if el.tag == gtag] + ngs = [el for el in seld if el.tag not in gigtags] + if len(gs) == 0 and len(ngs) == 0: + inkex.utils.errormsg("No objects selected!") + return + + if self.options.deepungroup: + # Unlink all clones + nels = [] + oels = [] + for el in seld: + if isinstance(el, inkex.Use): + useel = el.get_link("xlink:href") + if useel is not None and not (isinstance(useel, (inkex.Symbol))): + ul = dh.unlink2(el) + nels.append(ul) + oels.append(el) + for nel in nels: + seld += nel.descendants2() + for oel in oels: + seld.remove(oel) + gs = [el for el in seld if el.tag == gtag] + ngs = [el for el in seld if el.tag not in gigtags] + + commenttag = lxml.etree.Comment + commentdefs = {commenttag, defstag} + sorted_gs = sorted(gs, key=lambda group: len(list(group))) + # ascending order of size to reduce number of calls + for g in sorted_gs: + ks = g.getchildren() + if any([k.tag == commenttag for k in ks]) and all( + [ + k.tag in commentdefs or dh.EBget(k, "unlinked_clone") == "True" + for k in ks + ] + ): + # Leave Matplotlib text glyphs grouped together + cmnt = ";".join( + [ + str(k).strip("") + for k in ks + if k.tag == commenttag + ] + ) + g.set("mpl_comment", cmnt) + [g.remove(k) for k in ks if isinstance(k, lxml.etree._Comment)] + # remove comment, but leave grouped + elif dh.EBget(g, "mpl_comment") is not None: + pass + else: + dh.ungroup(g, removetextclips) + # dh.flush_stylesheet_entries(self.svg) + + prltag = dh.tags((PathElement, Rectangle, Line)) + if self.options.removerectw or reversions or self.options.revertpaths: + from inkex.text.utils import default_style_atts as dsa + + fltag = dh.tags((FlowPara, FlowRegion, FlowRoot)) + nones = {None, "none"} + wrects = [] + + minusp = inkex.Path( + "M 106,355 H 732 V 272 H 106 Z" + ) # Matplotlib minus sign + RECT_THRESHOLD = 2.49 # threshold ratio for rectangle reversion + for ii, el in enumerate(ngs): + if el.tag in prltag: + myp = el.getparent() + if ( + myp is not None + and not (myp.tag in fltag) + and isrectangle(el, includingtransform=False) + ): + sty = el.cspecified_style + strk = sty.get("stroke", dsa.get("stroke")) + fill = sty.get("fill", dsa.get("fill")) + if strk in nones and fill not in nones: + sf = dh.get_strokefill(el) + if sf.fill is not None and tuple(sf.fill) == ( + 255, + 255, + 255, + 1, + ): + wrects.append(el) + + if reversions: + dv = el.get("d") + if ( + dv is not None + and inkex.Path(dv)[0:3] == minusp[0:3] + ): + t0 = el.ccomposed_transform + if t0.a * t0.d - t0.b * t0.c < 0: + bb = dh.bounding_box2( + el, includestroke=False, dotransform=False + ) + trl = inkex.Transform( + "translate({0},{1})".format(bb.xc, bb.yc) + ) + scl = inkex.Transform("scale(1,-1)") + t0 = t0 @ trl @ scl @ (-trl) + nt = inkex.TextElement() + nt.set("id", el.get_id()) + myi = list(myp).index(el) + el.delete() + myp.insert(myi, nt) + nt.text = chr(0x2212) # minus sign + nt.ctransform = (-myp.ccomposed_transform) @ t0 + nt.set("x", str(19.3964)) + nt.set("y", str(626.924)) + nt.cstyle = "font-size:999.997; font-family:sans-serif; fill:{0};".format( + sf.fill + ) + + ngs.append(nt) + ngs.remove(el) + + if self.options.revertpaths: + bb = dh.bounding_box2( + el, includestroke=False, dotransform=False, includeclipmask=False + ) + if not bb.isnull: + if bb.w < bb.h / RECT_THRESHOLD and not sf.fill_isurl: + el.object_to_path() + npv = "m {0},{1} v {2}".format(bb.xc, bb.y1, bb.h) + el.set("d", npv) + el.cstyle["stroke"] = sf.fill.to_rgb() + if sf.fill.alpha != 1.0: + el.cstyle["stroke-opacity"] = sf.fill.alpha + el.cstyle["opacity"] = 1 + el.cstyle["fill"] = "none" + el.cstyle["stroke-width"] = str(bb.w) + el.cstyle["stroke-linecap"] = "butt" + elif bb.h < bb.w / RECT_THRESHOLD and not sf.fill_isurl: + el.object_to_path() + npv = "m {0},{1} h {2}".format(bb.x1, bb.yc, bb.w) + el.set("d", npv) + el.cstyle["stroke"] = sf.fill.to_rgb() + if sf.fill.alpha != 1.0: + el.cstyle["stroke-opacity"] = sf.fill.alpha + el.cstyle["opacity"] = 1 + el.cstyle["fill"] = "none" + el.cstyle["stroke-width"] = str(bb.h) + el.cstyle["stroke-linecap"] = "butt" + + TE_TAG = TextElement.ctag; + if self.options.fixtext: + if setreplacement: + repl = self.options.replacement + ttags = dh.tags((TextElement, Tspan)) + for el in ngs: + if el.tag in ttags and el.getparent() is not None: + # textelements not deleted + ff = el.cspecified_style.get("font-family") + el.cstyle["-inkscape-font-specification"] = None + if ff == None or ff == "none" or ff == "": + el.cstyle["font-family"] = repl + elif ff == repl: + pass + else: + ff = [ + x.strip("'").strip('"').strip() for x in ff.split(",") + ] + if not (ff[-1].lower() == repl.lower()): + ff.append(repl) + el.cstyle["font-family"] = ",".join(ff) + + if removemanualkerning or mergesubsuper or splitdistant or mergenearby: + jdict = {1: "middle", 2: "start", 3: "end", 4: None} + justification = jdict[self.options.justification] + ngs = remove_kerning( + ngs, + removemanualkerning, + mergesubsuper, + splitdistant, + mergenearby, + justification, + self.options.debugparser, + ) + if removetextclips: + for el in ngs: + if el.tag in dh.ttags: + el.set("clip-path", None) + el.set("mask", None) + + if self.options.removerectw or self.options.removeduppaths: + ngset = set(ngs) + ngs2 = [ + el for el in self.svg.descendants2() if el in ngset and dh.isdrawn(el) + ] + bbs = dh.BB2(self.svg, ngs2, roughpath=True, parsed=True) + + if self.options.removeduppaths: + # Prune identical overlapping paths + bbsp = {el:bbs[el.get_id()] for el in ngs2 if el.get_id() in bbs} + txts = [el for el in self.svg.descendants2() if el.tag==TE_TAG and 'shape-inside' in el.cspecified_style] + txt_inside = [el.cspecified_style.get_link("shape-inside",el.croot) for el in txts] + bbsp = {el:v for el,v in bbsp.items() if el.tag in prltag and el not in txt_inside} + + els = list(bbsp.keys()) + sfs = [None]*len(els) + + import numpy as np + if len(els)>0: + bbox_values = np.array(list(bbsp.values())) + left = bbox_values[:, 0] + top = bbox_values[:, 1] + width = bbox_values[:, 2] + height = bbox_values[:, 3] + right = left + width + bottom = top + height + size = np.maximum(width, height) + is_empty = (width == 0) | (height == 0) + left_diff = np.abs(left[:, np.newaxis] - left[np.newaxis, :]) + top_diff = np.abs(top[:, np.newaxis] - top[np.newaxis, :]) + right_diff = np.abs(right[:, np.newaxis] - right[np.newaxis, :]) + bottom_diff = np.abs(bottom[:, np.newaxis] - bottom[np.newaxis, :]) + size_i = size[:, np.newaxis] + size_j = size[np.newaxis, :] + size_max = np.maximum(size_i, size_j) + tol = 1e-6 * size_max + is_empty_pair = is_empty[:, np.newaxis] | is_empty[np.newaxis, :] + equal = (~is_empty_pair) & \ + (left_diff <= tol) & \ + (top_diff <= tol) & \ + (right_diff <= tol) & \ + (bottom_diff <= tol) + else: + equal = np.zeros((0, 0), dtype=bool) + + for jj in reversed(range(len(els))): + for ii in range(jj): + if equal[ii,jj]: + for kk in [ii,jj]: + if sfs[kk] is None: + sfs[kk] = dh.get_strokefill(els[kk]) + mysf = sfs[jj] + othsf = sfs[ii] + if mysf.stroke is None and mysf.fill is None: + continue + if mysf.stroke is not None: + if mysf.stroke.alpha != 1.0 or othsf.stroke is None: + continue + if not mysf.stroke == othsf.stroke: + continue + if mysf.fill is not None: + if mysf.fill.alpha != 1.0 or othsf.fill is None: + continue + if not mysf.fill == othsf.fill: + continue + if not els[jj].cspecified_style == els[ii].cspecified_style: + continue + + mypth = els[jj].cpath.transform(els[jj].ccomposed_transform).to_absolute(); + othpth= els[ii].cpath.transform(els[ii].ccomposed_transform).to_absolute(); + if mypth!=othpth and mypth!=othpth.reverse(): + continue + # dh.idebug(els[ii].get_id()) + els[ii].delete(deleteup=True) + equal[ii,:] = False + ngs2.remove(els[ii]) + + if self.options.removerectw: + ngs3 = [el for el in ngs2 if el.get_id() in bbs] + bbs3 = [dh.bbox(bbs.get(el.get_id())) for el in ngs3] + wriis = [ii for ii, el in enumerate(ngs3) if el in wrects] + wrbbs = [bbs3[ii] for ii in wriis] + intrscts = dh.bb_intersects(bbs3, wrbbs) + for jj, ii in enumerate(wriis): + if not any(intrscts[:ii, jj]): + ngs3[ii].delete(deleteup=True) + intrscts[ii, :] = False + ngs2.remove(ngs3[ii]) + + + + # Remove any unused clips we made, unnecessary white space in document + ds = self.svg.iddict.descendants + clips = [dh.EBget(el, "clip-path") for el in ds] + masks = [dh.EBget(el, "mask") for el in ds] + clips = [url[5:-1] for url in clips if url is not None] + masks = [url[5:-1] for url in masks if url is not None] + + ctag = inkex.ClipPath.ctag + if hasattr(self.svg, "newclips"): + for el in self.svg.newclips: + if el.tag == ctag and not (el.get_id() in clips): + el.delete(deleteup=True) + elif dh.isMask(el) and not (el.get_id() in masks): + el.delete(deleteup=True) + + ttags = dh.tags((Tspan, TextPath, FlowPara, FlowRegion, FlowSpan)) + ttags2 = dh.tags( + ( + StyleElement, + TextElement, + Tspan, + TextPath, + inkex.FlowRoot, + inkex.FlowPara, + inkex.FlowRegion, + inkex.FlowSpan, + ) + ) + for el in reversed(ds): + if not (el.tag in ttags): + if el.tail is not None: + el.tail = None + if not (el.tag in ttags2): + if el.text is not None: + el.text = None + + +if __name__ == "__main__": + dh.Run_SI_Extension(FlattenPlots(), "Flattener") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.inx b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.inx new file mode 100644 index 0000000..bbef0ec --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.inx @@ -0,0 +1,28 @@ + + + Gallery viewer + burghoff.gallery_viewer + + + + + + + 5001 + + + + + + + + text + + + + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.py new file mode 100644 index 0000000..131aeb5 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +DEBUGGING = False +dispprofile = False + +import dhelpers as dh +import inkex +import os, sys, copy, subprocess, platform + +# Convenience functions +def joinmod(dirc, f): + return os.path.join(os.path.abspath(dirc), f) + + +# Runs a Python script using a Python binary in a working directory +# It detaches from Inkscape, allowing it to continue running after the extension has finished +def run_python(python_bin, python_script, python_wd, interminal=False): + if platform.system() == "Windows": + DEVNULL = "nul" + else: + DEVNULL = "/dev/null" + # DEVNULL = dh.si_tmp(filename="si_gv_output.txt") + # dh.idebug(DEVNULL) + with open(DEVNULL, "w") as devnull: + subprocess.Popen([python_bin, python_script], stdout=devnull, stderr=devnull) + + +class GalleryViewer(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument("--portnum", help="Port number for server") + + def effect(self): + if dispprofile: + import cProfile, pstats, io + from pstats import SortKey + + pr = cProfile.Profile() + pr.enable() + + # Make an options copy we can pass to the external program + optcopy = copy.copy(self.options) + delattr(optcopy, "output") + delattr(optcopy, "input_file") + + bfn = inkex.inkscape_system_info.binary_location + bloc, bnm = os.path.split(bfn) + pyloc, pybin = os.path.split(sys.executable) + + aepy = os.path.join(dh.si_dir, "gallery_viewer_script.py") + + # Pass settings using a config file. Include the current path so Inkex can be called if needed. + import pickle + + optcopy.inkscape_bfn = bfn + optcopy.syspath = sys.path + optcopy.inshell = False + optcopy.logfile = dh.shared_temp(filename="si_gv_output.txt") + + import tempfile + settings = os.path.join( + os.path.abspath(tempfile.gettempdir()), "si_gv_settings.p" + ) + + with open(settings, "wb") as f: + pickle.dump(optcopy, f) + import warnings + warnings.simplefilter("ignore", ResourceWarning) # prevent process open warning + run_python(pybin, aepy, pyloc, optcopy.inshell) + + # Make a batch file that can run the Gallery Viewer directly on Windows + # Hardcodes the pickled settings + if platform.system() == "Windows": + python_cwd = os.getcwd() + pickled_file_path = settings + with open(pickled_file_path, "rb") as f: + pickled_data = f.read() + import base64 + pickled_data_base64 = base64.b64encode(pickled_data).decode('utf-8') + current_script_dir = os.path.dirname(os.path.abspath(__file__)) + batch_file_path = os.path.join(current_script_dir, "Gallery Viewer.bat") + batch_content = '''@echo off + cd "{python_cwd}" + + SET PYBIN="{pybin}" + SET AEPY="{aepy}" + SET PICKLED_FILE="{pickled_file}" + + REM Use PowerShell to decode the base64 string and write the binary pickled data + powershell -Command "[System.IO.File]::WriteAllBytes('%PICKLED_FILE%', [Convert]::FromBase64String('{pickled_data_base64}'))" + + REM Start the Python script in a new process without opening a new window + start "" %PYBIN% %AEPY% + '''.format( + python_cwd=python_cwd, # Add the current working directory + pybin=sys.executable, + aepy=aepy, + pickled_file=pickled_file_path.replace('\\', '\\\\'), + pickled_data_base64=pickled_data_base64 + ) + with open(batch_file_path, "w") as batch_file: + batch_file.write(batch_content) + + + if dispprofile: + pr.disable() + s = io.StringIO() + sortby = SortKey.CUMULATIVE + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + dh.debug(s.getvalue()) + + +if __name__ == "__main__": + dh.Run_SI_Extension(GalleryViewer(), "Gallery Viewer") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_script.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_script.py new file mode 100644 index 0000000..a57939e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_script.py @@ -0,0 +1,1227 @@ +# Inkscape Gallery Viewer, by David Burghoff +# Copyright (c) 2023 David Burghoff +# Prints written to [temp]/si_gv_output.txt + +# Load the settings file +import tempfile, os, pickle, sys +settings = os.path.join( + os.path.abspath(tempfile.gettempdir()), "si_gv_settings.p" +) +with open(settings, "rb") as f: + input_options = pickle.load(f) + +bfn = input_options.inkscape_bfn +sys.path.extend([p for p in input_options.syspath if p not in sys.path]) +bfn_dir = os.path.dirname(bfn) +if bfn_dir not in sys.path: + sys.path.append(bfn_dir) +PORTNUMBER = input_options.portnum +sys.stdout = open(input_options.logfile, "w") +sys.stderr = sys.stdout + +import sys, subprocess, threading, time, chardet +import random +import webbrowser, pathlib +from urllib import parse +import warnings +import re +from threading import Thread +import zipfile +import shutil +import hashlib +import requests +import builtins + +import dhelpers as dh +import inkex +import xml.etree.ElementTree as ET +from autoexporter import ORIG_KEY +from autoexporter import DUP_KEY +from autoexporter import hash_file + +WHILESLEEP = 0.25 +IMAGE_WIDTH = 175 +IMAGE_HEIGHT = IMAGE_WIDTH * 0.7 +MAXATTEMPTS = 1 + +original_print = print + + +def mprint(*args, **kwargs): + original_print(*args, **kwargs, flush=True) + + +builtins.print = mprint + +try: + import gi + + gi.require_version("Gtk", "3.0") + from gi.repository import Gtk + + guitype = "gtk" +except: + try: + import tkinter as tk + from tkinter import filedialog + + guitype = "tkinter" + except: + guitype = "terminal" + +current_script_directory = os.path.dirname(os.path.abspath(__file__)) +sys.path += [os.path.join(current_script_directory, "packages")] + +def file_uri_to_path(file_uri, path_class=pathlib.PurePath): + # https://stackoverflow.com/questions/5977576/is-there-a-convenient-way-to-map-a-file-uri-to-os-path + """ + This function returns a pathlib.PurePath object for the supplied file URI. + + :param str file_uri: The file URI ... + :param class path_class: The type of path in the file_uri. By default it uses + the system specific path pathlib.PurePath, to force a specific type of path + pass pathlib.PureWindowsPath or pathlib.PurePosixPath + :returns: the pathlib.PurePath object + :rtype: pathlib.PurePath + """ + windows_path = isinstance(path_class(), pathlib.PureWindowsPath) + file_uri_parsed = parse.urlparse(file_uri) + file_uri_path_unquoted = parse.unquote(file_uri_parsed.path) + if windows_path and file_uri_path_unquoted.startswith("/"): + result = path_class(file_uri_path_unquoted[1:]) + else: + result = path_class(file_uri_path_unquoted) + if result.is_absolute() == False: + raise ValueError( + "Invalid file uri {} : resulting path {} not absolute".format( + file_uri, result + ) + ) + return result + +refreshapp = False + +def trigger_refresh(): + global refreshapp + refreshapp = True + +def show_in_file_browser(path): + if not os.path.exists(path): + raise FileNotFoundError(f"The path {path} does not exist.") + + # Resolve the absolute path + path = os.path.abspath(path) + + if sys.platform.startswith("win"): + import ctypes + from ctypes import wintypes + + # Define necessary Windows types and constants + LPCTSTR = wintypes.LPCWSTR + # HWND = wintypes.HWND + UINT = wintypes.UINT + LPVOID = ctypes.c_void_p + + # Define the functions + SHOpenFolderAndSelectItems = ctypes.windll.shell32.SHOpenFolderAndSelectItems + ILCreateFromPathW = ctypes.windll.shell32.ILCreateFromPathW + ILFree = ctypes.windll.shell32.ILFree + + # Create an ITEMIDLIST for the path + ILCreateFromPathW.restype = LPVOID + ILCreateFromPathW.argtypes = [LPCTSTR] + pidl = ILCreateFromPathW(path) + + if not pidl: + raise FileNotFoundError(f"Unable to get PIDL for path: {path}") + + # Open the folder and select the item + SHOpenFolderAndSelectItems.restype = ctypes.HRESULT + SHOpenFolderAndSelectItems.argtypes = [LPVOID, UINT, LPVOID, UINT] + res = SHOpenFolderAndSelectItems(pidl, 0, None, 0) + + # Free the ITEMIDLIST + ILFree.argtypes = [LPVOID] + ILFree(pidl) + + if res != 0: + raise OSError(f"Failed to open folder and select item. HRESULT: {res}") + + elif sys.platform == "darwin": # macOS + # Use AppleScript to bring Finder to the front + script = f''' + tell application "Finder" + activate + reveal POSIX file "{path}" + end tell + ''' + subprocess.run(['osascript', '-e', script]) + + elif sys.platform.startswith("linux"): + directory, file = os.path.split(path) + # Open the directory and select the file using Nautilus or default file manager + subprocess.Popen(['xdg-open', directory]) + + else: + raise OSError(f"Unsupported OS: {sys.platform}") + +truepath = dict() +truepath_lock = threading.Lock(); + +app = None +app_lock = threading.Lock(); +def Make_Flask_App(): + warnings.simplefilter("ignore", DeprecationWarning) + # prevent warning that process is open + from flask import Flask, request, url_for, jsonify, send_from_directory, render_template, abort + + global app + app = Flask(__name__, template_folder='.') + + @app.route("/images//") + def send_image(folder, path): + with truepath_lock: + tp = truepath.get(folder) + if tp is None: + abort(404) # Sends a 404 response + return send_from_directory(os.path.abspath(tp), path) + + @app.route("/") + def index(): + # Render the gallery page (without data, which will be fetched via AJAX) + return render_template("gallery_viewer_template.html", image_width=IMAGE_WIDTH, image_height=IMAGE_HEIGHT, port=PORTNUMBER) + + + def get_folder_key(folder): + with truepath_lock: + temp_dir_name = os.path.split(temp_dir)[-1] + if folder not in truepath.values(): + key = temp_dir_name + "-dir" + str(random.randint(1, 100000)) + while key in truepath: + key = temp_dir_name + "-dir" + str(random.randint(1, 100000)) + truepath[key] = folder + else: + key = next(key for key, value in truepath.items() if value == folder) + return key + + + from functools import lru_cache + @lru_cache(maxsize=None) + def cached_url(path): + folder, filename = os.path.split(path) + return url_for("send_image", path=filename, folder=get_folder_key(folder)) + + @app.route("/gallery_data") + def gallery_data(): + # Collect the gallery data to be sent dynamically + gallery_data = [] + tic = time.time() + for fp in processors: + files_data = [] + for ii, f in enumerate(fp.files): + svg = f.name + if fp.files[ii].slidenum is not None: + label = f"Slide {fp.files[ii].slidenum}" + else: + pn = ( + " ({0})".format(fp.files[ii].pagenum) + if fp.files[ii].pagenum is not None + else "" + ) + label = os.path.split(svg)[-1] + pn + + # Determine currenttype accurately + if fp.isdir: + currenttype = "Current" + elif fp.files[ii].islinked: + currenttype = "Linked" + else: + currenttype = "Embedded" + + file_url = cached_url(svg) + thumbnail_url = cached_url(fp.files[ii].thumbnail) + + # Add file data + files_data.append({ + "file_url": file_url, + "thumbnail_url": thumbnail_url, + "file_uri": f.name_uri, + "label": label, + "currenttype": currenttype, + "embed": f.embed_uri, + }) + processing = not fp.run_on_fof_done or any(not t.done for t in fp.cthreads) + gallery_data.append({ + "header": fp.header, + "files": files_data, + "processing": processing + }) + + # Return gallery data as JSON + print(f'Done jsonifying in {time.time()-tic}') + return jsonify(gallery_data=gallery_data) + + + + @app.route("/stop") + def stop(): + func = request.environ.get("werkzeug.server.shutdown") + if func is None: + raise RuntimeError("Not running with the Werkzeug Server") + func() + return "Server shutting down..." + + @app.route("/process", methods=["GET"]) + def process(): + param = request.args.get("param") + svg_file = file_uri_to_path(param) + if svg_file is not None: + print("Opening " + str(svg_file)) + + if str(svg_file).endswith(".emf") or str(svg_file).endswith(".wmf"): + subprocess.Popen([bfn,svg_file]) + return f"The parameter received is: {param}" + + with OpenWithEncoding(svg_file) as f: + file_content = f.read() + if DUP_KEY in file_content: + deembedsmade = False + while not (deembedsmade): + deembeds = os.path.join(temp_dir, "deembeds") + deembedsmade = os.path.exists(deembeds) + if not (deembedsmade): + os.mkdir(deembeds) + + tsvg = os.path.join( + deembeds, "tmp_" + str(len(os.listdir(deembeds))) + ".svg" + ) + svg = dh.svg_from_file(svg_file) + + for d in svg.descendants2(): + if ( + isinstance(d, inkex.TextElement) + and d.text is not None + and DUP_KEY in d.text + ): + dupid = d.text[len(DUP_KEY + ": ") :] + dup = svg.getElementById(dupid) + if dup is not None: + dup.delete() + g = d.getparent() + d.delete() + g.set("display", None) # office converts to att + g.cstyle["display"] = None + list(g)[0].set_id(dupid) + dh.ungroup(g) + + dh.overwrite_svg(svg, tsvg) + subprocess.Popen([bfn, tsvg]) + else: + subprocess.Popen([bfn, svg_file]) + + return f"The parameter received is: {param}" + + @app.route("/show_file", methods=["GET"]) + def show_file(): + param = request.args.get("param") + svg_file = file_uri_to_path(param) + if svg_file is not None: + print("Showing in file browser: " + str(svg_file)) + show_in_file_browser(str(svg_file)) + return f"The parameter received is: {param}" + + @app.route("/check_for_refresh") + def check_for_refresh(): + global refreshapp, lastupdate, openedgallery + openedgallery = True + if refreshapp: + refreshapp = False + lastupdate = time.time() + return jsonify(lastupdate=lastupdate) + + def run_flask(): + app.run(port=PORTNUMBER) + + thread = Thread(target=run_flask) + thread.start() + +temp_dir, temp_head = dh.shared_temp("gv") +temp_base = os.path.join(temp_dir,temp_head) +MAXTHREADS = 10 +conv_sema = threading.Semaphore(MAXTHREADS) + +# Opens a file with unknown encoding, trying utf-8 first +# chardet can be slow +class OpenWithEncoding: + def __init__(self, filename, mode="r"): + self.filename = filename + self.mode = mode + self.file = None + + def __enter__(self): + try: + self.file = open(self.filename, self.mode, encoding="utf-8") + except UnicodeDecodeError: + with open(self.filename, "rb") as f: + raw_data = f.read() + result = chardet.detect(raw_data) + encoding = result["encoding"] + + self.file = open(self.filename, self.mode, encoding=encoding) + + return self.file + + def __exit__(self, exc_type, exc_value, traceback): + if self.file is not None: + self.file.close() + return False # Don't suppress exceptions + +cthread_no = 0 +cthread_lock = threading.Lock() +class ConversionThread(threading.Thread): + # Converts an EMF/WMF to PNG to be used as a thumbnail + def __init__(self, filein, parent_watcher, fileout): + super().__init__() + self.file = filein + self.parent = parent_watcher # Reference to the parent class instance + self.done = False + self.fileout = fileout + + global cthread_no + with cthread_lock: + self.no = cthread_no + cthread_no += 1 + + def run(self): + fname = self.file.name + + # Read and hash the input file to check for duplicates + with open(fname, "rb") as file: + file_content = file.read() + hashed = hashlib.sha256(file_content).hexdigest() + + + # Check if the file has already been converted + if hashed not in converted_files: + print("Starting export of "+fname) + + # Generate a unique conversion path + conv_path = os.path.join(temp_dir, f"{temp_head}_conv{self.no}.png") + + # Remove existing output file if it exists + if os.path.exists(self.fileout): + os.remove(self.fileout) + + try: + from PIL import Image + with open(fname, "rb") as file: + with Image.open(file) as im: + width, height = im.size + new_width = 400 + + if fname.endswith('.wmf'): + DEFAULT_DPI = 72 + new_dpi = max(10,int(new_width / width * DEFAULT_DPI)) + im.load(dpi=new_dpi) + width, height = im.size + + new_height = int((new_width / width) * height) + if 'info' in im.__dict__ and "dpi" in im.info and isinstance(im.info["dpi"],tuple) and len(im.info["dpi"]) == 2: + # print(im.info['dpi']) + new_height = int((new_width / width * im.info["dpi"][0]/im.info["dpi"][1]) * height) + resized_image = im.resize((new_width, new_height), Image.LANCZOS) + resized_image.save(conv_path) + print('PIL export of '+fname + ' originally '+str(width)+' x '+str(height)) + except Exception as e: + print(f"Failed export: {e}") + args = [ + bfn, + "--export-area-drawing", + "--export-background", + "#ffffff", + "--export-background-opacity", + "1.0", + "--export-width", + "400", + "--export-filename", + conv_path, + fname, + ] + # Execute the conversion command + with conv_sema: + print('Inkscape export of '+fname) + dh.subprocess_repeat(args) + + # Move the converted file to the final destination + shutil.move(conv_path, self.fileout) + print("Finished export...") + + # Trigger a refresh and update the converted files dictionary + converted_files[hashed] = self.fileout + else: + # If already converted, copy the existing file + shutil.copy2(converted_files[hashed], self.fileout) + if os.path.exists(self.fileout): + self.file.thumbnail = self.fileout + trigger_refresh() + self.done = True + +class DisplayedFile(): + """ Represents a single file we are displaying """ + def __init__(self,val): + self.name = str(val); # actual file + if self.name.endswith(".emf"): + self.thumbnail = os.path.join(dh.si_dir,'pngs','converting_emf.svg') + elif self.name.endswith(".wmf"): + self.thumbnail = os.path.join(dh.si_dir,'pngs','converting_wmf.svg') + else: + self.thumbnail = self.name + self.name_uri = pathlib.Path(self.name).as_uri() + self.slidenum = None + self.islinked = False + self.embed = None + self.embed_uri = None + self.pagenum = None + + def __str__(self): + return self.name + + def __lt__(self, other): + if isinstance(other, DisplayedFile): + return self.name < other.name + return self.name < other # Fallback for comparing with strings + +wthread_no = 0 +wthread_lock = threading.Lock() +class Processor(threading.Thread): + """ Creates a gallery for a single selection, either a file or folder """ + def __init__(self, file_or_folder, opened=True): + threading.Thread.__init__(self) + self.fof = file_or_folder + self.isdir = not os.path.isfile(self.fof) + + self.open_at_load = opened + self.run_on_fof_done = False + + self.files = [] + self.header = self.fof + self.cthreads = [] # conversion threads + + global wthread_no + with wthread_lock: + self.no = wthread_no + wthread_no += 1 + + def create_fcn(self,path): + print(f"Created: {path}") + self.run_on_fof() + def mod_fcn(self,path): + print(f"Modified: {path}") + self.run_on_fof() + def delete_fcn(self,path): + print(f"Deleted: {path}") + # self.run_on_fof() + + def get_image_slidenums(self, dirin): + relsdir = os.path.join(dirin, "ppt", "slides", "_rels") + numslides = len(os.listdir(relsdir)) + slide_filenames = [] + for slide_num in range(1, numslides + 1): + tree = ET.parse( + os.path.join( + dirin, "ppt", "slides", "_rels", f"slide{slide_num}.xml.rels" + ) + ) + root = tree.getroot() + image_filenames = [] + for elem in root.iter( + "{http://schemas.openxmlformats.org/package/2006/relationships}Relationship" + ): + if ( + elem.attrib["Type"] + == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + ): + image_filenames.append(elem.attrib["Target"]) + slide_filenames.append(image_filenames) + slide_lookup = {} + for index, filenames in enumerate(slide_filenames): + for filename in filenames: + slide_lookup[filename] = slide_lookup.get(filename, []) + [ + index + 1 + ] + return slide_lookup + + def get_linked_images_word(self,dirin): + rels_path = os.path.join(dirin, "word", "_rels", "document.xml.rels") + + # Parse the relationships file + tree = ET.parse(rels_path) + root = tree.getroot() + + linked_images = [] + for elem in root.iter("{http://schemas.openxmlformats.org/package/2006/relationships}Relationship"): + # Check if the relationship is an image and has an external TargetMode + if elem.attrib["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image": + if elem.attrib.get("TargetMode") == "External": + linked_images.append(elem.attrib["Target"]) + + return linked_images + + def get_images_onenote(self,target_file,outputdir): + ''' Extracts OneNote files to the output directory ''' + pkg_dir = os.path.join(dh.si_dir,'packages') + if pkg_dir not in sys.path: + sys.path.append(pkg_dir) + from onenoteextractor.one import OneNoteExtractor + from pathlib import Path + with Path(target_file).open("rb") as infile: + data = infile.read() + document = OneNoteExtractor(data=data, password=None) + import struct + def is_emf(file_data: bytes) -> bool: + """Check if the file_data represents an EMF file by inspecting the header.""" + if len(file_data) < 88: + return False # EMF header should be at least 88 bytes + # Unpack the first 4 bytes to get the Record Type + record_type, = struct.unpack(' bool: + """Check if the file_data represents a WMF file by inspecting the header.""" + if len(file_data) < 4: + return False # WMF header should be at least 4 bytes + # Check for Placeable WMF magic number + if file_data.startswith(b'\xD7\xCD\xC6\x9A'): + return True + # For non-placeable WMF files, check the Type and Header Size + if len(file_data) < 18: # Minimum WMF header size + return False + try: + type_, header_size, version = struct.unpack(' bool: + """Check if the file_data represents a PNG file by inspecting the header.""" + return file_data.startswith(b'\x89PNG\r\n\x1a\n') + + def is_jpeg(file_data: bytes) -> bool: + """Check if the file_data represents a JPEG file by inspecting the header.""" + return file_data.startswith(b'\xFF\xD8\xFF') + for index, file_data in enumerate(document.extract_files()): + bn = Path(target_file).stem # Use stem to get filename without extension + if is_emf(file_data): + extension = '.emf' + elif is_wmf(file_data): + extension = '.wmf' + elif is_png(file_data): + extension = '.png' + elif is_jpeg(file_data): + extension = '.jpg' + else: + extension = '.bin' # Default extension for unknown types + target_path = Path(outputdir) / f"{bn}_{index}{extension}" + print(f"Writing extracted file to: {target_path}") + with target_path.open("wb") as outf: + outf.write(file_data) + + def run_on_file(self, contents): + # Unzip the ppt file to the temp directory, allowing for multiple + # attempts in case the file was not originally found (which can + # happen in cloud drives) + if self.fof.endswith(".pptx") or self.fof.endswith(".pptm"): + ftype = "ppt" + elif self.fof.endswith(".one"): + ftype = "onenote" + else: + ftype = "word" + + print(f'Unzipping {self.fof}') + if ftype == "onenote": + media_dir = os.path.join(contents, ftype) + os.mkdir(media_dir) + self.get_images_onenote(self.fof,media_dir) + else: + media_dir = os.path.join(contents, ftype, "media") + attempts = 0 + max_attempts = 3 + while attempts < max_attempts: + try: + with zipfile.ZipFile(self.fof, "r") as zip_ref: + zip_ref.extractall(contents) + break # Exit the loop if successful + except zipfile.BadZipFile: + attempts += 1 + if attempts == max_attempts: + self.run_on_fof_done = True + print(f'Could not unzip {self.fof} after {max_attempts} attempts.') + return + else: + print(f'Attempt {attempts} to unzip {self.fof} failed. Retrying...') + + self.files = [] + if os.path.exists(media_dir): + self.files += Processor.get_svgs(media_dir) + trigger_refresh() + + + if ftype == "ppt": + image_slides = self.get_image_slidenums(contents) + + # Add linked images to self.files + linked = [ + DisplayedFile(file_uri_to_path(k)) + for k in image_slides.keys() + if "file:" in k + ] + self.files += linked + slidenums = { + os.path.join(contents, "ppt", "media", os.path.basename(k)) + if "file:" not in k + else str(file_uri_to_path(k)): v + for k, v in image_slides.items() + } + + # Sort the files by slide number and make slidenums a corresponding list + # Duplicates filenames if on multiple slides + exp_files = [] + for file in self.files: + slides = slidenums.get(file.name, [float("inf")]) + for slide in slides: + exp_files.append((file,slide)) + if len(exp_files)>0: # Sort by slide and then name + self.files, slidenums = map(list, zip(*sorted(exp_files, key=lambda x: (x[1], x[0])))) + else: + self.files, slidenums = [], [] + for i, v in enumerate(slidenums): + self.files[i].slidenum = v if v != float("inf") else "?" + self.files[i].islinked = self.files[i] in linked + else: + if ftype=='word': + linked = [DisplayedFile(file_uri_to_path(k)) for k in self.get_linked_images_word(contents)] + for f in linked: + f.islinked = True + self.files += linked + trigger_refresh() + + subfiles = None + for ii, fv in enumerate(self.files): + ev = False + if fv.name.endswith(".svg") and os.path.exists(fv.name): + with OpenWithEncoding(fv.name) as f: + file_content = f.read() + if ORIG_KEY in file_content: + key = ORIG_KEY + r":\s*(.+?)<" + match = re.search(key, file_content) + if match: + orig_file = match.group(1) + orig_hash = None + if ( + ", hash: " in orig_file + ): # introduced hashing later than ORIG_KEY + orig_file, orig_hash = orig_file.split( + ", hash: " + ) + if os.path.exists(orig_file): + ev = os.path.abspath(orig_file) + else: + # Check subdirectories of the file's location in case it was moved + + def list_all_files(directory): + for dirpath, dirs, files in os.walk( + directory + ): + for filename in files: + yield os.path.join( + dirpath, filename + ) + + fndir = os.path.split(self.fof)[0] + subfiles = ( + list(list_all_files(fndir)) + if subfiles is None + else subfiles + ) + + for tryfile in subfiles: + if os.path.split(orig_file)[ + -1 + ] == os.path.split(tryfile)[-1] and ( + orig_hash is None + or hash_file(tryfile) == orig_hash + ): + ev = os.path.abspath(tryfile) + break + self.files[ii].embed=ev + self.files[ii].embed_uri = pathlib.Path(ev).as_uri() if ev else None + + def run_on_folder(self): + self.files = Processor.get_svgs(self.fof) + trigger_refresh() + + ii = 0 + while ii < len(self.files): + fn = self.files[ii].name + svg_pgs = [] + + # Check if the file is an SVG file + if fn.endswith(".svg"): + with OpenWithEncoding(fn) as f: + try: + contents = f.read() + except OSError: + raise(f'Could not open {fn}') + if re.search(r"<\s*inkscape:page[\s\S]*?>", contents): + svg = dh.svg_from_file(fn) + pgs = svg.cdocsize.pgs + haspgs = inkex.installed_haspages + # If the file has multiple pages, split it + if haspgs and len(pgs) > 0: + vbs = [svg.cdocsize.pxtouu(pg.bbpx) for pg in pgs] + for vb in vbs: + svg.set_viewbox(vb) + tnsvg = os.path.join(self.tndir, str(self.numtns) + ".svg") + self.numtns += 1 + dh.overwrite_svg(svg, tnsvg) + svg_pgs.append(tnsvg) + + # If thumbnails were created (multiple pages), update files and thumbnails lists + if len(svg_pgs) > 0: + nfiles = [DisplayedFile(fn) for t in svg_pgs] + for i, n in enumerate(nfiles): + n.thumbnail = svg_pgs[i] + n.pagenum = i+1 + self.files[ii:ii + 1] = nfiles + trigger_refresh() + ii += len(nfiles) + else: + ii += 1 + + @staticmethod + def get_svgs(dirin): + svg_filenames = [] + for file in os.listdir(dirin): + if should_display(file): + svg_filenames.append(DisplayedFile(os.path.join(dirin, file))) + svg_filenames.sort() + return svg_filenames + + def run_on_fof(self): + print("Running on file: " + self.fof) + + contents = os.path.join( + temp_dir, f"{temp_head}_cont{self.no}" + ) + if not (os.path.exists(contents)): + os.mkdir(contents) + + self.tndir = os.path.join(contents, "thumbnails") + if not os.path.exists(self.tndir): + os.makedirs(self.tndir) + self.numtns = len(os.listdir(self.tndir)) + + if not self.isdir: + self.run_on_file(contents) + else: + self.run_on_folder() + + for ii, f in enumerate(self.files): + if f.islinked and f.thumbnail.endswith(".svg") and not os.path.exists(self.files[ii].name): + f.thumbnail = os.path.join(dh.si_dir,'pngs','missing_svg.svg') + + self.convert_emfs() # start ConversionThreads + self.run_on_fof_done = True + trigger_refresh() + + def convert_emfs(self): + for ii, f in enumerate(self.files): + if f.name.endswith(".emf") or f.name.endswith(".wmf"): + tnpng = os.path.join(self.tndir, str(self.numtns) + ".png") + self.numtns += 1 + thread = ConversionThread(f, self, tnpng) + self.cthreads.append(thread) + thread.start() + + def run(self): + with app_lock: + if app is None: + Make_Flask_App() + time.sleep(1) + # wait to see if check_for_refresh called + global openedgallery + if not (openedgallery): + webbrowser.open("http://localhost:{}".format(str(PORTNUMBER))) + openedgallery = True + self.run_on_fof() + watcher.add_watch(self) + +def should_display(file): + """ Criteron for whether a file should be displayed in the gallery """ + valid_exts = ['svg','emf','wmf','png','gif','jpg','jpeg'] + return any(file.lower().endswith('.'+ext) for ext in valid_exts) + + +warnings.filterwarnings( + "ignore", message="Failed to import fsevents. Fall back to kqueue" +) +mydir = os.path.dirname(os.path.abspath(__file__)) +packages = os.path.join(mydir, "packages") +if packages not in sys.path: + sys.path.append(packages) +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from collections import defaultdict +class Watcher(FileSystemEventHandler): + def __init__(self): + super().__init__() + self.observer = Observer() + self.dir_processors = defaultdict(list) # dirpath -> list of Processor + self.dir_refs = defaultdict(int) # dirpath -> number of watchers + self.dir_watches = {} # dirpath -> Watch object + self.debounce_timers = {} # (watcher, path) -> timer + self.file_mod_times = {} # full file path -> last mtime + self.observer.start() + + def add_watch(self, fp): + path = os.path.abspath(fp.fof) + dir_path = path if fp.isdir else os.path.dirname(path) + + # Initialize mod times since watchdog sometimes does modifier events + # that don't seem to be real + if fp.isdir: + cfiles = [os.path.join(fp.fof, f) for f in os.listdir(fp.fof)] + else: + cfiles = [fp.fof] + for f in cfiles: + if os.path.isfile(f) and self.is_target_file(f,fp): + mtime = self.get_mod_time(f) + if mtime: + self.file_mod_times[os.path.abspath(f)] = mtime + + if self.dir_refs[dir_path] == 0: + watch = self.observer.schedule(self, dir_path, recursive=False) + self.dir_watches[dir_path] = watch + print(f"Scheduled observer for {dir_path}") + + self.dir_processors[dir_path].append(fp) + self.dir_refs[dir_path] += 1 + + def remove_watch(self, fp): + path = os.path.abspath(fp.fof) + dir_path = path if fp.isdir else os.path.dirname(path) + + if fp in self.dir_processors[dir_path]: + self.dir_processors[dir_path].remove(fp) + self.dir_refs[dir_path] -= 1 + + if self.dir_refs[dir_path] <= 0: + print(f"Unscheduling observer for {dir_path}") + watch = self.dir_watches.pop(dir_path, None) + if watch: + self.observer.unschedule(watch) + self.dir_processors.pop(dir_path, None) + self.dir_refs.pop(dir_path, None) + + def stop(self): + self.observer.stop() + self.observer.join() + + @staticmethod + def get_mod_time(path): + try: + return os.path.getmtime(path) + except FileNotFoundError: + return None + + def is_target_file(self, file_path, watcher): + file_name = os.path.basename(file_path) + if not watcher.isdir: + return os.path.abspath(file_path) == os.path.abspath(watcher.fof) + return should_display(file_name) + + def handle_event(self, event): + if event.is_directory: + return + dir_path = os.path.dirname(event.src_path) + for watcher in self.dir_processors.get(dir_path, []): + if not self.is_target_file(event.src_path, watcher): + continue + key = (watcher, event.src_path) + + if key in self.debounce_timers: + self.debounce_timers[key].cancel() + self.debounce_timers[key] = threading.Timer( + 0.5, self.run_callback, [watcher, event] + ) + self.debounce_timers[key].start() + + + def run_callback(self, watcher, event): + if event.event_type == "created" and watcher.create_fcn: + watcher.create_fcn(event.src_path) + elif event.event_type == "modified" and watcher.mod_fcn: + watcher.mod_fcn(event.src_path) + elif event.event_type == "deleted" and watcher.delete_fcn: + watcher.delete_fcn(event.src_path) + + def on_created(self, event): + self.handle_event(event) + + def on_deleted(self, event): + self.file_mod_times.pop(event.src_path, None) + self.handle_event(event) + + def on_modified(self, event): + if event.is_directory: + return + new_mtime = self.get_mod_time(os.path.abspath(event.src_path)) + old_mtime = self.file_mod_times.get(os.path.abspath(event.src_path)) + + if new_mtime and new_mtime != old_mtime: + self.file_mod_times[event.src_path] = new_mtime + self.handle_event(event) +watcher = Watcher() + +converted_files = dict() +lastupdate = time.time() +processors = [] +openedgallery = False + +def process_selection(file, opened=True): + if os.path.isdir(file) and os.path.isfile(os.path.join(file, "Gallery.cfg")): + with open(os.path.join(file, "Gallery.cfg"), "r") as f: + lines = f.readlines() + lines = [line.strip() for line in lines] + for ii, ln in enumerate(lines): + process_selection(os.path.join(file, ln), True) + return + + for fp in processors: + if file == fp.fof: + processors.remove(fp) + watcher.remove_watch(fp) + print("About to start") + fp = Processor(file, opened=opened) + fp.win = win + processors.append(fp) + fp.start() + +def quitnow(): + requests.get( + "http://localhost:{}/stop".format(str(PORTNUMBER)) + ) # kill Flask app + + + # remove temp files + tmps = [] + for t in os.listdir(temp_dir): + tmp = os.path.join(temp_dir, t) + try: + one_day_ago = time.time() - 24 * 60 * 60 + if os.path.getmtime(tmp) < one_day_ago: + tmps.append(tmp) + except FileNotFoundError: # already deleted + pass + if tmp.startswith(temp_base): + tmps.append(tmp) + + for tmp in tmps: + if os.path.exists(tmp): + deleted = False + nattempts = 0 + while not deleted and nattempts < MAXATTEMPTS: + try: + if os.path.isdir(tmp): + shutil.rmtree(tmp) + else: + os.remove(tmp) + deleted = True + except PermissionError: + time.sleep(1) + nattempts += 1 + + for fp in processors: + watcher.remove_watch(fp) + + watcher.stop() + + pid = os.getpid() + import signal + + os.kill(pid, signal.SIGINT) # or signal.SIGTERM + +if guitype == "gtk": + import gi + + gi.require_version("Gtk", "3.0") + + class GalleryViewerServer(Gtk.Window): + def __init__(self): + Gtk.Window.__init__(self, title="Gallery Viewer") + self.set_default_size( + 400, -1 + ) # set width to 400 pixels, height can be automatic + self.set_position(Gtk.WindowPosition.CENTER) + + self.containing_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.containing_box.set_valign(Gtk.Align.CENTER) + self.containing_box.set_margin_top(20) + self.containing_box.set_margin_bottom(20) + + self.file_button = Gtk.Button(label="View contents of files (.pptx, .docx, .one)") + self.file_button.connect("clicked", self.on_file_button_clicked) + self.folder_button = Gtk.Button(label="View contents of folders") + self.folder_button.connect("clicked", self.on_folder_button_clicked) + self.clear_button = Gtk.Button(label="Clear selections") + self.clear_button.connect("clicked", self.clear_clicked) + self.gallery_button = Gtk.Button(label="Display gallery") + self.gallery_button.connect("clicked", self.gallery_button_clicked) + self.exit_button = Gtk.Button(label="Exit") + self.exit_button.connect("clicked", self.on_button_clicked) + + # Create a list store to hold the file information + self.liststore = Gtk.ListStore(str, str) + self.treeview = Gtk.TreeView(model=self.liststore) + renderer_text = Gtk.CellRendererText() + column_text = Gtk.TreeViewColumn("Name", renderer_text, text=0) + self.treeview.append_column(column_text) + renderer_text = Gtk.CellRendererText() + column_text = Gtk.TreeViewColumn("Location", renderer_text, text=1) + self.treeview.append_column(column_text) + self.scrolled_window_files = Gtk.ScrolledWindow() + self.scrolled_window_files.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + self.scrolled_window_files.set_size_request(600, 200) + self.scrolled_window_files.set_vexpand(True) + self.scrolled_window_files.add(self.treeview) + + self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + # self.box.pack_start(self.containing_box, True, True, 0) + self.box.pack_start(self.scrolled_window_files, True, True, 0) + self.box.pack_start(self.file_button, False, False, 0) + self.box.pack_start(self.folder_button, False, False, 0) + self.box.pack_start(self.clear_button, False, False, 0) + self.box.pack_start(self.gallery_button, False, False, 0) + self.box.pack_start(self.exit_button, False, False, 0) + self.add(self.box) + + def print_text(self, text): + buffer = self.selected_file_label.get_buffer() + start, end = buffer.get_bounds() + if buffer.get_text(start, end, False) == "No file selected.": + buffer.set_text("") + buffer.insert(buffer.get_end_iter(), text + "\n") + end_iter = buffer.get_end_iter() + buffer.move_mark(buffer.get_insert(), end_iter) + self.selected_file_label.scroll_to_mark( + buffer.get_insert(), 0, True, 0, 0 + ) + + def on_button_clicked(self, widget): + self.destroy() + + def on_file_button_clicked(self, widget): + native = Gtk.FileChooserNative.new( + "Please choose one or more files", self, Gtk.FileChooserAction.OPEN, None, None + ) + native.set_select_multiple(True) + filter_ppt = Gtk.FileFilter() + filter_ppt.set_name("Office files") + filter_ppt.add_pattern("*.docx") + filter_ppt.add_pattern("*.pptx") + filter_ppt.add_pattern("*.pptm") + filter_ppt.add_pattern("*.one") + native.add_filter(filter_ppt) + response = native.run() + if response == Gtk.ResponseType.ACCEPT: + selected_files = native.get_filenames() + for selected_file in selected_files: + file_name = os.path.basename(selected_file) + file_dir = os.path.dirname(selected_file) + self.liststore.append([file_name, file_dir]) + process_selection(selected_file) + native.destroy() + + def on_folder_button_clicked(self, widget): + native = Gtk.FileChooserNative.new( + "Please choose one or more directories", + self, + Gtk.FileChooserAction.SELECT_FOLDER, + None, + None, + ) + native.set_select_multiple(True) + response = native.run() + if response == Gtk.ResponseType.ACCEPT: + selected_files = native.get_filenames() + for selected_file in selected_files: + file_name = os.path.basename(selected_file) + file_dir = os.path.dirname(selected_file) + self.liststore.append([file_name, file_dir]) + process_selection(selected_file) + + if os.path.exists(selected_file): + for fnm in os.listdir(selected_file): + if fnm.endswith('.docx') or fnm.endswith('.pptx') or fnm.endswith('.one'): + if not fnm.startswith('~$'): # temp files + file_name = fnm + file_dir = selected_file + self.liststore.append([file_name, file_dir]) + process_selection(os.path.join(file_dir,file_name)) + native.destroy() + + def gallery_button_clicked(self, widget): + webbrowser.open("http://localhost:{}".format(str(PORTNUMBER))) + + def clear_clicked(self, widget): + for fp in reversed(processors): + processors.remove(fp) + watcher.remove_watch(fp) + self.liststore.clear() + + win = GalleryViewerServer() + win.set_keep_above(True) + + # win.connect("destroy", quitnow) + def quit_and_close(self): + Gtk.main_quit() + quitnow() + + win.connect("destroy", quit_and_close) + win.show_all() + win.set_keep_above(False) + Gtk.main() +elif guitype == "tkinter": + root = tk.Tk() + root.title("Gallery Viewer") + root.attributes("-topmost", True) + root.wm_minsize(width=350, height=-1) + + def open_file(): + file = filedialog.askopenfilename() + file_label.config(text=file) + process_selection(file) + + def end_program(): + print("Quitting") + root.destroy() + quitnow() + + file_label = tk.Label(root, text="No file selected.") + file_label.pack() + select_button = tk.Button(root, text="Select File", command=open_file) + select_button.pack() + end_button = tk.Button(root, text="End Program", command=end_program) + end_button.pack() + root.protocol("WM_DELETE_WINDOW", end_program) + root.mainloop() + +print("Finishing") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_template.html b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_template.html new file mode 100644 index 0000000..acc0a88 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/gallery_viewer_template.html @@ -0,0 +1,382 @@ + + + + + + Gallery Viewer + + + + + +

Gallery Viewer

+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/homogenizer.inx b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/homogenizer.inx new file mode 100644 index 0000000..77cda49 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/homogenizer.inx @@ -0,0 +1,68 @@ + + + Homogenizer + burghoff.homogenizer + + + + + + true + true + + + true + + + + + + + + + + 8 + false + + + + true + true + + + + + + + + + 1 + + + false + + + + + + + + + + text + + + + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/homogenizer.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/homogenizer.py new file mode 100644 index 0000000..c4928e4 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/homogenizer.py @@ -0,0 +1,396 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import dhelpers as dh +import inkex +from inkex import TextElement, FlowRoot, FlowPara, Tspan, Transform, Group, FlowSpan +from inkex.text.cache import BaseElementCache +otp_support_tags = BaseElementCache.otp_support_tags +from inkex.text.utils import default_style_atts + +from applytransform_mod import fuseTransform +import math + +badels = ( + inkex.NamedView, + inkex.Defs, + inkex.Metadata, + inkex.ForeignObject, + inkex.SVGfont, + inkex.FontFace, + inkex.MissingGlyph, +) + +dispprofile = False + + +class Homogenizer(inkex.EffectExtension): + # def document_path(self): + # return 'test' + + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument( + "--setfontsize", type=inkex.Boolean, default=False, help="Set font size?" + ) + pars.add_argument("--fontsize", type=float, default=8, help="New font size") + pars.add_argument( + "--fixtextdistortion", + type=inkex.Boolean, + default=False, + help="Fix distorted text?", + ) + pars.add_argument("--fontmodes", type=int, default=1, help="Font size options") + + pars.add_argument( + "--setfontfamily", + type=inkex.Boolean, + default=False, + help="Set font family?", + ) + pars.add_argument("--fontfamily", type=str, default="", help="New font family") + + # pars.add_argument("--setreplacement", type=inkex.Boolean, default=False,help="Replace missing fonts?") + # pars.add_argument("--replacement", type=str, default='', help="Missing fon replacement"); + + pars.add_argument( + "--setstroke", type=inkex.Boolean, default=False, help="Set stroke width?" + ) + pars.add_argument( + "--setstrokew", type=float, default=1, help="New stroke width" + ) + pars.add_argument( + "--strokemodes", type=int, default=1, help="Stroke width options" + ) + pars.add_argument( + "--clearclipmasks", type=inkex.Boolean, default=False, help="Clear clips and masks" + ) + pars.add_argument( + "--fusetransforms", + type=inkex.Boolean, + default=False, + help="Fuse transforms to paths?", + ) + pars.add_argument( + "--plotaware", + type=inkex.Boolean, + default=False, + help="Plot-aware text scaling?", + ) + + def effect(self): + if dispprofile: + import cProfile, pstats, io + from pstats import SortKey + + pr = cProfile.Profile() + pr.enable() + + setfontsize = self.options.setfontsize + # setfontsize = (self.options.fontmodes>1); + + fontsize = self.options.fontsize + setfontfamily = self.options.setfontfamily + fontfamily = self.options.fontfamily + setstroke = self.options.setstroke + setstrokew = self.options.setstrokew + fixtextdistortion = self.options.fixtextdistortion + + sel0 = [self.svg.selection[ii] for ii in range(len(self.svg.selection))] + # should work with both v1.0 and v1.1 + sel = [v for el in sel0 for v in el.descendants2()] + + itag = inkex.Image.ctag + if all([el.tag == itag for el in sel]): + inkex.utils.errormsg( + """Thanks for using Scientific Inkscape! + +It appears that you're attempting to homogenize a raster Image object. Please note that Inkscape is mainly for working with vector images, not raster images. Vector images preserve all of the information used to generate them, whereas raster images do not. Read about the difference here: +https://en.wikipedia.org/wiki/Vector_graphics + +Unfortunately, this means that there is not much the Homogenizer can do to edit raster images. If you want to edit a raster image, you will need to use a program like Photoshop or GIMP. + """ + ) + quit() + elif self.options.plotaware and any([not isinstance(k, Group) for k in sel0]): + inkex.utils.errormsg( + "Plot-aware scaling requires that every selected object be a grouped plot." + ) + return + + sela = [el for el in sel if not (isinstance(el, badels))] + sel = [ + el + for el in sel + if isinstance(el, (TextElement, Tspan, FlowRoot, FlowPara, FlowSpan)) + ] + + if setfontfamily or setfontsize or fixtextdistortion: + tels = [d for d in sel if isinstance(d, (TextElement, FlowRoot))] + if not self.options.plotaware: + bbs = dh.BB2(self.svg, tels, False) + else: + aels = [d for el in sel0 for d in el.descendants2()] + bbs = dh.BB2(self.svg, aels, False) + + if setfontsize: + # Get all font sizes and scale factors + onept = self.svg.cdocsize.unittouu("1pt") + szs = dict() + for el in tels: + cszs = [c.tfs / onept for ln in el.parsed_text.lns for c in ln.chrs] + if len(cszs) > 0: + szs[el] = max(cszs) + + # Determine scale and/or size + fixedscale = False + try: + if self.options.fontmodes == 3: + fixedscale = True + elif self.options.fontmodes == 4: + fixedscale = True + fontsize = fontsize / max(szs.values()) * 100 + elif self.options.fontmodes == 5: + from statistics import mean + + fontsize = mean(szs.values()) + elif self.options.fontmodes == 6: + from statistics import median + + fontsize = median(szs.values()) + elif self.options.fontmodes == 7: + fontsize = min(szs.values()) + elif self.options.fontmodes == 8: + fontsize = max(szs.values()) + except ValueError: + fontsize = 12 + + from inkex.text import parser + + for el in szs: + for d in reversed(el.descendants2()): + sty = d.cspecified_style + if el == d or "font-size" in sty: + dfs, sf, utdfs = dh.composed_width(d, "font-size") + if dfs==0: + continue + bshift = parser.TChar.get_baseline(sty, d.getparent()) + if bshift != 0 or "%" in sty.get("font-size", ""): + # Convert sub/superscripts into relative size + pfs, sf, _ = dh.composed_width(d.getparent(), "font-size") + d.cstyle["font-size"] = f"{dfs / pfs * 100:.2f}%" + else: + # Set absolute size + scl = ( + fontsize * onept / dfs + if not fixedscale + else fontsize / 100 + ) + nfs = utdfs * scl + nfs = f"{nfs:.2f}" if abs(nfs) > 1 else "{:.3g}".format(nfs) + d.cstyle["font-size"] = nfs.rstrip("0").rstrip(".") + "px" + + if fixtextdistortion: + # make a new transform that removes bad scaling and shearing (see General_affine_transformation.nb) + for el in sel: + ct = el.ccomposed_transform + detv = ct.a * ct.d - ct.b * ct.c + if detv!=0: + signdet = -1 * (detv < 0) + (detv >= 0) + sqrtdet = math.sqrt(abs(detv)) + magv = math.sqrt(ct.b**2 + ct.a**2) + ctnew = Transform( + [ + [ct.a * sqrtdet / magv, -ct.b * sqrtdet * signdet / magv, ct.e], + [ct.b * sqrtdet / magv, ct.a * sqrtdet * signdet / magv, ct.f], + ] + ) + dh.global_transform(el, (ctnew @ (-ct))) + + if setfontfamily: + from inkex.text.font_properties import inkscape_spec_to_css + sty = inkscape_spec_to_css(fontfamily) + if sty is None: + dh.idebug('Font seems to be invalid—check its spelling.') + import sys + sys.exit() + # If any type of Font Style is being set, reset the others to default + if any(k in sty for k in ["font-weight", "font-style", "font-stretch"]): + for k in ["font-weight", "font-style", "font-stretch"]: + sty.setdefault(k, default_style_atts[k]) + + for el in reversed(sel): + for k,v in sty.items(): + el.cstyle[k] = v + el.cstyle["-inkscape-font-specification"] = None + + from inkex.text import parser + + dh.character_fixer(tels) + + if setfontfamily or setfontsize or fixtextdistortion: + bbs2 = dh.BB2(self.svg, tels, True) + if not self.options.plotaware: + for el in sel: + myid = el.get_id() + if ( + isinstance(el, (TextElement, FlowRoot)) + and myid in bbs + and myid in bbs2 + ): + bb = bbs[el.get_id()] + bb2 = bbs2[el.get_id()] + tx = (bb2[0] + bb2[2] / 2) - (bb[0] + bb[2] / 2) + ty = (bb2[1] + bb2[3] / 2) - (bb[1] + bb[3] / 2) + trl = Transform("translate({0}, {1})".format(-tx, -ty)) + dh.global_transform(el, trl) + + else: + from scale_plots import ( + geometric_bbox, + Find_Plot_Area, + trtf, + appendInt, + ) + + gbbs = {elid: geometric_bbox(el, fbb).sbb for elid, fbb in bbs.items()} + for i0, g in enumerate(sel0): + pels = [k for k in g if k.get_id() in bbs] # plot elements list + vl, hl, lvel, lhel = Find_Plot_Area(pels, gbbs) + + if lvel is None or lhel is None: + lvel = None + lhel = None + # Display warning and proceed + numgroup = str(i0 + 1) + appendInt(i0 + 1) + inkex.utils.errormsg( + "A box-like plot area could not be automatically detected on the " + + numgroup + + " selected plot (group ID " + + g.get_id() + + ").\n\nDraw a box with a stroke to define the plot area." + + "\nAdjustment will still be performed, but the results may not be ideal." + ) + + bbp = dh.bbox(None) + # plot area + for el in pels: + if el.get_id() in [lvel, lhel]: + bbp = bbp.union(gbbs[el.get_id()]) + for el in g.descendants2(): + if el in tels: + bb1 = dh.bbox(bbs[el.get_id()]) + bb2 = dh.bbox(bbs2[el.get_id()]) + if bbp.isnull: + dx = bb1.xc - bb2.xc + dy = bb1.yc - bb2.yc + else: + # For elements outside the plot area, adjust position to maintain + # the scaled distance to the plot area + if bb1.xc < bbp.x1: + dx = (bbp.x1 - bb2.x2) - ( + bbp.x1 - bb1.x2 + ) * bb2.w / bb1.w + elif bb1.xc > bbp.x2: + dx = (bb1.x1 - bbp.x2) * bb2.w / bb1.w - ( + bb2.x1 - bbp.x2 + ) + else: + dx = bb1.xc - bb2.xc + if bb1.yc < bbp.y1: + dy = (bbp.y1 - bb2.y2) - ( + bbp.y1 - bb1.y2 + ) * bb2.h / bb1.h + elif bb1.yc > bbp.y2: + dy = (bb1.y1 - bbp.y2) * bb2.h / bb1.h - ( + bb2.y1 - bbp.y2 + ) + else: + dy = bb1.yc - bb2.yc + tr2 = trtf(dx, dy) + dh.global_transform(el, tr2) + + if setstroke: + szd = dict() + sfd = dict() + szs = [] + for el in sela: + sw, sf, _ = dh.composed_width(el, "stroke-width") + + elid = el.get_id() + szd[elid] = sw + sfd[elid] = sf + if sw is not None: + szs.append(sw) + + fixedscale = False + if self.options.strokemodes == 2: + setstrokew = self.svg.cdocsize.unittouu(str(setstrokew) + "px") + elif self.options.strokemodes == 3: + fixedscale = True + elif self.options.strokemodes == 5: + from statistics import mean + + setstrokew = mean(szs) + elif self.options.strokemodes == 6: + from statistics import median + + setstrokew = median(szs) + elif self.options.strokemodes == 7: + setstrokew = min(szs) + elif self.options.strokemodes == 8: + setstrokew = max(szs) + + for el in sela: + elid = el.get_id() + if not (szd[elid] is None): + if not (fixedscale): + newsize = setstrokew + else: + newsize = szd[elid] * (setstrokew / 100) + if sfd[elid]==0: + continue + el.cstyle["stroke-width"] = str(newsize / sfd[elid]) + "px" + + if self.options.fusetransforms: + for el in sela: + if el.tag in otp_support_tags: + # Fuse the composed transform onto the path + el.ctransform = el.ccomposed_transform + fuseTransform(el) + el.ctransform = -el.getparent().ccomposed_transform + + + if self.options.clearclipmasks: + for el in sela: + el.cstyle['clip-path'] = 'none' + el.cstyle['mask'] = 'none' + + if dispprofile: + pr.disable() + s = io.StringIO() + sortby = SortKey.CUMULATIVE + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + dh.debug(s.getvalue()) + + +if __name__ == "__main__": + dh.Run_SI_Extension(Homogenizer(), "Homogenizer") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/image_helpers.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/image_helpers.py new file mode 100644 index 0000000..5ed5a83 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/image_helpers.py @@ -0,0 +1,675 @@ +# Stuff copied from image_extract and image_embed +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Extract embedded images. +""" + +from __future__ import unicode_literals + +import os +import dhelpers # noqa +import inkex +from inkex import Image + +try: + from base64 import decodebytes + from base64 import b64encode +except ImportError: + from base64 import decodestring as decodebytes + from base64 import b64encode + + +def mime_to_ext(mime): + """Return an extension based on the mime type""" + # Most extensions are automatic (i.e. extension is same as minor part of mime type) + part = mime.split("/", 1)[1].split("+")[0] + return "." + { + # These are the non-matching ones. + "svg+xml": ".svg", + "jpeg": ".jpg", + "icon": ".ico", + }.get(part, part) + + +def extract_image(node, save_to): + """Extract the node as if it were an image.""" + xlink = node.get("xlink:href") + if not xlink.startswith("data:"): + return # Not embedded image data + + # This call will raise AbortExtension if the document wasn't saved + # and the user is trying to extract them to a relative directory. + # save_to = self.absolute_href(self.options.filepath, default=None) + # Make the target directory if it doesn't exist yet. + if not os.path.isdir(save_to): + os.makedirs(save_to) + + try: + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + except ValueError: + inkex.errormsg("Invalid image format found") + return + + if base != "base64": + inkex.errormsg("Can't decode encoding: {}".format(base)) + return + + file_ext = mime_to_ext(mimetype) + + pathwext = os.path.join(save_to, node.get("id") + file_ext) + if os.path.isfile(pathwext): + inkex.errormsg( + "Can't extract image, filename already used: {}".format(pathwext) + ) + return + + # self.msg('Image extracted to: {}'.format(pathwext)) + + with open(pathwext, "wb") as fhl: + fhl.write(decodebytes(data.encode("utf-8"))) + + # absolute for making in-mem cycles work + node.set("xlink:href", os.path.realpath(pathwext)) + + +""" +Embed images so they are base64 encoded data inside the svg. +""" + +from inkex.localization import inkex_gettext as _ + +try: + import urllib.request as urllib + import urllib.parse as urlparse + from base64 import encodebytes +except ImportError: + # python2 compatibility, remove when python3 only. + import urllib + import urlparse + from base64 import encodestring as encodebytes + + +def embed_image(node, svg_dir): + """Embed the data of the selected Image Tag element""" + xlink = node.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": + # No need, data already embedded + return + if xlink is None: + inkex.errormsg( + _('Attribute "xlink:href" not set on node {}.'.format(node.get_id())) + ) + return + + url = urlparse.urlparse(xlink) + href = urllib.url2pathname(url.path) + + # Primary location always the filename itself, we allow this + # call to search the user's home folder too. + path = absolute_href2(href or "", svg_dir) + + # Backup directory where we can find the image + if not os.path.isfile(path): + path = node.get("sodipodi:absref", path) + + if not os.path.isfile(path): + inkex.errormsg(_('File not found "{}". Unable to embed image.').format(path)) + return + + with open(path, "rb") as handle: + # Don't read the whole file to check the header + file_type = get_type(path, handle.read(10)) + handle.seek(0) + + if file_type: + # Future: Change encodestring to encodebytes when python3 only + node.set( + "xlink:href", + "data:{};base64,{}".format( + file_type, encodebytes(handle.read()).decode("ascii") + ), + ) + node.pop("sodipodi:absref") + else: + inkex.errormsg( + _( + "%s is not of type image/png, image/jpeg, " + "image/bmp, image/gif, image/tiff, or image/x-icon" + ) + % path + ) + + +def embed_external_image(el, filename): + """Embed the data of the selected Image Tag element""" + if filename is not None: + with open(filename, "rb") as handle: + # Don't read the whole file to check the header + file_type = get_type(filename, handle.read(10)) + handle.seek(0) + + if file_type: + # Future: Change encodestring to encodebytes when python3 only + el.set( + "xlink:href", + "data:{};base64,{}".format( + file_type, encodebytes(handle.read()).decode("ascii") + ), + ) + el.pop("sodipodi:absref") + else: + inkex.errormsg( + _( + "%s is not of type image/png, image/jpeg, " + "image/bmp, image/gif, image/tiff, or image/x-icon" + ) + % filename + ) + + +# Check if image is linked or embedded. If linked, check if path is valid +def check_linked(node, svg_dir): + """Embed the data of the selected Image Tag element""" + xlink = node.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": + return False, None + url = urlparse.urlparse(xlink) + href = urllib.url2pathname(url.path) + path = absolute_href2(href or "", svg_dir) + if not os.path.isfile(path): + path = node.get("sodipodi:absref", path) + fileexists = os.path.isfile(path) + if not (fileexists): + return True, None + else: + return True, path + + +# Gets the type of an image element +def get_image_type(node, svg_dir): + xlink = node.get("xlink:href") + + if not xlink.startswith("data:"): + # Linked image + xlink = node.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": + # No need, data already embedded + return None + if xlink is None: + print(_('Attribute "xlink:href" not set on node {}.'.format(node.get_id()))) + return None + + url = urlparse.urlparse(xlink) + href = urllib.url2pathname(url.path) + + # Primary location always the filename itself, we allow this + # call to search the user's home folder too. + path = absolute_href2(href or "", svg_dir) + + # Backup directory where we can find the image + if not os.path.isfile(path): + path = node.get("sodipodi:absref", path) + + if not os.path.isfile(path): + print(_('File not found "{}". Unable to embed image.').format(path)) + return None + + with open(path, "rb") as handle: + # Don't read the whole file to check the header + file_type = get_type(path, handle.read(10)) + return file_type + else: + try: + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + except ValueError: + print("Invalid image format found") + return None + + if base != "base64": + print("Can't decode encoding: {}".format(base)) + return None + return mimetype + + +def get_type(path, header): + """Basic magic header checker, returns mime type""" + for head, mime in ( + (b"\x89PNG", "image/png"), + (b"\xff\xd8", "image/jpeg"), + (b"BM", "image/bmp"), + (b"GIF87a", "image/gif"), + (b"GIF89a", "image/gif"), + (b"MM\x00\x2a", "image/tiff"), + (b"II\x2a\x00", "image/tiff"), + ): + if header.startswith(head): + return mime + + # ico files lack any magic... therefore we check the filename instead + for ext, mime in ( + # official IANA registered MIME is 'image/vnd.microsoft.icon' tho + (".ico", "image/x-icon"), + (".svg", "image/svg+xml"), + ): + if path.endswith(ext): + return mime + return None + + +# if __name__ == '__main__': +# EmbedImage().run() + + +# Modification to the built-in absolute_href function that doesn't require an +# extension class +def absolute_href2(filename, svg_dir, default="~/"): + """ + Process the filename such that it's turned into an absolute filename + with the working directory being the directory of the loaded svg. + + User's home folder is also resolved. So '~/a.png` will be `/home/bob/a.png` + + Default is a fallback working directory to use if the svg's filename is not + available, if you set default to None, then the user will be given errors if + there's no working directory available from Inkscape. + """ + filename = os.path.expanduser(filename) + if not os.path.isabs(filename): + filename = os.path.expanduser(filename) + if not os.path.isabs(filename): + filename = os.path.join(svg_dir, filename) + return os.path.realpath(os.path.expanduser(filename)) + + +# Stuff by David Burghoff +try: + from PIL import Image as ImagePIL + + hasPIL = True +except: + hasPIL = False + +# def remove_alpha(imin): +# background = ImagePIL.new('RGBA', imin.size, (255,255,255)) +# alpha_composite = ImagePIL.alpha_composite(background, imin) +# alpha_composite_3 = alpha_composite.convert('RGB') +# return alpha_composite_3 + + +# def remove_alpha(imin, background): +# # background = ImagePIL.new('RGBA', imin.size, (255,255,255)) +# alpha_composite = ImagePIL.alpha_composite(background, imin) +# alpha_composite_3 = alpha_composite.convert("RGB") +# return alpha_composite_3 + +# Convert and crop transparent image to JPG +# Requires the transparent version (imin) as well as the opaque version (opaqueimin) +# def to_jpeg(imin, opaqueimin, imout): +# with ImagePIL.open(imin) as im: +# with ImagePIL.open(opaqueimin) as oim: +# bbox = im.getbbox() +# # # Composite to remove transparent regions +# # compim = remove_alpha(im, imb) + +# # Crop to non-transparent region only +# # left,upper,right,lower (left & upper pixel is non-zero corner, right-1 & lower-1 is non-zero corner) +# if bbox is not None: +# oim = oim.crop(bbox) +# bbox = [ +# bbox[0] / im.size[0], +# bbox[1] / im.size[1], +# bbox[2] / im.size[0], +# bbox[3] / im.size[1], +# ] +# # normalize to original size +# oim.convert('RGB').save(imout) +# return imout, bbox + + +def to_jpeg(imin, imout): + with ImagePIL.open(imin) as im: + im.convert("RGB").save(imout) + + +def crop_image(imin): + with ImagePIL.open(imin) as im: + bbox = im.getbbox() + # left,upper,right,lower (left & upper pixel is non-zero corner, right-1 & lower-1 is non-zero corner) + if bbox is not None: + cropim = im.crop(bbox) + bbox = [ + bbox[0] / im.size[0], + bbox[1] / im.size[1], + bbox[2] / im.size[0], + bbox[3] / im.size[1], + ] + # normalized to original size + cropim.save(imin) + return imin, bbox + + +# Extract an embedded image +def extract_image_simple(node, save_to_base): + """Extract the node as if it were an image.""" + xlink = node.get("xlink:href") + data = xlink[5:] + try: + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + except ValueError: + return None + file_ext = mime_to_ext(mimetype) + pathwext = save_to_base + file_ext + # inkex.utils.debug(pathwext) + with open(pathwext, "wb") as fhl: + fhl.write(decodebytes(data.encode("utf-8"))) + return pathwext + + +# Get the size of an embedded image +def embedded_size(node): + xlink = node.get("xlink:href") + try: + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + return len(decodebytes(data.encode("utf-8"))) + except (ValueError, TypeError): + return None + + +# Get the data string of an embedded image with the alpha stripped out +# This allows images to be identified after conversion to PDF +def Stripped_Alpha_String(el): + import tempfile + + tf = tempfile.NamedTemporaryFile().name + fullpath = extract_image_simple(el, tf) + + with ImagePIL.open(fullpath) as im: + newfile = strip_ext(fullpath) + ".png" + + return str(im.size) + + # im.convert('RGB').save(newfile) + + # with open(newfile, "rb") as handle: + # # Don't read the whole file to check the header + # file_type = get_type(newfile, handle.read(10)) + # handle.seek(0) + + # if file_type: + # return "data:{};base64,{}".format( + # file_type, encodebytes(handle.read()).decode("ascii")) + # return None + + +def Make_Data_Image(datastr): + data = [ord(c) for c in datastr] + # inkex.utils.debug(data) + import numpy as np + + im = ImagePIL.fromarray(np.array([data], dtype="uint8")) + + import tempfile + + tf = tempfile.NamedTemporaryFile().name + newfile = tf + ".png" + im.save(newfile) + + with open(newfile, "rb") as handle: + # Don't read the whole file to check the header + file_type = get_type(newfile, handle.read(10)) + handle.seek(0) + + if file_type: + return "data:{};base64,{}".format( + file_type, encodebytes(handle.read()).decode("ascii") + ) + return None + + +def Read_Data_Image(imstr): + data = imstr[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + import io + + im = ImagePIL.open(io.BytesIO(decodebytes(data.encode("utf-8")))) + + import numpy as np + + npa = np.asarray(im) + if len(npa.shape) == 3: + data = npa[:, :, 0] + else: + data = npa + return "".join([chr(v) for v in list(data.ravel())]) + + +import io + + +def str_to_ImagePIL(imstr): + try: + data = imstr[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + im = ImagePIL.open(io.BytesIO(decodebytes(data.encode("utf-8")))) + return im + except: + return None + + +def ImagePIL_to_str(im): + try: + img_byte_arr = io.BytesIO() + im.save(img_byte_arr, format="png") + vals = img_byte_arr.getvalue() + file_type = get_type(None, vals[0:10]) + if file_type: + return "data:{};base64,{}".format( + file_type, encodebytes(vals).decode("ascii") + ) + except: + return None + + +# Get just the alpha channel of an image, which can be turned into a mask +# def make_alpha_mask(fin,maskout): +# from PIL import Image, ImageOps +# with Image.open(fin) as im: +# (r,g,b,a)=im.split() +# # (r,g,b)=Image.new('RGB',im.size,'black').split() +# # nim = Image.merge('RGBA',(r,g,b,ImageOps.invert(a))) +# # (r,g,b)=Image.new('RGB',im.size,'black').split() +# nim = Image.merge('L',(a,)) +# nim.save(maskout) + +# # Get just the alpha channel of an image, which can be turned into a mask +# def make_rgb(fin,rgbout): +# from PIL import Image, ImageOps +# with Image.open(fin) as im: +# (r,g,b,a)=im.split() +# # (r,g,b)=Image.new('RGB',im.size,'black').split() +# # nim = Image.merge('RGBA',(r,g,b,ImageOps.invert(a))) +# # (r,g,b)=Image.new('RGB',im.size,'black').split() +# nim = Image.merge('RGB',(r,g,b)) +# nim.save(rgbout) + + +# Strips image extensions +def strip_ext(fnin): + # strip existing extension + if fnin[-4:].lower() in [".png", ".gif", "jpg", "tif"]: + fnin = fnin[0:-4] + if fnin[-5:].lower() in ["jpeg", "tiff"]: + fnin = fnin[0:-5] + return fnin + + +# Extracts embedded images or returns the path of linked ones +def extract_img_file(el, svg_dir, newpath): + islinked, validpath = check_linked(el, svg_dir) + if islinked and validpath is not None: + impath = validpath + madenew = False + elif islinked and validpath is None: + impath = None + madenew = False + else: + # inkex.utils.debug(newpath) + extract = extract_image_simple(el, strip_ext(newpath)) + if extract is not None: + impath = extract + madenew = True + else: + impath = None + madenew = False + return impath, islinked + + +# For images with alpha=0 pixels, set the RGB of those pixels based on another +# image. This is usually the same image with a background and with other objects. +# For those pixels, alpha is then set to 1 (out of 255), which prevents the PDF +# renderer from replacing those pixels with black. This avoids the 'gray ring' +# issue that can happen on PDF exports. +def Set_Alpha0_RGB(img, imgref): + im1 = ImagePIL.open(img).convert("RGBA") + im2 = ImagePIL.open(imgref).convert("RGBA") + import numpy as np + + d1 = np.asarray(im1) + d2 = np.asarray(im2) + a = d1[:, :, 3] + nd = np.stack( + ( + np.where(a == 0, d2[:, :, 0], d1[:, :, 0]), + np.where(a == 0, d2[:, :, 1], d1[:, :, 1]), + np.where(a == 0, d2[:, :, 2], d1[:, :, 2]), + np.where(a == 0, 1 * np.ones_like(a), a), + ), + 2, + ) + ImagePIL.fromarray(nd).save(img) + # inkex.utils.debug(img) + anyalpha0 = np.where(a == 0, True, False).any() + return anyalpha0 + + +# Crop a list of images based on the transparency of the first one +# Returns the normalized bounding box, which we need later +def crop_images(ims_in): + bbox = None + with ImagePIL.open(ims_in[0]) as ref_im: + bbox = ref_im.getbbox() + nsz = ref_im.size + + if bbox is not None: + # left,upper,right,lower (left & upper pixel is non-zero corner, right-1 & lower-1 is non-zero corner) + nbbox = [ + bbox[0] / nsz[0], + bbox[1] / nsz[1], + bbox[2] / nsz[0], + bbox[3] / nsz[1], + ] # normalize to original size + for imf in ims_in: + with ImagePIL.open(imf) as im: + im.crop(bbox).save(imf) + return nbbox + else: + return None + + +# Get the absolute locations of all linked images when called by an extension +# Needed because the temp file has a different location from the actual one +def get_linked_locations(slf): + llocations = dict() + images = slf.svg.xpath("//svg:image") + for node in images: + xlink = node.get("xlink:href") + if xlink is not None and xlink[:5] != "data:": + try: + import urllib.request as urllib + import urllib.parse as urlparse + except ImportError: + # python2 compatibility, remove when python3 only. + import urllib + import urlparse + + url = urlparse.urlparse(xlink) + href = urllib.url2pathname(url.path) + + # Look relative to the *temporary* filename instead of the original filename. + try: # v1.2 forward + path = slf.absolute_href( + href or "", cwd=os.path.dirname(slf.options.input_file) + ) + except: # pre-v1.2 + # Primary location always the filename itself, we allow this + # call to search the user's home folder too. + path = slf.absolute_href(href or "") + + # Backup directory where we can find the image + if not os.path.isfile(path): + path = node.get("sodipodi:absref", path) + + if os.path.isfile(path): + llocations[node.get_id()] = path + else: + llocations[node.get_id()] = None + return llocations + + +# Get the absolute locations of all linked images when the absolute path is known +def get_linked_locations_file(fin, svg): + llocations = dict() + images = svg.xpath("//svg:image") + for node in images: + xlink = node.get("xlink:href") + if xlink is not None and xlink[:5] != "data:": + try: + import urllib.request as urllib + import urllib.parse as urlparse + except ImportError: + # python2 compatibility, remove when python3 only. + import urllib + import urlparse + + url = urlparse.urlparse(xlink) + href = urllib.url2pathname(url.path) + + path = absolute_href2(href or "", os.path.dirname(fin)) + + # Backup directory where we can find the image + if not os.path.isfile(path): + path = node.get("sodipodi:absref", path) + + if os.path.isfile(path): + llocations[node.get_id()] = path + else: + llocations[node.get_id()] = None + return llocations diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/__init__.py new file mode 100644 index 0000000..fa049ea --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/__init__.py @@ -0,0 +1,32 @@ +# coding=utf-8 +""" +This describes the core API for the inkex core modules. + +This provides the basis from which you can develop your inkscape extension. +""" + +# pylint: disable=wildcard-import +import sys + +from .extensions import * +from .utils import AbortExtension, DependencyError, Boolean, errormsg +from .styles import * +from .paths import Path, CubicSuperPath # Path commands are not exported +from .colors import * +from .transforms import * +from .elements import * + +# legacy proxies +from .deprecated import Effect +from .deprecated import localize +from .deprecated import debug + +# legacy functions +from .deprecated import are_near_relative +from .deprecated import unittouu + +MIN_VERSION = (3, 7) +if sys.version_info < MIN_VERSION: + sys.exit("Inkscape extensions require Python 3.7 or greater.") + +__version__ = "1.3.0" # Version number for inkex; may differ from Inkscape version. diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/base.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/base.py new file mode 100644 index 0000000..14de8b8 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/base.py @@ -0,0 +1,562 @@ +# coding=utf-8 +# +# Copyright (c) 2018 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +The ultimate base functionality for every Inkscape extension. +""" + +import io +import os +import re +import sys +import copy + +from typing import ( + Dict, + List, + Tuple, + Type, + Optional, + Callable, + Any, + Union, + IO, + TYPE_CHECKING, + cast, +) + +from argparse import ArgumentParser, Namespace +from lxml import etree + +from .interfaces.IElement import IBaseElement, ISVGDocumentElement +from .utils import filename_arg, AbortExtension, ABORT_STATUS, errormsg, do_nothing +from .elements._parser import load_svg +from .elements._utils import NSS +from .localization import localize + + +class InkscapeExtension: + """ + The base class extension, provides argument parsing and basic + variable handling features. + """ + + multi_inx = False # Set to true if this class is used by multiple inx files. + extra_nss = {} # type: Dict[str, str] + + # Provide a unique value to allow detection of no argument specified + # for `output` parameter of `run()`, not even `None`; this has to be an io + # type for type checking purposes: + output_unspecified = io.StringIO("") + + def __init__(self): + # type: () -> None + NSS.update(self.extra_nss) + self.file_io = None # type: Optional[IO] + self.options = Namespace() + self.document = None # type: Union[None, bytes, str, etree.element] + self.arg_parser = ArgumentParser(description=self.__doc__) + + self.arg_parser.add_argument( + "input_file", + nargs="?", + metavar="INPUT_FILE", + type=filename_arg, + help="Filename of the input file (default is stdin)", + default=None, + ) + + self.arg_parser.add_argument( + "--output", + type=str, + default=None, + help="Optional output filename for saving the result (default is stdout).", + ) + + self.add_arguments(self.arg_parser) + + localize() + + def add_arguments(self, pars): + # type: (ArgumentParser) -> None + """Add any extra arguments to your extension handle, use: + + def add_arguments(self, pars): + pars.add_argument("--num-cool-things", type=int, default=3) + pars.add_argument("--pos-in-doc", type=str, default="doobry") + """ + # No extra arguments by default so super is not required + + def parse_arguments(self, args): + # type: (List[str]) -> None + """Parse the given arguments and set 'self.options'""" + self.options = self.arg_parser.parse_args(args) + + def arg_method(self, prefix="method"): + # type: (str) -> Callable[[str], Callable[[Any], Any]] + """Used by add_argument to match a tab selection with an object method + + pars.add_argument("--tab", type=self.arg_method(), default="foo") + ... + self.options.tab(arguments) + ... + .. code-block:: python + .. def method_foo(self, arguments): + .. # do something + """ + + def _inner(value): + name = f"""{prefix}_{value.strip('"').lower()}""".replace("-", "_") + try: + return getattr(self, name) + except AttributeError as error: + if name.startswith("_"): + return do_nothing + raise AbortExtension(f"Can not find method {name}") from error + + return _inner + + @staticmethod + def arg_number_ranges(): + """Parses a number descriptor. e.g: + ``1,2,4-5,7,9-`` is parsed to ``1, 2, 4, 5, 7, 9, 10, ..., lastvalue`` + + .. versionadded:: 1.2 + + Usage: + + .. code-block:: python + + # in add_arguments() + pars.add_argument("--pages", type=self.arg_number_ranges(), default=1-) + # later on, pages is then a list of ints + pages = self.options.pages(lastvalue) + + """ + + def _inner(value): + def method(pages, lastvalue, startvalue=1): + # replace ranges, such as -3, 10- with startvalue,2,3,10..lastvalue + pages = re.sub( + r"(\d+|)\s?-\s?(\d+|)", + lambda m: ",".join( + map( + str, + range( + int(m.group(1) or startvalue), + int(m.group(2) or lastvalue) + 1, + ), + ) + ) + if not (m.group(1) or m.group(2)) == "" + else "", + pages, + ) + + pages = map(int, re.findall(r"(\d+)", pages)) + pages = tuple({i for i in pages if i <= lastvalue}) + return pages + + return lambda lastvalue, startvalue=1: method( + value, lastvalue, startvalue=startvalue + ) + + return _inner + + @staticmethod + def arg_class(options: List[Type]) -> Callable[[str], Any]: + """Used by add_argument to match an option with a class + + Types to choose from are given by the options list + + .. versionadded:: 1.2 + + Usage: + + .. code-block:: python + + pars.add_argument("--class", type=self.arg_class([ClassA, ClassB]), + default="ClassA") + """ + + def _inner(value: str): + name = value.strip('"') + for i in options: + if name == i.__name__: + return i + raise AbortExtension(f"Can not find class {name}") + + return _inner + + def debug(self, msg): + # type: (str) -> None + """Write a debug message""" + errormsg(f"DEBUG<{type(self).__name__}> {msg}\n") + + @staticmethod + def msg(msg): + # type: (str) -> None + """Write a non-error message""" + errormsg(msg) + + def run(self, args=None, output=output_unspecified): + # type: (Optional[List[str]], Union[str, IO]) -> None + """Main entrypoint for any Inkscape Extension""" + try: + if args is None: + args = sys.argv[1:] + + self.parse_arguments(args) + if self.options.input_file is None: + self.options.input_file = sys.stdin + elif "DOCUMENT_PATH" not in os.environ: + os.environ["DOCUMENT_PATH"] = self.options.input_file + + self.bin_stdout = None + if self.options.output is None: + # If no output was specified, attempt to extract a binary + # output from stdout, and if that doesn't seem possible, + # punt and try whatever stream stdout is: + if output is InkscapeExtension.output_unspecified: + output = sys.stdout + if "b" not in getattr(output, "mode", "") and not isinstance( + output, (io.RawIOBase, io.BufferedIOBase) + ): + if hasattr(output, "buffer"): + output = output.buffer # type: ignore + elif hasattr(output, "fileno"): + self.bin_stdout = os.fdopen( + output.fileno(), "wb", closefd=False + ) + output = self.bin_stdout + self.options.output = output + + self.load_raw() + self.save_raw(self.effect()) + except AbortExtension as err: + errormsg(str(err)) + sys.exit(ABORT_STATUS) + finally: + self.clean_up() + + def load_raw(self): + # type: () -> None + """Load the input stream or filename, save everything to self""" + if isinstance(self.options.input_file, str): + # pylint: disable=consider-using-with + self.file_io = open(self.options.input_file, "rb") + document = self.load(self.file_io) + else: + document = self.load(self.options.input_file) + self.document = document + + def save_raw(self, ret): + # type: (Any) -> None + """Save to the output stream, use everything from self""" + if self.has_changed(ret): + if isinstance(self.options.output, str): + with open(self.options.output, "wb") as stream: + self.save(stream) + else: + if sys.platform == "win32" and not "PYTEST_CURRENT_TEST" in os.environ: + # When calling an extension from within Inkscape on Windows, + # Python thinks that the output stream is seekable + # (https://gitlab.com/inkscape/inkscape/-/issues/3273) + self.options.output.seekable = lambda self: False + + def seek_replacement(offset: int, whence: int = 0): + raise AttributeError( + "We can't seek in the stream passed by Inkscape on Windows" + ) + + def tell_replacement(): + raise AttributeError( + "We can't tell in the stream passed by Inkscape on Windows" + ) + + # Some libraries (e.g. ZipFile) don't query seekable, but check for an error + # on seek + self.options.output.seek = seek_replacement + self.options.output.tell = tell_replacement + self.save(self.options.output) + + def load(self, stream): + # type: (IO) -> str + """Takes the input stream and creates a document for parsing""" + raise NotImplementedError(f"No input handle for {self.name}") + + def save(self, stream): + # type: (IO) -> None + """Save the given document to the output file""" + raise NotImplementedError(f"No output handle for {self.name}") + + def effect(self): + # type: () -> Any + """Apply some effects on the document or local context""" + raise NotImplementedError(f"No effect handle for {self.name}") + + def has_changed(self, ret): # pylint: disable=no-self-use + # type: (Any) -> bool + """Return true if the output should be saved""" + return ret is not False + + def clean_up(self): + # type: () -> None + """Clean up any open handles and other items""" + if hasattr(self, "bin_stdout"): + if self.bin_stdout is not None: + self.bin_stdout.close() + if self.file_io is not None: + self.file_io.close() + + @classmethod + def svg_path(cls, default=None): + # type: (Optional[str]) -> Optional[str] + """ + Return the folder the svg is contained in. + Returns None if there is no file. + + .. versionchanged:: 1.1 + A default path can be given which is returned in case no path to the + SVG file can be determined. + """ + path = cls.document_path() + if path: + return os.path.dirname(path) + if default: + return default + return path # Return None or '' for context + + @classmethod + def ext_path(cls): + # type: () -> str + """Return the folder the extension script is in""" + return os.path.dirname(sys.modules[cls.__module__].__file__ or "") + + @classmethod + def get_resource(cls, name, abort_on_fail=True): + # type: (str, bool) -> str + """Return the full filename of the resource in the extension's dir + + .. versionadded:: 1.1""" + filename = cls.absolute_href(name, cwd=cls.ext_path()) + if abort_on_fail and not os.path.isfile(filename): + raise AbortExtension(f"Could not find resource file: {filename}") + return filename + + @classmethod + def document_path(cls): + # type: () -> Optional[str] + """Returns the saved location of the document + + * Normal return is a string containing the saved location + * Empty string means the document was never saved + * 'None' means this version of Inkscape doesn't support DOCUMENT_PATH + + DO NOT READ OR WRITE TO THE DOCUMENT FILENAME! + + * Inkscape may have not written the latest changes, leaving you reading old + data. + * Inkscape will not respect anything you write to the file, causing data loss. + + .. versionadded:: 1.1 + """ + return os.environ.get("DOCUMENT_PATH", None) + + @classmethod + def absolute_href(cls, filename, default="~/", cwd=None): + # type: (str, str, Optional[str]) -> str + """ + Process the filename such that it's turned into an absolute filename + with the working directory being the directory of the loaded svg. + + User's home folder is also resolved. So '~/a.png` will be `/home/bob/a.png` + + Default is a fallback working directory to use if the svg's filename is not + available. + + .. versionchanged:: 1.1 + If you set default to None, then the user will be given errors if + there's no working directory available from Inkscape. + """ + filename = os.path.expanduser(filename) + if not os.path.isabs(filename): + filename = os.path.expanduser(filename) + if not os.path.isabs(filename): + if cwd is None: + cwd = cls.svg_path(default) + if cwd is None: + raise AbortExtension( + "Can not use relative path, Inkscape isn't telling us the " + "current working directory." + ) + if cwd == "": + raise AbortExtension( + "The SVG must be saved before you can use relative paths." + ) + filename = os.path.join(cwd, filename) + return os.path.realpath(os.path.expanduser(filename)) + + @property + def name(self): + # type: () -> str + """Return a fixed name for this extension""" + return type(self).__name__ + + +if TYPE_CHECKING: + _Base = InkscapeExtension +else: + _Base = object + + +class TempDirMixin(_Base): # pylint: disable=abstract-method + """ + Provide a temporary directory for extensions to stash files. + """ + + dir_suffix = "" + dir_prefix = "inktmp" + + def __init__(self, *args, **kwargs): + self.tempdir = None + self._tempdir = None + super().__init__(*args, **kwargs) + + def load_raw(self): + # type: () -> None + """Create the temporary directory""" + # pylint: disable=import-outside-toplevel + from tempfile import TemporaryDirectory + + # Need to hold a reference to the Directory object or else it might get GC'd + self._tempdir = TemporaryDirectory( # pylint: disable=consider-using-with + prefix=self.dir_prefix, suffix=self.dir_suffix + ) + self.tempdir = os.path.realpath(self._tempdir.name) + super().load_raw() + + def clean_up(self): + # type: () -> None + """Delete the temporary directory""" + self.tempdir = None + # if the file does not exist, _tempdir is never set. + if self._tempdir is not None: + self._tempdir.cleanup() + super().clean_up() + + +class SvgInputMixin(_Base): # pylint: disable=too-few-public-methods, abstract-method + """ + Expects the file input to be an svg document and will parse it. + """ + + # Select all objects if none are selected + select_all: Tuple[Type[IBaseElement], ...] = () + + def __init__(self): + super().__init__() + + self.arg_parser.add_argument( + "--id", + action="append", + type=str, + dest="ids", + default=[], + help="id attribute of object to manipulate", + ) + + self.arg_parser.add_argument( + "--selected-nodes", + action="append", + type=str, + dest="selected_nodes", + default=[], + help="id:subpath:position of selected nodes, if any", + ) + + def load(self, stream): + # type: (IO) -> etree + """Load the stream as an svg xml etree and make a backup""" + document = load_svg(stream) + self.original_document = copy.deepcopy(document) + self.svg: ISVGDocumentElement = document.getroot() + self.svg.selection.set(*self.options.ids) + if not self.svg.selection and self.select_all: + self.svg.selection = self.svg.descendants().filter(*self.select_all) + return document + + +class SvgOutputMixin(_Base): # pylint: disable=too-few-public-methods, abstract-method + """ + Expects the output document to be an svg document and will write an etree xml. + + A template can be specified to kick off the svg document building process. + """ + + template = """ + """ + + @classmethod + def get_template(cls, **kwargs): + """ + Opens a template svg document for building, the kwargs + MUST include all the replacement values in the template, the + default template has 'width' and 'height' of the document. + """ + kwargs.setdefault("unit", "") + return load_svg(str(cls.template.format(**kwargs))) + + def save(self, stream): + # type: (IO) -> None + """Save the svg document to the given stream""" + if isinstance(self.document, (bytes, str)): + document = self.document + elif "Element" in type(self.document).__name__: + # isinstance can't be used here because etree is broken + doc = cast(etree, self.document) + document = doc.getroot().tostring() + else: + raise ValueError( + f"Unknown type of document: {type(self.document).__name__} can not" + + "save." + ) + + try: + stream.write(document) + except TypeError: + # we hope that this happens only when document needs to be encoded + stream.write(document.encode("utf-8")) # type: ignore + + +class SvgThroughMixin(SvgInputMixin, SvgOutputMixin): # pylint: disable=abstract-method + """ + Combine the input and output svg document handling (usually for effects). + """ + + def has_changed(self, ret): # pylint: disable=unused-argument + # type: (Any) -> bool + """Return true if the svg document has changed""" + original = etree.tostring(self.original_document) + result = etree.tostring(self.document) + return original != result diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/bezier.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/bezier.py new file mode 100644 index 0000000..976fdab --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/bezier.py @@ -0,0 +1,488 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name,too-many-locals +# +""" +Bezier calculations +""" + +import cmath +import math + +import numpy + +from .transforms import DirectedLineSegment +from .localization import inkex_gettext as _ + +# bez = ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) + + +def pointdistance(point_a, point_b): + """The straight line distance between two points""" + return math.sqrt( + ((point_b[0] - point_a[0]) ** 2) + ((point_b[1] - point_a[1]) ** 2) + ) + + +def between_point(point_a, point_b, time=0.5): + """Returns the point between point a and point b""" + return point_a[0] + time * (point_b[0] - point_a[0]), point_a[1] + time * ( + point_b[1] - point_a[1] + ) + + +def percent_point(point_a, point_b, percent=50.0): + """Returns between_point but takes percent instead of 0.0-1.0""" + return between_point(point_a, point_b, percent / 100.0) + + +def root_wrapper(root_a, root_b, root_c, root_d): + """Get the Cubic function, moic formular of roots, simple root""" + if root_a: + # Monics formula, see + # http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots + mono_a, mono_b, mono_c = (root_b / root_a, root_c / root_a, root_d / root_a) + m = 2.0 * mono_a**3 - 9.0 * mono_a * mono_b + 27.0 * mono_c + k = mono_a**2 - 3.0 * mono_b + n = m**2 - 4.0 * k**3 + w1 = -0.5 + 0.5 * cmath.sqrt(-3.0) + w2 = -0.5 - 0.5 * cmath.sqrt(-3.0) + if n < 0: + m1 = pow(complex((m + cmath.sqrt(n)) / 2), 1.0 / 3) + n1 = pow(complex((m - cmath.sqrt(n)) / 2), 1.0 / 3) + else: + if m + math.sqrt(n) < 0: + m1 = -pow(-(m + math.sqrt(n)) / 2, 1.0 / 3) + else: + m1 = pow((m + math.sqrt(n)) / 2, 1.0 / 3) + if m - math.sqrt(n) < 0: + n1 = -pow(-(m - math.sqrt(n)) / 2, 1.0 / 3) + else: + n1 = pow((m - math.sqrt(n)) / 2, 1.0 / 3) + return ( + -1.0 / 3 * (mono_a + m1 + n1), + -1.0 / 3 * (mono_a + w1 * m1 + w2 * n1), + -1.0 / 3 * (mono_a + w2 * m1 + w1 * n1), + ) + if root_b: + det = root_c**2.0 - 4.0 * root_b * root_d + if det: + return ( + (-root_c + cmath.sqrt(det)) / (2.0 * root_b), + (-root_c - cmath.sqrt(det)) / (2.0 * root_b), + ) + return (-root_c / (2.0 * root_b),) + if root_c: + return (1.0 * (-root_d / root_c),) + return () + + +def bezlenapprx(sp1, sp2): + """Return the aproximate length between two beziers""" + return ( + pointdistance(sp1[1], sp1[2]) + + pointdistance(sp1[2], sp2[0]) + + pointdistance(sp2[0], sp2[1]) + ) + + +def cspbezsplit(sp1, sp2, time=0.5): + """Split a cubic bezier at the time period""" + m1 = tpoint(sp1[1], sp1[2], time) + m2 = tpoint(sp1[2], sp2[0], time) + m3 = tpoint(sp2[0], sp2[1], time) + m4 = tpoint(m1, m2, time) + m5 = tpoint(m2, m3, time) + m = tpoint(m4, m5, time) + return [[sp1[0][:], sp1[1][:], m1], [m4, m, m5], [m3, sp2[1][:], sp2[2][:]]] + + +def cspbezsplitatlength(sp1, sp2, length=0.5, tolerance=0.001): + """Split a cubic bezier at length""" + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + time = beziertatlength(bez, length, tolerance) + return cspbezsplit(sp1, sp2, time) + + +def cspseglength(sp1, sp2, tolerance=0.001): + """Get cubic bezier segment length""" + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + return bezierlength(bez, tolerance) + + +def csplength(csp): + """Get cubic bezier length""" + total = 0 + lengths = [] + for sp in csp: + lengths.append([]) + for i in range(1, len(sp)): + l = cspseglength(sp[i - 1], sp[i]) + lengths[-1].append(l) + total += l + return lengths, total + + +def bezierparameterize(bez): + """Return the bezier parameter size + Converts the bezier parametrisation from the default form + P(t) = (1-t)³ P_1 + 3(1-t)²t P_2 + 3(1-t)t² P_3 + t³ x_4 + to the a form which can be differentiated more easily + P(t) = a t³ + b t² + c t + P0 + + Args: + bez (List[Tuple[float, float]]): the Bezier curve. The elements of the list the + coordinates of the points (in this order): Start point, Start control point, + End control point, End point. + + Returns: + Tuple[float, float, float, float, float, float, float, float]: + the values ax, ay, bx, by, cx, cy, x0, y0 + """ + ((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)) = bez + # parametric bezier + x0 = bx0 + y0 = by0 + cx = 3 * (bx1 - x0) + bx = 3 * (bx2 - bx1) - cx + ax = bx3 - x0 - cx - bx + cy = 3 * (by1 - y0) + by = 3 * (by2 - by1) - cy + ay = by3 - y0 - cy - by + + return ax, ay, bx, by, cx, cy, x0, y0 + + +def linebezierintersect(arg_a, bez): + """Where a line and bezier intersect""" + ((lx1, ly1), (lx2, ly2)) = arg_a + # parametric line + dd = lx1 + cc = lx2 - lx1 + bb = ly1 + aa = ly2 - ly1 + + if aa: + coef1 = cc / aa + coef2 = 1 + else: + coef1 = 1 + coef2 = aa / cc + + ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize(bez) + # cubic intersection coefficients + a = coef1 * ay - coef2 * ax + b = coef1 * by - coef2 * bx + c = coef1 * cy - coef2 * cx + d = coef1 * (y0 - bb) - coef2 * (x0 - dd) + + roots = root_wrapper(a, b, c, d) + retval = [] + for i in roots: + if isinstance(i, complex) and i.imag == 0: + i = i.real + if not isinstance(i, complex) and 0 <= i <= 1: + retval.append(bezierpointatt(bez, i)) + return retval + + +def bezierpointatt(bez, t): + """Get coords at the given time point along a bezier curve""" + ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize(bez) + x = ax * (t**3) + bx * (t**2) + cx * t + x0 + y = ay * (t**3) + by * (t**2) + cy * t + y0 + return x, y + + +def bezierslopeatt(bez, t): + """Get slope at the given time point along a bezier curve + The slope is computed as (dx, dy) where dx = df_x(t)/dt and dy = df_y(t)/dt. + Note that for lines P1=P2 and P3=P4, so the slope at the end points is dx=dy=0 + (slope not defined). + + Args: + bez (List[Tuple[float, float]]): the Bezier curve. The elements of the list the + coordinates of the points (in this order): Start point, Start control point, + End control point, End point. + t (float): time in the interval [0, 1] + + Returns: + Tuple[float, float]: x and y increment + """ + ax, ay, bx, by, cx, cy, _, _ = bezierparameterize(bez) + dx = 3 * ax * (t**2) + 2 * bx * t + cx + dy = 3 * ay * (t**2) + 2 * by * t + cy + return dx, dy + + +def beziertatslope(bez, d): + """Reverse; get time from slope along a bezier curve""" + ax, ay, bx, by, cx, cy, _, _ = bezierparameterize(bez) + (dy, dx) = d + # quadratic coefficients of slope formula + if dx: + slope = 1.0 * (dy / dx) + a = 3 * ay - 3 * ax * slope + b = 2 * by - 2 * bx * slope + c = cy - cx * slope + elif dy: + slope = 1.0 * (dx / dy) + a = 3 * ax - 3 * ay * slope + b = 2 * bx - 2 * by * slope + c = cx - cy * slope + else: + return [] + + roots = root_wrapper(0, a, b, c) + retval = [] + for i in roots: + if isinstance(i, complex) and i.imag == 0: + i = i.real + if not isinstance(i, complex) and 0 <= i <= 1: + retval.append(i) + return retval + + +def tpoint(p1, p2, t): + """Linearly interpolate between p1 and p2. + + t = 0.0 returns p1, t = 1.0 returns p2. + + :return: Interpolated point + :rtype: tuple + + :param p1: First point as sequence of two floats + :param p2: Second point as sequence of two floats + :param t: Number between 0.0 and 1.0 + :type t: float + """ + x1, y1 = p1 + x2, y2 = p2 + return x1 + t * (x2 - x1), y1 + t * (y2 - y1) + + +def beziersplitatt(bez, t): + """Split bezier at given time""" + ((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)) = bez + m1 = tpoint((bx0, by0), (bx1, by1), t) + m2 = tpoint((bx1, by1), (bx2, by2), t) + m3 = tpoint((bx2, by2), (bx3, by3), t) + m4 = tpoint(m1, m2, t) + m5 = tpoint(m2, m3, t) + m = tpoint(m4, m5, t) + + return ((bx0, by0), m1, m4, m), (m, m5, m3, (bx3, by3)) + + +def addifclose(bez, l, error=0.001): + """Gravesen, Add if the line is closed, in-place addition to array l""" + box = 0 + for i in range(1, 4): + box += pointdistance(bez[i - 1], bez[i]) + chord = pointdistance(bez[0], bez[3]) + if (box - chord) > error: + first, second = beziersplitatt(bez, 0.5) + addifclose(first, l, error) + addifclose(second, l, error) + else: + l[0] += (box / 2.0) + (chord / 2.0) + + +# balfax, balfbx, balfcx, balfay, balfby, balfcy = 0, 0, 0, 0, 0, 0 + + +def balf(t, args): + """Bezier Arc Length Function""" + ax, bx, cx, ay, by, cy = args + retval = (ax * (t**2) + bx * t + cx) ** 2 + (ay * (t**2) + by * t + cy) ** 2 + return math.sqrt(retval) + + +def simpson(start, end, maxiter, tolerance, bezier_args): + """Calculate the length of a bezier curve using Simpson's algorithm: + http://steve.hollasch.net/cgindex/curves/cbezarclen.html + + Args: + start (int): Start time (between 0 and 1) + end (int): End time (between start time and 1) + maxiter (int): Maximum number of iterations. If not a power of 2, the algorithm + will behave like the value is set to the next power of 2. + tolerance (float): maximum error ratio + bezier_args (list): arguments as computed by bezierparametrize() + + Returns: + float: the appoximate length of the bezier curve + """ + + n = 2 + multiplier = (end - start) / 6.0 + endsum = balf(start, bezier_args) + balf(end, bezier_args) + interval = (end - start) / 2.0 + asum = 0.0 + bsum = balf(start + interval, bezier_args) + est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum)) + est0 = 2.0 * est1 + # print(multiplier, endsum, interval, asum, bsum, est1, est0) + while n < maxiter and abs(est1 - est0) > tolerance: + n *= 2 + multiplier /= 2.0 + interval /= 2.0 + asum += bsum + bsum = 0.0 + est0 = est1 + for i in range(1, n, 2): + bsum += balf(start + (i * interval), bezier_args) + est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum)) + # print(multiplier, endsum, interval, asum, bsum, est1, est0) + return est1 + + +def bezierlength(bez, tolerance=0.001, time=1.0): + """Get length of bezier curve""" + ax, ay, bx, by, cx, cy, _, _ = bezierparameterize(bez) + return simpson(0.0, time, 4096, tolerance, [3 * ax, 2 * bx, cx, 3 * ay, 2 * by, cy]) + + +def beziertatlength(bez, l=0.5, tolerance=0.001): + """Get bezier curve time at the length specified""" + curlen = bezierlength(bez, tolerance, 1.0) + time = 1.0 + tdiv = time + targetlen = l * curlen + diff = curlen - targetlen + while abs(diff) > tolerance: + tdiv /= 2.0 + if diff < 0: + time += tdiv + else: + time -= tdiv + curlen = bezierlength(bez, tolerance, time) + diff = curlen - targetlen + return time + + +def maxdist(bez): + """Get maximum distance within bezier curve""" + seg = DirectedLineSegment(bez[0], bez[3]) + return max(seg.distance_to_point(*bez[1]), seg.distance_to_point(*bez[2])) + + +def cspsubdiv(csp, flat): + """Sub-divide cubic sub-paths""" + for sp in csp: + subdiv(sp, flat) + + +def subdiv(sp, flat, i=1): + """sub divide bezier curve""" + while i < len(sp): + p0 = sp[i - 1][1] + p1 = sp[i - 1][2] + p2 = sp[i][0] + p3 = sp[i][1] + + bez = (p0, p1, p2, p3) + mdist = maxdist(bez) + if mdist <= flat: + i += 1 + else: + one, two = beziersplitatt(bez, 0.5) + sp[i - 1][2] = one[1] + sp[i][0] = two[2] + p = [one[2], one[3], two[1]] + sp[i:1] = [p] + + +def csparea(csp): + """Get area in cubic sub-path""" + MAT_AREA = numpy.array( + [[0, 2, 1, -3], [-2, 0, 1, 1], [-1, -1, 0, 2], [3, -1, -2, 0]] + ) + area = 0.0 + for sp in csp: + if len(sp) < 2: + continue + for x, coord in enumerate(sp): # calculate polygon area + area += 0.5 * sp[x - 1][1][0] * (coord[1][1] - sp[x - 2][1][1]) + for i in range(1, len(sp)): # add contribution from cubic Bezier + vec_x = numpy.array( + [sp[i - 1][1][0], sp[i - 1][2][0], sp[i][0][0], sp[i][1][0]] + ) + vec_y = numpy.array( + [sp[i - 1][1][1], sp[i - 1][2][1], sp[i][0][1], sp[i][1][1]] + ) + vex = numpy.matmul(vec_x, MAT_AREA) + area += 0.15 * numpy.matmul(vex, vec_y.T) + return -area + + +def cspcofm(csp): + """Get cubic sub-path coefficient""" + MAT_COFM_0 = numpy.array( + [[0, 35, 10, -45], [-35, 0, 12, 23], [-10, -12, 0, 22], [45, -23, -22, 0]] + ) + + MAT_COFM_1 = numpy.array( + [[0, 15, 3, -18], [-15, 0, 9, 6], [-3, -9, 0, 12], [18, -6, -12, 0]] + ) + + MAT_COFM_2 = numpy.array( + [[0, 12, 6, -18], [-12, 0, 9, 3], [-6, -9, 0, 15], [18, -3, -15, 0]] + ) + + MAT_COFM_3 = numpy.array( + [[0, 22, 23, -45], [-22, 0, 12, 10], [-23, -12, 0, 35], [45, -10, -35, 0]] + ) + area = csparea(csp) + xc = 0.0 + yc = 0.0 + if abs(area) < 1.0e-8: + raise ValueError(_("Area is zero, cannot calculate Center of Mass")) + for sp in csp: + for x, coord in enumerate(sp): # calculate polygon moment + xc += ( + sp[x - 1][1][1] + * (sp[x - 2][1][0] - coord[1][0]) + * (sp[x - 2][1][0] + sp[x - 1][1][0] + coord[1][0]) + / 6 + ) + yc += ( + sp[x - 1][1][0] + * (coord[1][1] - sp[x - 2][1][1]) + * (sp[x - 2][1][1] + sp[x - 1][1][1] + coord[1][1]) + / 6 + ) + for i in range(1, len(sp)): # add contribution from cubic Bezier + vec_x = numpy.array( + [sp[i - 1][1][0], sp[i - 1][2][0], sp[i][0][0], sp[i][1][0]] + ) + vec_y = numpy.array( + [sp[i - 1][1][1], sp[i - 1][2][1], sp[i][0][1], sp[i][1][1]] + ) + + def _mul(MAT, vec_x=vec_x, vec_y=vec_y): + return numpy.matmul(numpy.matmul(vec_x, MAT), vec_y.T) + + vec_t = numpy.array( + [_mul(MAT_COFM_0), _mul(MAT_COFM_1), _mul(MAT_COFM_2), _mul(MAT_COFM_3)] + ) + xc += numpy.matmul(vec_x, vec_t.T) / 280 + yc += numpy.matmul(vec_y, vec_t.T) / 280 + return -xc / area, -yc / area diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/colors.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/colors.py new file mode 100644 index 0000000..637522f --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/colors.py @@ -0,0 +1,535 @@ +# coding=utf-8 +# +# Copyright (C) 2006 Jos Hirth, kaioa.com +# Copyright (C) 2007 Aaron C. Spike +# Copyright (C) 2009 Monash University +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Basic color controls +""" + + +# All the names that get added to the inkex API itself. +__all__ = ("Color", "ColorError", "ColorIdError") + +SVG_COLOR = { + "aliceblue": "#f0f8ff", + "antiquewhite": "#faebd7", + "aqua": "#00ffff", + "aquamarine": "#7fffd4", + "azure": "#f0ffff", + "beige": "#f5f5dc", + "bisque": "#ffe4c4", + "black": "#000000", + "blanchedalmond": "#ffebcd", + "blue": "#0000ff", + "blueviolet": "#8a2be2", + "brown": "#a52a2a", + "burlywood": "#deb887", + "cadetblue": "#5f9ea0", + "chartreuse": "#7fff00", + "chocolate": "#d2691e", + "coral": "#ff7f50", + "cornflowerblue": "#6495ed", + "cornsilk": "#fff8dc", + "crimson": "#dc143c", + "cyan": "#00ffff", + "darkblue": "#00008b", + "darkcyan": "#008b8b", + "darkgoldenrod": "#b8860b", + "darkgray": "#a9a9a9", + "darkgreen": "#006400", + "darkgrey": "#a9a9a9", + "darkkhaki": "#bdb76b", + "darkmagenta": "#8b008b", + "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", + "darkorchid": "#9932cc", + "darkred": "#8b0000", + "darksalmon": "#e9967a", + "darkseagreen": "#8fbc8f", + "darkslateblue": "#483d8b", + "darkslategray": "#2f4f4f", + "darkslategrey": "#2f4f4f", + "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", + "deeppink": "#ff1493", + "deepskyblue": "#00bfff", + "dimgray": "#696969", + "dimgrey": "#696969", + "dodgerblue": "#1e90ff", + "firebrick": "#b22222", + "floralwhite": "#fffaf0", + "forestgreen": "#228b22", + "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", + "ghostwhite": "#f8f8ff", + "gold": "#ffd700", + "goldenrod": "#daa520", + "gray": "#808080", + "grey": "#808080", + "green": "#008000", + "greenyellow": "#adff2f", + "honeydew": "#f0fff0", + "hotpink": "#ff69b4", + "indianred": "#cd5c5c", + "indigo": "#4b0082", + "ivory": "#fffff0", + "khaki": "#f0e68c", + "lavender": "#e6e6fa", + "lavenderblush": "#fff0f5", + "lawngreen": "#7cfc00", + "lemonchiffon": "#fffacd", + "lightblue": "#add8e6", + "lightcoral": "#f08080", + "lightcyan": "#e0ffff", + "lightgoldenrodyellow": "#fafad2", + "lightgray": "#d3d3d3", + "lightgreen": "#90ee90", + "lightgrey": "#d3d3d3", + "lightpink": "#ffb6c1", + "lightsalmon": "#ffa07a", + "lightseagreen": "#20b2aa", + "lightskyblue": "#87cefa", + "lightslategray": "#778899", + "lightslategrey": "#778899", + "lightsteelblue": "#b0c4de", + "lightyellow": "#ffffe0", + "lime": "#00ff00", + "limegreen": "#32cd32", + "linen": "#faf0e6", + "magenta": "#ff00ff", + "maroon": "#800000", + "mediumaquamarine": "#66cdaa", + "mediumblue": "#0000cd", + "mediumorchid": "#ba55d3", + "mediumpurple": "#9370db", + "mediumseagreen": "#3cb371", + "mediumslateblue": "#7b68ee", + "mediumspringgreen": "#00fa9a", + "mediumturquoise": "#48d1cc", + "mediumvioletred": "#c71585", + "midnightblue": "#191970", + "mintcream": "#f5fffa", + "mistyrose": "#ffe4e1", + "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", + "navy": "#000080", + "oldlace": "#fdf5e6", + "olive": "#808000", + "olivedrab": "#6b8e23", + "orange": "#ffa500", + "orangered": "#ff4500", + "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", + "palegreen": "#98fb98", + "paleturquoise": "#afeeee", + "palevioletred": "#db7093", + "papayawhip": "#ffefd5", + "peachpuff": "#ffdab9", + "peru": "#cd853f", + "pink": "#ffc0cb", + "plum": "#dda0dd", + "powderblue": "#b0e0e6", + "purple": "#800080", + "rebeccapurple": "#663399", + "red": "#ff0000", + "rosybrown": "#bc8f8f", + "royalblue": "#4169e1", + "saddlebrown": "#8b4513", + "salmon": "#fa8072", + "sandybrown": "#f4a460", + "seagreen": "#2e8b57", + "seashell": "#fff5ee", + "sienna": "#a0522d", + "silver": "#c0c0c0", + "skyblue": "#87ceeb", + "slateblue": "#6a5acd", + "slategray": "#708090", + "slategrey": "#708090", + "snow": "#fffafa", + "springgreen": "#00ff7f", + "steelblue": "#4682b4", + "tan": "#d2b48c", + "teal": "#008080", + "thistle": "#d8bfd8", + "tomato": "#ff6347", + "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", + "white": "#ffffff", + "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", + "yellowgreen": "#9acd32", + "none": None, +} +COLOR_SVG = {value: name for name, value in SVG_COLOR.items()} + + +def is_color(color): + """Determine if it is a color that we can use. If not, leave it unchanged.""" + try: + return bool(Color(color)) + except ColorError: + return False + + +def constrain(minim, value, maxim, channel): + """Returns the value so long as it is between min and max values""" + if channel == "h": # Hue + return value % maxim # Wrap around hue value + return min([maxim, max([minim, value])]) + + +class ColorError(KeyError): + """Specific color parsing error""" + + +class ColorIdError(ColorError): + """Special color error for gradient and color stop ids""" + + +class Color(list): + """An RGB array for the color + + Can be constructed from valid CSS color attributes, as well as + tuple/list + color space. Percentage values are supported. + + .. versionchanged:: 1.2 + Clarification with respect to values denoting unity: For RGB color channels, + "1.0", 1.0 and "100%" are treated as 255, while "1" and 1 are treated as 1. + + """ + + red = property( + lambda self: self.to_rgb()[0], lambda self, value: self._set(0, value) + ) + green = property( + lambda self: self.to_rgb()[1], lambda self, value: self._set(1, value) + ) + blue = property( + lambda self: self.to_rgb()[2], lambda self, value: self._set(2, value) + ) + alpha = property( + lambda self: self.to_rgba()[3], + lambda self, value: self._set(3, value, ("rgba",)), + ) + hue = property( + lambda self: self.to_hsl()[0], lambda self, value: self._set(0, value, ("hsl",)) + ) + saturation = property( + lambda self: self.to_hsl()[1], lambda self, value: self._set(1, value, ("hsl",)) + ) + lightness = property( + lambda self: self.to_hsl()[2], lambda self, value: self._set(2, value, ("hsl",)) + ) + + def __init__(self, color=None, space="rgb"): + super().__init__() + if isinstance(color, Color): + space, color = color.space, list(color) + + if isinstance(color, str): + # String from xml or css attributes + space, color = self.parse_str(color.strip()) + + if isinstance(color, int): + # Number from arg parser colour value + space, color = self.parse_int(color) + + # Empty list means 'none', or no color + if color is None: + color = [] + + if not isinstance(color, (list, tuple)): + raise ColorError("Not a known a color value") + + self.space = space + try: + for val in color: + self.append(val) + except ValueError as error: + raise ColorError("Bad color list") from error + + def __hash__(self): + """Allow colors to be hashable""" + return tuple(self.to_rgba()).__hash__() + + def _set(self, index, value, spaces=("rgb", "rgba")): + """Set the color value in place, limits setter to specific color space""" + # Named colors are just rgb, so dump name memory + if self.space == "named": + self.space = "rgb" + if not self.space in spaces: + if index == 3 and self.space == "rgb": + # Special, add alpha, don't convert back to rgb + self.space = "rgba" + self.append(constrain(0.0, float(value), 1.0, "a")) + return + # Set in other colour space and convert back and forth + target = self.to(spaces[0]) + target[index] = constrain(0, int(value), 255, spaces[0][index]) + self[:] = target.to(self.space) + return + self[index] = constrain(0, int(value), 255, spaces[0][index]) + + def append(self, val): + """Append a value to the local list""" + if len(self) == len(self.space): + raise ValueError("Can't add any more values to color.") + + if isinstance(val, str): + val = val.strip() + if val.endswith("%"): + val = float(val.strip("%")) / 100 + elif "." in val: + val = float(val) + else: + val = int(val) + + end_type = int + if len(self) == 3: # Alpha value + val = min([1.0, val]) + end_type = float + elif isinstance(val, float) and val <= 1.0: + val *= 255 + + if isinstance(val, (int, float)): + super().append(max(end_type(val), 0)) + + @staticmethod + def parse_str(color): + """Creates a rgb int array""" + # Handle pre-defined svg color values + if color and color.lower() in SVG_COLOR: + return "named", Color.parse_str(SVG_COLOR[color.lower()])[1] + + if color is None: + return "rgb", None + + if color.startswith("url("): + raise ColorIdError("Color references other element id, e.g. a gradient") + + # Next handle short colors (css: #abc -> #aabbcc) + if color.startswith("#"): + # Remove any icc or ilab directives + # FUTURE: We could use icc or ilab information + col = color.split(" ")[0] + if len(col) == 4: + # pylint: disable=consider-using-f-string + col = "#{1}{1}{2}{2}{3}{3}".format(*col) + + # Convert hex to integers + try: + return "rgb", (int(col[1:3], 16), int(col[3:5], 16), int(col[5:], 16)) + except ValueError as error: + raise ColorError(f"Bad RGB hex color value {col}") from error + + # Handle other css color values + elif "(" in color and ")" in color: + space, values = color.lower().strip().strip(")").split("(") + return space, values.split(",") + + try: + return Color.parse_int(int(color)) + except ValueError: + pass + + raise ColorError(f"Unknown color format: {color}") + + @staticmethod + def parse_int(color): + """Creates an rgb or rgba from a long int""" + space = "rgb" + color = [ + ((color >> 24) & 255), # red + ((color >> 16) & 255), # green + ((color >> 8) & 255), # blue + ((color & 255) / 255.0), # opacity + ] + if color[-1] == 1.0: + color.pop() + else: + space = "rgba" + return space, color + + def __str__(self): + """int array to #rrggbb""" + # pylint: disable=consider-using-f-string + if not self: + return "none" + if self.space == "named": + rgbhex = "#{0:02x}{1:02x}{2:02x}".format(*self) + if rgbhex in COLOR_SVG: + return COLOR_SVG[rgbhex] + self.space = "rgb" + if self.space == "rgb": + return "#{0:02x}{1:02x}{2:02x}".format(*self) + if self.space == "rgba": + if self[3] == 1.0: + return "rgb({:g}, {:g}, {:g})".format(*self[:3]) + return "rgba({:g}, {:g}, {:g}, {:g})".format(*self) + if self.space == "hsl": + return "hsl({0:g}, {1:g}, {2:g})".format(*self) + raise ColorError(f"Can't print colour space '{self.space}'") + + def __int__(self): + """int array to large integer""" + if not self: + return -1 + color = self.to_rgba() + return ( + (color[0] << 24) + + (color[1] << 16) + + (color[2] << 8) + + (int(color[3] * 255)) + ) + + def to(self, space): # pylint: disable=invalid-name + """Dynamic caller for to_hsl, to_rgb, etc""" + return getattr(self, "to_" + space)() + + def to_hsl(self): + """Turn this color into a Hue/Saturation/Lightness colour space""" + if not self and self.space in ("rgb", "named"): + return self.to_rgb().to_hsl() + if self.space == "hsl": + return self + if self.space in ("named"): + return self.to_rgb().to_hsl() + if self.space == "rgb": + return Color(rgb_to_hsl(*self.to_floats()), space="hsl") + raise ColorError(f"Unknown color conversion {self.space}->hsl") + + def to_rgb(self): + """Turn this color into a Red/Green/Blue colour space""" + if not self and self.space in ("rgb", "named"): + return Color([0, 0, 0]) + if self.space == "rgb": + return self + if self.space in ("rgba", "named"): + return Color(self[:3], space="rgb") + if self.space == "hsl": + return Color(hsl_to_rgb(*self.to_floats()), space="rgb") + raise ColorError(f"Unknown color conversion {self.space}->rgb") + + def to_rgba(self, alpha=1.0): + """Turn this color isn't an RGB with Alpha colour space""" + if self.space == "rgba": + return self + return Color(self.to_rgb() + [alpha], "rgba") + + def to_floats(self): + """Returns the colour values as percentage floats (0.0 - 1.0)""" + return [val / 255.0 for val in self] + + def to_named(self): + """Convert this color to a named color if possible""" + if not self: + return Color() + return Color(COLOR_SVG.get(str(self), str(self))) + + def interpolate(self, other, fraction): + """Interpolate two colours by the given fraction + + .. versionadded:: 1.1""" + from .tween import ColorInterpolator # pylint: disable=import-outside-toplevel + + return ColorInterpolator(self, other).interpolate(fraction) + + @staticmethod + def isnone(x): + """Checks if a given color is none + + .. versionadded:: 1.2""" + + if x is None or (isinstance(x, str) and x.lower() == "none"): + return True + return False + + @staticmethod + def iscolor(x, accept_none=False): + """Checks if a given value can be parsed as a color + + .. versionadded:: 1.2""" + if isinstance(x, str) and (accept_none or not (Color.isnone(x))): + try: + Color(x) + return True + except ColorError: + pass + if isinstance(x, Color): + return True + return False + + +def rgb_to_hsl(red, green, blue): + """RGB to HSL colour conversion""" + rgb_max = max(red, green, blue) + rgb_min = min(red, green, blue) + delta = rgb_max - rgb_min + hsl = [0.0, 0.0, (rgb_max + rgb_min) / 2.0] + if delta != 0: + if hsl[2] <= 0.5: + hsl[1] = delta / (rgb_max + rgb_min) + else: + hsl[1] = delta / (2 - rgb_max - rgb_min) + + if red == rgb_max: + hsl[0] = (green - blue) / delta + elif green == rgb_max: + hsl[0] = 2.0 + (blue - red) / delta + elif blue == rgb_max: + hsl[0] = 4.0 + (red - green) / delta + + hsl[0] /= 6.0 + if hsl[0] < 0: + hsl[0] += 1 + if hsl[0] > 1: + hsl[0] -= 1 + return hsl + + +def hsl_to_rgb(hue, sat, light): + """HSL to RGB Color Conversion""" + if sat == 0: + return [light, light, light] # Gray + + if light < 0.5: + val2 = light * (1 + sat) + else: + val2 = light + sat - light * sat + val1 = 2 * light - val2 + return [ + _hue_to_rgb(val1, val2, hue * 6 + 2.0), + _hue_to_rgb(val1, val2, hue * 6), + _hue_to_rgb(val1, val2, hue * 6 - 2.0), + ] + + +def _hue_to_rgb(val1, val2, hue): + if hue < 0: + hue += 6.0 + if hue > 6: + hue -= 6.0 + if hue < 1: + return val1 + (val2 - val1) * hue + if hue < 3: + return val2 + if hue < 4: + return val1 + (val2 - val1) * (4 - hue) + return val1 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/command.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/command.py new file mode 100644 index 0000000..6bb7292 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/command.py @@ -0,0 +1,347 @@ +# coding=utf-8 +# +# Copyright (C) 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +This API provides methods for calling Inkscape to execute a given +Inkscape command. This may be needed for various compiling options +(e.g., png), running other extensions or performing other options only +available via the shell API. + +Best practice is to avoid using this API except when absolutely necessary, +since it is resource-intensive to invoke a new Inkscape instance. + +However, in any circumstance when it is necessary to call Inkscape, it +is strongly recommended that you do so through this API, rather than calling +it yourself, to take advantage of the security settings and testing functions. + +""" + +import os +import re +import sys +from shutil import which as warlock + +from subprocess import Popen, PIPE +from tempfile import TemporaryDirectory +from typing import List +from lxml.etree import ElementTree + +from .elements import SvgDocumentElement + +INKSCAPE_EXECUTABLE_NAME = os.environ.get("INKSCAPE_COMMAND") +if INKSCAPE_EXECUTABLE_NAME is None: + if sys.platform == "win32": + # prefer inkscape.exe over inkscape.com which spawns a command window + INKSCAPE_EXECUTABLE_NAME = "inkscape.exe" + else: + INKSCAPE_EXECUTABLE_NAME = "inkscape" + + +class CommandNotFound(IOError): + """Command is not found""" + + +class ProgramRunError(ValueError): + """A specialized ValueError that is raised when a call to an external command fails. + It stores additional information about a failed call to an external program. + + If only the ``program`` parameter is given, it is interpreted as the error message. + Otherwise, the error message is compiled from all constructor parameters.""" + + program: str + """The absolute path to the called executable""" + + returncode: int + """Return code of the program call""" + + stderr: str + """stderr stream output of the call""" + + stdout: str + """stdout stream output of the call""" + + arguments: List + """Arguments of the call""" + + def __init__(self, program, returncode=None, stderr=None, stdout=None, args=None): + self.program = program + self.returncode = returncode + self.stderr = stderr + self.stdout = stdout + self.arguments = args + super().__init__(str(self)) + + def __str__(self): + if self.returncode is None: + return self.program + return ( + f"Return Code: {self.returncode}: {self.stderr}\n{self.stdout}" + f"\nargs: {self.args}" + ) + + +def which(program): + """ + Attempt different methods of trying to find if the program exists. + """ + if os.path.isabs(program) and os.path.isfile(program): + return program + # On Windows, shutil.which may give preference to .py files in the current directory + # (such as pdflatex.py), e.g. if .PY is in pathext, because the current directory is + # prepended to PATH. This can be suppressed by explicitly appending the current + # directory. + + try: + if sys.platform == "win32": + prog = warlock(program, path=os.environ["PATH"] + ";" + os.curdir) + if prog: + return prog + except ImportError: + pass + + try: + # Python3 only version of which + prog = warlock(program) + if prog: + return prog + except ImportError: + pass # python2 + + # There may be other methods for doing a `which` command for other + # operating systems; These should go here as they are discovered. + + raise CommandNotFound(f"Can not find the command: '{program}'") + + +def write_svg(svg, *filename): + """Writes an svg to the given filename""" + filename = os.path.join(*filename) + if os.path.isfile(filename): + return filename + with open(filename, "wb") as fhl: + if isinstance(svg, SvgDocumentElement): + svg = ElementTree(svg) + if hasattr(svg, "write"): + # XML document + svg.write(fhl) + elif isinstance(svg, bytes): + fhl.write(svg) + else: + raise ValueError("Not sure what type of SVG data this is.") + return filename + + +def to_arg(arg, oldie=False): + """Convert a python argument to a command line argument""" + if isinstance(arg, (tuple, list)): + (arg, val) = arg + arg = "-" + arg + if len(arg) > 2 and not oldie: + arg = "-" + arg + if val is True: + return arg + if val is False: + return None + return f"{arg}={str(val)}" + return str(arg) + + +def to_args(prog, *positionals, **arguments): + """Compile arguments and keyword arguments into a list of strings which Popen will + understand. + + :param prog: + Program executable prepended to the output. + :type first: ``str`` + + :Arguments: + * (``str``) -- String added as given + * (``tuple``) -- Ordered version of Keyword Arguments, see below + + :Keyword Arguments: + * *name* (``str``) -- + Becomes ``--name="val"`` + * *name* (``bool``) -- + Becomes ``--name`` + * *name* (``list``) -- + Becomes ``--name="val1"`` ... + * *n* (``str``) -- + Becomes ``-n=val`` + * *n* (``bool``) -- + Becomes ``-n`` + + :return: Returns a list of compiled arguments ready for Popen. + :rtype: ``list[str]`` + """ + args = [prog] + oldie = arguments.pop("oldie", False) + for arg, value in arguments.items(): + arg = arg.replace("_", "-").strip() + + if isinstance(value, tuple): + value = list(value) + elif not isinstance(value, list): + value = [value] + + for val in value: + args.append(to_arg((arg, val), oldie)) + + args += [to_arg(pos, oldie) for pos in positionals if pos is not None] + # Filter out empty non-arguments + return [arg for arg in args if arg is not None] + + +def to_args_sorted(prog, *positionals, **arguments): + """same as :func:`to_args`, but keyword arguments are sorted beforehand + + .. versionadded:: 1.2""" + return to_args(prog, *positionals, **dict(sorted(arguments.items()))) + + +def _call(program, *args, **kwargs): + stdin = kwargs.pop("stdin", None) + if isinstance(stdin, str): + stdin = stdin.encode("utf-8") + inpipe = PIPE if stdin else None + + args = to_args(which(program), *args, **kwargs) + + kwargs = {} + if sys.platform == "win32": + kwargs["creationflags"] = 0x08000000 # create no console window + + with Popen( + args, + shell=False, # Never have shell=True + stdin=inpipe, # StdIn not used (yet) + stdout=PIPE, # Grab any output (return it) + stderr=PIPE, # Take all errors, just incase + **kwargs, + ) as process: + (stdout, stderr) = process.communicate(input=stdin) + if process.returncode == 0: + return stdout + raise ProgramRunError(program, process.returncode, stderr, stdout, args) + + +def call(program, *args, **kwargs): + """ + Generic caller to open any program and return its stdout:: + + stdout = call('executable', arg1, arg2, dash_dash_arg='foo', d=True, ...) + + Will raise :class:`ProgramRunError` if return code is not 0. + + Keyword arguments: + return_binary: Should stdout return raw bytes (default: False) + + .. versionadded:: 1.1 + stdin: The string or bytes containing the stdin (default: None) + + All other arguments converted using :func:`to_args` function. + """ + # We use this long input because it's less likely to conflict with --binary= + binary = kwargs.pop("return_binary", False) + stdout = _call(program, *args, **kwargs) + # Convert binary to string when we wish to have strings we do this here + # so the mock tests will also run the conversion (always returns bytes) + if not binary and isinstance(stdout, bytes): + return stdout.decode(sys.stdout.encoding or "utf-8") + return stdout + + +def inkscape(svg_file, *args, **kwargs): + """ + Call Inkscape with the given svg_file and the given arguments, see call(). + + Returns the stdout of the call. + + .. versionchanged:: 1.3 + If the "actions" kwargs parameter is passed, it is checked whether the length of + the action string might lead to issues with the Windows CLI call character + limit. In this case, Inkscape is called in `--shell` + mode and the actions are fed in via stdin. This avoids violating the character + limit for command line arguments on Windows, which results in errors like this: + `[WinError 206] The filename or extension is too long`. + This workaround is also possible when calling Inkscape with long arguments + to `--export-id` and `--query-id`, by converting the call to the appropriate + action sequence. The stdout is cleaned to resemble non-interactive mode. + """ + os.environ["SELF_CALL"] = "true" + actions = kwargs.get("actions", None) + strip_stdout = False + # Keep some safe margin to the 8191 character limit. + if actions is not None and len(actions) > 7000: + args = args + ("--shell",) + kwargs["stdin"] = actions + kwargs.pop("actions") + strip_stdout = True + stdout = call(INKSCAPE_EXECUTABLE_NAME, svg_file, *args, **kwargs) + if strip_stdout: + split = re.split(r"\n> ", stdout) + if len(split) > 1: + if "\n" in split[1]: + stdout = "\n".join(split[1].split("\n")[1:]) + else: + stdout = "" + return stdout + + +def inkscape_command(svg, select=None, actions=None, *args, **kwargs): + """ + Executes Inkscape batch actions with the given input and returns a new . + + inkscape_command('', [select=...], [actions=...], [...]) + """ + with TemporaryDirectory(prefix="inkscape-command") as tmpdir: + svg_file = write_svg(svg, tmpdir, "input.svg") + select = ("select", select) if select else None + inkscape( + svg_file, + select, + batch_process=True, + export_overwrite=True, + actions=actions, + *args, + **kwargs, + ) + with open(svg_file, "rb") as fhl: + return fhl.read() + + +def take_snapshot(svg, dirname, name="snapshot", ext="png", dpi=96, **kwargs): + """ + Take a snapshot of the given svg file. + + Resulting filename is yielded back, after generator finishes, the + file is deleted so you must deal with the file inside the for loop. + """ + svg_file = write_svg(svg, dirname, name + ".svg") + ext_file = os.path.join(dirname, name + "." + str(ext).lower()) + inkscape( + svg_file, export_dpi=dpi, export_filename=ext_file, export_type=ext, **kwargs + ) + return ext_file + + +def is_inkscape_available(): + """Return true if the Inkscape executable is available.""" + try: + return bool(which(INKSCAPE_EXECUTABLE_NAME)) + except CommandNotFound: + return False diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/css.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/css.py new file mode 100644 index 0000000..ebb9234 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/css.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# +# Copyright (C) 2021 - Jonathan Neuhauser +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +"""Toplevel CSS utils that do not depend on other inkex functionality + +.. versionadded:: 1.2 + Previously a part of :py:mod:`inkex.styles`""" + + +import re +import cssselect + + +class ConditionalRule: + """A single css rule + + .. versionchanged:: 1.2 + The CSS rule is now processed using cssselect.""" + + step_to_xpath = [ + # namespace addition + (re.compile(r"(::|\/)([a-z]+)(?=\W)(?!-)"), r"\1svg:\2"), + ] + + def __init__(self, rule): + self.rule = rule.strip() + self.selector = cssselect.parse(self.rule)[0] + + def __str__(self): + return self.rule + + def to_xpath(self): + """Attempt to convert the rule into a simplified xpath""" + # the space in the end is needed for the negative lookbehind in the regex, will + # be removed on return + ret = cssselect.HTMLTranslator().selector_to_xpath(self.selector) + " " + for matcher, replacer in self.step_to_xpath: + ret = matcher.sub(replacer, ret) + return ret.strip() + + def get_specificity(self): + """gets the css specificity of this selector + + .. versionadded:: 1.2""" + return self.selector.specificity() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/README.rst b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/README.rst new file mode 100644 index 0000000..df3e2dc --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/README.rst @@ -0,0 +1,4 @@ +# coding=utf-8This directory contains compatibility layers for all the `simple` modules, such as `simplepath` and `simplestyle` + +This directory IS NOT a module path, to denote this we are using a dash in the name and there is no '__init__.py' + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/bezmisc.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/bezmisc.py new file mode 100644 index 0000000..92b63f6 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/bezmisc.py @@ -0,0 +1,46 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name,unused-argument +"""Deprecated bezmisc API""" + +from inkex.deprecated import deprecate +from inkex import bezier + +bezierparameterize = deprecate(bezier.bezierparameterize) +linebezierintersect = deprecate(bezier.linebezierintersect) +bezierpointatt = deprecate(bezier.bezierpointatt) +bezierslopeatt = deprecate(bezier.bezierslopeatt) +beziertatslope = deprecate(bezier.beziertatslope) +tpoint = deprecate(bezier.tpoint) +beziersplitatt = deprecate(bezier.beziersplitatt) +pointdistance = deprecate(bezier.pointdistance) +Gravesen_addifclose = deprecate(bezier.addifclose) +balf = deprecate(bezier.balf) +bezierlengthSimpson = deprecate(bezier.bezierlength) +beziertatlength = deprecate(bezier.beziertatlength) +bezierlength = bezierlengthSimpson + + +@deprecate +def Simpson(func, a, b, n_limit, tolerance): + """bezier.simpson(a, b, n_limit, tolerance, balf_arguments)""" + raise AttributeError( + """Because bezmisc.Simpson used global variables, it's not possible to + call the replacement code automatically. In fact it's unlikely you were + using the code or functionality you think you were since it's a highly + broken way of writing python.""" + ) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cspsubdiv.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cspsubdiv.py new file mode 100644 index 0000000..91b2237 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cspsubdiv.py @@ -0,0 +1,25 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name +"""Deprecated cspsubdiv API""" + +from inkex.deprecated import deprecate +from inkex import bezier + +maxdist = deprecate(bezier.maxdist) +cspsubdiv = deprecate(bezier.cspsubdiv) +subdiv = deprecate(bezier.subdiv) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cubicsuperpath.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cubicsuperpath.py new file mode 100644 index 0000000..cb6f124 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/cubicsuperpath.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name +"""Deprecated cubic super path API""" + +from inkex.deprecated import deprecate +from inkex import paths + + +@deprecate +def ArcToPath(p1, params): + return paths.arc_to_path(p1, params) + + +@deprecate +def CubicSuperPath(simplepath): + return paths.Path(simplepath).to_superpath() + + +@deprecate +def unCubicSuperPath(csp): + return paths.CubicSuperPath(csp).to_path().to_arrays() + + +@deprecate +def parsePath(d): + return paths.CubicSuperPath(paths.Path(d)) + + +@deprecate +def formatPath(p): + return str(paths.Path(unCubicSuperPath(p))) + + +matprod = deprecate(paths.matprod) +rotmat = deprecate(paths.rotmat) +applymat = deprecate(paths.applymat) +norm = deprecate(paths.norm) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/ffgeom.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/ffgeom.py new file mode 100644 index 0000000..2feb3bd --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/ffgeom.py @@ -0,0 +1,92 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name,missing-docstring +"""Deprecated ffgeom API""" + +from collections import namedtuple + +from inkex.deprecated import deprecate +from inkex.transforms import DirectedLineSegment as NewSeg + +try: + NaN = float("NaN") +except ValueError: + PosInf = 1e300000 + NaN = PosInf / PosInf + + +class Point(namedtuple("Point", "x y")): + __slots__ = () + + def __getitem__(self, key): + if isinstance(key, str): + key = "xy".index(key) + return super(Point, self).__getitem__(key) + + +class Segment(NewSeg): + @deprecate + def __init__(self, e0, e1): + """inkex.transforms.Segment(((x1, y1), (x2, y2)))""" + if isinstance(e0, dict): + e0 = (e0["x"], e0["y"]) + if isinstance(e1, dict): + e1 = (e1["x"], e1["y"]) + super(Segment, self).__init__((e0, e1)) + + def __getitem__(self, key): + if key: + return {"x": self.x.maximum, "y": self.y.maximum} + return {"x": self.x.minimum, "y": self.y.minimum} + + delta_x = lambda self: self.width + delta_y = lambda self: self.height + run = delta_x + rise = delta_y + + def distanceToPoint(self, p): + return self.distance_to_point(p["x"], p["y"]) + + def perpDistanceToPoint(self, p): + return self.perp_distance(p["x"], p["y"]) + + def angle(self): + return super(Segment, self).angle + + def length(self): + return super(Segment, self).length + + def pointAtLength(self, length): + return self.point_at_length(length) + + def pointAtRatio(self, ratio): + return self.point_at_ratio(ratio) + + def createParallel(self, p): + self.parallel(p["x"], p["y"]) + + +@deprecate +def intersectSegments(s1, s2): + """transforms.Segment(s1).intersect(s2)""" + return Point(*s1.intersect(s2)) + + +@deprecate +def dot(s1, s2): + """transforms.Segment(s1).dot(s2)""" + return s1.dot(s2) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/run_command.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/run_command.py new file mode 100644 index 0000000..84f4a4b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/run_command.py @@ -0,0 +1,79 @@ +# coding=utf-8 +# +# Copyright (C) 2008 Stephen Silver +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +Deprecated module for running SVG-generating commands in Inkscape extensions +""" +import os +import sys +import tempfile +from subprocess import Popen, PIPE + +from inkex.deprecated import deprecate + + +def run(command_format, prog_name): + """inkex.commands.call(...)""" + svgfile = tempfile.mktemp(".svg") + command = command_format % svgfile + msg = None + # ps2pdf may attempt to write to the current directory, which may not + # be writeable, so we switch to the temp directory first. + try: + os.chdir(tempfile.gettempdir()) + except IOError: + pass + + try: + proc = Popen(command, shell=True, stdout=PIPE, stderr=PIPE) + return_code = proc.wait() + out = proc.stdout.read() + err = proc.stderr.read() + + if msg is None: + if return_code: + msg = "{} failed:\n{}\n{}\n".format(prog_name, out, err) + elif err: + sys.stderr.write( + "{} executed but logged the following error:\n{}\n{}\n".format( + prog_name, out, err + ) + ) + except Exception as inst: + msg = "Error attempting to run {}: {}".format(prog_name, str(inst)) + + # If successful, copy the output file to stdout. + if msg is None: + if os.name == "nt": # make stdout work in binary on Windows + import msvcrt + + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + try: + with open(svgfile, "rb") as fhl: + sys.stdout.write(fhl.read().decode(sys.stdout.encoding)) + except IOError as inst: + msg = "Error reading temporary file: {}".format(str(inst)) + + try: + # Clean up. + os.remove(svgfile) + except (IOError, OSError): + pass + + # Output error message (if any) and exit. + return msg diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplepath.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplepath.py new file mode 100644 index 0000000..6eaaf3b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplepath.py @@ -0,0 +1,68 @@ +# coding=utf-8 +# COPYRIGHT +# +# pylint: disable=invalid-name +# +""" +Depreicated simplepath replacements with documentation +""" + +import math +from inkex.deprecated import deprecate, DeprecatedDict +from inkex.transforms import Transform +from inkex.paths import Path + +pathdefs = DeprecatedDict( + { + "M": ["L", 2, [float, float], ["x", "y"]], + "L": ["L", 2, [float, float], ["x", "y"]], + "H": ["H", 1, [float], ["x"]], + "V": ["V", 1, [float], ["y"]], + "C": [ + "C", + 6, + [float, float, float, float, float, float], + ["x", "y", "x", "y", "x", "y"], + ], + "S": ["S", 4, [float, float, float, float], ["x", "y", "x", "y"]], + "Q": ["Q", 4, [float, float, float, float], ["x", "y", "x", "y"]], + "T": ["T", 2, [float, float], ["x", "y"]], + "A": [ + "A", + 7, + [float, float, float, int, int, float, float], + ["r", "r", "a", 0, "s", "x", "y"], + ], + "Z": ["L", 0, [], []], + } +) + + +@deprecate +def parsePath(d): + """element.path.to_arrays()""" + return Path(d).to_arrays() + + +@deprecate +def formatPath(a): + """str(element.path) or str(Path(array))""" + return str(Path(a)) + + +@deprecate +def translatePath(p, x, y): + """Path(array).translate(x, y)""" + p[:] = Path(p).translate(x, y).to_arrays() + + +@deprecate +def scalePath(p, x, y): + """Path(array).scale(x, y)""" + p[:] = Path(p).scale(x, y).to_arrays() + + +@deprecate +def rotatePath(p, a, cx=0, cy=0): + """Path(array).rotate(angle_degrees, (center_x, center_y))""" + p[:] = Path(p).rotate(math.degrees(a), (cx, cy)).to_arrays() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplestyle.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplestyle.py new file mode 100644 index 0000000..cab6d9d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simplestyle.py @@ -0,0 +1,55 @@ +# coding=utf-8 +# COPYRIGHT +"""DOCSTRING""" + +import inkex +from inkex.colors import SVG_COLOR as svgcolors +from inkex.deprecated import deprecate + + +@deprecate +def parseStyle(s): + """dict(inkex.Style.parse_str(s))""" + return dict(inkex.Style.parse_str(s)) + + +@deprecate +def formatStyle(a): + """str(inkex.Style(a))""" + return str(inkex.Style(a)) + + +@deprecate +def isColor(c): + """inkex.colors.is_color(c)""" + return inkex.colors.is_color(c) + + +@deprecate +def parseColor(c): + """inkex.Color(c).to_rgb()""" + return tuple(inkex.Color(c).to_rgb()) + + +@deprecate +def formatColoria(a): + """str(inkex.Color(a))""" + return str(inkex.Color(a)) + + +@deprecate +def formatColorfa(a): + """str(inkex.Color(a))""" + return str(inkex.Color(a)) + + +@deprecate +def formatColor3i(r, g, b): + """str(inkex.Color((r, g, b)))""" + return str(inkex.Color((r, g, b))) + + +@deprecate +def formatColor3f(r, g, b): + """str(inkex.Color((r, g, b)))""" + return str(inkex.Color((r, g, b))) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simpletransform.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simpletransform.py new file mode 100644 index 0000000..f408cd0 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated-simple/simpletransform.py @@ -0,0 +1,122 @@ +# coding=utf-8 +# +# pylint: disable=invalid-name +# +""" +Depreicated simpletransform replacements with documentation +""" + +import warnings + +from inkex.deprecated import deprecate +from inkex.transforms import Transform, BoundingBox, cubic_extrema +from inkex.paths import Path + +import inkex, cubicsuperpath + + +def _lists(mat): + return [list(row) for row in mat] + + +@deprecate +def parseTransform(transf, mat=None): + """Transform(str).matrix""" + t = Transform(transf) + if mat is not None: + t = Transform(mat) @ t + return _lists(t.matrix) + + +@deprecate +def formatTransform(mat): + """str(Transform(mat))""" + if len(mat) == 3: + warnings.warn("3x3 matrices not suported") + mat = mat[:2] + return str(Transform(mat)) + + +@deprecate +def invertTransform(mat): + """-Transform(mat)""" + return _lists((-Transform(mat)).matrix) + + +@deprecate +def composeTransform(mat1, mat2): + """Transform(M1) * Transform(M2)""" + return _lists((Transform(mat1) @ Transform(mat2)).matrix) + + +@deprecate +def composeParents(node, mat): + """elem.composed_transform() or elem.transform * Transform(mat)""" + return (node.transform @ Transform(mat)).matrix + + +@deprecate +def applyTransformToNode(mat, node): + """elem.transform = Transform(mat) * elem.transform""" + node.transform = Transform(mat) @ node.transform + + +@deprecate +def applyTransformToPoint(mat, pt): + """Transform(mat).apply_to_point(pt)""" + pt2 = Transform(mat).apply_to_point(pt) + # Apply in place as original method was modifying arrays in place. + # but don't do this in your code! This is not good code design. + pt[0] = pt2[0] + pt[1] = pt2[1] + + +@deprecate +def applyTransformToPath(mat, path): + """Path(path).transform(mat)""" + return Path(path).transform(Transform(mat)).to_arrays() + + +@deprecate +def fuseTransform(node): + """node.apply_transform()""" + return node.apply_transform() + + +@deprecate +def boxunion(b1, b2): + """list(BoundingBox(b1) + BoundingBox(b2))""" + bbox = BoundingBox(b1[:2], b1[2:]) + BoundingBox(b2[:2], b2[2:]) + return bbox.x.minimum, bbox.x.maximum, bbox.y.minimum, bbox.y.maximum + + +@deprecate +def roughBBox(path): + """list(Path(path)).bounding_box())""" + bbox = Path(path).bounding_box() + return bbox.x.minimum, bbox.x.maximum, bbox.y.minimum, bbox.y.maximum + + +@deprecate +def refinedBBox(path): + """list(Path(path)).bounding_box())""" + bbox = Path(path).bounding_box() + return bbox.x.minimum, bbox.x.maximum, bbox.y.minimum, bbox.y.maximum + + +@deprecate +def cubicExtrema(y0, y1, y2, y3): + """from inkex.transforms import cubic_extrema""" + return cubic_extrema(y0, y1, y2, y3) + + +@deprecate +def computeBBox(aList, mat=[[1, 0, 0], [0, 1, 0]]): + """sum([node.bounding_box() for node in aList])""" + return sum([node.bounding_box() for node in aList], None) + + +@deprecate +def computePointInNode(pt, node, mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): + """(-Transform(node.transform * mat)).apply_to_point(pt)""" + return (-Transform(node.transform * mat)).apply_to_point(pt) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/__init__.py new file mode 100644 index 0000000..180a1c2 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/__init__.py @@ -0,0 +1,3 @@ +from .main import * +from .meta import deprecate, _deprecated +from .deprecatedeffect import DeprecatedEffect, Effect diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/deprecatedeffect.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/deprecatedeffect.py new file mode 100644 index 0000000..ee13670 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/deprecatedeffect.py @@ -0,0 +1,313 @@ +# coding=utf-8 +# +# Copyright (C) 2018 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Deprecation functionality for the pre-1.0 Inkex main effect class. +""" +# +# We ignore a lot of pylint warnings here: +# +# pylint: disable=invalid-name,unused-argument,missing-docstring,too-many-public-methods +# + +import sys +import argparse +from argparse import ArgumentParser + +from .. import utils +from .. import base +from ..base import SvgThroughMixin, InkscapeExtension +from ..localization import inkex_gettext as _ +from .meta import _deprecated + + +class DeprecatedEffect: + """An Inkscape effect, takes SVG in and outputs SVG, providing a deprecated layer""" + + options = argparse.Namespace() + + def __init__(self): + super().__init__() + self._doc_ids = None + self._args = None + + # These are things we reference in the deprecated code, they are provided + # by the new effects code, but we want to keep this as a Mixin so these + # items will keep pylint happy and let use check our code as we write. + if not hasattr(self, "svg"): + from ..elements import SvgDocumentElement + + self.svg = SvgDocumentElement() + if not hasattr(self, "arg_parser"): + self.arg_parser = ArgumentParser() + if not hasattr(self, "run"): + self.run = self.affect + + @classmethod + def _deprecated( + cls, name, msg=_("{} is deprecated and should be removed"), stack=3 + ): + """Give the user a warning about their extension using a deprecated API""" + _deprecated( + msg.format("Effect." + name, cls=cls.__module__ + "." + cls.__name__), + stack=stack, + ) + + @property + def OptionParser(self): + self._deprecated( + "OptionParser", + _( + "{} or `optparse` has been deprecated and replaced with `argparser`. " + "You must change `self.OptionParser.add_option` to " + "`self.arg_parser.add_argument`; the arguments are similar." + ), + ) + return self + + def add_option(self, *args, **kw): + # Convert type string into type method as needed + if "type" in kw: + kw["type"] = { + "string": str, + "int": int, + "float": float, + "inkbool": utils.Boolean, + }.get(kw["type"]) + if kw.get("action", None) == "store": + # Default store action not required, removed. + kw.pop("action") + args = [arg for arg in args if arg != ""] + self.arg_parser.add_argument(*args, **kw) + + def effect(self): + self._deprecated( + "effect", + _( + "{} method is now a required method. It should " + "be created on {cls}, even if it does nothing." + ), + ) + + @property + def current_layer(self): + self._deprecated( + "current_layer", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.get_current_layer()` instead." + ), + ) + return self.svg.get_current_layer() + + @property + def view_center(self): + self._deprecated( + "view_center", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.get_center_position()` instead." + ), + ) + return self.svg.namedview.center + + @property + def selected(self): + self._deprecated( + "selected", + _( + "{} is now a dict in the SvgDocumentElement class. " + "Use `self.svg.selected`." + ), + ) + return {elem.get("id"): elem for elem in self.svg.selected} + + @property + def doc_ids(self): + self._deprecated( + "doc_ids", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.get_ids()` instead." + ), + ) + if self._doc_ids is None: + self._doc_ids = dict.fromkeys(self.svg.get_ids()) + return self._doc_ids + + def getdocids(self): + self._deprecated( + "getdocids", _("Use `self.svg.get_ids()` instead of {} and `doc_ids`.") + ) + self._doc_ids = None + self.svg.ids.clear() + + def getselected(self): + self._deprecated("getselected", _("{} has been removed")) + + def getElementById(self, eid): + self._deprecated( + "getElementById", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.getElementById(eid)` instead." + ), + ) + return self.svg.getElementById(eid) + + def xpathSingle(self, xpath): + self._deprecated( + "xpathSingle", + _( + "{} is now a new method in the SvgDocumentElement class. " + "Use `self.svg.getElement(path)` instead." + ), + ) + return self.svg.getElement(xpath) + + def getParentNode(self, node): + self._deprecated( + "getParentNode", + _("{} is no longer in use. Use the lxml `.getparent()` method instead."), + ) + return node.getparent() + + def getNamedView(self): + self._deprecated( + "getNamedView", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.namedview` to access this element." + ), + ) + return self.svg.namedview + + def createGuide(self, posX, posY, angle): + from ..elements import Guide + + self._deprecated( + "createGuide", + _( + "{} is now a method of the namedview element object. " + "Use `self.svg.namedview.add(Guide().move_to(x, y, a))` instead." + ), + ) + return self.svg.namedview.add(Guide().move_to(posX, posY, angle)) + + def affect( + self, args=sys.argv[1:], output=True + ): # pylint: disable=dangerous-default-value + # We need a list as the default value to preserve backwards compatibility + self._deprecated( + "affect", _("{} is now `Effect.run()`. The `output` argument has changed.") + ) + self._args = args[-1:] + return self.run(args=args) + + @property + def args(self): + self._deprecated("args", _("self.args[-1] is now self.options.input_file.")) + return self._args + + @property + def svg_file(self): + self._deprecated("svg_file", _("self.svg_file is now self.options.input_file.")) + return self.options.input_file + + def save_raw(self, ret): + # Derived class may implement "output()" + # Attention: 'cubify.py' implements __getattr__ -> hasattr(self, 'output') + # returns True + if hasattr(self.__class__, "output"): + self._deprecated("output", "Use `save()` or `save_raw()` instead.", stack=5) + return getattr(self, "output")() + return base.InkscapeExtension.save_raw(self, ret) + + def uniqueId(self, old_id, make_new_id=True): + self._deprecated( + "uniqueId", + _( + "{} is now a method in the SvgDocumentElement class. " + " Use `self.svg.get_unique_id(old_id)` instead." + ), + ) + return self.svg.get_unique_id(old_id) + + def getDocumentWidth(self): + self._deprecated( + "getDocumentWidth", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.width` instead." + ), + ) + return self.svg.get("width") + + def getDocumentHeight(self): + self._deprecated( + "getDocumentHeight", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.height` instead." + ), + ) + return self.svg.get("height") + + def getDocumentUnit(self): + self._deprecated( + "getDocumentUnit", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.unit` instead." + ), + ) + return self.svg.unit + + def unittouu(self, string): + self._deprecated( + "unittouu", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.unittouu(str)` instead." + ), + ) + return self.svg.unittouu(string) + + def uutounit(self, val, unit): + self._deprecated( + "uutounit", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.uutounit(value, unit)` instead." + ), + ) + return self.svg.uutounit(val, unit) + + def addDocumentUnit(self, value): + self._deprecated( + "addDocumentUnit", + _( + "{} is now a method in the SvgDocumentElement class. " + "Use `self.svg.add_unit(value)` instead." + ), + ) + return self.svg.add_unit(value) + + +class Effect(SvgThroughMixin, DeprecatedEffect, InkscapeExtension): + """An Inkscape effect, takes SVG in and outputs SVG""" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/main.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/main.py new file mode 100644 index 0000000..a747bda --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/main.py @@ -0,0 +1,178 @@ +# coding=utf-8 +# +# Copyright (C) 2018 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Provide some documentation to existing extensions about why they're failing. +""" +# +# We ignore a lot of pylint warnings here: +# +# pylint: disable=invalid-name,unused-argument,missing-docstring,too-many-public-methods +# + +import os +import sys +import warnings +import argparse + +from ..transforms import Transform +from .. import utils +from .. import units +from ..elements._base import BaseElement, ShapeElement +from ..elements._selected import ElementList +from .meta import deprecate, _deprecated + +warnings.simplefilter("default") +# To load each of the deprecated sub-modules (the ones without a namespace) +# we will add the directory to our pythonpath so older scripts can find them + +INKEX_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +SIMPLE_DIR = os.path.join(INKEX_DIR, "deprecated-simple") + +if os.path.isdir(SIMPLE_DIR): + sys.path.append(SIMPLE_DIR) + + +class DeprecatedDict(dict): + @deprecate + def __getitem__(self, key): + return super().__getitem__(key) + + @deprecate + def __iter__(self): + return super().__iter__() + + +# legacy inkex members + + +class lazyproxy: + """Proxy, use as decorator on a function with provides the wrapped object. + The decorated function is called when a member is accessed on the proxy. + """ + + def __init__(self, getwrapped): + """ + :param getwrapped: Callable which returns the wrapped object + """ + self._getwrapped = getwrapped + + def __getattr__(self, name): + return getattr(self._getwrapped(), name) + + def __call__(self, *args, **kwargs): + return self._getwrapped()(*args, **kwargs) + + +@lazyproxy +def localize(): + _deprecated("inkex.localize was moved to inkex.localization.localize.", stack=3) + from ..localization import localize as wrapped + + return wrapped + + +def are_near_relative(a, b, eps): + _deprecated( + "inkex.are_near_relative was moved to " "inkex.units.are_near_relative", stack=2 + ) + return units.are_near_relative(a, b, eps) + + +def debug(what): + _deprecated("inkex.debug was moved to inkex.utils.debug.", stack=2) + return utils.debug(what) + + +# legacy inkex members <= 0.48.x + + +def unittouu(string): + _deprecated( + "inkex.unittouu is now a method in the SvgDocumentElement class. " + "Use `self.svg.unittouu(str)` instead.", + stack=2, + ) + return units.convert_unit(string, "px") + + +# optparse.Values.ensure_value + + +def ensure_value(self, attr, value): + _deprecated("Effect().options.ensure_value was removed.", stack=2) + if getattr(self, attr, None) is None: + setattr(self, attr, value) + return getattr(self, attr) + + +argparse.Namespace.ensure_value = ensure_value # type: ignore + + +@deprecate +def zSort(inNode, idList): + """self.svg.get_z_selected()""" + sortedList = [] + theid = inNode.get("id") + if theid in idList: + sortedList.append(theid) + for child in inNode: + if len(sortedList) == len(idList): + break + sortedList += zSort(child, idList) + return sortedList + + +# This can't be handled as a mixin class because of circular importing. +def description(self, value): + """Use elem.desc = value""" + self.desc = value + + +BaseElement.description = deprecate(description, "1.1") + + +def composed_style(element: ShapeElement): + """Calculate the final styles applied to this element + This function has been deprecated in favor of BaseElement.specified_style()""" + return element.specified_style() + + +ShapeElement.composed_style = deprecate(composed_style, "1.2") + + +def paint_order(selection: ElementList): + """Use :func:`rendering_order`""" + return selection.rendering_order() + + +ElementList.paint_order = deprecate(paint_order, "1.2") # type: ignore + + +def transform_imul(self, matrix): + """Use @= operator instead""" + return self.__imatmul__(matrix) + + +def transform_mul(self, matrix): + """Use @ operator instead""" + return self.__matmul__(matrix) + + +Transform.__imul__ = deprecate(transform_imul, "1.2") # type: ignore +Transform.__mul__ = deprecate(transform_mul, "1.2") # type: ignore diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/meta.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/meta.py new file mode 100644 index 0000000..cebd93c --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/deprecated/meta.py @@ -0,0 +1,109 @@ +# coding=utf-8 +# +# Copyright (C) 2018 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Deprecation functionality which does not require imports from Inkex. +""" + +import os +import traceback +import warnings +from typing import Optional + +try: + DEPRECATION_LEVEL = int(os.environ.get("INKEX_DEPRECATION_LEVEL", 1)) +except ValueError: + DEPRECATION_LEVEL = 1 + + +def _deprecated(msg, stack=2, level=DEPRECATION_LEVEL): + """Internal method for raising a deprecation warning""" + if level > 1: + msg += " ; ".join(traceback.format_stack()) + if level: + warnings.warn(msg, category=DeprecationWarning, stacklevel=stack + 1) + + +def deprecate(func, version: Optional[str] = None): + r"""Function decorator for deprecation functions which have a one-liner + equivalent in the new API. The one-liner has to passed as a string + to the decorator. + + >>> @deprecate + >>> def someOldFunction(*args): + >>> '''Example replacement code someNewFunction('foo', ...)''' + >>> someNewFunction('foo', *args) + + Or if the args API is the same: + + >>> someOldFunction = deprecate(someNewFunction) + + """ + + def _inner(*args, **kwargs): + _deprecated(f"{func.__module__}.{func.__name__} -> {func.__doc__}", stack=2) + return func(*args, **kwargs) + + _inner.__name__ = func.__name__ + if func.__doc__: + if version is None: + _inner.__doc__ = "Deprecated -> " + func.__doc__ + else: + _inner.__doc__ = f"""{func.__doc__}\n\n.. deprecated:: {version}\n""" + return _inner + + +class DeprecatedSvgMixin: + """Mixin which adds deprecated API elements to the SvgDocumentElement""" + + @property + def selected(self): + """svg.selection""" + return self.selection + + @deprecate + def set_selected(self, *ids): + r"""svg.selection.set(\*ids)""" + return self.selection.set(*ids) + + @deprecate + def get_z_selected(self): + """svg.selection.rendering_order()""" + return self.selection.rendering_order() + + @deprecate + def get_selected(self, *types): + r"""svg.selection.filter(\*types).values()""" + return self.selection.filter(*types).values() + + @deprecate + def get_selected_or_all(self, *types): + """Set select_all = True in extension class""" + if not self.selection: + self.selection.set_all() + return self.selection.filter(*types) + + @deprecate + def get_selected_bbox(self): + """selection.bounding_box()""" + return self.selection.bounding_box() + + @deprecate + def get_first_selected(self, *types): + r"""selection.filter(\*types).first() or [0] if you'd like an error""" + return self.selection.filter(*types).first() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/__init__.py new file mode 100644 index 0000000..d426ff6 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/__init__.py @@ -0,0 +1,56 @@ +""" +Element based interface provides the bulk of features that allow you to +interact directly with the SVG xml interface. + +See the documentation for each of the elements for details on how it works. +""" + +from ._utils import addNS, NSS +from ._parser import SVG_PARSER, load_svg +from ._base import ShapeElement, BaseElement +from ._svg import SvgDocumentElement +from ._groups import Group, Layer, Anchor, Marker, ClipPath +from ._polygons import PathElement, Polyline, Polygon, Line, Rectangle, Circle, Ellipse +from ._text import ( + FlowRegion, + FlowRoot, + FlowPara, + FlowDiv, + FlowSpan, + TextElement, + TextPath, + Tspan, + SVGfont, + FontFace, + Glyph, + MissingGlyph, +) +from ._use import Symbol, Use +from ._meta import ( + Defs, + StyleElement, + Script, + Desc, + Title, + NamedView, + Guide, + Metadata, + ForeignObject, + Switch, + Grid, + Page, +) +from ._filters import ( + Filter, + Pattern, + Mask, + Gradient, + LinearGradient, + RadialGradient, + PathEffect, + Stop, + MeshGradient, + MeshRow, + MeshPatch, +) +from ._image import Image diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_base.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_base.py new file mode 100644 index 0000000..4f8048c --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_base.py @@ -0,0 +1,811 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Provide extra utility to each svg element type specific to its type. + +This is useful for having a common interface for each element which can +give path, transform, and property access easily. +""" +from __future__ import annotations + +from copy import deepcopy +from typing import Any, Tuple, Optional, overload, TypeVar, List +from lxml import etree +import re + +from ..interfaces.IElement import IBaseElement, ISVGDocumentElement + +from ..base import SvgOutputMixin +from ..paths import Path +from ..styles import Style, Classes +from ..transforms import Transform, BoundingBox +from ..utils import FragmentError +from ..units import convert_unit, render_unit, parse_unit +from ._utils import ChildToProperty, NSS, addNS, removeNS, splitNS +from ..properties import BaseStyleValue, ShorthandValue, all_properties +from ._selected import ElementList +from ._parser import NodeBasedLookup, SVG_PARSER + +T = TypeVar("T", bound="BaseElement") # pylint: disable=invalid-name + + +class BaseElement(IBaseElement): + """Provide automatic namespaces to all calls""" + + # pylint: disable=too-many-public-methods + + def __init_subclass__(cls): + if cls.tag_name: + NodeBasedLookup.register_class(cls) + + @classmethod + def is_class_element( # pylint: disable=unused-argument + cls, elem: etree.Element + ) -> bool: + """Hook to do more restrictive check in addition to (ns,tag) match + + .. versionadded:: 1.2 + The function has been made public.""" + return True + + tag_name = "" + + @property + def TAG(self): # pylint: disable=invalid-name + """Return the tag_name without NS""" + if not self.tag_name: + return removeNS(super().tag)[-1] + return removeNS(self.tag_name)[-1] + + @classmethod + def new(cls, *children, **attrs): + """Create a new element, converting attrs values to strings.""" + obj = cls(*children) + obj.update(**attrs) + return obj + + NAMESPACE = property(lambda self: splitNS(self.tag_name)[0]) + """Get namespace of element""" + + PARSER = SVG_PARSER + """A reference to the :attr:`inkex.elements._parser.SVG_PARSER`""" + WRAPPED_ATTRS = ( + # (prop_name, [optional: attr_name], cls) + ("transform", Transform), + ("style", Style), + ("classes", "class", Classes), + ) # type: Tuple[Tuple[Any, ...], ...] + """A list of attributes that are automatically converted to objects.""" + + # We do this because python2 and python3 have different ways + # of combining two dictionaries that are incompatible. + # This allows us to update these with inheritance. + @property + def wrapped_attrs(self): + """Map attributes to property name and wrapper class""" + return {row[-2]: (row[0], row[-1]) for row in self.WRAPPED_ATTRS} + + @property + def wrapped_props(self): + """Map properties to attribute name and wrapper class""" + return {row[0]: (row[-2], row[-1]) for row in self.WRAPPED_ATTRS} + + typename = property(lambda self: type(self).__name__) + """Type name of the element""" + xml_path = property(lambda self: self.getroottree().getpath(self)) + """XPath representation of the element in its tree + + .. versionadded:: 1.1""" + desc = ChildToProperty("svg:desc", prepend=True) + """The element's long-form description (for accessibility purposes) + + .. versionadded:: 1.1""" + title = ChildToProperty("svg:title", prepend=True) + """The element's short-form description (for accessibility purposes) + + .. versionadded:: 1.1""" + + def __getattr__(self, name): + """Get the attribute, but load it if it is not available yet""" + if name in self.wrapped_props: + (attr, cls) = self.wrapped_props[name] + + # The reason we do this here and not in _init is because lxml + # is inconsistant about when elements are initialised. + # So we make this a lazy property. + def _set_attr(new_item): + if new_item: + self.set(attr, str(new_item)) + else: + self.attrib.pop(attr, None) # pylint: disable=no-member + + # pylint: disable=no-member + value = cls(self.attrib.get(attr, None), callback=_set_attr) + if name == "style": + value.element = self + setattr(self, name, value) + return value + raise AttributeError(f"Can't find attribute {self.typename}.{name}") + + def __setattr__(self, name, value): + """Set the attribute, update it if needed""" + if name in self.wrapped_props: + (attr, cls) = self.wrapped_props[name] + # Don't call self.set or self.get (infinate loop) + if value: + if not isinstance(value, cls): + value = cls(value) + self.attrib[attr] = str(value) + else: + self.attrib.pop(attr, None) # pylint: disable=no-member + else: + super().__setattr__(name, value) + + def get(self, attr, default=None): + """Get element attribute named, with addNS support.""" + if attr in self.wrapped_attrs: + (prop, _) = self.wrapped_attrs[attr] + value = getattr(self, prop, None) + # We check the boolean nature of the value, because empty + # transformations and style attributes are equiv to not-existing + ret = str(value) if value else (default or None) + return ret + return super().get(addNS(attr), default) + + def set(self, attr, value): + """Set element attribute named, with addNS support""" + if attr in self.wrapped_attrs: + # Always keep the local wrapped class up to date. + (prop, cls) = self.wrapped_attrs[attr] + setattr(self, prop, cls(value)) + value = getattr(self, prop) + if not value: + return + if value is None: + self.attrib.pop(addNS(attr), None) # pylint: disable=no-member + else: + value = str(value) + super().set(addNS(attr), value) + + def update(self, **kwargs): + """ + Update element attributes using keyword arguments + + Note: double underscore is used as namespace separator, + i.e. "namespace__attr" argument name will be treated as "namespace:attr" + + :param kwargs: dict with name=value pairs + :return: self + """ + for name, value in kwargs.items(): + self.set(name, value) + return self + + def pop(self, attr, default=None): + """Delete/remove the element attribute named, with addNS support.""" + if attr in self.wrapped_attrs: + # Always keep the local wrapped class up to date. + (prop, cls) = self.wrapped_attrs[attr] + value = getattr(self, prop) + setattr(self, prop, cls(None)) + return value + return self.attrib.pop(addNS(attr), default) # pylint: disable=no-member + + @overload + def add( + self, child1: BaseElement, child2: BaseElement, *children: BaseElement + ) -> Tuple[BaseElement]: + ... + + @overload + def add(self, child: T) -> T: + ... + + def add(self, *children): + """ + Like append, but will do multiple children and will return + children or only child + """ + for child in children: + self.append(child) + return children if len(children) != 1 else children[0] + + def tostring(self): + """Return this element as it would appear in an svg document""" + # This kind of hack is pure maddness, but etree provides very little + # in the way of fragment printing, prefering to always output valid xml + + svg = SvgOutputMixin.get_template(width=0, height=0).getroot() + svg.append(self.copy()) + return svg.tostring().split(b">\n ", 1)[-1][:-6] + + def set_random_id( + self, + prefix: Optional[str] = None, + size: Optional[int] = None, + backlinks: bool = False, + blacklist: Optional[List[str]] = None, + ): + """Sets the id attribute if it is not already set. + + The id consists of a prefix and an appended random integer of length size. + Args: + prefix (str, optional): the prefix of the new ID. Defaults to the tag name. + size (Optional[int], optional): number of digits of the second part of the + id. If None, the length is chosen based on the amount of existing + objects. Defaults to None. + + .. versionchanged:: 1.2 + The default of this value has been changed from 4 to None. + backlinks (bool, optional): Whether to update the links in existing objects + that reference this element. Defaults to False. + blacklist (List[str], optional): An additional list of ids that are not + allowed to be used. This is useful when bulk inserting objects. + Defaults to None. + + .. versionadded:: 1.2 + """ + prefix = str(self) if prefix is None else prefix + self.set_id( + self.root.get_unique_id(prefix, size=size, blacklist=blacklist), + backlinks=backlinks, + ) + + def set_random_ids( + self, + prefix: Optional[str] = None, + levels: int = -1, + backlinks: bool = False, + blacklist: Optional[List[str]] = None, + ): + """Same as set_random_id, but will apply also to children + + The id consists of a prefix and an appended random integer of length size. + Args: + prefix (str, optional): the prefix of the new ID. Defaults to the tag name. + levels (int, optional): the depth of the tree traversion, if negative, no + limit is imposed. Defaults to -1. + backlinks (bool, optional): Whether to update the links in existing objects + that reference this element. Defaults to False. + blacklist (List[str], optional): An additional list of ids that are not + allowed to be used. This is useful when bulk inserting objects. + Defaults to None. + + .. versionadded:: 1.2 + """ + self.set_random_id(prefix=prefix, backlinks=backlinks, blacklist=blacklist) + if levels != 0: + for child in self: + if hasattr(child, "set_random_ids"): + child.set_random_ids( + prefix=prefix, levels=levels - 1, backlinks=backlinks + ) + + eid = property(lambda self: self.get_id()) + """Property to access the element's id; will set a new unique id if not set.""" + + def get_id(self, as_url=0) -> str: + """Get the id for the element, will set a new unique id if not set. + + as_url - If set to 1, returns #{id} as a string + If set to 2, returns url(#{id}) as a string + + Args: + as_url (int, optional): + - If set to 1, returns #{id} as a string + - If set to 2, returns url(#{id}) as a string. + + Defaults to 0. + + .. versionadded:: 1.1 + + Returns: + str: formatted id + """ + if "id" not in self.attrib: + self.set_random_id(self.TAG) + eid = self.get("id") + if as_url > 0: + eid = "#" + eid + if as_url > 1: + eid = f"url({eid})" + return eid + + def set_id(self, new_id, backlinks=False): + """Set the id and update backlinks to xlink and style urls if needed""" + old_id = self.get("id", None) + self.set("id", new_id) + if backlinks and old_id: + for elem in self.root.getElementsByHref(old_id): + elem.href = self + for attr in ["clip-path", "mask"]: + for elem in self.root.getElementsByHref(old_id, attribute=attr): + elem.set(attr, self.get_id(2)) + for elem in self.root.getElementsByStyleUrl(old_id): + elem.style.update_urls(old_id, new_id) + + @property + def root(self): + """Get the root document element from any element descendent""" + root, parent = self, self + while parent is not None: + root, parent = parent, parent.getparent() + + if not isinstance(root, ISVGDocumentElement): + raise FragmentError("Element fragment does not have a document root!") + return root + + def get_or_create(self, xpath, nodeclass=None, prepend=False): + """Get or create the given xpath, pre/append new node if not found. + + .. versionchanged:: 1.1 + The ``nodeclass`` attribute is optional; if not given, it is looked up + using :func:`~inkex.elements._parser.NodeBasedLookup.find_class`""" + node = self.findone(xpath) + if node is None: + if nodeclass is None: + nodeclass = NodeBasedLookup.find_class(xpath) + node = nodeclass() + if prepend: + self.insert(0, node) + else: + self.append(node) + return node + + def descendants(self): + """Walks the element tree and yields all elements, parent first + + .. versionchanged:: 1.1 + The ``*types`` attribute was removed + + """ + + return ElementList( + self.root, + [ + element + for element in self.iter() + if isinstance(element, (BaseElement, str)) + ], + ) + + def ancestors(self, elem=None, stop_at=()): + """ + Walk the parents and yield all the ancestor elements, parent first + + Args: + elem (BaseElement, optional): If provided, it will stop at the last common + ancestor. Defaults to None. + + .. versionadded:: 1.1 + + stop_at (tuple, optional): If provided, it will stop at the first parent + that is in this list. Defaults to (). + + .. versionadded:: 1.1 + + Returns: + ElementList: list of ancestors + """ + + return ElementList(self.root, self._ancestors(elem=elem, stop_at=stop_at)) + + def _ancestors(self, elem, stop_at): + if isinstance(elem, BaseElement): + stop_at = list(elem.ancestors()) + for parent in self.iterancestors(): + yield parent + if parent in stop_at: + break + + def backlinks(self, *types): + """Get elements which link back to this element, like ancestors but via + xlinks""" + if not types or isinstance(self, types): + yield self + my_id = self.get("id") + if my_id is not None: + elems = list(self.root.getElementsByHref(my_id)) + list( + self.root.getElementsByStyleUrl(my_id) + ) + for elem in elems: + if hasattr(elem, "backlinks"): + for child in elem.backlinks(*types): + yield child + + def xpath(self, pattern, namespaces=NSS): # pylint: disable=dangerous-default-value + """Wrap xpath call and add svg namespaces""" + return super().xpath(pattern, namespaces=namespaces) + + def findall( + self, pattern, namespaces=NSS + ): # pylint: disable=dangerous-default-value + """Wrap findall call and add svg namespaces""" + return super().findall(pattern, namespaces=namespaces) + + def findone(self, xpath): + """Gets a single element from the given xpath or returns None""" + el_list = self.xpath(xpath) + return el_list[0] if el_list else None + + def delete(self): + """Delete this node from it's parent node""" + if self.getparent() is not None: + self.getparent().remove(self) + + def remove_all(self, *types): + """Remove all children or child types + + .. versionadded:: 1.1""" + types = tuple(NodeBasedLookup.find_class(t) for t in types) + for child in self: + if not types or isinstance(child, types): + self.remove(child) + + def replace_with(self, elem): + """Replace this element with the given element""" + self.addnext(elem) + if not elem.get("id") and self.get("id"): + elem.set("id", self.get("id")) + if not elem.label and self.label: + elem.label = self.label + self.delete() + return elem + + def copy(self): + """Make a copy of the element and return it""" + elem = deepcopy(self) + elem.set("id", None) + return elem + + def duplicate(self): + """Like copy(), but the copy stays in the tree and sets a random id on the + duplicate. + + .. versionchanged:: 1.2 + A random id is also set on all the duplicate's descendants""" + elem = self.copy() + self.addnext(elem) + elem.set_random_ids() + return elem + + def __str__(self): + # We would do more here, but lxml is VERY unpleseant when it comes to + # namespaces, basically over printing details and providing no + # supression mechanisms to turn off xml's over engineering. + return str(self.tag).split("}", maxsplit=1)[-1] + + @property + def href(self): + """Returns the referred-to element if available + + .. versionchanged:: 1.1 + A setter for href was added.""" + ref = self.get("href") or self.get("xlink:href") + if not ref: + return None + return self.root.getElementById(ref.strip("#")) + + @href.setter + def href(self, elem): + """Set the href object""" + if isinstance(elem, BaseElement): + elem = elem.get_id() + if self.get("href"): + self.set("href", "#" + elem) + else: + self.set("xlink:href", "#" + elem) + + @property + def label(self): + """Returns the inkscape label""" + return self.get("inkscape:label", None) + + @label.setter + def label(self, value): + """Sets the inkscape label""" + self.set("inkscape:label", str(value)) + + def is_sensitive(self): + """Return true if this element is sensitive in inkscape + + .. versionadded:: 1.1""" + return self.get("sodipodi:insensitive", None) != "true" + + def set_sensitive(self, sensitive=True): + """Set the sensitivity of the element/layer + + .. versionadded:: 1.1""" + # Sensitive requires None instead of 'false' + self.set("sodipodi:insensitive", ["true", None][sensitive]) + + @property + def unit(self): + """Return the unit being used by the owning document, cached + + .. versionadded:: 1.1""" + try: + return self.root.unit + except FragmentError: + return "px" # Don't cache. + + @staticmethod + def to_dimensional(value, to_unit="px"): + """Convert a value given in user units (px) the given unit type + + .. versionadded:: 1.2""" + return convert_unit(value, to_unit) + + @staticmethod + def to_dimensionless(value): + """Convert a length value into user units (px) + + .. versionadded:: 1.2""" + return convert_unit(value, "px") + + def uutounit(self, value, to_unit="px"): + """Convert a unit value to a given unit. If the value does not have a unit, + "Document" units are assumed. "Document units" are an Inkscape-specific concept. + For most use-cases, :func:`to_dimensional` is more appropriate. + + .. versionadded:: 1.1""" + return convert_unit(value, to_unit, default=self.unit) + + def unittouu(self, value): + """Convert a unit value into document units. "Document unit" is an + Inkscape-specific concept. For most use-cases, :func:`viewport_to_unit` (when + the size of an object given in viewport units is needed) or + :func:`to_dimensionless` (when the equivalent value without unit is needed) is + more appropriate. + + .. versionadded:: 1.1""" + return convert_unit(value, self.unit) + + def unit_to_viewport(self, value, unit="px"): + """Converts a length value to viewport units, as defined by the width/height + element on the root (i.e. applies the equivalent transform of the viewport) + + .. versionadded:: 1.2""" + return self.to_dimensional( + self.to_dimensionless(value) * self.root.equivalent_transform_scale, unit + ) + + def viewport_to_unit(self, value, unit="px"): + """Converts a length given on the viewport to the specified unit in the user + coordinate system + + .. versionadded:: 1.2""" + return self.to_dimensional( + self.to_dimensionless(value) / self.root.equivalent_transform_scale, unit + ) + + def add_unit(self, value): + """Add document unit when no unit is specified in the string. + + .. versionadded:: 1.1""" + return render_unit(value, self.unit) + + def cascaded_style(self): + """Returns the cascaded style of an element (all rules that apply the element + itself), based on the stylesheets, the presentation attributes and the inline + style using the respective specificity of the style. + + see https://www.w3.org/TR/CSS22/cascade.html#cascading-order + + .. versionadded:: 1.2 + + Returns: + Style: the cascaded style + + """ + return Style.cascaded_style(self) + + def specified_style(self): + """Returns the specified style of an element, i.e. the cascaded style + + inheritance, see https://www.w3.org/TR/CSS22/cascade.html#specified-value. + + Returns: + Style: the specified style + + .. versionadded:: 1.2 + """ + return Style.specified_style(self) + + def presentation_style(self): + """Return presentation attributes of an element as style + + .. versionadded:: 1.2""" + style = Style() + for key in self.keys(): + if ( + key in all_properties + and all_properties[key][2] + and not issubclass(all_properties[key][0], ShorthandValue) + ): + # Shorthands cannot be set by presentation attributes + result = BaseStyleValue.factory_errorhandled( + key=key, value=self.attrib[key] + ) + if result is not None: # parsing error + style[key] = result[1] + return style + + def composed_transform(self, other=None): + """Calculate every transform down to the other element + if none specified the transform is to the root document element + """ + parent = self.getparent() + if parent is not other and isinstance(parent, BaseElement): + return parent.composed_transform(other) @ self.transform + return self.transform + + +NodeBasedLookup.default = BaseElement + + +class ShapeElement(BaseElement): + """Elements which have a visible representation on the canvas""" + + @property + def path(self): + """Gets the outline or path of the element, this may be a simple bounding box""" + return Path(self.get_path()) + + @path.setter + def path(self, path): + self.set_path(path) + + @property + def clip(self): + """Gets the clip path element (if any). May be set through CSS. + + .. versionadded:: 1.1""" + ref = self.get("clip-path") + if not ref: + return self.specified_style()("clip-path") + return self.root.getElementById(ref) + + @clip.setter + def clip(self, elem): + self.set("clip-path", elem.get_id(as_url=2)) + + def get_path(self) -> Path: + """Generate a path for this object which can inform the bounding box""" + raise NotImplementedError( + f"Path should be provided by svg elem {self.typename}." + ) + + def set_path(self, path): + """Set the path for this object (if possible)""" + raise AttributeError( + f"Path can not be set on this element: {self.typename} <- {path}." + ) + + def to_path_element(self): + """Replace this element with a path element""" + from ._polygons import PathElement + + elem = PathElement() + elem.path = self.path + elem.style = self.effective_style() + elem.transform = self.transform + return elem + + def effective_style(self): + """Without parent styles, what is the effective style is""" + return self.style + + def bounding_box(self, transform=None): + # type: (Optional[Transform]) -> Optional[BoundingBox] + """BoundingBox of the shape + + .. versionchanged:: 1.1 + result adjusted for element's clip path if applicable.""" + shape_box = self.shape_box(transform) + clip = self.clip + if clip is None or shape_box is None: + return shape_box + return shape_box & clip.bounding_box(Transform(transform) @ self.transform) + + def shape_box(self, transform=None): + # type: (Optional[Transform]) -> Optional[BoundingBox] + """BoundingBox of the unclipped shape + + .. versionadded:: 1.1 + Previous :func:`bounding_box` function, returning the bounding box + without computing the effect of a possible clip.""" + path = self.path.to_absolute() + if transform is True: + path = path.transform(self.composed_transform()) + else: + path = path.transform(self.transform) + if transform: # apply extra transformation + path = path.transform(transform) + return path.bounding_box() + + def is_visible(self): + """Returns false if this object is invisible + + .. versionchanged:: 1.3 + rely on cascaded_style() to include CSS and presentation attributes + include `visibility` attribute with check for inherit + include ancestors + + .. versionadded:: 1.1""" + return self._is_visible() + + def _is_visible(self, inherit_visibility=True): + # iterate over self and ancestors + for element in [self] + list(self.ancestors()): + get_style = element.cascaded_style().get + # case display:none + if get_style("display", "inline") == "none": + return False + # case opacity:0 + if not float(get_style("opacity", 1.0)): + return False + # only check if childs visibility is inherited + if inherit_visibility: + # case visibility:hidden + if get_style("visibility", "inherit") in ( + "hidden", + "collapse", + ): + return False + # case visibility: not inherit + elif get_style("visibility", "inherit") != "inherit": + inherit_visibility = False + + return True + + def get_line_height_uu(self): + """Returns the specified value of line-height, in user units + + .. versionadded:: 1.1""" + style = self.specified_style() + font_size = style("font-size") # already in uu + line_height = style("line-height") + parsed = parse_unit(line_height) + if parsed is None: + return font_size * 1.2 + if parsed[1] == "%": + return font_size * parsed[0] * 0.01 + return self.to_dimensionless(line_height) + + +class ViewboxMixin: + """Mixin for elements with viewboxes, such as , """ + + def parse_viewbox(self, vbox: Optional[str]) -> Optional[List[float]]: + """Parses a viewbox. If an error occurs during parsing, + (0, 0, 0, 0) is returned. If the viewbox is None, None is returned. + + .. versionadded:: 1.3""" + if vbox is not None and isinstance(vbox, str): + try: + result = [float(unit) for unit in re.split(r",\s*|\s+", vbox)] + except ValueError: + result = [] + if len(result) != 4: + result = [0, 0, 0, 0] + return result + return None diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_filters.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_filters.py new file mode 100644 index 0000000..8744f6d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_filters.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Element interface for patterns, filters, gradients and path effects. +""" +from __future__ import annotations +from typing import List, Tuple, TYPE_CHECKING, Optional + +from lxml import etree + +from ..utils import parse_percent + +from ..transforms import Transform + +from ..styles import Style + +from ._utils import addNS +from ._base import BaseElement, ViewboxMixin +from ._groups import GroupBase +from ..units import convert_unit + + +if TYPE_CHECKING: + from ._svg import SvgDocumentElement + + +class Filter(BaseElement): + """A filter (usually in defs)""" + + tag_name = "filter" + + def add_primitive(self, fe_type, **args): + """Create a filter primitive with the given arguments""" + elem = etree.SubElement(self, addNS(fe_type, "svg")) + elem.update(**args) + return elem + + class Primitive(BaseElement): + """Any filter primitive""" + + class Blend(Primitive): + """Blend Filter element""" + + tag_name = "feBlend" + + class ColorMatrix(Primitive): + """ColorMatrix Filter element""" + + tag_name = "feColorMatrix" + + class ComponentTransfer(Primitive): + """ComponentTransfer Filter element""" + + tag_name = "feComponentTransfer" + + class Composite(Primitive): + """Composite Filter element""" + + tag_name = "feComposite" + + class ConvolveMatrix(Primitive): + """ConvolveMatrix Filter element""" + + tag_name = "feConvolveMatrix" + + class DiffuseLighting(Primitive): + """DiffuseLightning Filter element""" + + tag_name = "feDiffuseLighting" + + class DisplacementMap(Primitive): + """Flood Filter element""" + + tag_name = "feDisplacementMap" + + class Flood(Primitive): + """DiffuseLightning Filter element""" + + tag_name = "feFlood" + + class GaussianBlur(Primitive): + """GaussianBlur Filter element""" + + tag_name = "feGaussianBlur" + + class Image(Primitive): + """Image Filter element""" + + tag_name = "feImage" + + class Merge(Primitive): + """Merge Filter element""" + + tag_name = "feMerge" + + class Morphology(Primitive): + """Morphology Filter element""" + + tag_name = "feMorphology" + + class Offset(Primitive): + """Offset Filter element""" + + tag_name = "feOffset" + + class SpecularLighting(Primitive): + """SpecularLighting Filter element""" + + tag_name = "feSpecularLighting" + + class Tile(Primitive): + """Tile Filter element""" + + tag_name = "feTile" + + class Turbulence(Primitive): + """Turbulence Filter element""" + + tag_name = "feTurbulence" + + +class Stop(BaseElement): + """Gradient stop + + .. versionadded:: 1.1""" + + tag_name = "stop" + + @property + def offset(self) -> float: + """The offset of the gradient stop""" + value = self.get("offset", default="0") + return parse_percent(value) + + @offset.setter + def offset(self, number): + self.set("offset", number) + + def interpolate(self, other, fraction): + """Interpolate gradient stops""" + from ..tween import StopInterpolator + + return StopInterpolator(self, other).interpolate(fraction) + + +class Pattern(BaseElement, ViewboxMixin): + """Pattern element which is used in the def to control repeating fills""" + + tag_name = "pattern" + WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (("patternTransform", Transform),) + + def get_fallback(self, prop, default="0"): + val = self.get(prop, None) + if val is None: + if isinstance(self.href, Pattern): + return getattr(self.href, prop) + val = default + return val + + x = property(lambda self: self.get_fallback("x")) + y = property(lambda self: self.get_fallback("y")) + width = property(lambda self: self.get_fallback("width")) + height = property(lambda self: self.get_fallback("height")) + patternUnits = property( + lambda self: self.get_fallback("patternUnits", "objectBoundingBox") + ) + + def get_viewbox(self) -> Optional[List[float]]: + """Get the viewbox of the pattern, falling back to the href's viewbox + + .. versionadded:: 1.3""" + vbox = self.get("viewBox", None) + if vbox is None: + if isinstance(self.href, Pattern): + return self.href.get_viewbox() + return self.parse_viewbox(vbox) + + def get_effective_parent(self, depth=0, maxDepth=10): + """If a pattern has no children, but a href, it uses the children from the href. + Avoids infinite recursion. + + .. versionadded:: 1.3""" + if ( + len(self) == 0 + and self.href is not None + and isinstance(self.href, Pattern) + and depth < maxDepth + ): + return self.href.get_effective_parent(depth + 1, maxDepth) + return self + + +class Mask(GroupBase): + """A structural object that serves as opacity mask + + .. versionadded:: 1.3""" + + tag_name = "mask" + + def get_fallback(self, prop, default="0"): + return self.to_dimensionless(self.get(prop, default)) + + x = property(lambda self: self.get_fallback("x")) + y = property(lambda self: self.get_fallback("y")) + width = property(lambda self: self.get_fallback("width")) + height = property(lambda self: self.get_fallback("height")) + maskUnits = property(lambda self: self.get("maskUnits", "objectBoundingBox")) + + +class Gradient(BaseElement): + """A gradient instruction usually in the defs.""" + + WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (("gradientTransform", Transform),) + """Additional to the :attr:`~inkex.elements._base.BaseElement.WRAPPED_ATTRS` of + :class:`~inkex.elements._base.BaseElement`, ``gradientTransform`` is wrapped.""" + + orientation_attributes = () # type: Tuple[str, ...] + """ + .. versionadded:: 1.1 + """ + + @property + def stops(self): + """Return an ordered list of own or linked stop nodes + + .. versionadded:: 1.1""" + gradcolor = ( + self.href + if isinstance(self.href, (LinearGradient, RadialGradient)) + else self + ) + return [child for child in gradcolor if isinstance(child, Stop)] + + @property + def stop_offsets(self): + # type: () -> List[float] + """Return a list of own or linked stop offsets + + .. versionadded:: 1.1""" + return [child.offset for child in self.stops] + + @property + def stop_styles(self): # type: () -> List[Style] + """Return a list of own or linked offset styles + + .. versionadded:: 1.1""" + return [child.style for child in self.stops] + + def remove_orientation(self): + """Remove all orientation attributes from this element + + .. versionadded:: 1.1""" + for attr in self.orientation_attributes: + self.pop(attr) + + def interpolate( + self, + other: LinearGradient, + fraction: float, + svg: Optional[SvgDocumentElement] = None, + ): + """Interpolate with another gradient. + + .. versionadded:: 1.1""" + from ..tween import GradientInterpolator + + return GradientInterpolator(self, other, svg).interpolate(fraction) + + def stops_and_orientation(self): + """Return a copy of all the stops in this gradient + + .. versionadded:: 1.1""" + stops = self.copy() + stops.remove_orientation() + orientation = self.copy() + orientation.remove_all(Stop) + return stops, orientation + + def get_percentage_parsed_unit(self, attribute, value, svg=None): + """Parses an attribute of a gradient, respecting percentage values of + "userSpaceOnUse" as percentages of document size. See + https://www.w3.org/TR/SVG2/pservers.html#LinearGradientAttributes for details + + .. versionadded:: 1.3""" + if isinstance(value, (float, int)): + return value + value = value.strip() + if len(value) > 0 and value[-1] == "%": + try: + value = float(value.strip()[0:-1]) / 100.0 + gradientunits = self.get("gradientUnits", "objectBoundingBox") + if gradientunits == "userSpaceOnUse": + if svg is None: + raise ValueError("Need root SVG to determine percentage value") + bbox = svg.get_page_bbox() + if attribute in ("cx", "fx", "x1", "x2"): + return bbox.width * value + if attribute in ("cy", "fy", "y1", "y2"): + return bbox.height * value + if attribute in ("r"): + return bbox.diagonal_length * value + if gradientunits == "objectBoundingBox": + return value + except ValueError: + value = None + return convert_unit(value, "px") + + def _get_or_href(self, attr, default, svg=None): + val = self.get(attr) + if val is None: + if type(self.href) is type(self): + return getattr(self.href, attr)() + val = default + return self.get_percentage_parsed_unit(attr, val, svg) + + +class LinearGradient(Gradient): + """LinearGradient element""" + + tag_name = "linearGradient" + orientation_attributes = ("x1", "y1", "x2", "y2") + """ + .. versionadded:: 1.1 + """ + + def apply_transform(self): # type: () -> None + """Apply transform to orientation points and set it to identity. + .. versionadded:: 1.1 + """ + trans = self.pop("gradientTransform") + pt1 = ( + self.to_dimensionless(self.get("x1")), + self.to_dimensionless(self.get("y1")), + ) + pt2 = ( + self.to_dimensionless(self.get("x2")), + self.to_dimensionless(self.get("y2")), + ) + p1t = trans.apply_to_point(pt1) + p2t = trans.apply_to_point(pt2) + self.update( + x1=self.to_dimensionless(p1t[0]), + y1=self.to_dimensionless(p1t[1]), + x2=self.to_dimensionless(p2t[0]), + y2=self.to_dimensionless(p2t[1]), + ) + + def x1(self, svg=None): + """Get the x1 attribute + + .. versionadded:: 1.3""" + return self._get_or_href("x1", "0%", svg) + + def x2(self, svg=None): + """Get the x2 attribute + + .. versionadded:: 1.3""" + return self._get_or_href("x2", "100%", svg) + + def y1(self, svg=None): + """Get the y1 attribute + + .. versionadded:: 1.3""" + return self._get_or_href("y1", "0%", svg) + + def y2(self, svg=None): + """Get the y2 attribute + + .. versionadded:: 1.3""" + return self._get_or_href("y2", "0%", svg) + + +class RadialGradient(Gradient): + """RadialGradient element""" + + tag_name = "radialGradient" + orientation_attributes = ("cx", "cy", "fx", "fy", "r") + """ + .. versionadded:: 1.1 + """ + + def apply_transform(self): # type: () -> None + """Apply transform to orientation points and set it to identity. + + .. versionadded:: 1.1 + """ + trans = self.pop("gradientTransform") + pt1 = ( + self.to_dimensionless(self.get("cx")), + self.to_dimensionless(self.get("cy")), + ) + pt2 = ( + self.to_dimensionless(self.get("fx")), + self.to_dimensionless(self.get("fy")), + ) + p1t = trans.apply_to_point(pt1) + p2t = trans.apply_to_point(pt2) + self.update( + cx=self.to_dimensionless(p1t[0]), + cy=self.to_dimensionless(p1t[1]), + fx=self.to_dimensionless(p2t[0]), + fy=self.to_dimensionless(p2t[1]), + ) + + def cx(self, svg=None): + """Get the effective cx (horizontal center) attribute in user units + + .. versionadded:: 1.3""" + return self._get_or_href("cx", "50%", svg) + + def cy(self, svg=None): + """Get the effective cy (vertical center) attribute in user units + + .. versionadded:: 1.3""" + return self._get_or_href("cy", "50%", svg) + + def fx(self, svg=None): + """Get the effective fx (horizontal focal point) attribute in user units + + .. versionadded:: 1.3""" + return self._get_or_href("fx", self.cx(svg), svg) + + def fy(self, svg=None): + """Get the effective fx (vertical focal point) attribute in user units + + .. versionadded:: 1.3""" + return self._get_or_href("fy", self.cy(svg), svg) + + def r(self, svg=None): + """Get the effective r (gradient radius) attribute in user units + + .. versionadded:: 1.3""" + return self._get_or_href("r", "50%", svg) + + +class PathEffect(BaseElement): + """Inkscape LPE element""" + + tag_name = "inkscape:path-effect" + + +class MeshGradient(Gradient): + """Usable MeshGradient XML base class + + .. versionadded:: 1.1""" + + tag_name = "meshgradient" + + @classmethod + def new_mesh(cls, pos=None, rows=1, cols=1, autocollect=True): + """Return skeleton of 1x1 meshgradient definition.""" + # initial point + if pos is None or len(pos) != 2: + pos = [0.0, 0.0] + # create nested elements for rows x cols mesh + meshgradient = cls() + for _ in range(rows): + meshrow: BaseElement = meshgradient.add(MeshRow()) + for _ in range(cols): + meshrow.append(MeshPatch()) + # set meshgradient attributes + meshgradient.set("gradientUnits", "userSpaceOnUse") + meshgradient.set("x", pos[0]) + meshgradient.set("y", pos[1]) + if autocollect: + meshgradient.set("inkscape:collect", "always") + return meshgradient + + +class MeshRow(BaseElement): + """Each row of a mesh gradient + + .. versionadded:: 1.1""" + + tag_name = "meshrow" + + +class MeshPatch(BaseElement): + """Each column or 'patch' in a mesh gradient + + .. versionadded:: 1.1""" + + tag_name = "meshpatch" + + def stops(self, edges, colors): + """Add or edit meshpatch stops with path and stop-color.""" + # iterate stops based on number of edges (path data) + for i, edge in enumerate(edges): + if i < len(self): + stop = self[i] + else: + stop = self.add(Stop()) + + # set edge path data + stop.set("path", str(edge)) + # set stop color + stop.style["stop-color"] = str(colors[i % 2]) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_groups.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_groups.py new file mode 100644 index 0000000..e72620c --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_groups.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Ryan Jarvis +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Interface for all group based elements such as Groups, Use, Markers etc. +""" + +from lxml import etree # pylint: disable=unused-import + +from ..paths import Path +from ..transforms import Transform + +from ._utils import addNS +from ._base import ShapeElement, ViewboxMixin +from ._polygons import PathElement + +try: + from typing import Optional, List # pylint: disable=unused-import +except ImportError: + pass + + +class GroupBase(ShapeElement): + """Base Group element""" + + def get_path(self): + ret = Path() + for child in self: + if isinstance(child, ShapeElement): + ret += child.path.transform(child.transform) + return ret + + def shape_box(self, transform=None): + bbox = None + effective_transform = Transform(transform) @ self.transform + for child in self: + if isinstance(child, ShapeElement): + child_bbox = child.bounding_box(transform=effective_transform) + if child_bbox is not None: + bbox += child_bbox + return bbox + + def bake_transforms_recursively(self, apply_to_paths=True): + """Bake transforms, i.e. each leaf node has the effective transform (starting + from this group) set, and parent transforms are removed. + + .. versionadded:: 1.4 + + Args: + apply_to_paths (bool, optional): For path elements, the + path data is transformed with its effective transform. Nodes and handles + will have the same position as before, but visual appearance of the + stroke may change (stroke-width is not touched). Defaults to True. + """ + # pylint: disable=attribute-defined-outside-init + self.transform: Transform + for element in self: + if isinstance(element, PathElement) and apply_to_paths: + element.path = element.path.transform(self.transform) + else: + element.transform = self.transform @ element.transform + if isinstance(element, GroupBase): + element.bake_transforms_recursively(apply_to_paths) + self.transform = None + + +class Group(GroupBase): + """Any group element (layer or regular group)""" + + tag_name = "g" + + @classmethod + def new(cls, label, *children, **attrs): + attrs["inkscape:label"] = label + return super().new(*children, **attrs) + + def effective_style(self): + """A blend of each child's style mixed together (last child wins)""" + style = self.style + for child in self: + style.update(child.effective_style()) + return style + + @property + def groupmode(self): + """Return the type of group this is""" + return self.get("inkscape:groupmode", "group") + + +class Layer(Group): + """Inkscape extension of svg:g""" + + def _init(self): + self.set("inkscape:groupmode", "layer") + + @classmethod + def is_class_element(cls, elem): + # type: (etree.Element) -> bool + return elem.attrib.get(addNS("inkscape:groupmode"), None) == "layer" + + +class Anchor(GroupBase): + """An anchor or link tag""" + + tag_name = "a" + + @classmethod + def new(cls, href, *children, **attrs): + attrs["xlink:href"] = href + return super().new(*children, **attrs) + + +class ClipPath(GroupBase): + """A path used to clip objects""" + + tag_name = "clipPath" + + +class Marker(GroupBase, ViewboxMixin): + """The element defines the graphic that is to be used for drawing + arrowheads or polymarkers on a given , , or + element.""" + + tag_name = "marker" + + def get_viewbox(self) -> List[float]: + """Returns the viewbox of the Marker, falling back to + [0 0 markerWidth markerHeight] + + .. versionadded:: 1.3""" + vbox = self.get("viewBox", None) + result = self.parse_viewbox(vbox) + if result is None: + # use viewport, https://www.w3.org/TR/SVG11/painting.html#MarkerElement + return [ + 0, + 0, + self.to_dimensionless(self.get("markerWidth")), + self.to_dimensionless(self.get("markerHeight")), + ] + return result diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_image.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_image.py new file mode 100644 index 0000000..9294d73 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_image.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Image element interface. +""" + +from ._polygons import RectangleBase + + +class Image(RectangleBase): + """Provide a useful extension for image elements""" + + tag_name = "image" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_meta.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_meta.py new file mode 100644 index 0000000..eceb2c1 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_meta.py @@ -0,0 +1,466 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Maren Hachmann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Provide extra utility to each svg element type specific to its type. + +This is useful for having a common interface for each element which can +give path, transform, and property access easily. +""" + +from __future__ import annotations +import math + +from typing import List, Optional + +from lxml import etree + +from inkex.deprecated.meta import deprecate + +from ..styles import StyleSheet +from ..transforms import BoundingBox, Vector2d, VectorLike, DirectedLineSegment + +from ._base import BaseElement + + +class Defs(BaseElement): + """A header defs element, one per document""" + + tag_name = "defs" + + +class StyleElement(BaseElement): + """A CSS style element containing multiple style definitions""" + + tag_name = "style" + + def set_text(self, content): + """Sets the style content text as a CDATA section""" + self.text = etree.CDATA(str(content)) + + def stylesheet(self): + """Return the StyleSheet() object for the style tag""" + return StyleSheet(self.text, callback=self.set_text) + + +class Script(BaseElement): + """A javascript tag in SVG""" + + tag_name = "script" + + def set_text(self, content): + """Sets the style content text as a CDATA section""" + self.text = etree.CDATA(str(content)) + + +class Desc(BaseElement): + """Description element""" + + tag_name = "desc" + + +class Title(BaseElement): + """Title element""" + + tag_name = "title" + + +class NamedView(BaseElement): + """The NamedView element is Inkscape specific metadata about the file""" + + tag_name = "sodipodi:namedview" + + current_layer = property(lambda self: self.get("inkscape:current-layer")) + + @property + def center(self): + """Returns view_center in terms of document units""" + return Vector2d( + self.root.viewport_to_unit(self.get("inkscape:cx") or 0), + self.root.viewport_to_unit(self.get("inkscape:cy") or 0), + ) + + def get_guides(self): + """Returns a list of guides""" + return self.findall("sodipodi:guide") + + def add_guide(self, position, orient=True, name=None) -> Guide: + """Creates a new guide in this namedview + + .. versionadded:: 1.3 + + Args: + position: a float containing the y position for ``orient is True``, or + the x position for ``orient is False``. The position is specified in the + post-1.0 coordinate system, i.e. y=0 is at the top left of the viewbox, + positive y axis pointing down. + + Alternatively, the position may be given as Tuple (or VectorLike) + orient: True for horizontal, False for Vertical + + alternatively: Tuple / Vector specifying x and y coordinates of the + normal vector of the guide, or the (clockwise) angle between the + horizontal axis and the guide. Defaults to True (horizontal) + name: label of the guide + + Returns: + the created guide""" + elem = self.add(Guide()) + + if orient is True: + elem.set_position(0, position, (0, -1)) + elif orient is False: + elem.set_position(position, self.root.viewbox_height, (1, 0)) + else: + pos = Vector2d(position) + elem.set_position(pos.x, pos.y, orient) + if name: + elem.set("inkscape:label", str(name)) + return elem + + @deprecate + def new_guide(self, position, orient=True, name=None): + """ + .. deprecated:: 1.3 + Use :func:`add_guide` instead. + + Creates a new guide in this namedview + + Args: + position: a float containing the y position for ``orient is True``, or + the x position for ``orient is False`` + + .. versionchanged:: 1.2 + Alternatively, the position may be given as Tuple (or VectorLike) + orient: True for horizontal, False for Vertical + + .. versionchanged:: 1.2 + Tuple / Vector specifying x and y coordinates of the normal vector + of the guide. + name: label of the guide + + Returns: + the created guide""" + if orient is True: + elem = Guide().move_to(0, position, (0, 1)) + elif orient is False: + elem = Guide().move_to(position, 0, (1, 0)) + else: + elem = Guide().move_to(*position, orient) + if name: + elem.set("inkscape:label", str(name)) + return self.add(elem) + + @deprecate + def new_unique_guide( + self, position: VectorLike, orientation: VectorLike + ) -> Optional[Guide]: + """ + .. deprecated:: 1.3 + Use :func:`add_unique_guide` instead. + + Add a guide iif there is no guide that looks the same. + + .. versionadded:: 1.2 + + """ + elem = Guide().move_to(position[0], position[1], orientation) + return self.add(elem) if self.get_similar_guide(elem) is None else None + + def add_unique_guide( + self, position: VectorLike, orientation: VectorLike + ) -> Optional[Guide]: + """Add a guide iif there is no guide that looks the same. + + .. versionadded:: 1.3 + + Args: + position: Position as Tuple / Vector + orientation: Tuple / Vector specifying x and y coordinates of the normal + vector of the guide. + name: label of the guide + """ + elem = self.add(Guide()).set_position(position[0], position[1], orientation) + if self.get_similar_guide(elem) is not None: + self.remove(elem) + return None + return elem + + def get_similar_guide(self, other: Guide) -> Optional[Guide]: + """Check if the namedview contains a guide that looks identical to one + defined by (position, orientation) and is not identity (same element) as the + first one. If such a guide exists, return it; otherwise, return None. + + .. versionadded:: 1.2""" + for guide in self.get_guides(): + if Guide.guides_coincident(guide, other) and guide != other: + return guide + return None + + def _get_pages(self) -> List[Page]: + """Returns all page elements""" + return self.findall("inkscape:page") + + def _equivalent_page(self) -> Page: + """Returns an unrooted page based on the viewbox dimensions""" + return Page.new(self.root.viewbox_width, self.root.viewbox_height, 0, 0) + + def get_pages(self) -> List[Page]: + """Returns a list of pages within the document. For single page documents, + a detached page element with dimensions according to the viewbox will be + returned. + + .. versionadded:: 1.2 + + .. versionchanged:: 1.3 + For single-page documents, this function now returns the viewbox + dimensions. + """ + pages = self._get_pages() + if len(pages) < 2: + return [self._equivalent_page()] + return pages + + def new_page(self, x, y, width, height, label=None): + """Creates a new page in this namedview. Always add pages through this + function to ensure that single-page documents are treated correctly. + + .. versionadded:: 1.2 + + .. versionchanged:: 1.3 + If none exists, a page element with the viewbox dimensions will be + inserted before the new page.""" + if len(self._get_pages()) == 0: + self.add(self._equivalent_page()) + elem = Page(width=width, height=height, x=x, y=y) + if label: + elem.set("inkscape:label", str(label)) + return self.add(elem) + + +class Guide(BaseElement): + """An inkscape guide""" + + tag_name = "sodipodi:guide" + + @property + def orientation(self) -> Vector2d: + """Vector normal to the guide, in the pre-1.0 coordinate system (y axis upwards) + + .. versionadded:: 1.2""" + return Vector2d(self.get("orientation"), fallback=(1, 0)) + + @property + def angle(self) -> float: + """(Clockwise) angle between the guide and the horizontal axis in degrees + (i.e. what Inkscape 1.2+ shows as "Angle" in the guide properties) + + .. versionadded:: 1.3""" + return math.degrees(math.atan2(*self.orientation)) + + is_horizontal = property( + lambda self: self.orientation[0] == 0 and self.orientation[1] != 0 + ) + is_vertical = property( + lambda self: self.orientation[0] != 0 and self.orientation[1] == 0 + ) + + @property + def raw_position(self) -> Vector2d: + """Position of the guide handle. The y coordinate is flipped and relative + to the bottom of the viewbox, this is a remnant of the pre-1.0 coordinate system + """ + return Vector2d(self.get("position"), fallback=(0, 0)) + + def point(self): + """Use raw_position or position instead""" + return self.raw_position + + point = property(deprecate(point)) # type: ignore + + @property + def position(self) -> Vector2d: + """Position of the guide handle in normal coordinates, i.e. (0,0) is at + the top left corner of the viewbox, positive y axis pointing downwards. + + This function can only be used for guides which are attached to a root + svg element.""" + pos = self.raw_position + return Vector2d(pos.x, self.root.viewbox_height - pos.y) + + @classmethod + def new(cls, pos_x, pos_y, angle, **attrs): + guide = super().new(**attrs) + guide.set_position(pos_x, pos_y, angle=angle) + return guide + + def set_position(self, pos_x, pos_y, angle=None): + """ + Move this guide to the given x,y position and optionally set its orientation. + The coordinate system used is the post-1.0 coordinate system (origin in the + top left corner, y axis pointing down), which also defines the sense of + rotation. + + The guide must be rooted for this function to be used. Preferably, use + :func:`inkex.elements._meta.add_guide` to create a new guide. + + .. versionadded:: 1.3 + + Args: + pos_x (Union[str, int, float]): x position of the guide's reference point + pos_y (Union[str, int, float]): y position of the guide's reference point + angle (Union[str, float, int, tuple, list], optional): Angle may be a + string, float or integer, which will set the clockwise angle between the + horizontal axis and the guide. + + Alternatively, it may be a pair of numbers (tuple) which will be set + as normal vector. + + If not given at all, the orientation remains unchanged. + Defaults to None. + + Returns: + Guide: the modified guide + """ + pos_y = self.root.viewbox_height - float(pos_y) + self.set("position", f"{float(pos_x):g},{float(pos_y):g}") + if isinstance(angle, str): + if "," not in angle: + angle = float(angle) + + if isinstance(angle, (float, int)): + # Generate orientation from angle + angle = (math.sin(math.radians(angle)), -math.cos(math.radians(angle))) + + if isinstance(angle, (tuple, list)) and len(angle) == 2: + angle = ",".join(f"{i:g}" for i in [angle[0], -angle[1]]) + + if angle is not None: + self.set("orientation", angle) + return self + + @deprecate + def move_to(self, pos_x, pos_y, angle=None): + """ + .. deprecated:: 1.3 + Use :func:`set_position` instead. + + Move this guide to the given x,y position, + + Angle may be a float or integer, which will change the orientation. Alternately, + it may be a pair of numbers (tuple) which will set the orientation directly. + If not given at all, the orientation remains unchanged. + """ + self.set("position", f"{float(pos_x):g},{float(pos_y):g}") + if isinstance(angle, str): + if "," not in angle: + angle = float(angle) + + if isinstance(angle, (float, int)): + # Generate orientation from angle + angle = (math.sin(math.radians(angle)), -math.cos(math.radians(angle))) + + if isinstance(angle, (tuple, list)) and len(angle) == 2: + angle = ",".join(f"{i:g}" for i in angle) + + if angle is not None: + self.set("orientation", angle) + return self + + @staticmethod + def guides_coincident(guide1, guide2): + """Check if two guides defined by (position, orientation) and (opos, oor) look + identical (i.e. the position lies on the other guide AND the guide is + (anti)parallel to the other guide). + + .. versionadded:: 1.2""" + # normalize orientations first + orientation = guide1.orientation / guide1.orientation.length + oor = guide2.orientation / guide2.orientation.length + + position = guide1.raw_position + opos = guide2.raw_position + + return ( + DirectedLineSegment( + position, position + Vector2d(orientation[1], -orientation[0]) + ).perp_distance(*opos) + < 1e-6 + and abs(abs(orientation[1] * oor[0]) - abs(orientation[0] * oor[1])) < 1e-6 + ) + + +class Metadata(BaseElement): + """Inkscape Metadata element""" + + tag_name = "metadata" + + +class ForeignObject(BaseElement): + """SVG foreignObject element""" + + tag_name = "foreignObject" + + +class Switch(BaseElement): + """A switch element""" + + tag_name = "switch" + + +class Grid(BaseElement): + """A namedview grid child""" + + tag_name = "inkscape:grid" + + +class Page(BaseElement): + """A namedview page child + + .. versionadded:: 1.2""" + + tag_name = "inkscape:page" + + width = property(lambda self: self.to_dimensionless(self.get("width") or 0)) + height = property(lambda self: self.to_dimensionless(self.get("height") or 0)) + x = property(lambda self: self.to_dimensionless(self.get("x") or 0)) + y = property(lambda self: self.to_dimensionless(self.get("y") or 0)) + + @classmethod + def new(cls, width, height, x, y): + """Creates a new page element in the namedview""" + page = super().new() + page.move_to(x, y) + page.set("width", width) + page.set("height", height) + return page + + def move_to(self, x, y): + """Move this page to the given x,y position""" + self.set("x", f"{float(x):g}") + self.set("y", f"{float(y):g}") + return self + + @property + def bounding_box(self) -> BoundingBox: + """Returns the bounding box of the page.""" + return BoundingBox( + (self.x, self.x + self.width), (self.y, self.y + self.height) + ) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_parser.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_parser.py new file mode 100644 index 0000000..ffe4e08 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_parser.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +"""Utilities for parsing SVG documents. + +.. versionadded:: 1.2 + Separated out from :py:mod:`inkex.elements._base`""" + +from collections import defaultdict +from typing import DefaultDict, List, Any, Type + +from lxml import etree + +from ..interfaces.IElement import IBaseElement + +from ._utils import splitNS +from ..utils import errormsg +from ..localization import inkex_gettext as _ + + +class NodeBasedLookup(etree.PythonElementClassLookup): + """ + We choose what kind of Elements we should return for each element, providing useful + SVG based API to our extensions system. + """ + + default: Type[IBaseElement] + + # (ns,tag) -> list(cls) ; ascending priority + lookup_table = defaultdict(list) # type: DefaultDict[str, List[Any]] + + @classmethod + def register_class(cls, klass): + """Register the given class using it's attached tag name""" + cls.lookup_table[splitNS(klass.tag_name)].append(klass) + + @classmethod + def find_class(cls, xpath): + """Find the class for this type of element defined by an xpath + + .. versionadded:: 1.1""" + if isinstance(xpath, type): + return xpath + for kls in cls.lookup_table[splitNS(xpath.split("/")[-1])]: + # TODO: We could create a apply the xpath attrs to the test element + # to narrow the search, but this does everything we need right now. + test_element = kls() + if kls.is_class_element(test_element): + return kls + raise KeyError(f"Could not find svg tag for '{xpath}'") + + def lookup(self, doc, element): # pylint: disable=unused-argument + """Lookup called by lxml when assigning elements their object class""" + try: + for kls in reversed(self.lookup_table[splitNS(element.tag)]): + if kls.is_class_element(element): # pylint: disable=protected-access + return kls + except TypeError: + # Handle non-element proxies case + # The documentation implies that it's not possible + # Didn't found a reliable way to check whether proxy corresponds to element + # or not + # Look like lxml issue to me. + # The troubling element is "" + return None + return NodeBasedLookup.default + + +SVG_PARSER = etree.XMLParser(huge_tree=True, strip_cdata=False, recover=True) +SVG_PARSER.set_element_class_lookup(NodeBasedLookup()) + + +def load_svg(stream): + """Load SVG file using the SVG_PARSER""" + if (isinstance(stream, str) and stream.lstrip().startswith("<")) or ( + isinstance(stream, bytes) and stream.lstrip().startswith(b"<") + ): + parsed = etree.ElementTree(etree.fromstring(stream, parser=SVG_PARSER)) + else: + parsed = etree.parse(stream, parser=SVG_PARSER) + if len(SVG_PARSER.error_log) > 0: + errormsg( + _( + "A parsing error occurred, which means you are likely working with " + "a non-conformant SVG file. The following errors were found:\n" + ) + ) + for __, element in enumerate(SVG_PARSER.error_log): + errormsg( + _("{}. Line {}, column {}").format( + element.message, element.line, element.column + ) + ) + errormsg( + _( + "\nProcessing will continue; however we encourage you to fix your" + " file manually." + ) + ) + return parsed diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_polygons.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_polygons.py new file mode 100644 index 0000000..04ea9aa --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_polygons.py @@ -0,0 +1,509 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Interface for all shapes/polygons such as lines, paths, rectangles, circles etc. +""" + +from math import cos, pi, sin +from typing import Optional, Tuple +from ..paths import Arc, Curve, Move, Path, ZoneClose +from ..paths import Line as PathLine +from ..transforms import Transform, ImmutableVector2d, Vector2d +from ..bezier import pointdistance + +from ._utils import addNS +from ._base import ShapeElement + + +class PathElementBase(ShapeElement): + """Base element for path based shapes""" + + get_path = lambda self: Path(self.get("d")) + + @classmethod + def new(cls, path, **attrs): + return super().new(d=Path(path), **attrs) + + def set_path(self, path): + """Set the given data as a path as the 'd' attribute""" + self.set("d", str(Path(path))) + + def apply_transform(self): + """Apply the internal transformation to this node and delete""" + if "transform" in self.attrib: + self.path = self.path.transform(self.transform) + self.set("transform", Transform()) + + @property + def original_path(self): + """Returns the original path if this is a LPE, or the path if not""" + return Path(self.get("inkscape:original-d", self.path)) + + @original_path.setter + def original_path(self, path): + if addNS("inkscape:original-d") in self.attrib: + self.set("inkscape:original-d", str(Path(path))) + else: + self.path = path + + +class PathElement(PathElementBase): + """Provide a useful extension for path elements""" + + tag_name = "path" + + @staticmethod + def _arcpath( + cx: float, + cy: float, + rx: float, + ry: float, + start: float, + end: float, + arctype: str, + ) -> Optional[Path]: + """Compute the path for an arc defined by Inkscape-specific attributes. + + For details on arguments, see :func:`arc`. + + .. versionadded:: 1.2""" + if abs(rx) < 1e-8 or abs(ry) < 1e-8: + return None + incr = end - start + if incr < 0: + incr += 2 * pi + numsegs = min(1 + int(incr * 2.0 / pi), 4) + incr = incr / numsegs + + computed = Path() + computed.append(Move(cos(start), sin(start))) + for seg in range(1, numsegs + 1): + computed.append( + Arc(1, 1, 0, 0, 1, cos(start + seg * incr), sin(start + seg * incr)) + ) + if abs(incr * numsegs - 2 * pi) > 1e-8 and ( + arctype in ("slice", "") + ): # slice is default + computed.append(PathLine(0, 0)) + if arctype != "arc": + computed.append(ZoneClose()) + computed.transform( + Transform().add_translate(cx, cy).add_scale(rx, ry), inplace=True + ) + return computed.to_relative() + + @classmethod + def arc( + cls, center, rx, ry=None, arctype="", pathonly=False, **kw + ): # pylint: disable=invalid-name + """Generates a sodipodi elliptical arc (special type). Also computes the path + that Inkscape uses under the hood. + All data may be given as parseable strings or using numeric data types. + + Args: + center (tuple-like): Coordinates of the star/polygon center as tuple or + Vector2d + rx (Union[float, str]): Radius in x direction + ry (Union[float, str], optional): Radius in y direction. If not given, + ry=rx. Defaults to None. + arctype (str, optional): "arc", "chord" or "slice". Defaults to "", i.e. + "slice". + + .. versionadded:: 1.2 + Previously set to "arc" as fixed value + pathonly (bool, optional): Whether to create the path without + Inkscape-specific attributes. Defaults to False. + + .. versionadded:: 1.2 + Keyword args: + start (Union[float, str]): start angle in radians + end (Union[float, str]): end angle in radians + open (str): whether the path should be open (true/false). Not used in + Inkscape > 1.1 + + Returns: + PathElement : the created star/polygon + """ + others = [(name, kw.pop(name, None)) for name in ("start", "end", "open")] + elem = cls(**kw) + elem.set("sodipodi:cx", center[0]) + elem.set("sodipodi:cy", center[1]) + elem.set("sodipodi:rx", rx) + elem.set("sodipodi:ry", ry or rx) + elem.set("sodipodi:type", "arc") + if arctype != "": + elem.set("sodipodi:arc-type", arctype) + for name, value in others: + if value is not None: + elem.set("sodipodi:" + name, str(value).lower()) + + path = cls._arcpath( + float(center[0]), + float(center[1]), + float(rx), + float(ry or rx), + float(elem.get("sodipodi:start", 0)), + float(elem.get("sodipodi:end", 2 * pi)), + arctype, + ) + if pathonly: + elem = cls(**kw) + if path is not None: + elem.path = path + return elem + + @staticmethod + def _starpath( + c: Tuple[float, float], + sides: int, + r: Tuple[float, float], # pylint: disable=invalid-name + arg: Tuple[float, float], + rounded: float, + flatsided: bool, + ): + """Helper method to generate the path for an Inkscape star/ polygon; randomized + is ignored. + + For details on arguments, see :func:`star`. + + .. versionadded:: 1.2""" + + def _star_get_xy(point, index): + cur_arg = arg[point] + 2 * pi / sides * (index % sides) + return Vector2d(*c) + r[point] * Vector2d(cos(cur_arg), sin(cur_arg)) + + def _rot90_rel(origin, other): + """Returns a unit length vector at 90 deg from origin to other""" + return ( + 1 + / pointdistance(other, origin) + * Vector2d(other.y - origin.y, other.x - origin.x) + ) + + def _star_get_curvepoint(point, index, is_prev: bool): + index = index % sides + orig = _star_get_xy(point, index) + previ = (index - 1 + sides) % sides + nexti = (index + 1) % sides + # neighbors of the current point depend on polygon or star + prev = ( + _star_get_xy(point, previ) + if flatsided + else _star_get_xy(1 - point, index if point == 1 else previ) + ) + nextp = ( + _star_get_xy(point, nexti) + if flatsided + else _star_get_xy(1 - point, index if point == 0 else nexti) + ) + mid = 0.5 * (prev + nextp) + # direction of bezier handles + rot = _rot90_rel(orig, mid + 100000 * _rot90_rel(mid, nextp)) + ret = ( + rounded + * rot + * ( + -1 * pointdistance(prev, orig) + if is_prev + else pointdistance(nextp, orig) + ) + ) + return orig + ret + + pointy = abs(rounded) < 1e-4 + result = Path() + result.append(Move(*_star_get_xy(0, 0))) + for i in range(0, sides): + # draw to point type 1 for stars + if not flatsided: + if pointy: + result.append(PathLine(*_star_get_xy(1, i))) + else: + result.append( + Curve( + *_star_get_curvepoint(0, i, False), + *_star_get_curvepoint(1, i, True), + *_star_get_xy(1, i), + ) + ) + # draw to point type 0 for both stars and rectangles + if pointy and i < sides - 1: + result.append(PathLine(*_star_get_xy(0, i + 1))) + if not pointy: + if not flatsided: + result.append( + Curve( + *_star_get_curvepoint(1, i, False), + *_star_get_curvepoint(0, i + 1, True), + *_star_get_xy(0, i + 1), + ) + ) + else: + result.append( + Curve( + *_star_get_curvepoint(0, i, False), + *_star_get_curvepoint(0, i + 1, True), + *_star_get_xy(0, i + 1), + ) + ) + + result.append(ZoneClose()) + return result.to_relative() + + @classmethod + def star( + cls, + center, + radii, + sides=5, + rounded=0, + args=(0, 0), + flatsided=False, + pathonly=False, + ): + """Generate a sodipodi star / polygon. Also computes the path that Inkscape uses + under the hood. The arguments for center, radii, sides, rounded and args can be + given as strings or as numeric data. + + .. versionadded:: 1.1 + + Args: + center (Tuple-like): Coordinates of the star/polygon center as tuple or + Vector2d + radii (tuple): Radii of the control points, i.e. their distances from the + center. The control points are specified in polar coordinates. Only the + first control point is used for polygons. + sides (int, optional): Number of sides / tips of the polygon / star. + Defaults to 5. + rounded (int, optional): Controls the rounding radius of the polygon / star. + For `rounded=0`, only straight lines are used. Defaults to 0. + args (tuple, optional): Angle between horizontal axis and control points. + Defaults to (0,0). + + .. versionadded:: 1.2 + Previously fixed to (0.85, 1.3) + flatsided (bool, optional): True for polygons, False for stars. + Defaults to False. + + .. versionadded:: 1.2 + pathonly (bool, optional): Whether to create the path without + Inkscape-specific attributes. Defaults to False. + + .. versionadded:: 1.2 + + Returns: + PathElement : the created star/polygon + """ + elem = cls() + elem.set("sodipodi:cx", center[0]) + elem.set("sodipodi:cy", center[1]) + elem.set("sodipodi:r1", radii[0]) + elem.set("sodipodi:r2", radii[1]) + elem.set("sodipodi:arg1", args[0]) + elem.set("sodipodi:arg2", args[1]) + elem.set("sodipodi:sides", max(sides, 3) if flatsided else max(sides, 2)) + elem.set("inkscape:rounded", rounded) + elem.set("inkscape:flatsided", str(flatsided).lower()) + elem.set("sodipodi:type", "star") + + path = cls._starpath( + (float(center[0]), float(center[1])), + int(sides), + (float(radii[0]), float(radii[1])), + (float(args[0]), float(args[1])), + float(rounded), + flatsided, + ) + if pathonly: + elem = cls() + # inkex.errormsg(path) + if path is not None: + elem.path = path + + return elem + + +class Polyline(ShapeElement): + """Like a path, but made up of straight line segments only""" + + tag_name = "polyline" + + def get_path(self): + return Path("M" + self.get("points")) + + def set_path(self, path): + points = [f"{x:g},{y:g}" for x, y in Path(path).end_points] + self.set("points", " ".join(points)) + + +class Polygon(ShapeElement): + """A closed polyline""" + + tag_name = "polygon" + get_path = lambda self: Path("M" + self.get("points") + " Z") + + +class Line(ShapeElement): + """A line segment connecting two points""" + + tag_name = "line" + x1 = property(lambda self: self.to_dimensionless(self.get("x1", 0))) + y1 = property(lambda self: self.to_dimensionless(self.get("y1", 0))) + x2 = property(lambda self: self.to_dimensionless(self.get("x2", 0))) + y2 = property(lambda self: self.to_dimensionless(self.get("y2", 0))) + get_path = lambda self: Path(f"M{self.x1},{self.y1} L{self.x2},{self.y2}") + + @classmethod + def new(cls, start, end, **attrs): + start = Vector2d(start) + end = Vector2d(end) + return super().new(x1=start.x, y1=start.y, x2=end.x, y2=end.y, **attrs) + + +class RectangleBase(ShapeElement): + """Provide a useful extension for rectangle elements""" + + left = property(lambda self: self.to_dimensionless(self.get("x", "0"))) + top = property(lambda self: self.to_dimensionless(self.get("y", "0"))) + right = property(lambda self: self.left + self.width) + bottom = property(lambda self: self.top + self.height) + width = property(lambda self: self.to_dimensionless(self.get("width", "0"))) + height = property(lambda self: self.to_dimensionless(self.get("height", "0"))) + rx = property( + lambda self: self.to_dimensionless(self.get("rx", self.get("ry", 0.0))) + ) + ry = property( + lambda self: self.to_dimensionless(self.get("ry", self.get("rx", 0.0))) + ) # pylint: disable=invalid-name + + def get_path(self): + """Calculate the path as the box around the rect""" + if self.rx or self.ry: + # pylint: disable=invalid-name + rx = min(self.rx if self.rx > 0 else self.ry, self.width / 2) + ry = min(self.ry if self.ry > 0 else self.rx, self.height / 2) + cpts = [self.left + rx, self.right - rx, self.top + ry, self.bottom - ry] + return ( + f"M {cpts[0]},{self.top}" + f"L {cpts[1]},{self.top} " + f"A {rx},{ry} 0 0 1 {self.right},{cpts[2]}" + f"L {self.right},{cpts[3]} " + f"A {rx},{ry} 0 0 1 {cpts[1]},{self.bottom}" + f"L {cpts[0]},{self.bottom} " + f"A {rx},{ry} 0 0 1 {self.left},{cpts[3]}" + f"L {self.left},{cpts[2]} " + f"A {rx},{ry} 0 0 1 {cpts[0]},{self.top} z" + ) + + return f"M {self.left},{self.top} h{self.width}v{self.height}h{-self.width} z" + + +class Rectangle(RectangleBase): + """Provide a useful extension for rectangle elements""" + + tag_name = "rect" + + @classmethod + def new(cls, left, top, width, height, **attrs): + return super().new(x=left, y=top, width=width, height=height, **attrs) + + +class EllipseBase(ShapeElement): + """Absorbs common part of Circle and Ellipse classes""" + + def get_path(self): + """Calculate the arc path of this circle""" + rx, ry = self.rxry() + cx, y = self.center.x, self.center.y - ry + return ( + "M {cx},{y} " + "a {rx},{ry} 0 1 0 {rx}, {ry} " + "a {rx},{ry} 0 0 0 -{rx}, -{ry} z" + ).format(cx=cx, y=y, rx=rx, ry=ry) + + @property + def center(self): + """Return center of circle/ellipse""" + return ImmutableVector2d( + self.to_dimensionless(self.get("cx", "0")), + self.to_dimensionless(self.get("cy", "0")), + ) + + @center.setter + def center(self, value): + value = Vector2d(value) + self.set("cx", value.x) + self.set("cy", value.y) + + def rxry(self): + # type: () -> Vector2d + """Helper function""" + raise NotImplementedError() + + @classmethod + def new(cls, center, radius, **attrs): + circle = super().new(**attrs) + circle.center = center + circle.radius = radius + return circle + + +class Circle(EllipseBase): + """Provide a useful extension for circle elements""" + + tag_name = "circle" + + @property + def radius(self) -> float: + """Return radius of circle""" + return self.to_dimensionless(self.get("r", "0")) + + @radius.setter + def radius(self, value): + self.set("r", self.to_dimensionless(value)) + + def rxry(self): + r = self.radius + return Vector2d(r, r) + + +class Ellipse(EllipseBase): + """Provide a similar extension to the Circle interface for ellipses""" + + tag_name = "ellipse" + + @property + def radius(self) -> ImmutableVector2d: + """Return radii of ellipse""" + return ImmutableVector2d( + self.to_dimensionless(self.get("rx", "0")), + self.to_dimensionless(self.get("ry", "0")), + ) + + @radius.setter + def radius(self, value): + value = Vector2d(value) + self.set("rx", str(value.x)) + self.set("ry", str(value.y)) + + def rxry(self): + return self.radius diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_selected.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_selected.py new file mode 100644 index 0000000..356486b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_selected.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc.,Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +When elements are selected, these structures provide an advanced API. + +.. versionadded:: 1.1 +""" + +from collections import OrderedDict +from typing import Any, overload, Union, Optional + +from ..interfaces.IElement import IBaseElement +from ._utils import natural_sort_key +from ..localization import inkex_gettext +from ..utils import AbortExtension + + +class ElementList(OrderedDict): + """ + A list of elements, selected by id, iterator or xpath + + This may look like a dictionary, but it is really a list of elements. + The default iterator is the element objects themselves (not keys) and it is + possible to key elements by their numerical index. + + It is also possible to look up items by their id and the element object itself. + """ + + def __init__(self, svg, _iter=None): + self.svg = svg + self.ids = OrderedDict() + super().__init__() + if _iter is not None: + self.set(*list(_iter)) + + def __iter__(self): + return self.values().__iter__() + + def __getitem__(self, key): + return super().__getitem__(self._to_key(key)) + + def __contains__(self, key): + return super().__contains__(self._to_key(key)) + + def __setitem__(self, orig_key, elem): + if orig_key != elem and orig_key != elem.get("id"): + raise ValueError(f"Refusing to set bad key in ElementList {orig_key}") + if isinstance(elem, str): + key = elem + elem = self.svg.getElementById(elem, literal=True) + if elem is None: + return + if isinstance(elem, IBaseElement): + # Selection is a list of elements to select + key = elem.xml_path + element_id = elem.get("id") + if element_id is not None: + self.ids[element_id] = key + super().__setitem__(key, elem) + else: + kind = type(elem).__name__ + raise ValueError(f"Unknown element type: {kind}") + + @overload + def _to_key(self, key: None, default: Any) -> Any: + ... + + @overload + def _to_key(self, key: Union[int, IBaseElement, str], default: Any) -> str: + ... + + def _to_key(self, key, default=None) -> str: + """Takes a key (id, element, etc) and returns an xml_path key""" + + if self and key is None: + key = default + if isinstance(key, int): + return list(self.keys())[key] + if isinstance(key, IBaseElement): + return key.xml_path + if isinstance(key, str) and key[0] != "/": + return self.ids.get(key, key) + return key + + def clear(self): + """Also clear ids""" + self.ids.clear() + super().clear() + + def set(self, *ids): + """ + Sets the currently selected elements to these ids, any existing + selection is cleared. + + Arguments a list of element ids, element objects or + a single xpath expression starting with ``//``. + + All element objects must have an id to be correctly set. + + >>> selection.set("rect123", "path456", "text789") + >>> selection.set(elem1, elem2, elem3) + >>> selection.set("//rect") + """ + self.clear() + self.add(*ids) + + def pop(self, key=None): + """Remove the key item or remove the last item selected""" + item = super().pop(self._to_key(key, default=-1)) + self.ids.pop(item.get("id")) + return item + + def add(self, *ids): + """Like set() but does not clear first""" + # Allow selecting of xpath elements directly + if len(ids) == 1 and isinstance(ids[0], str) and ids[0].startswith("//"): + ids = self.svg.xpath(ids[0]) + + for elem in ids: + self[elem] = elem # This doesn't matter + + def rendering_order(self): + """Get the selected elements by z-order (stacking order), ordered from bottom to + top + + .. versionadded:: 1.2 + :func:`paint_order` has been renamed to :func:`rendering_order`""" + new_list = ElementList(self.svg) + # the elements are stored with their xpath index, so a natural sort order + # '3' < '20' < '100' has to be applied + new_list.set( + *[ + elem + for _, elem in sorted( + self.items(), key=lambda x: natural_sort_key(x[0]) + ) + ] + ) + return new_list + + def filter(self, *types): + """Filter selected elements of the given type, returns a new SelectedElements + object""" + return ElementList( + self.svg, [e for e in self if not types or isinstance(e, types)] + ) + + def filter_nonzero(self, *types, error_msg: Optional[str] = None): + """Filter selected elements of the given type, returns a new SelectedElements + object. If the selection is empty, abort the extension. + + .. versionadded:: 1.2 + + :param error_msg: e + :type error_msg: str, optional + + Args: + *types (Type) : type(s) to filter the selection by + error_msg (str, optional): error message that is displayed if the selection + is empty, defaults to + ``_("Please select at least one element of type(s) {}")``. + Defaults to None. + + Raises: + AbortExtension: if the selection is empty + + Returns: + ElementList: filtered selection + """ + filtered = self.filter(*types) + if not filtered: + if error_msg is None: + error_msg = inkex_gettext( + "Please select at least one element of the following type(s): {}" + ).format(", ".join([type.__name__ for type in types])) + raise AbortExtension(error_msg) + return filtered + + def get(self, *types): + """Like filter, but will enter each element searching for any child of the given + types""" + + def _recurse(elem): + if not types or isinstance(elem, types): + yield elem + for child in elem: + yield from _recurse(child) + + return ElementList( + self.svg, + [ + r + for e in self + for r in _recurse(e) + if isinstance(r, (IBaseElement, str)) + ], + ) + + def id_dict(self): + """For compatibility, return regular dictionary of id -> element pairs""" + return {eid: self[xid] for eid, xid in self.ids.items()} + + def bounding_box(self): + """ + Gets a :class:`inkex.transforms.BoundingBox` object for the selected items. + + Text objects have a bounding box without width or height that only + reflects the coordinate of their anchor. If a text object is a part of + the selection's boundary, the bounding box may be inaccurate. + + When no object is selected or when the object's location cannot be + determined (e.g. empty group or layer), all coordinates will be None. + """ + return sum([elem.bounding_box() for elem in self], None) + + def first(self): + """Returns the first item in the selected list""" + for elem in self: + return elem + return None diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_svg.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_svg.py new file mode 100644 index 0000000..85d9604 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_svg.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Thomas Holder +# Sergei Izmailov +# Windell Oskay +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=attribute-defined-outside-init +# +""" +Provide a way to load lxml attributes with an svg API on top. +""" + +import random +import math +import re + +from lxml import etree + +from ..css import ConditionalRule +from ..interfaces.IElement import ISVGDocumentElement + +from ..deprecated.meta import DeprecatedSvgMixin, deprecate +from ..units import discover_unit, parse_unit +from ._selected import ElementList +from ..transforms import BoundingBox +from ..styles import StyleSheets + +from ._base import BaseElement, ViewboxMixin +from ._meta import StyleElement, NamedView +from ._utils import registerNS + +from typing import Optional, List, Tuple + +if False: # pylint: disable=using-constant-test + import typing # pylint: disable=unused-import + + +class SvgDocumentElement( + DeprecatedSvgMixin, ISVGDocumentElement, BaseElement, ViewboxMixin +): + """Provide access to the document level svg functionality""" + + # pylint: disable=too-many-public-methods + tag_name = "svg" + + selection: ElementList + """The selection as passed by Inkscape (readonly)""" + + def _init(self): + self.current_layer = None + self.view_center = (0.0, 0.0) + self.selection = ElementList(self) + self.ids = {} + + def tostring(self): + """Convert document to string""" + return etree.tostring(etree.ElementTree(self)) + + def get_ids(self): + """Returns a set of unique document ids""" + if not self.ids: + self.ids = set(self.xpath("//@id")) + return self.ids + + def get_unique_id( + self, + prefix: str, + size: Optional[int] = None, + blacklist: Optional[List[str]] = None, + ): + """Generate a new id from an existing old_id + + The id consists of a prefix and an appended random integer with size digits. + + If size is not given, it is determined automatically from the length of + existing ids, i.e. those in the document plus those in the blacklist. + + Args: + prefix (str): the prefix of the new ID. + size (Optional[int], optional): number of digits of the second part of the + id. If None, the length is chosen based on the amount of existing + objects. Defaults to None. + + .. versionchanged:: 1.1 + The default of this parameter has been changed from 4 to None. + blacklist (Optional[Iterable[str]], optional): An additional iterable of ids + that are not allowed to be used. This is useful when bulk inserting + objects. + Defaults to None. + + .. versionadded:: 1.2 + + Returns: + _type_: _description_ + """ + ids = self.get_ids() + if size is None: + size = max(math.ceil(math.log10(len(ids) or 1000)) + 1, 4) + if blacklist is not None: + ids.update(blacklist) + new_id = None + _from = 10**size - 1 + _to = 10**size + while new_id is None or new_id in ids: + # Do not use randint because py2/3 incompatibility + new_id = prefix + str(int(random.random() * _from - _to) + _to) + self.ids.add(new_id) + return new_id + + def get_page_bbox(self, page=None) -> BoundingBox: + """Gets the page dimensions as a bbox. For single-page documents, the viewbox + dimensions are returned. + + Args: + page (int, optional): Page number. Defaults to the first page. + + .. versionadded:: 1.3 + + Raises: + IndexError: if the page number provided does not exist in the document. + + Returns: + BoundingBox: the bounding box of the page + """ + if page is None: + page = 0 + pages = self.namedview.get_pages() + if 0 <= page < len(pages): + return pages[page].bounding_box + raise IndexError("Invalid page number") + + def get_current_layer(self): + """Returns the currently selected layer""" + layer = self.getElementById(self.namedview.current_layer, "svg:g") + if layer is None: + return self + return layer + + def add_namespace(self, prefix, url): + """Adds an xml namespace to the xml parser with the desired prefix. + + If the prefix or url are already in use with different values, this + function will raise an error. Remove any attributes or elements using + this namespace before calling this function in order to rename it. + + .. versionadded:: 1.3 + """ + if self.nsmap.get(prefix, None) == url: + registerNS(prefix, url) + return + + # Attempt to clean any existing namespaces + if prefix in self.nsmap or url in self.nsmap.values(): + nskeep = [k for k, v in self.nsmap.items() if k != prefix and v != url] + etree.cleanup_namespaces(self, keep_ns_prefixes=nskeep) + if prefix in self.nsmap: + raise KeyError("ns prefix already used with a different url") + if url in self.nsmap.values(): + raise ValueError("ns url already used with a different prefix") + + # These are globals, but both will overwrite previous uses. + registerNS(prefix, url) + etree.register_namespace(prefix, url) + + # Set and unset an attribute to add the namespace to this root element. + self.set(f"{prefix}:temp", "1") + self.set(f"{prefix}:temp", None) + + def getElement(self, xpath): # pylint: disable=invalid-name + """Gets a single element from the given xpath or returns None""" + return self.findone(xpath) + + def getElementById( + self, eid: str, elm="*", literal=False + ): # pylint: disable=invalid-name + """Get an element in this svg document by it's ID attribute. + + Args: + eid (str): element id + elm (str, optional): element type, including namespace, e.g. ``svg:path``. + Defaults to "*". + literal (bool, optional): If ``False``, ``#url()`` is stripped from ``eid``. + Defaults to False. + + .. versionadded:: 1.1 + + Returns: + Union[BaseElement, None]: found element + """ + if eid is not None and not literal: + eid = eid.strip()[4:-1] if eid.startswith("url(") else eid + eid = eid.lstrip("#") + return self.getElement(f'//{elm}[@id="{eid}"]') + + def getElementByName(self, name, elm="*"): # pylint: disable=invalid-name + """Get an element by it's inkscape:label (aka name)""" + return self.getElement(f'//{elm}[@inkscape:label="{name}"]') + + def getElementsByClass(self, class_name): # pylint: disable=invalid-name + """Get elements by it's class name""" + + return self.xpath(ConditionalRule(f".{class_name}").to_xpath()) + + def getElementsByHref( + self, eid: str, attribute="href" + ): # pylint: disable=invalid-name + """Get elements that reference the element with id eid. + + Args: + eid (str): _description_ + attribute (str, optional): Attribute to look for. + Valid choices: "href", "xlink:href", "mask", "clip-path". + Defaults to "href". + + .. versionadded:: 1.2 + + attribute set to "href" or "xlink:href" handles both cases. + .. versionchanged:: 1.3 + + Returns: + Any: list of elements + """ + if attribute == "href" or attribute == "xlink:href": + return self.xpath(f'//*[@href|@xlink:href="#{eid}"]') + elif attribute == "mask": + return self.xpath(f'//*[@mask="url(#{eid})"]') + elif attribute == "clip-path": + return self.xpath(f'//*[@clip-path="url(#{eid})"]') + + def getElementsByStyleUrl(self, eid, style=None): # pylint: disable=invalid-name + """Get elements by a style attribute url""" + url = f"url(#{eid})" + if style is not None: + url = style + ":" + url + return self.xpath(f'//*[contains(@style,"{url}")]') + + @property + def name(self): + """Returns the Document Name""" + return self.get("sodipodi:docname", "") + + @property + def namedview(self) -> NamedView: + """Return the sp namedview meta information element""" + return self.get_or_create("//sodipodi:namedview", prepend=True) + + @property + def metadata(self): + """Return the svg metadata meta element container""" + return self.get_or_create("//svg:metadata", prepend=True) + + @property + def defs(self): + """Return the svg defs meta element container""" + return self.get_or_create("//svg:defs", prepend=True) + + def get_viewbox(self) -> List[float]: + """Parse and return the document's viewBox attribute""" + return self.parse_viewbox(self.get("viewBox", "0")) or [0, 0, 0, 0] + + @property + def viewbox_width(self) -> float: # getDocumentWidth(self): + """Returns the width of the `user coordinate system + `_ in user units, i.e. + the width of the viewbox, as defined in the SVG file. If no viewbox is defined, + the value of the width attribute is returned. If the height is not defined, + returns 0. + + .. versionadded:: 1.2""" + return self.get_viewbox()[2] or self.viewport_width + + @property + def viewport_width(self) -> float: + """Returns the width of the `viewport coordinate system + `_ in user units, i.e. the + width attribute of the svg element converted to px + + .. versionadded:: 1.2""" + return self.to_dimensionless(self.get("width")) or self.get_viewbox()[2] + + @property + def viewbox_height(self) -> float: # getDocumentHeight(self): + """Returns the height of the `user coordinate system + `_ in user units, i.e. the + height of the viewbox, as defined in the SVG file. If no viewbox is defined, the + value of the height attribute is returned. If the height is not defined, + returns 0. + + .. versionadded:: 1.2""" + return self.get_viewbox()[3] or self.viewport_height + + @property + def viewport_height(self) -> float: + """Returns the width of the `viewport coordinate system + `_ in user units, i.e. the + height attribute of the svg element converted to px + + .. versionadded:: 1.2""" + return self.to_dimensionless(self.get("height")) or self.get_viewbox()[3] + + @property + def scale(self): + """Returns the ratio between the viewBox width and the page width. + + .. versionchanged:: 1.2 + Previously, the scale as shown by the document properties was computed, + but the computation of this in core Inkscape changed in Inkscape 1.2, so + this was moved to :attr:`inkscape_scale`.""" + return self._base_scale() + + @property + def inkscape_scale(self): + """Returns the ratio between the viewBox width (in width/height units) and the + page width, which is displayed as "scale" in the Inkscape document + properties. + + .. versionadded:: 1.2""" + + viewbox_unit = ( + parse_unit(self.get("width")) or parse_unit(self.get("height")) or (0, "px") + )[1] + return self._base_scale(viewbox_unit) + + def _base_scale(self, unit="px"): + """Returns what Inkscape shows as "user units per `unit`" + + .. versionadded:: 1.2""" + try: + scale_x = ( + self.to_dimensional(self.viewport_width, unit) / self.viewbox_width + ) + scale_y = ( + self.to_dimensional(self.viewport_height, unit) / self.viewbox_height + ) + value = max([scale_x, scale_y]) + return 1.0 if value == 0 else value + except (ValueError, ZeroDivisionError): + return 1.0 + + @property + def equivalent_transform_scale(self) -> float: + """Return the scale of the equivalent transform of the svg tag, as defined by + https://www.w3.org/TR/SVG2/coords.html#ComputingAViewportsTransform + (highly simplified) + + .. versionadded:: 1.2""" + return self.scale + + @property + def unit(self): + """Returns the unit used for in the SVG document. + In the case the SVG document lacks an attribute that explicitly + defines what units are used for SVG coordinates, it tries to calculate + the unit from the SVG width and viewBox attributes. + Defaults to 'px' units.""" + if not hasattr(self, "_unit"): + self._unit = "px" # Default is px + viewbox = self.get_viewbox() + if viewbox and set(viewbox) != {0}: + self._unit = discover_unit(self.get("width"), viewbox[2], default="px") + return self._unit + + @property + def document_unit(self): + """Returns the display unit (Inkscape-specific attribute) of the document + + .. versionadded:: 1.2""" + return self.namedview.get("inkscape:document-units", "px") + + @property + def stylesheets(self): + """Get all the stylesheets, bound together to one, (for reading)""" + sheets = StyleSheets(self) + for node in self.xpath("//svg:style"): + sheets.append(node.stylesheet()) + return sheets + + @property + def stylesheet(self): + """Return the first stylesheet or create one if needed (for writing)""" + for sheet in self.stylesheets: + return sheet + + style_node = StyleElement() + self.defs.append(style_node) + return style_node.stylesheet() + + +def width(self): + """Use :func:`viewport_width` instead""" + return self.viewport_width + + +def height(self): + """Use :func:`viewport_height` instead""" + return self.viewport_height + + +SvgDocumentElement.width = property(deprecate(width, "1.2")) +SvgDocumentElement.height = property(deprecate(height, "1.2")) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_text.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_text.py new file mode 100644 index 0000000..8fc1756 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_text.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Provide text based element classes interface. + +Because text is not rendered at all, no information about a text's path +size or actual location can be generated yet. +""" +from __future__ import annotations + +from tempfile import TemporaryDirectory + +from ..interfaces.IElement import BaseElementProtocol +from ..paths import Path +from ..transforms import Transform, BoundingBox +from ..command import inkscape, write_svg +from ._base import BaseElement, ShapeElement +from ._polygons import PathElementBase + + +class TextBBMixin: # pylint: disable=too-few-public-methods + """Mixin to query the bounding box from Inkscape + + .. versionadded:: 1.2""" + + def get_inkscape_bbox(self: BaseElementProtocol) -> BoundingBox: + """Query the bbbox of a single object. This calls the Inkscape command, + so it is rather slow to use in a loop.""" + with TemporaryDirectory(prefix="inkscape-command") as tmpdir: + svg_file = write_svg(self.root, tmpdir, "input.svg") + out = inkscape(svg_file, "-X", "-Y", "-W", "-H", query_id=self.get_id()) + out = list(map(self.root.viewport_to_unit, out.splitlines())) + if len(out) != 4: + raise ValueError("Error: Bounding box computation failed") + return BoundingBox.new_xywh(*out) + + +class FlowRegion(ShapeElement): + """SVG Flow Region (SVG 2.0)""" + + tag_name = "flowRegion" + + def get_path(self): + # This ignores flowRegionExcludes + return sum([child.path for child in self], Path()) + + +class FlowRoot(ShapeElement, TextBBMixin): + """SVG Flow Root (SVG 2.0)""" + + tag_name = "flowRoot" + + @property + def region(self): + """Return the first flowRegion in this flowRoot""" + return self.findone("svg:flowRegion") + + def get_path(self): + region = self.region + return region.get_path() if region is not None else Path() + + +class FlowPara(ShapeElement): + """SVG Flow Paragraph (SVG 2.0)""" + + tag_name = "flowPara" + + def get_path(self): + # XXX: These empty paths mean the bbox for text elements will be nothing. + return Path() + + +class FlowDiv(ShapeElement): + """SVG Flow Div (SVG 2.0)""" + + tag_name = "flowDiv" + + def get_path(self): + # XXX: These empty paths mean the bbox for text elements will be nothing. + return Path() + + +class FlowSpan(ShapeElement): + """SVG Flow Span (SVG 2.0)""" + + tag_name = "flowSpan" + + def get_path(self): + # XXX: These empty paths mean the bbox for text elements will be nothing. + return Path() + + +class TextElement(ShapeElement, TextBBMixin): + """A Text element""" + + tag_name = "text" + x = property(lambda self: self.to_dimensionless(self.get("x", 0))) + y = property(lambda self: self.to_dimensionless(self.get("y", 0))) + + def get_path(self): + return Path() + + def tspans(self): + """Returns all children that are tspan elements""" + return self.findall("svg:tspan") + + def get_text(self, sep="\n"): + """Return the text content including tspans""" + nodes = [self] + list(self.tspans()) + return sep.join([elem.text for elem in nodes if elem.text is not None]) + + def shape_box(self, transform=None): + """ + Returns a horrible bounding box that just contains the coord points + of the text without width or height (which is impossible to calculate) + """ + effective_transform = Transform(transform) @ self.transform + x, y = effective_transform.apply_to_point((self.x, self.y)) + bbox = BoundingBox(x, y) + for tspan in self.tspans(): + bbox += tspan.bounding_box(effective_transform) + return bbox + + +class TextPath(ShapeElement, TextBBMixin): + """A textPath element""" + + tag_name = "textPath" + + def get_path(self): + return Path() + + +class Tspan(ShapeElement, TextBBMixin): + """A tspan text element""" + + tag_name = "tspan" + x = property(lambda self: self.to_dimensionless(self.get("x", 0))) + y = property(lambda self: self.to_dimensionless(self.get("y", 0))) + + @classmethod + def superscript(cls, text): + """Adds a superscript tspan element""" + return cls(text, style="font-size:65%;baseline-shift:super") + + def get_path(self): + return Path() + + def shape_box(self, transform=None): + """ + Returns a horrible bounding box that just contains the coord points + of the text without width or height (which is impossible to calculate) + """ + effective_transform = Transform(transform) @ self.transform + x1, y1 = effective_transform.apply_to_point((self.x, self.y)) + fontsize = self.to_dimensionless(self.style.get("font-size", "12px")) + x2 = self.x + 0 # XXX This is impossible to calculate! + y2 = self.y + float(fontsize) + x2, y2 = effective_transform.apply_to_point((x2, y2)) + return BoundingBox((x1, x2), (y1, y2)) + + +class SVGfont(BaseElement): + """An svg font element""" + + tag_name = "font" + + +class FontFace(BaseElement): + """An svg font font-face element""" + + tag_name = "font-face" + + +class Glyph(PathElementBase): + """An svg font glyph element""" + + tag_name = "glyph" + + +class MissingGlyph(BaseElement): + """An svg font missing-glyph element""" + + tag_name = "missing-glyph" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_use.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_use.py new file mode 100644 index 0000000..52b7050 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_use.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Interface for the Use and Symbol elements +""" + +from ..transforms import Transform + +from ._groups import Group, GroupBase +from ._base import ShapeElement + + +class Symbol(GroupBase): + """SVG symbol element""" + + tag_name = "symbol" + + +class Use(ShapeElement): + """A 'use' element that links to another in the document""" + + tag_name = "use" + + @classmethod + def new(cls, elem, x, y, **attrs): # pylint: disable=arguments-differ + ret = super().new(x=x, y=y, **attrs) + ret.href = elem + return ret + + def get_path(self): + """Returns the path of the cloned href plus any transformation + + .. versionchanged:: 1.3 + include transform of the referenced element + """ + path = self.href.path + path = path.transform(self.href.transform) + return path + + def effective_style(self): + """Href's style plus this object's own styles""" + style = self.href.effective_style() + style.update(self.style) + return style + + def unlink(self): + """Unlink this clone, replacing it with a copy of the original""" + copy = self.href.copy() + if isinstance(copy, Symbol): + group = Group(**copy.attrib) + group.extend(copy) + copy = group + copy.transform = self.transform @ copy.transform + copy.transform.add_translate( + self.to_dimensionless(self.get("x", 0)), + self.to_dimensionless(self.get("y", 0)), + ) + copy.style = self.style + copy.style + # Preserve the id of the clone to not break links that link the + # As we replace exactly one element by exactly one, this should be safe. + old_id = self.get_id() + self.replace_with(copy) + copy.set_random_ids() + copy.set_id(old_id) + return copy + + def shape_box(self, transform=None): + """BoundingBox of the unclipped shape + + .. versionadded:: 1.1""" + effective_transform = Transform(transform) @ self.transform + return self.href.bounding_box(effective_transform) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_utils.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_utils.py new file mode 100644 index 0000000..bc72c6b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/elements/_utils.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Useful utilities specifically for elements (that aren't base classes) + +.. versionadded:: 1.1 + Most of the methods in this module were moved from inkex.utils. +""" + +from collections import defaultdict +import re + +# a dictionary of all of the xmlns prefixes in a standard inkscape doc +NSS = { + "sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd", + "cc": "http://creativecommons.org/ns#", + "ccOLD": "http://web.resource.org/cc/", + "svg": "http://www.w3.org/2000/svg", + "dc": "http://purl.org/dc/elements/1.1/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "inkscape": "http://www.inkscape.org/namespaces/inkscape", + "xlink": "http://www.w3.org/1999/xlink", + "xml": "http://www.w3.org/XML/1998/namespace", +} +SSN = dict((b, a) for (a, b) in NSS.items()) + + +def registerNS(prefix, url): + """Register the given prefix as a namespace url.""" + NSS[prefix] = url + SSN[url] = prefix + + +def addNS(tag, ns=None, namespaces=NSS): # pylint: disable=invalid-name + """Add a known namespace to a name for use with lxml""" + if tag.startswith("{") and ns: + _, tag = removeNS(tag) + if not tag.startswith("{"): + tag = tag.replace("__", ":") + if ":" in tag: + (ns, tag) = tag.rsplit(":", 1) + ns = namespaces.get(ns, None) or ns + if ns is not None: + return f"{{{ns}}}{tag}" + return tag + + +def removeNS( + name, reverse_namespaces=SSN, default="svg" +): # pylint: disable=invalid-name + """The reverse of addNS, finds any namespace and returns tuple (ns, tag)""" + if name[0] == "{": + (url, tag) = name[1:].split("}", 1) + return reverse_namespaces.get(url, default), tag + if ":" in name: + return name.rsplit(":", 1) + return default, name + + +def splitNS(name, namespaces=NSS): # pylint: disable=invalid-name + """Like removeNS, but returns a url instead of a prefix""" + (prefix, tag) = removeNS(name) + return (namespaces[prefix], tag) + + +def natural_sort_key(key, _nsre=re.compile("([0-9]+)")): + """Helper for a natural sort, see + https://stackoverflow.com/a/16090640/3298143""" + return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(key)] + + +class ChildToProperty(property): + """Use when you have a singleton child element who's text + content is the canonical value for the property""" + + def __init__(self, tag, prepend=False): + super().__init__() + self.tag = tag + self.prepend = prepend + + def __get__(self, obj, klass=None): + elem = obj.findone(self.tag) + return elem.text if elem is not None else None + + def __set__(self, obj, value): + elem = obj.get_or_create(self.tag, prepend=self.prepend) + elem.text = value + + def __delete__(self, obj): + obj.remove_all(self.tag) + + @property + def __doc__(self): + return f"Get, set or delete the {self.tag} property." + + +class CloningVat: + """ + When modifying defs, sometimes we want to know if every backlink would have + needed changing, or it was just some of them. + + This tracks the def elements, their promises and creates clones if needed. + """ + + def __init__(self, svg): + self.svg = svg + self.tracks = defaultdict(set) + self.set_ids = defaultdict(list) + + def track(self, elem, parent, set_id=None, **kwargs): + """Track the element and connected parent""" + elem_id = elem.get("id") + parent_id = parent.get("id") + self.tracks[elem_id].add(parent_id) + self.set_ids[elem_id].append((set_id, kwargs)) + + def process(self, process, types=(), make_clones=True, **kwargs): + """ + Process each tracked item if the backlinks match the parents + + Optionally make clones, process the clone and set the new id. + """ + for elem_id in list(self.tracks): + parents = self.tracks[elem_id] + elem = self.svg.getElementById(elem_id) + backlinks = {blk.get("id") for blk in elem.backlinks(*types)} + if backlinks == parents: + # No need to clone, we're processing on-behalf of all parents + process(elem, **kwargs) + elif make_clones: + clone = elem.copy() + elem.getparent().append(clone) + clone.set_random_id() + for update, upkw in self.set_ids.get(elem_id, ()): + update(elem.get("id"), clone.get("id"), **upkw) + process(clone, **kwargs) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/extensions.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/extensions.py new file mode 100644 index 0000000..4c2752b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/extensions.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +A helper module for creating Inkscape effect extensions + +This provides the basic generic types of extensions which most writers should +use in their code. See below for the different types. +""" + +import os +import re +import sys +import types +from abc import ABC + +from .utils import errormsg, Boolean +from .colors import Color, ColorError +from .elements import ( + load_svg, + BaseElement, + ShapeElement, + Group, + Layer, + Grid, + TextElement, + FlowPara, + FlowDiv, + Pattern, +) +from .elements._utils import CloningVat +from .base import ( + InkscapeExtension, + SvgThroughMixin, + SvgInputMixin, + SvgOutputMixin, + TempDirMixin, +) +from .transforms import Transform +from .elements import LinearGradient, RadialGradient +from .command import write_svg, inkscape, ProgramRunError +from .utils import errormsg +from .localization import inkex_gettext as _ + +# All the names that get added to the inkex API itself. +__all__ = ( + "EffectExtension", + "GenerateExtension", + "InputExtension", + "OutputExtension", + "RasterOutputExtension", + "CallExtension", + "TemplateExtension", + "ColorExtension", + "TextExtension", +) + +stdout = sys.stdout + + +class EffectExtension(SvgThroughMixin, InkscapeExtension, ABC): + """ + Takes the SVG from Inkscape, modifies the selection or the document + and returns an SVG to Inkscape. + """ + + +class OutputExtension(SvgInputMixin, TempDirMixin, InkscapeExtension): + """ + Takes the SVG from Inkscape and outputs it to something that's not an SVG. + + Used in functions for `Save As` + """ + + def effect(self): + """Effect isn't needed for a lot of Output extensions""" + + def save(self, stream): + """But save certainly is, we give a more exact message here""" + raise NotImplementedError("Output extensions require a save(stream) method!") + + def preprocess(self, types_to_path=None, unlink_clones=True): + """Preprocess the SVG into an export-friendly document by converting + certain objects to path beforehand. + + Args: + types_to_path (List[str], optional): List of element types to convert to + path. Defaults to all text elements and all non-path shape elements. + unlink_clones (bool, optional): If clones should be unlinked. Defaults to + True. + + Returns: + _type_: _description_ + """ + if types_to_path is None: + types_to_path = [ + "flowRoot", + "rect", + "circle", + "ellipse", + "line", + "polyline", + "polygon", + "text", + ] + actions = ["unlock-all"] + if "flowRoot" in types_to_path: + # Flow roots contain rectangles inside them, so they need to be + # converted to paths separately from other shapes + actions += [ + "select-by-element:flowRoot", + "object-to-path", + "select-clear", + ] + types_to_path.remove("flowRoot") + + # Now convert all non-paths to paths + actions += ["select-by-element:" + i for i in types_to_path] + actions += ["object-to-path", "select-clear"] + # unlink clones + if unlink_clones: + actions += ["select-by-element:use", "object-unlink-clones"] + # save and overwrite + actions += ["export-overwrite", "export-do"] + + infile = os.path.join(self.tempdir, "input.svg") + write_svg(self.document, infile) + try: + inkscape(infile, actions=";".join(actions)) + except ProgramRunError as err: + errormsg(_("An error occurred during document preparation")) + errormsg(err.stderr.decode("utf-8")) + + with open(infile, "r") as stream: + self.document = load_svg(stream) + self.svg = self.document.getroot() + + +class RasterOutputExtension(InkscapeExtension): + """ + Takes a PNG from Inkscape and outputs it to another raster format. + + .. versionadded:: 1.1 + """ + + def __init__(self): + super().__init__() + self.img = None + + def load(self, stream): + from PIL import Image + + # disable the PIL decompression bomb DOS attack check. + Image.MAX_IMAGE_PIXELS = None + + self.img = Image.open(stream) + + def effect(self): + """Not needed since image isn't being changed""" + + def save(self, stream): + """Implement raster image saving here from PIL""" + raise NotImplementedError("Raster Output extension requires a save method!") + + +class InputExtension(SvgOutputMixin, InkscapeExtension): + """ + Takes any type of file as input and outputs SVG which Inkscape can read. + + Used in functions for `Open` + """ + + def effect(self): + """Effect isn't needed for a lot of Input extensions""" + + def load(self, stream): + """But load certainly is, we give a more exact message here""" + raise NotImplementedError("Input extensions require a load(stream) method!") + + +class CallExtension(TempDirMixin, InputExtension): + """Call an external program to get the output""" + + input_ext = "svg" + output_ext = "svg" + + def load(self, stream): + pass # Not called (load_raw instead) + + def load_raw(self): + # Don't call InputExtension.load_raw + TempDirMixin.load_raw(self) + input_file = self.options.input_file + + if not isinstance(input_file, str): + data = input_file.read() + input_file = os.path.join(self.tempdir, "input." + self.input_ext) + with open(input_file, "wb") as fhl: + fhl.write(data) + + output_file = os.path.join(self.tempdir, "output." + self.output_ext) + document = self.call(input_file, output_file) or output_file + if isinstance(document, str): + if not os.path.isfile(document): + raise IOError(f"Can't find generated document: {document}") + + if self.output_ext == "svg": + with open(document, "r", encoding="utf-8") as fhl: + document = fhl.read() + if "<" in document: + document = load_svg(document.encode("utf-8")) + else: + with open(document, "rb") as fhl: + document = fhl.read() + + self.document = document + + def call(self, input_file, output_file): + """Call whatever programs are needed to get the desired result.""" + raise NotImplementedError("Call extensions require a call(in, out) method!") + + +class GenerateExtension(EffectExtension): + """ + Does not need any SVG, but instead just outputs an SVG fragment which is + inserted into Inkscape, centered on the selection. + """ + + container_label = "" + container_layer = False + + def generate(self): + """ + Return an SVG fragment to be inserted into the selected layer of the document + OR yield multiple elements which will be grouped into a container group + element which will be given an automatic label and transformation. + """ + raise NotImplementedError("Generate extensions must provide generate()") + + def container_transform(self): + """ + Generate the transformation for the container group, the default is + to return the center position of the svg document or view port. + """ + (pos_x, pos_y) = self.svg.namedview.center + if pos_x is None: + pos_x = 0 + if pos_y is None: + pos_y = 0 + return Transform(translate=(pos_x, pos_y)) + + def create_container(self): + """ + Return the container the generated elements will go into. + + Default is a new layer or current layer depending on the :attr:`container_layer` + flag. + + .. versionadded:: 1.1 + """ + container = (Layer if self.container_layer else Group).new(self.container_label) + if self.container_layer: + self.svg.append(container) + else: + container.transform = self.container_transform() + parent = self.svg.get_current_layer() + try: + parent_transform = parent.composed_transform() + except AttributeError: + pass + else: + container.transform = -parent_transform @ container.transform + parent.append(container) + return container + + def effect(self): + layer = self.svg.get_current_layer() + fragment = self.generate() + if isinstance(fragment, types.GeneratorType): + container = self.create_container() + for child in fragment: + if isinstance(child, BaseElement): + container.append(child) + elif isinstance(fragment, BaseElement): + layer.append(fragment) + else: + errormsg("Nothing was generated\n") + + +class TemplateExtension(EffectExtension): + """ + Provide a standard way of creating templates. + """ + + size_rex = re.compile(r"([\d.]*)(\w\w)?x([\d.]*)(\w\w)?") + template_id = "SVGRoot" + + def __init__(self): + self.svg = None + super().__init__() + # Arguments added on after add_arguments so it can be overloaded cleanly. + self.arg_parser.add_argument("--size", type=self.arg_size(), dest="size") + self.arg_parser.add_argument("--width", type=int, default=800) + self.arg_parser.add_argument("--height", type=int, default=600) + self.arg_parser.add_argument("--orientation", default=None) + self.arg_parser.add_argument("--unit", default="px") + self.arg_parser.add_argument("--grid", type=Boolean) + # self.svg = None + + def get_template(self, **kwargs): + """Can be over-ridden with custom svg loading here""" + return self.document + + def arg_size(self, unit="px"): + """Argument is a string of the form X[unit]xY[unit], default units apply + when missing""" + + def _inner(value): + try: + value = float(value) + return (value, unit, value, unit) + except ValueError: + pass + match = self.size_rex.match(str(value)) + if match is not None: + size = match.groups() + return ( + float(size[0]), + size[1] or unit, + float(size[2]), + size[3] or unit, + ) + return None + + return _inner + + def get_size(self): + """Get the size of the new template (defaults to size options)""" + size = self.options.size + if self.options.size is None: + size = ( + self.options.width, + self.options.unit, + self.options.height, + self.options.unit, + ) + if ( + self.options.orientation == "horizontal" + and size[0] < size[2] + or self.options.orientation == "vertical" + and size[0] > size[2] + ): + size = size[2:4] + size[0:2] + return size + + def effect(self): + """Creates a template, do not over-ride""" + (width, width_unit, height, height_unit) = self.get_size() + width_px = int(self.svg.uutounit(width, "px")) + height_px = int(self.svg.uutounit(height, "px")) + + self.document = self.get_template() + self.svg = self.document.getroot() + self.svg.set("id", self.template_id) + self.svg.set("width", str(width) + width_unit) + self.svg.set("height", str(height) + height_unit) + self.svg.set("viewBox", f"0 0 {width} {height}") + self.set_namedview(width_px, height_px, width_unit) + + def set_namedview(self, width, height, unit): + """Setup the document namedview""" + self.svg.namedview.set("inkscape:document-units", unit) + self.svg.namedview.set("inkscape:zoom", "0.25") + self.svg.namedview.set("inkscape:cx", str(width / 2.0)) + self.svg.namedview.set("inkscape:cy", str(height / 2.0)) + if self.options.grid: + self.svg.namedview.set("showgrid", "true") + self.svg.namedview.add(Grid(type="xygrid")) + + +class ColorExtension(EffectExtension): + """ + A standard way to modify colours in an svg document. + """ + + process_none = False # should we call modify_color for the "none" color. + select_all = (ShapeElement,) + pass_rgba = False + """ + If true, color and opacity are processed together (as RGBA color) + by :func:`modify_color`. + + If false (default), they are processed independently by `modify_color` and + `modify_opacity`. + + .. versionadded:: 1.2 + """ + + def __init__(self): + super().__init__() + self._renamed = {} + + def effect(self): + # Limiting to shapes ignores Gradients (and other things) from the select_all + # this prevents defs from being processed twice. + self._renamed = {} + gradients = CloningVat(self.svg) + for elem in self.svg.selection.get(ShapeElement): + self.process_element(elem, gradients) + gradients.process(self.process_elements, types=(ShapeElement,)) + + def process_elements(self, elem): + """Process multiple elements (gradients)""" + for child in elem.descendants(): + self.process_element(child) + + def process_element(self, elem, gradients=None): + """Process one of the selected elements""" + style = elem.specified_style() + # Colours first + for name in ( + elem.style.associated_props if self.pass_rgba else elem.style.color_props + ): + if name not in style: + continue # we don't want to process default values + try: + value = style(name) + except ColorError: + continue # bad color value, don't touch. + if isinstance(value, Color): + col = Color(value) + if self.pass_rgba: + col = col.to_rgba( + alpha=elem.style(elem.style.associated_props[name]) + ) + rgba_result = self._modify_color(name, col) + elem.style.set_color(rgba_result, name) + + if isinstance(value, (LinearGradient, RadialGradient, Pattern)): + gradients.track(value, elem, self._ref_cloned, style=style, name=name) + if value.href is not None: + gradients.track(value.href, elem, self._xlink_cloned, linker=value) + # Then opacities (usually does nothing) + if self.pass_rgba: + return + for name in elem.style.opacity_props: + value = style(name) + result = self.modify_opacity(name, value) + if result not in (value, 1): # only modify if not equal to old or default + elem.style[name] = result + + def _ref_cloned(self, old_id, new_id, style, name): + self._renamed[old_id] = new_id + style[name] = f"url(#{new_id})" + + def _xlink_cloned(self, old_id, new_id, linker): # pylint: disable=unused-argument + lid = linker.get("id") + linker = self.svg.getElementById(self._renamed.get(lid, lid)) + linker.href = new_id + + def _modify_color(self, name, color): + """Pre-process color value to filter out bad colors""" + if color or self.process_none: + return self.modify_color(name, color) + return color + + def modify_color(self, name, color): + """Replace this method with your colour modifier method""" + raise NotImplementedError("Provide a modify_color method.") + + def modify_opacity( + self, name, opacity + ): # pylint: disable=no-self-use, unused-argument + """Optional opacity modification""" + return opacity + + +class TextExtension(EffectExtension): + """ + A base effect for changing text in a document. + """ + + newline = True + newpar = True + + def effect(self): + nodes = self.svg.selection or {None: self.document.getroot()} + for elem in nodes.values(): + self.process_element(elem) + + def process_element(self, node): + """Reverse the node text""" + if node.get("sodipodi:role") == "line": + self.newline = True + elif isinstance(node, (TextElement, FlowPara, FlowDiv)): + self.newline = True + self.newpar = True + + if node.text is not None: + node.text = self.process_chardata(node.text) + self.newline = False + self.newpar = False + + for child in node: + self.process_element(child) + + if node.tail is not None: + node.tail = self.process_chardata(node.tail) + + def process_chardata(self, text): + """Replaceable chardata method for processing the text""" + return "".join(map(self.map_char, text)) + + @staticmethod + def map_char(char): + """Replaceable map_char method for processing each letter""" + raise NotImplementedError( + "Please provide a process_chardata or map_char static method." + ) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/README.md b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/README.md new file mode 100644 index 0000000..fede673 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/README.md @@ -0,0 +1,13 @@ +# What is inkex.gui + +This module is a Gtk based GUI creator. It helps extensions launch their own user interfaces and can help make sure those interfaces will work on all platforms that Inkscape ships with. + +# How do I use it + +You can create custom user interfaces by using the [Gnome Glade builder program](https://gitlab.gnome.org/GNOME/glade). Once you have a layout of all the widgets you want, you then make a GtkApp and Window classes inside your Python program, when the GtkApp is run, the windows will be shown to the user and all signals specified for the widgets will call functions on your window class. + +Please see the existing code for examples of how to do this. + +# This is a fork + +This code was originally part of the package `gtkme` which contained some parts we didn't want to ship—such as Ubuntu indicators and internet pixmaps. To avoid conflicts, our stripped down version of the `gtkme` module is renamed and placed inside of Inkscape's `inkex` module. diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/__init__.py new file mode 100644 index 0000000..0f9ac69 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/__init__.py @@ -0,0 +1,58 @@ +# +# Copyright 2011-2022 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +# pylint: disable=wrong-import-position +""" +This is a wrapper layer to make interacting with Gtk a little less painful. +The main issues with Gtk is that it expects an awful lot of the developer, +code which is repeated over and over and patterns which every single developer +will use are not given easy to use convenience functions. + +This makes Gtk programming WET, unattractive and error prone. This module steps +inbetween and adds in all those missing bits. It's not meant to replace Gtk and +certainly it's possible to use Gtk and threading directly. + +.. versionadded:: 1.2 +""" + +import os +import sys +import logging +import threading + +from ..utils import DependencyError + +try: + import gi + + gi.require_version("Gtk", "3.0") + + # Importing while covering stderr because pygobject has broken + # warnings support and will force import warnings on our users. + tmp, sys.stderr = sys.stderr, None # type: ignore + from gi.repository import Gtk, GLib + + sys.stderr = tmp # type: ignore +except ImportError: # pragma: no cover + raise DependencyError( + "You are missing the required libraries for Gtk." + " Please report this problem to the Inkscape developers." + ) + +from .app import GtkApp +from .window import Window, ChildWindow, FakeWidget +from .listview import TreeView, IconView, ViewColumn, ViewSort, Separator +from .pixmap import PixmapManager diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/app.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/app.py new file mode 100644 index 0000000..ba6fbf1 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/app.py @@ -0,0 +1,176 @@ +# coding=utf-8 +# +# Copyright 2011-2022 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +""" +Gtk Application base classes, providing a way to load a GtkBuilder +with a specific glade/ui file containing windows, and building +a usable pythonic interface from them. +""" +import os +import signal +import logging + +from gi.repository import Gtk, GLib + + +class GtkApp: + """ + This wraps gtk builder and allows for some extra functionality with + windows, especially the management of gtk main loops. + + Args: + start_loop (bool, optional): If set to true will start a new gtk main loop. + Defaults to False. + start_gui (bool, optional): Used as local propertes if unset and passed to + primary window when loaded. Defaults to True. + """ + + @property + def prefix(self): + """Folder prefix added to ui_dir""" + return self.kwargs.get("prefix", "") + + @property + def windows(self): + """Returns a list of windows for this app""" + return self.kwargs.get("windows", []) + + @property + def ui_dir(self): + """This is often the local directory""" + return self.kwargs.get("ui_dir", "./") + + @property + def ui_file(self): + """If a single file is used for multiple windows""" + return self.kwargs.get("ui_file", None) + + @property + def app_name(self): + """Set this variable in your class""" + try: + return self.kwargs["app_name"] + except KeyError: + raise NotImplementedError( + "App name is not set, pass in or set 'app_name' in class." + ) + + @property + def window(self): + """Return the primary window""" + return self._primary + + def __init__(self, start_loop=False, start_gui=True, **kwargs): + """Creates a new GtkApp.""" + self.kwargs = kwargs + self._loaded = {} + self._initial = {} + self._primary = None + + self.main_loop = GLib.main_depth() + + # Start with creating all the defined windows. + if start_gui: + self.init_gui() + # Start up a gtk main loop when requested + if start_loop: + self.run() + + def run(self): + """Run the gtk mainloop with ctrl+C and keyboard interrupt additions""" + if not Gtk.init_check()[0]: # pragma: no cover + raise RuntimeError( + "Gtk failed to start." " Make sure $DISPLAY variable is set.\n" + ) + try: + # Add a signal to force quit on Ctrl+C (just like the old days) + signal.signal(signal.SIGINT, signal.SIG_DFL) + Gtk.main() + except KeyboardInterrupt: # pragma: no cover + logging.info("User Interrupted") + logging.debug("Exiting %s", self.app_name) + + def get_ui_file(self, window): + """Load any given gtk builder file from a standard location.""" + paths = [ + os.path.join(self.ui_dir, self.prefix, f"{window}.ui"), + os.path.join(self.ui_dir, self.prefix, f"{self.ui_file}.ui"), + ] + for path in paths: + if os.path.isfile(path): + return path + raise FileNotFoundError(f"Gtk Builder file is missing: {paths}") + + def init_gui(self): + """Initialise all of our windows and load their signals""" + if self.windows: + for cls in self.windows: + window = cls + logging.debug("Adding window %s to GtkApp", window.name) + self._initial[window.name] = window + for window in self._initial.values(): + if window.primary: + if not self._primary: + self._primary = self.load_window(window.name) + if not self.windows or not self._primary: + raise KeyError(f"No primary window found for '{self.app_name}' app.") + + def load_window(self, name, *args, **kwargs): + """Load a specific window from our group of windows""" + window = self.proto_window(name) + window.init(*args, **kwargs) + return window + + def load_window_extract(self, name, **kwargs): + """Load a child window as a widget container""" + window = self.proto_window(name) + window.load_widgets(**kwargs) + return window.extract() + + def proto_window(self, name): + """ + Loads a Glade window as a window without initialisation, used for + extracting widgets from windows without loading them as windows. + """ + logging.debug("Loading '%s' from %s", name, self._initial) + if name in self._initial: + # Create a new instance of this window + window = self._initial[name](self) + # Save the window object linked against the gtk window instance + self._loaded[window.wid] = window + return window + raise KeyError(f"Can't load window '{name}', class not found.") + + def remove_window(self, window): + """Remove the window from the list and exit if none remain""" + if window.wid in self._loaded: + self._loaded.pop(window.wid) + else: + logging.warning("Missing window '%s' on exit.", window.name) + logging.debug("Loaded windows: %s", self._loaded) + if not self._loaded: + self.exit() + + def exit(self): + """Exit our gtk application and kill gtk main if we have to""" + if self.main_loop < GLib.main_depth(): + # Quit Gtk loop if we started one. + tag = self._primary.name if self._primary else "program" + logging.debug("Quit '%s' Main Loop.", tag) + Gtk.main_quit() + # You have to return in order for the loop to exit + return 0 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/asyncme.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/asyncme.py new file mode 100644 index 0000000..d12ec5d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/asyncme.py @@ -0,0 +1,329 @@ +# +# Copyright 2015 Ian Denhardt +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +"""Convenience library for concurrency + +GUI apps frequently need concurrency, for example to avoid blocking UI while +doing some long running computation. This module provides helpers for doing +this kind of thing. + +The functions/methods here which spawn callables asynchronously +don't supply a direct way to provide arguments. Instead, the user is +expected to use a lambda, e.g:: + + holding(lck, lambda: do_stuff(1,2,3, x='hello')) + +This is because the calling function may have additional arguments which +could obscure the user's ability to pass arguments expected by the called +function. For example, in the call:: + + holding(lck, lambda: run_task(blocking=True), blocking=False) + +the blocking argument to holding might otherwise conflict with the +blocking argument to run_task. +""" +import time +import threading +from datetime import datetime, timedelta + +from functools import wraps +from typing import Any, Tuple +from gi.repository import Gdk, GLib + + +class Future: + """A deferred result + + A `Future` is a result-to-be; it can be used to deliver a result + asynchronously. Typical usage: + + >>> def background_task(task): + ... ret = Future() + ... def _task(x): + ... return x - 4 + 2 + ... thread = threading.Thread(target=lambda: ret.run(lambda: _task(7))) + ... thread.start() + ... return ret + >>> # Do other stuff + >>> print(ret.wait()) + 5 + + :func:`run` will also propagate exceptions; see its docstring for details. + """ + + def __init__(self): + self._lock = threading.Lock() + self._value = None + self._exception = None + self._lock.acquire() + + def is_ready(self): + """Return whether the result is ready""" + result = self._lock.acquire(False) + if result: + self._lock.release() + return result + + def wait(self): + """Wait for the result. + + `wait` blocks until the result is ready (either :func:`result` or + :func:`exception` has been called), and then returns it (in the case + of :func:`result`), or raises it (in the case of :func:`exception`). + """ + with self._lock: + if self._exception is None: + return self._value + else: + raise self._exception # pylint: disable=raising-bad-type + + def result(self, value): + """Supply the result as a return value. + + ``value`` is the result to supply; it will be returned when + :func:`wait` is called. + """ + self._value = value + self._lock.release() + + def exception(self, err): + """Supply an exception as the result. + + Args: + err (Exception): an exception, which will be raised when :func:`wait` + is called. + """ + self._exception = err + self._lock.release() + + def run(self, task): + """Calls task(), and supplies the result. + + If ``task`` raises an exception, pass it to :func:`exception`. + Otherwise, pass the return value to :func:`result`. + """ + try: + self.result(task()) + except Exception as err: # pylint: disable=broad-except + self.exception(err) + + +class DebouncedSyncVar: + """A synchronized variable, which debounces its value + + :class:`DebouncedSyncVar` supports three operations: put, replace, and get. + get will only retrieve a value once it has "settled," i.e. at least + a certain amount of time has passed since the last time the value + was modified. + """ + + def __init__(self, delay_seconds=0): + """Create a new dsv with the supplied delay, and no initial value.""" + self._cv = threading.Condition() + self._delay = timedelta(seconds=delay_seconds) + + self._deadline = None + self._value = None + + self._have_value = False + + def set_delay(self, delay_seconds): + """Set the delay in seconds of the debounce.""" + with self._cv: + self._delay = timedelta(seconds=delay_seconds) + + def get(self, blocking=True, remove=True) -> Tuple[Any, bool]: + """Retrieve a value. + + Args: + blocking (bool, optional): if True, block until (1) the dsv has a value + and (2) the value has been unchanged for an amount of time greater + than or equal to the dsv's delay. Otherwise, if these conditions + are not met, return ``(None, False)`` immediately. Defaults to True. + remove (bool, optional): if True, remove the value when returning it. + Otherwise, leave it where it is.. Defaults to True. + + Returns: + Tuple[Any, bool]: Tuple (value, ok). ``value`` is the value of the variable + (if successful, see above), and ok indicates whether or not a value was + successfully retrieved. + """ + while True: + with self._cv: + # If there's no value, either wait for one or return + # failure. + while not self._have_value: + if blocking: + self._cv.wait() + else: + return None, False # pragma: no cover + + now = datetime.now() + deadline = self._deadline + value = self._value + if deadline <= now: + # Okay, we're good. Remove the value if necessary, and + # return it. + if remove: + self._have_value = False + self._value = None + self._cv.notify() + return value, True + + # Deadline hasn't passed yet. Either wait or return failure. + if blocking: + time.sleep((deadline - now).total_seconds()) + else: + return None, False # pragma: no cover + + def replace(self, value): + """Replace the current value of the dsv (if any) with ``value``. + + replace never blocks (except briefly to acquire the lock). It does not + wait for any unit of time to pass (though it does reset the timer on + completion), nor does it wait for the dsv's value to appear or + disappear. + """ + with self._cv: + self._replace(value) + + def put(self, value): + """Set the dsv's value to ``value``. + + If the dsv already has a value, this blocks until the value is removed. + Upon completion, this resets the timer. + """ + with self._cv: + while self._have_value: + self._cv.wait() + self._replace(value) + + def _replace(self, value): + self._have_value = True + self._value = value + self._deadline = datetime.now() + self._delay + self._cv.notify() + + +def spawn_thread(func): + """Call ``func()`` in a separate thread + + Returns the corresponding :class:`threading.Thread` object. + """ + thread = threading.Thread(target=func) + thread.start() + return thread + + +def in_mainloop(func): + """Run f() in the gtk main loop + + Returns a :class:`Future` object which can be used to retrieve the return + value of the function call. + + :func:`in_mainloop` exists because Gtk isn't threadsafe, and therefore cannot be + manipulated except in the thread running the Gtk main loop. :func:`in_mainloop` + can be used by other threads to manipulate Gtk safely. + """ + future = Future() + + def handler(*_args, **_kwargs): + """Function to be called in the future""" + future.run(func) + + Gdk.threads_add_idle(0, handler, None) + return future + + +def mainloop_only(f): + """A decorator which forces a function to only be run in Gtk's main loop. + + Invoking a decorated function as ``f(*args, **kwargs)`` is equivalent to + using the undecorated function (from a thread other than the one running + the Gtk main loop) as:: + + in_mainloop(lambda: f(*args, **kwargs)).wait() + + :func:`mainloop_only` should be used to decorate functions which are unsafe + to run outside of the Gtk main loop. + """ + + @wraps(f) + def wrapper(*args, **kwargs): + if GLib.main_depth(): + # Already in a mainloop, so just run it. + return f(*args, **kwargs) + return in_mainloop(lambda: f(*args, **kwargs)).wait() + + return wrapper + + +def holding(lock, task, blocking=True): + """Run task() while holding ``lock``. + + Args: + blocking (bool, optional): if True, wait for the lock before running. + Otherwise, if the lock is busy, return None immediately, and don't + spawn `task`. Defaults to True. + + Returns: + Union[Future, None]: The return value is a future which can be used to retrieve + the result of running task (or None if the task was not run). + """ + if not lock.acquire(False): + return None + ret = Future() + + def _target(): + ret.run(task) + if ret._exception: # pragma: no cover + ret.wait() + lock.release() + + threading.Thread(target=_target).start() + return ret + + +def run_or_wait(func): + """A decorator which runs the function using :func:`holding` + + This function creates a single lock for this function and + waits for the lock to release before returning. + + See :func:`holding` above, with ``blocking=True`` + """ + lock = threading.Lock() + + def _inner(*args, **kwargs): + return holding(lock, lambda: func(*args, **kwargs), blocking=True) + + return _inner + + +def run_or_none(func): + """A decorator which runs the function using :func:`holding` + + This function creates a single lock for this function and + returns None if the process is already running (locked) + + See :func:`holding` above with ``blocking=True`` + """ + lock = threading.Lock() + + def _inner(*args, **kwargs): + return holding(lock, lambda: func(*args, **kwargs), blocking=False) + + return _inner diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/listview.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/listview.py new file mode 100644 index 0000000..56939e7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/listview.py @@ -0,0 +1,562 @@ +# +# Copyright 2011-2022 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +""" +Wraps the gtk treeview and iconview in something a little nicer. +""" + +import logging + +from typing import Tuple, Type, Optional +from gi.repository import Gtk, Gdk, GObject, GdkPixbuf, Pango + +from .pixmap import PixmapManager, SizeFilter + +GOBJ = GObject.TYPE_PYOBJECT + + +def default(item, attr, d=None): + """Python logic to choose an attribute, call it if required and return""" + if hasattr(item, attr): + prop = getattr(item, attr) + if callable(prop): + prop = prop() + return prop + return d + + +def cmp(a, b): + """Compare two objects""" + return (a > b) - (a < b) + + +def item_property(name, d=None): + def inside(item): + return default(item, name, d) + + return inside + + +def label(obj): + if isinstance(obj, tuple): + return " or ".join([label(o) for o in obj]) + if not isinstance(obj, type): + obj = type(obj) + return obj.__name__ + + +class BaseView: + """Controls for tree and icon views, a base class""" + + widget_type: Optional[Type[Gtk.Widget]] = None + + def __init__(self, widget, liststore=None, **kwargs): + if not isinstance(widget, self.widget_type): + lbl1 = label(self.widget_type) + lbl2 = label(widget) + raise TypeError(f"Wrong widget type: Expected {lbl1} got {lbl2}") + + self.selected_signal = kwargs.get("selected", None) + self._iids = [] + self._list = widget + self.args = kwargs + self.selected = None + self._data = None + self.no_dupes = True + self._model = self.create_model(liststore or widget.get_model()) + self._list.set_model(self._model) + self.setup() + + self._list.connect(self.changed_signal, self.item_selected_signal) + + def get_model(self): + """Returns the current data store model""" + return self._model + + def create_model(self, liststore): + """Setup the model and list""" + if not isinstance(liststore, (Gtk.ListStore, Gtk.TreeStore)): + lbl = label(liststore) + raise TypeError(f"Expected List or TreeStore, got {lbl}") + return liststore + + def refresh(self): + """Attempt to refresh the listview""" + self._list.queue_draw() + + def setup(self): + """Setup columns, views, sorting etc""" + pass + + def get_item_id(self, item): + """ + Return an id set against this item. + + If item.get_id() is set then duplicates will be ignored. + """ + if hasattr(item, "get_id"): + return item.get_id() + return None + + def replace(self, new_item, item_iter=None): + """Replace all items, or a single item with object""" + if item_iter: + self.remove_item(item_iter) + self.add_item(new_item) + else: + self.clear() + self._data = new_item + self.add_item(new_item) + + def item_selected(self, item=None, *others): + """Base method result, called as an item is selected""" + if self.selected != item: + self.selected = item + if self.selected_signal and item: + self.selected_signal(item) + + def remove_item(self, item=None): + """Remove an item from this view""" + return self._model.remove(self.get_iter(item)) + + def check_item_id(self, item): + """Item id is recorded to guard against duplicates""" + iid = self.get_item_id(item) + if iid in self._iids and self.no_dupes: + raise ValueError(f"Will not add duplicate row {iid}") + if iid: + self._iids.append(iid) + + def __iter__(self): + ret = [] + + def collect_all(store, treepath, treeiter): + ret.append((self.get_item(treeiter), treepath, treeiter)) + + self._model.foreach(collect_all) + return ret.__iter__() + + def set_sensitive(self, sen=True): + """Proxy the GTK property for sensitivity""" + self._list.set_sensitive(sen) + + def clear(self): + """Clear all items from this treeview""" + self._iids = [] + self._model.clear() + + def item_double_clicked(self, *items): + """What happens when you double click an item""" + return items # Nothing + + def get_item(self, item_iter): + """Return the object of attention from an iter""" + return self._model[self.get_iter(item_iter)][0] + + def get_iter(self, item, path=False): + """Return the iter given the item""" + if isinstance(item, Gtk.TreePath): + return item if path else self._model.get_iter(item) + if isinstance(item, Gtk.TreeIter): + return self._model.get_path(item) if path else item + for src_item, src_path, src_iter in self: + if item == src_item: + return src_path if path else src_iter + return None + + +class TreeView(BaseView): + """Controls and operates a tree view.""" + + column_size = 16 + widget_type = Gtk.TreeView + changed_signal = "cursor_changed" + + def setup(self): + """Setup the treeview""" + self._sel = self._list.get_selection() + self._sel.set_mode(Gtk.SelectionMode.MULTIPLE) + self._list.connect("button-press-event", self.item_selected_signal) + # Separators should do something + self._list.set_row_separator_func(TreeView.is_separator, None) + super().setup() + + @staticmethod + def is_separator(model, item_iter, data): + """Internal function for seperator checking""" + return isinstance(model.get_value(item_iter, 0), Separator) + + def get_selected_items(self): + """Return a list of selected item objects""" + return [self.get_item(row) for row in self._sel.get_selected_rows()[1]] + + def set_selected_items(self, *items): + """Select the given items""" + self._sel.unselect_all() + for item in items: + path_item = self.get_iter(item, path=True) + if path_item is not None: + self._sel.select_path(path_item) + + def is_selected(self, item): + """Return true if the item is selected""" + return self._sel.iter_is_selected(self.get_iter(item)) + + def add(self, target, parent=None): + """Add all items from the target to the treeview""" + for item in target: + self.add_item(item, parent=parent) + + def add_item(self, item, parent=None): + """Add a single item image to the control, returns the TreePath""" + if item is not None: + self.check_item_id(item) + return self._add_item([item], self.get_iter(parent)) + raise ValueError("Item can not be None.") + + def _add_item(self, item, parent): + return self.get_iter(self._model.append(parent, item), path=True) + + def item_selected_signal(self, *args, **kwargs): + """Signal for selecting an item""" + return self.item_selected(*self.get_selected_items()) + + def item_button_clicked(self, _, event): + """Signal for mouse button click""" + if event is None or event.type == Gdk.EventType._2BUTTON_PRESS: + self.item_double_clicked(*self.get_selected_items()) + + def expand_item(self, item, expand=True): + """Expand one of our nodes""" + self._list.expand_row(self.get_iter(item, path=True), expand) + + def create_model(self, liststore=None): + """Set up an icon view for showing gallery images""" + if liststore is None: + liststore = Gtk.TreeStore(GOBJ) + return super().create_model(liststore) + + def create_column(self, name, expand=True): + """ + Create and pack a new column to this list. + + name - Label in the column header + expand - Should the column expand + """ + return ViewColumn(self._list, name, expand=expand) + + def create_sort(self, *args, **kwargs): + """ + Create and attach a sorting view to this list. + + see ViewSort arguments for details. + """ + return ViewSort(self._list, *args, **kwargs) + + +class ComboBox(TreeView): + """Controls and operates a combo box list.""" + + widget_type = Gtk.ComboBox + changed_signal = "changed" + + def setup(self): + pass + + def get_selected_item(self): + """Return the selected item of this combo box""" + return self.get_item(self._list.get_active_iter()) + + def set_selected_item(self, item): + """Set the given item as the selected item""" + self._list.set_active_iter(self.get_iter(item)) + + def is_selected(self, item): + """Returns true if this item is the selected item""" + return self.get_selected_item() == item + + def get_selected_items(self): + """Return a list of selected items (one)""" + return [self.get_selected_item()] + + +class IconView(BaseView): + """Allows a simpler IconView for DBus List Objects""" + + widget_type = Gtk.IconView + changed_signal = "selection-changed" + + def __init__(self, widget, pixmaps, *args, **kwargs): + super().__init__(widget, *args, **kwargs) + self.pixmaps = pixmaps + + def set_selected_item(self, item): + """Sets the selected item to this item""" + path = self.get_iter(item, path=True) + if path: + self._list.set_cursor(path, None, False) + + def get_selected_items(self): + """Return the seleced item""" + return [self.get_item(path) for path in self._list.get_selected_items()] + + def create_model(self, liststore): + """Setup the icon view control and model""" + if not liststore: + liststore = Gtk.ListStore(GOBJ, str, GdkPixbuf.Pixbuf) + return super().create_model(liststore) + + def setup(self): + """Setup the columns for the iconview""" + self._list.set_markup_column(1) + self._list.set_pixbuf_column(2) + super().setup() + + def add(self, target): + """Add all items from the target to the iconview""" + for item in target: + self.add_item(item) + + def add_item(self, item): + """Add a single item image to the control""" + if item is not None: + self.check_item_id(item) + return self._add_item(item) + raise ValueError("Item can not be None.") + + def get_markup(self, item): + """Default text return for markup.""" + return default(item, "name", str(item)) + + def get_icon(self, item): + """Default icon return, pixbuf or gnome theme name""" + return default(item, "icon", None) + + def _get_icon(self, item): + return self.pixmaps.get(self.get_icon(item), item=item) + + def _add_item(self, item): + """ + Each item's properties must be stuffed into the ListStore directly + or the IconView won't see them, but only if on auto. + """ + if not isinstance(item, (tuple, list)): + item = [item, self.get_markup(item), self._get_icon(item)] + return self._model.append(item) + + def item_selected_signal(self, *args, **kwargs): + """Item has been selected""" + return self.item_selected(*self.get_selected_items()) + + +class ViewSort(object): + """ + A sorting function for use is ListViews + + ascending - Boolean which direction to sort + contains - Contains this string + data - A string or function to get data from each item. + exact - Compare to this exact string instead. + """ + + def __init__(self, widget, data=None, ascending=False, exact=None, contains=None): + self.tree = None + self.data = data + self.asc = ascending + self.comp = exact.lower() if exact else None + self.cont = contains + self.tree = widget + self.resort() + + def get_data(self, model, list_iter): + """Generate sortable data from the item""" + item = model.get_value(list_iter, 0) + if isinstance(self.data, str): + value = getattr(item, self.data) + elif callable(self.data): + value = self.data(item) + return value + + def sort_func(self, model, iter1, iter2, data): + """Called by Gtk to sort items""" + value1 = self.get_data(model, iter1) + value2 = self.get_data(model, iter2) + if value1 == None or value2 == None: + return 0 + if self.comp: + if cmp(self.comp, value1.lower()) == 0: + return 1 + elif cmp(self.comp, value2.lower()) == 0: + return -1 + return 0 + elif self.cont: + if self.cont in value1.lower(): + return 1 + elif self.cont in value2.lower(): + return -1 + return 0 + if value1 < value2: + return 1 + if value2 < value1: + return -1 + return 0 + + def resort(self): + model = self.tree.get_model() + model.set_sort_func(0, self.sort_func, None) + if self.asc: + model.set_sort_column_id(0, Gtk.SortType.ASCENDING) + else: + model.set_sort_column_id(0, Gtk.SortType.DESCENDING) + + +class ViewColumn(object): + """ + Add a column to a gtk treeview. + + name - The column name used as a label. + expand - Set column expansion. + """ + + def __init__(self, widget, name, expand=False): + if isinstance(widget, Gtk.TreeView): + column = Gtk.TreeViewColumn((name)) + column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + column.set_expand(expand) + self._column = column + widget.append_column(self._column) + else: + # Deal with possible drop down lists + self._column = widget + + def add_renderer(self, renderer, func, expand=True): + """Set a custom renderer""" + self._column.pack_start(renderer, expand) + self._column.set_cell_data_func(renderer, func, None) + return renderer + + def add_image_renderer(self, icon, pad=0, pixmaps=None, size=None): + """ + Set the image renderer + + icon - The function that returns the image to be dsplayed. + pad - The amount of padding around the image. + pixmaps - The pixmap manager to use to get images. + size - Restrict the images to this size. + """ + # Manager where icons will be pulled from + filters = [SizeFilter] if size else [] + pixmaps = pixmaps or PixmapManager( + "", pixmap_dir="./", filters=filters, size=size + ) + + renderer = Gtk.CellRendererPixbuf() + renderer.set_property("ypad", pad) + renderer.set_property("xpad", pad) + func = self.image_func(icon or self.default_icon, pixmaps) + return self.add_renderer(renderer, func, expand=False) + + def add_text_renderer(self, text, wrap=None, template=None): + """ + Set the text renderer. + + text - the function that returns the text to be displayed. + wrap - The wrapping setting for this renderer. + template - A standard template used for this text markup. + """ + + renderer = Gtk.CellRendererText() + if wrap is not None: + renderer.props.wrap_width = wrap + renderer.props.wrap_mode = Pango.WrapMode.WORD + + renderer.props.background_set = True + renderer.props.foreground_set = True + + func = self.text_func(text or self.default_text, template) + return self.add_renderer(renderer, func, expand=True) + + @classmethod + def clean(cls, text, markup=False): + """Clean text of any pango markup confusing chars""" + if text is None: + text = "" + if isinstance(text, (str, int, float)): + if markup: + text = str(text).replace("<", "<").replace(">", ">") + return str(text).replace("&", "&") + elif isinstance(text, dict): + return dict([(k, cls.clean(v)) for k, v in text.items()]) + elif isinstance(text, (list, tuple)): + return tuple([cls.clean(value) for value in text]) + raise TypeError("Unknown value type for text: %s" % str(type(text))) + + def get_callout(self, call, default=None): + """Returns the right kind of method""" + if isinstance(call, str): + call = item_property(call, default) + return call + + def text_func(self, call, template=None): + """Wrap up our text functionality""" + callout = self.get_callout(call) + + def internal(column, cell, model, item_iter, data): + if TreeView.is_separator(model, item_iter, data): + return + item = model.get_value(item_iter, 0) + markup = template is not None + text = callout(item) + if isinstance(template, str): + text = template.format(self.clean(text, markup=True)) + else: + text = self.clean(text) + cell.set_property("markup", str(text)) + + return internal + + def image_func(self, call, pixmaps=None): + """Wrap, wrap wrap the func""" + callout = self.get_callout(call) + + def internal(column, cell, model, item_iter, data): + if TreeView.is_separator(model, item_iter, data): + return + item = model.get_value(item_iter, 0) + icon = callout(item) + # The or blank asks for the default icon from the pixmaps + if isinstance(icon or "", str) and pixmaps: + # Expect a Gnome theme icon + icon = pixmaps.get(icon) + elif icon: + icon = pixmaps.apply_filters(icon) + + cell.set_property("pixbuf", icon) + cell.set_property("visible", True) + + return internal + + def default_text(self, item): + """Default text return for markup.""" + return default(item, "name", str(item)) + + def default_icon(self, item): + """Default icon return, pixbuf or gnome theme name""" + return default(item, "icon", None) + + +class Separator: + """Reprisentation of a separator in a list""" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/pixmap.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/pixmap.py new file mode 100644 index 0000000..02f4ce8 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/pixmap.py @@ -0,0 +1,346 @@ +# +# Copyright 2011-2022 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +""" +Provides wrappers for pixmap access. +""" + +import os +import logging + +from typing import List +from collections.abc import Iterable +from gi.repository import Gtk, GLib, GdkPixbuf + +ICON_THEME = Gtk.IconTheme.get_default() +BILINEAR = GdkPixbuf.InterpType.BILINEAR +HYPER = GdkPixbuf.InterpType.HYPER + +SIZE_ASPECT = 0 +SIZE_ASPECT_GROW = 1 +SIZE_ASPECT_CROP = 2 +SIZE_STRETCH = 3 + + +class PixmapLoadError(ValueError): + """Failed to load a pixmap""" + + +class PixmapFilter: # pylint: disable=too-few-public-methods + """Base class for filtering the pixmaps in a manager's output. + + required - List of values required for this filter. + + Use: + + class Foo(PixmapManager): + filters = [ PixmapFilterFoo ] + + """ + + required: List[str] = [] + optional: List[str] = [] + + def __init__(self, **kwargs): + self.enabled = True + for key in self.required: + if key not in kwargs: + self.enabled = False + else: + setattr(self, key, kwargs[key]) + + for key in self.optional: + if key in kwargs: + setattr(self, key, kwargs[key]) + + def filter(self, img, **kwargs): + """Run filter, replace this methodwith your own""" + raise NotImplementedError( + "Please add 'filter' method to your PixmapFilter class %s." + % type(self).__name__ + ) + + @staticmethod + def to_size(dat): + """Tries to calculate a size that will work for the data""" + if isinstance(dat, (int, float)): + return (dat, dat) + if isinstance(dat, Iterable) and len(dat) >= 2: + return (dat[0], dat[1]) + return None + + +class OverlayFilter(PixmapFilter): + """Adds an overlay to output images, overlay can be any name that + the owning pixmap manager can find. + + overlay : Name of overlay image + position : Location of the image: + 0 - Full size (1 to 1 overlay, default) + (x,y) - Percentage from one end to the other position 0-1 + alpha : Blending alpha, 0 - 255 + + """ + + optional = ["position", "overlay", "alpha"] + + def __init__(self, *args, **kwargs): + self.position = (0, 0) + self.overlay = None + self.alpha = 255 + super().__init__(*args, **kwargs) + self.pad_x, self.pad_y = self.to_size(self.position) + + def get_overlay(self, **kwargs): + if "manager" not in kwargs: + raise ValueError("PixmapManager must be provided when adding an overlay.") + return kwargs["manager"].get( + kwargs.get("overlay", None) or self.overlay, no_overlay=True + ) + + def filter(self, img, no_overlay=False, **kwargs): + # Recursion protection + if no_overlay: + return img + + overlay = self.get_overlay(**kwargs) + if overlay: + img = img.copy() + + (x, y, width, height) = self.set_position(overlay, img) + overlay.composite( + img, x, y, width, height, x, y, 1, 1, BILINEAR, self.alpha + ) + return img + + def set_position(self, overlay, img): + """Sets the position of img on the given width and height""" + img_w, img_h = img.get_width(), img.get_height() + ovl_w, ovl_h = overlay.get_width(), overlay.get_height() + return ( + max([0, (img_w - ovl_w) * self.pad_x]), + max([0, (img_h - ovl_h) * self.pad_y]), + min([ovl_w, img_w]), + min([ovl_h, img_h]), + ) + + +class SizeFilter(PixmapFilter): + """Resizes images to a certain size: + + resize_mode - Way in which the size is calculated + 0 - Best Aspect, don't grow + 1 - Best Aspect, grow + 2 - Cropped Aspect + 3 - Stretch + """ + + required = ["size"] + optional = ["resize_mode"] + + def __init__(self, *args, **kwargs): + self.size = None + self.resize_mode = SIZE_ASPECT + super().__init__(*args, **kwargs) + self.img_w, self.img_h = self.to_size(self.size) or (0, 0) + + def aspect(self, img_w, img_h): + """Get the aspect ratio of the image resized""" + if self.resize_mode == SIZE_STRETCH: + return (self.img_w, self.img_h) + + if ( + self.resize_mode == SIZE_ASPECT + and img_w < self.img_w + and img_h < self.img_h + ): + return (img_w, img_h) + (pcw, pch) = (self.img_w / img_w, self.img_h / img_h) + factor = ( + max(pcw, pch) if self.resize_mode == SIZE_ASPECT_CROP else min(pcw, pch) + ) + return (int(img_w * factor), int(img_h * factor)) + + def filter(self, img, **kwargs): + if self.size is not None: + (width, height) = self.aspect(img.get_width(), img.get_height()) + return img.scale_simple(width, height, HYPER) + return img + + +class PadFilter(SizeFilter): + """Add padding to the image to make it a standard size""" + + optional = ["padding"] + + def __init__(self, *args, **kwargs): + self.size = None + self.padding = 0.5 + super().__init__(*args, **kwargs) + self.pad_x, self.pad_y = self.to_size(self.padding) + + def filter(self, img, **kwargs): + (width, height) = (img.get_width(), img.get_height()) + if width < self.img_w or height < self.img_h: + target = GdkPixbuf.Pixbuf.new( + img.get_colorspace(), + True, + img.get_bits_per_sample(), + max([width, self.img_w]), + max([height, self.img_h]), + ) + target.fill(0x0) # Transparent black + + x = (target.get_width() - width) * self.pad_x + y = (target.get_height() - height) * self.pad_y + + img.composite(target, x, y, width, height, x, y, 1, 1, BILINEAR, 255) + return target + return img + + +class PixmapManager: + """Manage a set of cached pixmaps, returns the default image + if it can't find one or the missing image if that's available.""" + + missing_image = "image-missing" + default_image = "application-default-icon" + icon_theme = ICON_THEME + theme_size = 32 + filters: List[type] = [] + pixmap_dir = None + + def __init__(self, location="", **kwargs): + self.location = location + if self.pixmap_dir and not os.path.isabs(location): + self.location = os.path.join(self.pixmap_dir, location) + + self.loader_size = PixmapFilter.to_size(kwargs.pop("load_size", None)) + + # Add any instance specified filters first + self._filters = [] + for item in kwargs.get("filters", []) + self.filters: + if isinstance(item, PixmapFilter): + self._filters.append(item) + elif callable(item): + # Now add any class specified filters with optional kwargs + self._filters.append(item(**kwargs)) + + self.cache = {} + self.get_pixmap(self.default_image) + + def get(self, *args, **kwargs): + """Get a pixmap of any kind""" + return self.get_pixmap(*args, **kwargs) + + def get_missing_image(self): + """Get a missing image when other images aren't found""" + return self.get(self.missing_image) + + @staticmethod + def data_is_file(data): + """Test the file to see if it's a filename or not""" + return isinstance(data, str) and " +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +""" +Structures for consistant testing of Gtk GUI programs. +""" + +import sys +from gi.repository import Gtk, GLib + + +class MainLoopProtection: + """ + This protection class provides a way to launch the Gtk mainloop in a test + friendly way. + + Exception handling hooks provide a way to see errors that happen + inside the main loop, raising them back to the caller. + A full timeout in seconds stops the gtk mainloop from operating + beyond a set time, acting as a kill switch in the event something + has gone horribly wrong. + + Use: + with MainLoopProtection(timeout=10s): + app.run() + """ + + def __init__(self, timeout=10): + self.timeout = timeout * 1000 + self._hooked = None + self._old_excepthook = None + + def __enter__(self): + # replace sys.excepthook with our own and remember hooked raised error + self._old_excepthook = sys.excepthook + sys.excepthook = self.excepthook + # Remove mainloop by force if it doesn't die within 10 seconds + self._timeout = GLib.timeout_add(self.timeout, self.idle_exit) + + def __exit__(self, exc, value, traceback): # pragma: no cover + """Put the except handler back, cancel the timer and raise if needed""" + if self._old_excepthook: + sys.excepthook = self._old_excepthook + # Remove the timeout, so we don't accidentally kill later mainloops + if self._timeout: + GLib.source_remove(self._timeout) + # Raise an exception if one happened during the test run + if self._hooked: + exc, value, traceback = self._hooked + if value and traceback: + raise value.with_traceback(traceback) + + def idle_exit(self): # pragma: no cover + """Try to going to kill any running mainloop.""" + GLib.idle_add(Gtk.main_quit) + + def excepthook(self, ex_type, ex_value, traceback): # pragma: no cover + """Catch errors thrown by the Gtk mainloop""" + self.idle_exit() + # Remember the exception data for raising inside the test context + if ex_value is not None: + self._hooked = [ex_type, ex_value, traceback] + # Fallback and double print the exception (remove if double printing is problematic) + return self._old_excepthook(ex_type, ex_value, traceback) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/window.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/window.py new file mode 100644 index 0000000..a5c1ef6 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/gui/window.py @@ -0,0 +1,201 @@ +# +# Copyright 2012-2022 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +# pylint: disable=too-many-instance-attributes +""" +Wraps the gtk windows with something a little nicer. +""" +import logging + +from gi.repository import Gtk + +PROPS = { + "Box": ["expand", "fill", "padding", "pack-type"], + "Grid": ["top-attach", "left-attach", "height", "width"], + "Table": ["top-attach", "left-attach", "bottom-attach", "right-attach"], +} + + +def protect(cls, *methods): + """Simple check for protecting an inherrited class from having + certain methods over-ridden""" + if not isinstance(cls, type): + cls = type(cls) + for method in methods: + if method in cls.__dict__: # pragma: no cover + raise RuntimeError( + f"{cls.__name__} in {cls.__module__} has" f" protected def {method}()" + ) + + +class Window: + """ + This wraps gtk windows and allows for having parent windows + + name = 'name-of-the-window' + + Should the window be the first loaded and end gtk when closed: + + primary = True/False + """ + + primary = True + name = None + + def __init__(self, gapp): + self.gapp = gapp + self.dead = False + self.parent = None + self.args = () + ui_file = gapp.get_ui_file(self.name) + + # Setup the gtk app connection + self.w_tree = Gtk.Builder() + self.widget = self.w_tree.get_object + self.w_tree.set_translation_domain(gapp.app_name) + self.w_tree.add_from_file(ui_file) + + # Setup the gtk builder window + self.window = self.widget(self.name) + if not self.window: # pragma: no cover + raise KeyError(f"Missing window widget '{self.name}' from '{ui_file}'") + + # Give us a window id to track this window + self.wid = str(hash(self.window)) + + def extract(self): + """Extract this window's container for use in other apps""" + for child in self.window.get_children(): + self.window.remove(child) + return child + + def init(self, parent=None, **kwargs): + """Initialise the window within the GtkApp""" + if "replace" not in kwargs: + protect(self, "destroy", "exit", "load_window", "proto_window") + self.args = kwargs + # Set object defaults + self.parent = parent + + self.w_tree.connect_signals(self) + + # These are some generic convience signals + self.window.connect("destroy", self.exit) + + # If we have a parent window, then we expect not to quit + if self.parent: + self.window.set_transient_for(self.parent) + self.parent.set_sensitive(False) + + # We may have some more gtk widgets to setup + self.load_widgets(**self.args) + self.window.show() + + def load_window(self, name, *args, **kwargs): + """Load child window, automatically sets parent""" + kwargs["parent"] = self.window + return self.gapp.load_window(name, *args, **kwargs) + + def load_widgets(self): + """Child class should use this to create widgets""" + + def destroy(self, widget=None): # pylint: disable=unused-argument + """Destroy the window""" + logging.debug("Destroying Window '%s'", self.name) + self.window.destroy() + # We don't need to call self.exit(), handeled by window event. + + def pre_exit(self): + """Internal method for what to do when the window has died""" + + def exit(self, widget=None): + """Called when the window needs to exit.""" + # Is the signal called by the window or by something else? + if not widget or not isinstance(widget, Gtk.Window): + self.destroy() + # Clean up any required processes + self.pre_exit() + if self.parent: + # We assume the parent didn't load another gtk loop + self.parent.set_sensitive(True) + # Exit our entire app if this is the primary window + # Or just remove from parent window list, which may still exit. + if self.primary: + logging.debug("Exiting the application") + self.gapp.exit() + else: + logging.debug("Removing Window %s from parent", self.name) + self.gapp.remove_window(self) + # Now finish up what ever is left to do now the window is dead. + self.dead = True + self.post_exit() + return widget + + def post_exit(self): + """Called after we've killed the window""" + + def if_widget(self, name): + """ + Attempt to get the widget from gtk, but if not return a fake that won't + cause any trouble if we don't further check if it's real. + """ + return self.widget(name) or FakeWidget(name) + + def replace(self, old, new): + """Replace the old widget with the new widget""" + if isinstance(old, str): + old = self.widget(old) + if isinstance(new, str): + new = self.widget(new) + target = old.get_parent() + source = new.get_parent() + if target is not None: + if source is not None: + source.remove(new) + target.remove(old) + target.add(new) + + @staticmethod + def get_widget_name(obj): + """Return the widget's name in the builder file""" + return Gtk.Buildable.get_name(obj) + + +class ChildWindow(Window): + """ + Base class for child window objects, these child windows are typically + window objects in the same gtk builder file as their parents. If you just want + to make a window that interacts with a parent window, use the normal + Window class and call with the optional parent attribute. + """ + + primary = False + + +class FakeWidget: + """A fake widget class that can take calls""" + + def __init__(self, name): + self._name = name + + def __getattr__(self, name): + def _fake(*args, **kwargs): + logging.info("Calling fake method: %s:%s", args, kwargs) + + return _fake + + def __bool__(self): + return False diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/IElement.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/IElement.py new file mode 100644 index 0000000..3e9df53 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/IElement.py @@ -0,0 +1,39 @@ +"""Element abstractions for type comparisons without circular imports + +.. versionadded:: 1.2""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + +import sys +from lxml import etree + +if sys.version_info >= (3, 8): + from typing import Protocol +else: + from typing_extensions import Protocol + + +class IBaseElement(ABC, etree.ElementBase): + """Abstraction for BaseElement to avoid circular imports""" + + @abstractmethod + def get_id(self, as_url=0): + """Returns the element ID. If not set, generates a unique ID.""" + raise NotImplementedError + + +class BaseElementProtocol(Protocol): + """Abstraction for BaseElement, to be used as typehint in mixin classes""" + + def get_id(self, as_url=0) -> str: + """Returns the element ID. If not set, generates a unique ID.""" + + @property + def root(self) -> ISVGDocumentElement: + """Returns the element's root.""" + + +class ISVGDocumentElement(IBaseElement): + """Abstraction for SVGDocumentElement""" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/inx.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/inx.py new file mode 100644 index 0000000..2360bc6 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/inx.py @@ -0,0 +1,244 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Parsing inx files for checking and generating. +""" + +import os +from inspect import isclass +from importlib import util +from lxml import etree + +from .base import InkscapeExtension +from .utils import Boolean + +NSS = { + "inx": "http://www.inkscape.org/namespace/inkscape/extension", + "inkscape": "http://www.inkscape.org/namespaces/inkscape", +} +SSN = {b: a for (a, b) in NSS.items()} + + +class InxLookup(etree.CustomElementClassLookup): + """Custom inx xml file lookup""" + + def lookup( + self, node_type, document, namespace, name + ): # pylint: disable=unused-argument + if name == "param": + return ParamElement + return InxElement + + +INX_PARSER = etree.XMLParser() +INX_PARSER.set_element_class_lookup(InxLookup()) + + +class InxFile: + """Open an INX file and provide useful functions""" + + name = property(lambda self: self.xml.get_text("name")) + ident = property(lambda self: self.xml.get_text("id")) + slug = property(lambda self: self.ident.split(".")[-1].title().replace("_", "")) + kind = property(lambda self: self.metadata["type"]) + warnings = property(lambda self: sorted(list(set(self.xml.warnings)))) + + def __init__(self, filename): + if isinstance(filename, str) and "<" in filename: + filename = filename.encode("utf8") + if isinstance(filename, bytes) and b"<" in filename: + self.filename = None + self.doc = etree.ElementTree(etree.fromstring(filename, parser=INX_PARSER)) + else: + self.filename = os.path.basename(filename) + self.doc = etree.parse(filename, parser=INX_PARSER) + self.xml = self.doc.getroot() + self.xml.warnings = [] + + def __repr__(self): + return f"" + + @property + def script(self): + """Returns information about the called script""" + command = self.xml.find_one("script/command") + if command is None: + return {} + return { + "interpreter": command.get("interpreter", None), + "location": command.get("location", None), + "script": command.text, + } + + @property + def extension_class(self): + """Attempt to get the extension class""" + script = self.script.get("script", None) + if script is not None: + name = script[:-3].replace("/", ".") + spec = util.spec_from_file_location(name, script) + mod = util.module_from_spec(spec) + spec.loader.exec_module(mod) + for value in mod.__dict__.values(): + if ( + "Base" not in name + and isclass(value) + and value.__module__ == name + and issubclass(value, InkscapeExtension) + ): + return value + return None + + @property + def metadata(self): + """Returns information about what type of extension this is""" + effect = self.xml.find_one("effect") + output = self.xml.find_one("output") + inputs = self.xml.find_one("input") + data = {} + if effect is not None: + template = self.xml.find_one("inkscape:templateinfo") + if template is not None: + data["type"] = "template" + data["desc"] = self.xml.get_text( + "templateinfo/shortdesc", nss="inkscape" + ) + data["author"] = self.xml.get_text( + "templateinfo/author", nss="inkscape" + ) + else: + data["type"] = "effect" + data["preview"] = Boolean(effect.get("needs-live-preview", "true")) + data["objects"] = effect.get_text("object-type", "all") + elif inputs is not None: + data["type"] = "input" + data["extension"] = inputs.get_text("extension") + data["mimetype"] = inputs.get_text("mimetype") + data["tooltip"] = inputs.get_text("filetypetooltip") + data["name"] = inputs.get_text("filetypename") + elif output is not None: + data["type"] = "output" + data["dataloss"] = Boolean(output.get_text("dataloss", "false")) + data["extension"] = output.get_text("extension") + data["mimetype"] = output.get_text("mimetype") + data["tooltip"] = output.get_text("filetypetooltip") + data["name"] = output.get_text("filetypename") + return data + + @property + def menu(self): + """Return the menu this effect ends up in""" + + def _recurse_menu(parent): + for child in parent.xpath("submenu"): + yield child.get("name") + for subchild in _recurse_menu(child): + yield subchild + break # Not more than one menu chain? + + menu = self.xml.find_one("effect/effects-menu") + return list(_recurse_menu(menu)) + [self.name] + + @property + def params(self): + """Get all params at all levels""" + # Returns any params at any levels + return list(self.xml.xpath("//param")) + + +class InxElement(etree.ElementBase): + """Any element in an inx file + + .. versionadded:: 1.1""" + + def set_warning(self, msg): + """Set a warning for slightly incorrect inx contents""" + root = self.get_root() + if hasattr(root, "warnings"): + root.warnings.append(msg) + + def get_root(self): + """Get the root document element from any element descendent""" + if self.getparent() is not None: + return self.getparent().get_root() + return self + + def get_default_prefix(self): + """Set default xml namespace prefix. If none is defined, set warning""" + tag = self.get_root().tag + if "}" in tag: + (url, tag) = tag[1:].split("}", 1) + return SSN.get(url, "inx") + self.set_warning("No inx xml prefix.") + return None # no default prefix + + def apply_nss(self, xpath, nss=None): + """Add prefixes to any xpath string""" + if nss is None: + nss = self.get_default_prefix() + + def _process(seg): + if ":" in seg or not seg or not nss: + return seg + return f"{nss}:{seg}" + + return "/".join([_process(seg) for seg in xpath.split("/")]) + + def xpath(self, xpath, nss=None): + """Namespace specific xpath searches + + .. versionadded:: 1.1""" + return super().xpath(self.apply_nss(xpath, nss=nss), namespaces=NSS) + + def find_one(self, name, nss=None): + """Return the first element matching the given name + + .. versionadded:: 1.1""" + for elem in self.xpath(name, nss=nss): + return elem + return None + + def get_text(self, name, default=None, nss=None): + """Get text content agnostically""" + for pref in ("", "_"): + elem = self.find_one(pref + name, nss=nss) + if elem is not None and elem.text: + if pref == "_": + self.set_warning(f"Use of old translation scheme: <_{name}...>") + return elem.text + return default + + +class ParamElement(InxElement): + """ + A param in an inx file. + """ + + name = property(lambda self: self.get("name")) + param_type = property(lambda self: self.get("type", "string")) + + @property + def options(self): + """Return a list of option values""" + if self.param_type == "notebook": + return [option.get("name") for option in self.xpath("page")] + return [option.get("value") for option in self.xpath("option")] + + def __repr__(self): + return f"" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/localization.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/localization.py new file mode 100644 index 0000000..7de6c57 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/localization.py @@ -0,0 +1,117 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Allow extensions to translate messages. +""" + +import gettext +import os, sys + +# Get gettext domain and matching locale directory for translation of extensions strings +# (both environment variables are set by Inkscape) +GETTEXT_DOMAIN = os.environ.get("INKEX_GETTEXT_DOMAIN") +GETTEXT_DIRECTORY = os.environ.get("INKEX_GETTEXT_DIRECTORY") + +# INKSCAPE_LOCALEDIR can be used to override the default locale directory Inkscape uses +INKSCAPE_LOCALEDIR = os.environ.get("INKSCAPE_LOCALEDIR") + + +def localize(domain=GETTEXT_DOMAIN, localedir=GETTEXT_DIRECTORY): + """Configure gettext and install _() function into builtins namespace for easy + access""" + + # Do not enable translation if GETTEXT_DOMAIN is unset. + # This is the case when translationdomain="none", but also when no catalog was + # found. + # Install a NullTranslation just to be sure + # (so we do not get errors about undefined '_') + if domain is None: + gettext.NullTranslations().install() + return + + # Use the default system locale by default, + # but prefer LANGUAGE environment variable + # (which is set by Inkscape according to UI language) + languages = None + + trans = gettext.translation(domain, localedir, languages, fallback=True) + trans.install() + + +def inkex_localize(): + """ + Return internal Translations instance for translation of the inkex module itself + Those will always use the 'inkscape' domain and attempt to lookup the same catalog + Inkscape uses + """ + + domain = "inkscape" + localedir = INKSCAPE_LOCALEDIR + languages = None + + return gettext.translation(domain, localedir, languages, fallback=True) + + +inkex_gettext = inkex_localize().gettext # pylint: disable=invalid-name +""" +Shortcut for gettext. Import as:: + + from inkex.localize import inkex_gettext as _ + +""" + +inkex_ngettext = inkex_localize().ngettext +""" +Shortcut for ngettext + + .. versionadded:: 1.2 +""" + + +def inkex_fgettext(message, *args, **kwargs): + """ + Shortcut for gettext and subsequent formatting. Import as:: + + from inkex.localize import inkex_fgettext as _f + + The positionals and keyword arguments are passed to ``str.format()``. + + The call to xgettext must contain:: + + --keyword=_f + """ + return inkex_gettext(message).format(*args, **kwargs) + + +if sys.version_info >= (3, 8): + inkex_pgettext = inkex_localize().pgettext + """ + Gettext with context. Import as:: + + from inkex.localize import inkex_pgettext as pgettext + + Both parameters **must** be string literals. The call to xgettext must contain:: + + --keyword=pgettext:1c,2 + + .. versionadded:: 1.2 + """ +else: + inkex_pgettext = lambda context, message: message diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/paths.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/paths.py new file mode 100644 index 0000000..ba4a525 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/paths.py @@ -0,0 +1,2035 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +functions for digesting paths +""" +from __future__ import annotations + +import re +import copy +import abc + +from math import atan2, cos, pi, sin, sqrt, acos, tan +from typing import ( + Any, + Type, + Dict, + Optional, + Union, + Tuple, + List, + Generator, + TypeVar, +) +from .transforms import ( + Transform, + BoundingBox, + Vector2d, + cubic_extrema, + quadratic_extrema, +) +from .utils import classproperty, strargs + + +Pathlike = TypeVar("Pathlike", bound="PathCommand") +AbsolutePathlike = TypeVar("AbsolutePathlike", bound="AbsolutePathCommand") + +# All the names that get added to the inkex API itself. +__all__ = ( + "Path", + "CubicSuperPath", + "PathCommand", + "AbsolutePathCommand", + "RelativePathCommand", + # Path commands: + "Line", + "line", + "Move", + "move", + "ZoneClose", + "zoneClose", + "Horz", + "horz", + "Vert", + "vert", + "Curve", + "curve", + "Smooth", + "smooth", + "Quadratic", + "quadratic", + "TepidQuadratic", + "tepidQuadratic", + "Arc", + "arc", + # errors + "InvalidPath", +) + +LEX_REX = re.compile(r"([MLHVCSQTAZmlhvcsqtaz])([^MLHVCSQTAZmlhvcsqtaz]*)") +NONE = lambda obj: obj is not None + + +class InvalidPath(ValueError): + """Raised when given an invalid path string""" + + +class PathCommand(abc.ABC): + """ + Base class of all path commands + """ + + # Number of arguments that follow this path commands letter + nargs = -1 + + @classproperty # From python 3.9 on, just combine @classmethod and @property + def name(cls): # pylint: disable=no-self-argument + """The full name of the segment (i.e. Line, Arc, etc)""" + return cls.__name__ # pylint: disable=no-member + + @classproperty + def letter(cls): # pylint: disable=no-self-argument + """The single letter representation of this command (i.e. L, A, etc)""" + return cls.name[0] + + @classproperty + def next_command(self): + """The implicit next command. This is for automatic chains where the next + command isn't given, just a bunch on numbers which we automatically parse.""" + return self + + @property + def is_relative(self): # type: () -> bool + """Whether the command is defined in relative coordinates, i.e. relative to + the previous endpoint (lower case path command letter)""" + raise NotImplementedError + + @property + def is_absolute(self): # type: () -> bool + """Whether the command is defined in absolute coordinates (upper case path + command letter)""" + raise NotImplementedError + + def to_relative(self, prev): # type: (Vector2d) -> RelativePathCommand + """Return absolute counterpart for absolute commands or copy for relative""" + raise NotImplementedError + + def to_absolute(self, prev): # type: (Vector2d) -> AbsolutePathCommand + """Return relative counterpart for relative commands or copy for absolute""" + raise NotImplementedError + + def reverse(self, first, prev): + """Reverse path command + + .. versionadded:: 1.1""" + + def to_non_shorthand(self, prev, prev_control): # pylint: disable=unused-argument + # type: (Vector2d, Vector2d) -> AbsolutePathCommand + """Return an absolute non-shorthand command + + .. versionadded:: 1.1""" + return self.to_absolute(prev) + + # The precision of the numbers when converting to string + number_template = "{:.6g}" + + # Maps single letter path command to corresponding class + # (filled at the bottom of file, when all classes already defined) + _letter_to_class = {} # type: Dict[str, Type[Any]] + + @staticmethod + def letter_to_class(letter): + """Returns class for given path command letter""" + return PathCommand._letter_to_class[letter] + + @property + def args(self): # type: () -> List[float] + """Returns path command arguments as tuple of floats""" + raise NotImplementedError() + + def control_points( + self, first: Vector2d, prev: Vector2d, prev_prev: Vector2d + ) -> Union[List[Vector2d], Generator[Vector2d, None, None]]: + """Returns list of path command control points""" + raise NotImplementedError + + @classmethod + def _argt(cls, sep): + return sep.join([cls.number_template] * cls.nargs) + + def __str__(self): + return f"{self.letter} {self._argt(' ').format(*self.args)}".strip() + + def __repr__(self): + # pylint: disable=consider-using-f-string + return "{{}}({})".format(self._argt(", ")).format(self.name, *self.args) + + def __eq__(self, other): + previous = Vector2d() + if type(self) == type(other): # pylint: disable=unidiomatic-typecheck + return self.args == other.args + if isinstance(other, tuple): + return self.args == other + if not isinstance(other, PathCommand): + raise ValueError("Can't compare types") + try: + if self.is_relative == other.is_relative: + return self.to_curve(previous) == other.to_curve(previous) + except ValueError: + pass + return False + + def end_point(self, first, prev): # type: (Vector2d, Vector2d) -> Vector2d + """Returns last control point of path command""" + raise NotImplementedError() + + def update_bounding_box( + self, first: Vector2d, last_two_points: List[Vector2d], bbox: BoundingBox + ): + # pylint: disable=unused-argument + """Enlarges given bbox to contain path element. + + Args: + first (Vector2d): first point of path. Required to calculate Z segment + last_two_points (List[Vector2d]): list with last two control points in abs + coords. + bbox (BoundingBox): bounding box to update + """ + + raise NotImplementedError(f"Bounding box is not implemented for {self.name}") + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + """Convert command to :py:class:`Curve` + + Curve().to_curve() returns a copy + """ + raise NotImplementedError(f"To curve not supported for {self.name}") + + def to_curves(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> List[Curve] + """Convert command to list of :py:class:`Curve` commands""" + return [self.to_curve(prev, prev_prev)] + + def to_line(self, prev): + # type: (Vector2d) -> Line + """Converts this segment to a line (copies if already a line)""" + return Line(*self.end_point(Vector2d(), prev)) + + +class RelativePathCommand(PathCommand): + """ + Abstract base class for relative path commands. + + Implements most of methods of :py:class:`PathCommand` through + conversion to :py:class:`AbsolutePathCommand` + """ + + @property + def is_relative(self): + return True + + @property + def is_absolute(self): + return False + + def control_points( + self, first: Vector2d, prev: Vector2d, prev_prev: Vector2d + ) -> Union[List[Vector2d], Generator[Vector2d, None, None]]: + return self.to_absolute(prev).control_points(first, prev, prev_prev) + + def to_relative(self, prev): + # type: (Pathlike, Vector2d) -> Pathlike + return self.__class__(*self.args) + + def update_bounding_box(self, first, last_two_points, bbox): + self.to_absolute(last_two_points[-1]).update_bounding_box( + first, last_two_points, bbox + ) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return self.to_absolute(prev).end_point(first, prev) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + return self.to_absolute(prev).to_curve(prev, prev_prev) + + def to_curves(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> List[Curve] + return self.to_absolute(prev).to_curves(prev, prev_prev) + + +class AbsolutePathCommand(PathCommand): + """Absolute path command. Unlike :py:class:`RelativePathCommand` can be transformed + directly.""" + + @property + def is_relative(self): + return False + + @property + def is_absolute(self): + return True + + def to_absolute( + self, prev + ): # type: (AbsolutePathlike, Vector2d) -> AbsolutePathlike + return self.__class__(*self.args) + + def transform( + self, transform + ): # type: (AbsolutePathlike, Transform) -> AbsolutePathlike + """Returns new transformed segment + + :param transform: a transformation to apply + """ + raise NotImplementedError() + + def rotate( + self, degrees, center + ): # type: (AbsolutePathlike, float, Vector2d) -> AbsolutePathlike + """ + Returns new transformed segment + + :param degrees: rotation angle in degrees + :param center: invariant point of rotation + """ + return self.transform(Transform(rotate=(degrees, center[0], center[1]))) + + def translate(self, dr): # type: (AbsolutePathlike, Vector2d) -> AbsolutePathlike + """Translate or scale this path command by dr""" + return self.transform(Transform(translate=dr)) + + def scale( + self, factor + ): # type: (AbsolutePathlike, Union[float, Tuple[float,float]]) -> AbsolutePathlike + """Returns new transformed segment + + :param factor: scale or (scale_x, scale_y) + """ + return self.transform(Transform(scale=factor)) + + +class Line(AbsolutePathCommand): + """Line segment""" + + nargs = 2 + + @property + def args(self): + return self.x, self.y + + def __init__(self, x, y): + self.x = x + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox( + (last_two_points[-1].x, self.x), (last_two_points[-1].y, self.y) + ) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, self.y) + + def to_relative(self, prev): + # type: (Vector2d) -> line + return line(self.x - prev.x, self.y - prev.y) + + def transform(self, transform): + # type: (Line, Transform) -> Line + return Line(*transform.apply_to_point((self.x, self.y))) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, self.y) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + return Curve(prev.x, prev.y, self.x, self.y, self.x, self.y) + + def reverse(self, first, prev): + return Line(prev.x, prev.y) + + +class line(RelativePathCommand): # pylint: disable=invalid-name + """Relative line segment""" + + nargs = 2 + + @property + def args(self): + return self.dx, self.dy + + def __init__(self, dx, dy): + self.dx = dx + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> Line + return Line(prev.x + self.dx, prev.y + self.dy) + + def reverse(self, first, prev): + return line(-self.dx, -self.dy) + + +class Move(AbsolutePathCommand): + """Move pen segment without a line""" + + nargs = 2 + next_command = Line + + @property + def args(self): + return self.x, self.y + + def __init__(self, x, y): + self.x = x + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox(self.x, self.y) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, self.y) + + def to_relative(self, prev): + # type: (Vector2d) -> move + return move(self.x - prev.x, self.y - prev.y) + + def transform(self, transform): + # type: (Transform) -> Move + return Move(*transform.apply_to_point((self.x, self.y))) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, self.y) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + raise ValueError("Move segments can not be changed into curves.") + + def reverse(self, first, prev): + return Move(prev.x, prev.y) + + +class move(RelativePathCommand): # pylint: disable=invalid-name + """Relative move segment""" + + nargs = 2 + next_command = line + + @property + def args(self): + return self.dx, self.dy + + def __init__(self, dx, dy): + self.dx = dx + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> Move + return Move(prev.x + self.dx, prev.y + self.dy) + + def reverse(self, first, prev): + return move(prev.x - first.x, prev.y - first.y) + + +class ZoneClose(AbsolutePathCommand): + """Close segment to finish a path""" + + nargs = 0 + next_command = Move + + @property + def args(self): + return () + + def update_bounding_box(self, first, last_two_points, bbox): + pass + + def transform(self, transform): + # type: (Transform) -> ZoneClose + return ZoneClose() + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield first + + def to_relative(self, prev): + # type: (Vector2d) -> zoneClose + return zoneClose() + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return first + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + raise ValueError("ZoneClose segments can not be changed into curves.") + + def reverse(self, first, prev): + return Line(prev.x, prev.y) + + +class zoneClose(RelativePathCommand): # pylint: disable=invalid-name + """Same as above (svg says no difference)""" + + nargs = 0 + next_command = Move + + @property + def args(self): + return () + + def to_absolute(self, prev): + return ZoneClose() + + def reverse(self, first, prev): + return line(prev.x - first.x, prev.y - first.y) + + +class Horz(AbsolutePathCommand): + """Horizontal Line segment""" + + nargs = 1 + + @property + def args(self): + return (self.x,) + + def __init__(self, x): + self.x = x + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox((last_two_points[-1].x, self.x), last_two_points[-1].y) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, prev.y) + + def to_relative(self, prev): + # type: (Vector2d) -> horz + return horz(self.x - prev.x) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> Line + return self.to_line(prev) + + def transform(self, transform): + # type: (Pathlike, Transform) -> Pathlike + raise ValueError("Horizontal lines can't be transformed directly.") + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, prev.y) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + """Convert a horizontal line into a curve""" + return self.to_line(prev).to_curve(prev) + + def to_line(self, prev): + # type: (Vector2d) -> Line + """Return this path command as a Line instead""" + return Line(self.x, prev.y) + + def reverse(self, first, prev): + return Horz(prev.x) + + +class horz(RelativePathCommand): # pylint: disable=invalid-name + """Relative horz line segment""" + + nargs = 1 + + @property + def args(self): + return (self.dx,) + + def __init__(self, dx): + self.dx = dx + + def to_absolute(self, prev): # type: (Vector2d) -> Horz + return Horz(prev.x + self.dx) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> Line + return self.to_line(prev) + + def to_line(self, prev): # type: (Vector2d) -> Line + """Return this path command as a Line instead""" + return Line(prev.x + self.dx, prev.y) + + def reverse(self, first, prev): + return horz(-self.dx) + + +class Vert(AbsolutePathCommand): + """Vertical Line segment""" + + nargs = 1 + + @property + def args(self): + return (self.y,) + + def __init__(self, y): + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox(last_two_points[-1].x, (last_two_points[-1].y, self.y)) + + def transform(self, transform): # type: (Pathlike, Transform) -> Pathlike + raise ValueError("Vertical lines can't be transformed directly.") + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(prev.x, self.y) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> Line + return self.to_line(prev) + + def to_relative(self, prev): + # type: (Vector2d) -> vert + return vert(self.y - prev.y) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(prev.x, self.y) + + def to_line(self, prev): + # type: (Vector2d) -> Line + """Return this path command as a line instead""" + return Line(prev.x, self.y) + + def to_curve( + self, prev, prev_prev=Vector2d() + ): # type: (Vector2d, Optional[Vector2d]) -> Curve + """Convert a horizontal line into a curve""" + return self.to_line(prev).to_curve(prev) + + def reverse(self, first, prev): + return Vert(prev.y) + + +class vert(RelativePathCommand): # pylint: disable=invalid-name + """Relative vertical line segment""" + + nargs = 1 + + @property + def args(self): + return (self.dy,) + + def __init__(self, dy): + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> Vert + return Vert(prev.y + self.dy) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> Line + return self.to_line(prev) + + def to_line(self, prev): # type: (Vector2d) -> Line + """Return this path command as a line instead""" + return Line(prev.x, prev.y + self.dy) + + def reverse(self, first, prev): + return vert(-self.dy) + + +class Curve(AbsolutePathCommand): + """Absolute Curved Line segment""" + + nargs = 6 + + @property + def args(self): + return self.x2, self.y2, self.x3, self.y3, self.x4, self.y4 + + def __init__(self, x2, y2, x3, y3, x4, y4): # pylint: disable=too-many-arguments + self.x2 = x2 + self.y2 = y2 + + self.x3 = x3 + self.y3 = y3 + + self.x4 = x4 + self.y4 = y4 + + def update_bounding_box(self, first, last_two_points, bbox): + x1, x2, x3, x4 = last_two_points[-1].x, self.x2, self.x3, self.x4 + y1, y2, y3, y4 = last_two_points[-1].y, self.y2, self.y3, self.y4 + + if not (x1 in bbox.x and x2 in bbox.x and x3 in bbox.x and x4 in bbox.x): + bbox.x += cubic_extrema(x1, x2, x3, x4) + + if not (y1 in bbox.y and y2 in bbox.y and y3 in bbox.y and y4 in bbox.y): + bbox.y += cubic_extrema(y1, y2, y3, y4) + + def transform(self, transform): + # type: (Transform) -> Curve + x2, y2 = transform.apply_to_point((self.x2, self.y2)) + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + x4, y4 = transform.apply_to_point((self.x4, self.y4)) + return Curve(x2, y2, x3, y3, x4, y4) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x2, self.y2) + yield Vector2d(self.x3, self.y3) + yield Vector2d(self.x4, self.y4) + + def to_relative(self, prev): # type: (Vector2d) -> curve + return curve( + self.x2 - prev.x, + self.y2 - prev.y, + self.x3 - prev.x, + self.y3 - prev.y, + self.x4 - prev.x, + self.y4 - prev.y, + ) + + def end_point(self, first, prev): + return Vector2d(self.x4, self.y4) + + def to_curve( + self, prev, prev_prev=Vector2d() + ): # type: (Vector2d, Optional[Vector2d]) -> Curve + """No conversion needed, pass-through, returns self""" + return Curve(*self.args) + + def to_bez(self): + """Returns the list of coords for SuperPath""" + return [list(self.args[:2]), list(self.args[2:4]), list(self.args[4:6])] + + def reverse(self, first, prev): + return Curve(self.x3, self.y3, self.x2, self.y2, prev.x, prev.y) + + +class curve(RelativePathCommand): # pylint: disable=invalid-name + """Relative curved line segment""" + + nargs = 6 + + @property + def args(self): + return self.dx2, self.dy2, self.dx3, self.dy3, self.dx4, self.dy4 + + def __init__( + self, dx2, dy2, dx3, dy3, dx4, dy4 + ): # pylint: disable=too-many-arguments + self.dx2 = dx2 + self.dy2 = dy2 + + self.dx3 = dx3 + self.dy3 = dy3 + + self.dx4 = dx4 + self.dy4 = dy4 + + def to_absolute(self, prev): # type: (Vector2d) -> Curve + return Curve( + self.dx2 + prev.x, + self.dy2 + prev.y, + self.dx3 + prev.x, + self.dy3 + prev.y, + self.dx4 + prev.x, + self.dy4 + prev.y, + ) + + def reverse(self, first, prev): + return curve( + -self.dx4 + self.dx3, + -self.dy4 + self.dy3, + -self.dx4 + self.dx2, + -self.dy4 + self.dy2, + -self.dx4, + -self.dy4, + ) + + +class Smooth(AbsolutePathCommand): + """Absolute Smoothed Curved Line segment""" + + nargs = 4 + + @property + def args(self): + return self.x3, self.y3, self.x4, self.y4 + + def __init__(self, x3, y3, x4, y4): + self.x3 = x3 + self.y3 = y3 + + self.x4 = x4 + self.y4 = y4 + + def update_bounding_box(self, first, last_two_points, bbox): + self.to_curve(last_two_points[-1], last_two_points[-2]).update_bounding_box( + first, last_two_points, bbox + ) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + + x1, x2, x3, x4 = prev_prev.x, prev.x, self.x3, self.x4 + y1, y2, y3, y4 = prev_prev.y, prev.y, self.y3, self.y4 + + # infer reflected point + x2 = 2 * x2 - x1 + y2 = 2 * y2 - y1 + + yield Vector2d(x2, y2) + yield Vector2d(x3, y3) + yield Vector2d(x4, y4) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> Curve + return self.to_curve(prev, prev_control) + + def to_relative(self, prev): # type: (Vector2d) -> smooth + return smooth( + self.x3 - prev.x, self.y3 - prev.y, self.x4 - prev.x, self.y4 - prev.y + ) + + def transform(self, transform): + # type: (Transform) -> Smooth + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + x4, y4 = transform.apply_to_point((self.x4, self.y4)) + return Smooth(x3, y3, x4, y4) + + def end_point(self, first, prev): + return Vector2d(self.x4, self.y4) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + """ + Convert this Smooth curve to a regular curve by creating a mirror + set of nodes based on the previous node. Previous should be a curve. + """ + (x2, y2), (x3, y3), (x4, y4) = self.control_points(prev, prev, prev_prev) + return Curve(x2, y2, x3, y3, x4, y4) + + def reverse(self, first, prev): + return Smooth(self.x3, self.y3, prev.x, prev.y) + + +class smooth(RelativePathCommand): # pylint: disable=invalid-name + """Relative smoothed curved line segment""" + + nargs = 4 + + @property + def args(self): + return self.dx3, self.dy3, self.dx4, self.dy4 + + def __init__(self, dx3, dy3, dx4, dy4): + self.dx3 = dx3 + self.dy3 = dy3 + + self.dx4 = dx4 + self.dy4 = dy4 + + def to_absolute(self, prev): # type: (Vector2d) -> Smooth + return Smooth( + self.dx3 + prev.x, self.dy3 + prev.y, self.dx4 + prev.x, self.dy4 + prev.y + ) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> Curve + return self.to_absolute(prev).to_non_shorthand(prev, prev_control) + + def reverse(self, first, prev): + return smooth(-self.dx4 + self.dx3, -self.dy4 + self.dy3, -self.dx4, -self.dy4) + + +class Quadratic(AbsolutePathCommand): + """Absolute Quadratic Curved Line segment""" + + nargs = 4 + + @property + def args(self): + return self.x2, self.y2, self.x3, self.y3 + + def __init__(self, x2, y2, x3, y3): + self.x2 = x2 + self.y2 = y2 + + self.x3 = x3 + self.y3 = y3 + + def update_bounding_box(self, first, last_two_points, bbox): + x1, x2, x3 = last_two_points[-1].x, self.x2, self.x3 + y1, y2, y3 = last_two_points[-1].y, self.y2, self.y3 + + if not (x1 in bbox.x and x2 in bbox.x and x3 in bbox.x): + bbox.x += quadratic_extrema(x1, x2, x3) + + if not (y1 in bbox.y and y2 in bbox.y and y3 in bbox.y): + bbox.y += quadratic_extrema(y1, y2, y3) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x2, self.y2) + yield Vector2d(self.x3, self.y3) + + def to_relative(self, prev): + # type: (Vector2d) -> quadratic + return quadratic( + self.x2 - prev.x, self.y2 - prev.y, self.x3 - prev.x, self.y3 - prev.y + ) + + def transform(self, transform): + # type: (Transform) -> Quadratic + x2, y2 = transform.apply_to_point((self.x2, self.y2)) + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + return Quadratic(x2, y2, x3, y3) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x3, self.y3) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + """Attempt to convert a quadratic to a curve""" + prev = Vector2d(prev) + x1 = 1.0 / 3 * prev.x + 2.0 / 3 * self.x2 + x2 = 2.0 / 3 * self.x2 + 1.0 / 3 * self.x3 + y1 = 1.0 / 3 * prev.y + 2.0 / 3 * self.y2 + y2 = 2.0 / 3 * self.y2 + 1.0 / 3 * self.y3 + return Curve(x1, y1, x2, y2, self.x3, self.y3) + + def reverse(self, first, prev): + return Quadratic(self.x2, self.y2, prev.x, prev.y) + + +class quadratic(RelativePathCommand): # pylint: disable=invalid-name + """Relative quadratic line segment""" + + nargs = 4 + + @property + def args(self): + return self.dx2, self.dy2, self.dx3, self.dy3 + + def __init__(self, dx2, dy2, dx3, dy3): + self.dx2 = dx2 + self.dx3 = dx3 + self.dy2 = dy2 + self.dy3 = dy3 + + def to_absolute(self, prev): # type: (Vector2d) -> Quadratic + return Quadratic( + self.dx2 + prev.x, self.dy2 + prev.y, self.dx3 + prev.x, self.dy3 + prev.y + ) + + def reverse(self, first, prev): + return quadratic( + -self.dx3 + self.dx2, -self.dy3 + self.dy2, -self.dx3, -self.dy3 + ) + + +class TepidQuadratic(AbsolutePathCommand): + """Continued Quadratic Line segment""" + + nargs = 2 + + @property + def args(self): + return self.x3, self.y3 + + def __init__(self, x3, y3): + self.x3 = x3 + self.y3 = y3 + + def update_bounding_box(self, first, last_two_points, bbox): + self.to_quadratic(last_two_points[-1], last_two_points[-2]).update_bounding_box( + first, last_two_points, bbox + ) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + + x1, x2, x3 = prev_prev.x, prev.x, self.x3 + y1, y2, y3 = prev_prev.y, prev.y, self.y3 + + # infer reflected point + x2 = 2 * x2 - x1 + y2 = 2 * y2 - y1 + + yield Vector2d(x2, y2) + yield Vector2d(x3, y3) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> AbsolutePathCommand + return self.to_quadratic(prev, prev_control) + + def to_relative(self, prev): # type: (Vector2d) -> tepidQuadratic + return tepidQuadratic(self.x3 - prev.x, self.y3 - prev.y) + + def transform(self, transform): + # type: (Transform) -> TepidQuadratic + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + return TepidQuadratic(x3, y3) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x3, self.y3) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + return self.to_quadratic(prev, prev_prev).to_curve(prev) + + def to_quadratic(self, prev, prev_prev): + # type: (Vector2d, Vector2d) -> Quadratic + """ + Convert this continued quadratic into a full quadratic + """ + (x2, y2), (x3, y3) = self.control_points(prev, prev, prev_prev) + return Quadratic(x2, y2, x3, y3) + + def reverse(self, first, prev): + return TepidQuadratic(prev.x, prev.y) + + +class tepidQuadratic(RelativePathCommand): # pylint: disable=invalid-name + """Relative continued quadratic line segment""" + + nargs = 2 + + @property + def args(self): + return self.dx3, self.dy3 + + def __init__(self, dx3, dy3): + self.dx3 = dx3 + self.dy3 = dy3 + + def to_absolute(self, prev): + # type: (Vector2d) -> TepidQuadratic + return TepidQuadratic(self.dx3 + prev.x, self.dy3 + prev.y) + + def to_non_shorthand(self, prev, prev_control): + # type: (Vector2d, Vector2d) -> AbsolutePathCommand + return self.to_absolute(prev).to_non_shorthand(prev, prev_control) + + def reverse(self, first, prev): + return tepidQuadratic(-self.dx3, -self.dy3) + + +class Arc(AbsolutePathCommand): + """Special Arc segment""" + + nargs = 7 + + @property + def args(self): + return ( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + self.sweep, + self.x, + self.y, + ) + + def __init__( + self, rx, ry, x_axis_rotation, large_arc, sweep, x, y + ): # pylint: disable=too-many-arguments + self.rx = rx + self.ry = ry + self.x_axis_rotation = x_axis_rotation + self.large_arc = large_arc + self.sweep = sweep + self.x = x + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + prev = last_two_points[-1] + for seg in self.to_curves(prev=prev): + seg.update_bounding_box(first, [None, prev], bbox) + prev = seg.end_point(first, prev) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, self.y) + + def to_curves(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> List[Curve] + """Convert this arc into bezier curves""" + path = CubicSuperPath([arc_to_path(list(prev), self.args)]).to_path( + curves_only=True + ) + # Ignore the first move command from to_path() + return list(path)[1:] + + def transform(self, transform): + # type: (Transform) -> Arc + # pylint: disable=invalid-name, too-many-locals + x_, y_ = transform.apply_to_point((self.x, self.y)) + + T = transform # type: Transform + if self.x_axis_rotation != 0: + T = T @ Transform(rotate=self.x_axis_rotation) + a, c, b, d, _, _ = list(T.to_hexad()) + # T = | a b | + # | c d | + + detT = a * d - b * c + detT2 = detT**2 + + rx = float(self.rx) + ry = float(self.ry) + + if rx == 0.0 or ry == 0.0 or detT2 == 0.0: + # invalid Arc parameters + # transform only last point + return Arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + self.sweep, + x_, + y_, + ) + + A = (d**2 / rx**2 + c**2 / ry**2) / detT2 + B = -(d * b / rx**2 + c * a / ry**2) / detT2 + D = (b**2 / rx**2 + a**2 / ry**2) / detT2 + + theta = atan2(-2 * B, D - A) / 2 + theta_deg = theta * 180.0 / pi + DA = D - A + l2 = 4 * B**2 + DA**2 + + if l2 == 0: + delta = 0.0 + else: + delta = 0.5 * (-(DA**2) - 4 * B**2) / sqrt(l2) + + half = (A + D) / 2 + + rx_ = 1.0 / sqrt(half + delta) + ry_ = 1.0 / sqrt(half - delta) + + x_, y_ = transform.apply_to_point((self.x, self.y)) + + if detT > 0: + sweep = self.sweep + else: + sweep = 0 if self.sweep > 0 else 1 + + return Arc(rx_, ry_, theta_deg, self.large_arc, sweep, x_, y_) + + def to_relative(self, prev): + # type: (Vector2d) -> arc + return arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + self.sweep, + self.x - prev.x, + self.y - prev.y, + ) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, self.y) + + def reverse(self, first, prev): + return Arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + 1 - self.sweep, + prev.x, + prev.y, + ) + + +class arc(RelativePathCommand): # pylint: disable=invalid-name + """Relative Arc line segment""" + + nargs = 7 + + @property + def args(self): + return ( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + self.sweep, + self.dx, + self.dy, + ) + + def __init__( + self, rx, ry, x_axis_rotation, large_arc, sweep, dx, dy + ): # pylint: disable=too-many-arguments + self.rx = rx + self.ry = ry + self.x_axis_rotation = x_axis_rotation + self.large_arc = large_arc + self.sweep = sweep + self.dx = dx + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> "Arc" + x1, y1 = prev + return Arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + self.sweep, + self.dx + x1, + self.dy + y1, + ) + + def reverse(self, first, prev): + return arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + 1 - self.sweep, + -self.dx, + -self.dy, + ) + + +PathCommand._letter_to_class = { # pylint: disable=protected-access + "M": Move, + "L": Line, + "V": Vert, + "H": Horz, + "A": Arc, + "C": Curve, + "S": Smooth, + "Z": ZoneClose, + "Q": Quadratic, + "T": TepidQuadratic, + "m": move, + "l": line, + "v": vert, + "h": horz, + "a": arc, + "c": curve, + "s": smooth, + "z": zoneClose, + "q": quadratic, + "t": tepidQuadratic, +} + + +class Path(list): + """A list of segment commands which combine to draw a shape""" + + class PathCommandProxy: + """ + A handy class for Path traverse and coordinate access + + Reduces number of arguments in user code compared to bare + :class:`PathCommand` methods + """ + + def __init__( + self, command, first_point, previous_end_point, prev2_control_point + ): + self.command = command # type: PathCommand + self.first_point = first_point # type: Vector2d + self.previous_end_point = previous_end_point # type: Vector2d + self.prev2_control_point = prev2_control_point # type: Vector2d + + @property + def name(self): + """The full name of the segment (i.e. Line, Arc, etc)""" + return self.command.name + + @property + def letter(self): + """The single letter representation of this command (i.e. L, A, etc)""" + return self.command.letter + + @property + def next_command(self): + """The implicit next command.""" + return self.command.next_command + + @property + def is_relative(self): + """Whether the command is defined in relative coordinates, i.e. relative to + the previous endpoint (lower case path command letter)""" + return self.command.is_relative + + @property + def is_absolute(self): + """Whether the command is defined in absolute coordinates (upper case path + command letter)""" + return self.command.is_absolute + + @property + def args(self): + """Returns path command arguments as tuple of floats""" + return self.command.args + + @property + def control_points(self): + """Returns list of path command control points""" + return self.command.control_points( + self.first_point, self.previous_end_point, self.prev2_control_point + ) + + @property + def end_point(self): + """Returns last control point of path command""" + return self.command.end_point(self.first_point, self.previous_end_point) + + def reverse(self): + """Reverse path command""" + return self.command.reverse(self.end_point, self.previous_end_point) + + def to_curve(self): + """Convert command to :py:class:`Curve` + Curve().to_curve() returns a copy + """ + return self.command.to_curve( + self.previous_end_point, self.prev2_control_point + ) + + def to_curves(self): + """Convert command to list of :py:class:`Curve` commands""" + return self.command.to_curves( + self.previous_end_point, self.prev2_control_point + ) + + def to_absolute(self): + """Return relative counterpart for relative commands or copy for absolute""" + return self.command.to_absolute(self.previous_end_point) + + def __str__(self): + return str(self.command) + + def __repr__(self): + return "<" + self.__class__.__name__ + ">" + repr(self.command) + + def __init__(self, path_d=None): + super().__init__() + if isinstance(path_d, str): + # Returns a generator returning PathCommand objects + path_d = self.parse_string(path_d) + elif isinstance(path_d, CubicSuperPath): + path_d = path_d.to_path() + + for item in path_d or (): + if isinstance(item, PathCommand): + self.append(item) + elif isinstance(item, (list, tuple)) and len(item) == 2: + if isinstance(item[1], (list, tuple)): + self.append(PathCommand.letter_to_class(item[0])(*item[1])) + else: + self.append(Line(*item)) + else: + raise TypeError( + f"Bad path type: {type(path_d).__name__}" + f"({type(item).__name__}, ...): {item}" + ) + + @classmethod + def parse_string(cls, path_d): + """Parse a path string and generate segment objects""" + for cmd, numbers in LEX_REX.findall(path_d): + args = list(strargs(numbers)) + cmd = PathCommand.letter_to_class(cmd) + i = 0 + while i < len(args) or cmd.nargs == 0: + if len(args[i : i + cmd.nargs]) != cmd.nargs: + return + seg = cmd(*args[i : i + cmd.nargs]) + i += cmd.nargs + cmd = seg.next_command + yield seg + + def bounding_box(self): + # type: () -> Optional[BoundingBox] + """Return bounding box of the Path""" + if not self: + return None + iterator = self.proxy_iterator() + proxy = next(iterator) + bbox = BoundingBox(proxy.first_point.x, proxy.first_point.y) + try: + while True: + proxy = next(iterator) + proxy.command.update_bounding_box( + proxy.first_point, + [ + proxy.prev2_control_point, + proxy.previous_end_point, + ], + bbox, + ) + except StopIteration: + return bbox + + def append(self, cmd): + """Append a command to this path including any chained commands""" + if isinstance(cmd, list): + self.extend(cmd) + elif isinstance(cmd, PathCommand): + super().append(cmd) + + def translate(self, x, y, inplace=False): # pylint: disable=invalid-name + """Move all coords in this path by the given amount""" + return self.transform(Transform(translate=(x, y)), inplace=inplace) + + def scale(self, x, y, inplace=False): # pylint: disable=invalid-name + """Scale all coords in this path by the given amounts""" + return self.transform(Transform(scale=(x, y)), inplace=inplace) + + def rotate(self, deg, center=None, inplace=False): + """Rotate the path around the given point""" + if center is None: + # Default center is center of bbox + bbox = self.bounding_box() + if bbox: + center = bbox.center + else: + center = Vector2d() + center = Vector2d(center) + return self.transform( + Transform(rotate=(deg, center.x, center.y)), inplace=inplace + ) + + @property + def control_points(self): + """Returns all control points of the Path""" + prev = Vector2d() + prev_prev = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand + cpts = list(seg.control_points(first, prev, prev_prev)) + if isinstance(seg, (zoneClose, ZoneClose, move, Move)): + first = cpts[-1] + for cpt in cpts: + prev_prev = prev + prev = cpt + yield cpt + + @property + def end_points(self): + """Returns all endpoints of all path commands (i.e. the nodes)""" + prev = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand + end_point = seg.end_point(first, prev) + if isinstance(seg, (zoneClose, ZoneClose, move, Move)): + first = end_point + prev = end_point + yield end_point + + def transform(self, transform, inplace=False): + """Convert to new path""" + result = Path() + previous = Vector2d() + previous_new = Vector2d() + start_zone = True + first = Vector2d() + first_new = Vector2d() + + for i, seg in enumerate(self): # type: PathCommand + if start_zone: + first = seg.end_point(first, previous) + + if isinstance(seg, (horz, Horz, Vert, vert)): + seg = seg.to_line(previous) + + if seg.is_relative: + new_seg = ( + seg.to_absolute(previous) + .transform(transform) + .to_relative(previous_new) + ) + else: + new_seg = seg.transform(transform) + + if start_zone: + first_new = new_seg.end_point(first_new, previous_new) + + if inplace: + self[i] = new_seg + else: + result.append(new_seg) + previous = seg.end_point(first, previous) + previous_new = new_seg.end_point(first_new, previous_new) + start_zone = isinstance(seg, (zoneClose, ZoneClose)) + if inplace: + return self + return result + + def reverse(self): + """Returns a reversed path""" + result = Path() + *_, first = self.end_points + closer = None + + # Go through the path in reverse order + for index, prcom in reversed(list(enumerate(self.proxy_iterator()))): + if isinstance(prcom.command, (Move, move, ZoneClose, zoneClose)): + if closer is not None: + if len(result) > 0 and isinstance( + result[-1], (Line, line, Vert, vert, Horz, horz) + ): + result.pop() # We can replace simple lines with Z + result.append(closer) # replace with same type (rel or abs) + if isinstance(prcom.command, (ZoneClose, zoneClose)): + closer = prcom.command + else: + closer = None + + if index == 0: + if prcom.letter == "M": + result.insert(0, Move(first.x, first.y)) + elif prcom.letter == "m": + result.insert(0, move(first.x, first.y)) + else: + result.append(prcom.reverse()) + + return result + + def break_apart(self) -> List[Path]: + """Breaks apart a path into its subpaths + + .. versionadded:: 1.3""" + result = [Path()] + current = result[0] + + for cmnd in self.proxy_iterator(): + if cmnd.letter.lower() == "m": + current = Path() + result.append(current) + current.append(Move(*cmnd.end_point)) + else: + current.append(cmnd.command) + # Remove all subpaths that are empty or only contain move commands + return [ + i + for i in result + if len(i) != 0 and not all(j.letter.lower() == "m" for j in i) + ] + + def close(self): + """Attempt to close the last path segment""" + if self and not isinstance(self[-1], (zoneClose, ZoneClose)): + self.append(ZoneClose()) + + def proxy_iterator(self): + """ + Yields :py:class:`AugmentedPathIterator` + + :rtype: Iterator[ Path.PathCommandProxy ] + """ + + previous = Vector2d() + prev_prev = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand# + if isinstance(seg, (zoneClose, ZoneClose, move, Move)): + first = seg.end_point(first, previous) + yield Path.PathCommandProxy(seg, first, previous, prev_prev) + if isinstance( + seg, + ( + curve, + tepidQuadratic, + quadratic, + smooth, + Curve, + TepidQuadratic, + Quadratic, + Smooth, + ), + ): + prev_prev = list(seg.control_points(first, previous, prev_prev))[-2] + previous = seg.end_point(first, previous) + + def to_absolute(self): + """Convert this path to use only absolute coordinates""" + return self._to_absolute(True) + + def to_non_shorthand(self): + # type: () -> Path + """Convert this path to use only absolute non-shorthand coordinates + + .. versionadded:: 1.1""" + return self._to_absolute(False) + + def _to_absolute(self, shorthand: bool) -> Path: + """Make entire Path absolute. + + Args: + shorthand (bool): If false, then convert all shorthand commands to + non-shorthand. + + Returns: + Path: the input path, converted to absolute coordinates. + """ + + abspath = Path() + + previous = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand + if isinstance(seg, (move, Move)): + first = seg.end_point(first, previous) + + if shorthand: + abspath.append(seg.to_absolute(previous)) + else: + if abspath and isinstance(abspath[-1], (Curve, Quadratic)): + prev_control = list( + abspath[-1].control_points(Vector2d(), Vector2d(), Vector2d()) + )[-2] + else: + prev_control = previous + + abspath.append(seg.to_non_shorthand(previous, prev_control)) + + previous = seg.end_point(first, previous) + + return abspath + + def to_relative(self): + """Convert this path to use only relative coordinates""" + abspath = Path() + + previous = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand + if isinstance(seg, (move, Move)): + first = seg.end_point(first, previous) + + abspath.append(seg.to_relative(previous)) + previous = seg.end_point(first, previous) + + return abspath + + def __str__(self): + return " ".join([str(seg) for seg in self]) + + def __add__(self, other): + acopy = copy.deepcopy(self) + if isinstance(other, str): + other = Path(other) + if isinstance(other, list): + acopy.extend(other) + return acopy + + def to_arrays(self): + """Returns path in format of parsePath output, returning arrays of absolute + command data + + .. deprecated:: 1.0 + This is compatibility function for older API. Should not be used in new code + + """ + return [[seg.letter, list(seg.args)] for seg in self.to_non_shorthand()] + + def to_superpath(self): + """Convert this path into a cubic super path""" + return CubicSuperPath(self) + + def copy(self): + """Make a copy""" + return copy.deepcopy(self) + + +class CubicSuperPath(list): + """ + A conversion of a path into a predictable list of cubic curves which + can be operated on as a list of simplified instructions. + + When converting back into a path, all lines, arcs etc will be converted + to curve instructions. + + Structure is held as [SubPath[(point_a, bezier, point_b), ...]], ...] + """ + + def __init__(self, items): + super().__init__() + self._closed = True + self._prev = Vector2d() + self._prev_prev = Vector2d() + + if isinstance(items, str): + items = Path(items) + + if isinstance(items, Path): + items = items.to_absolute() + + for item in items: + self.append(item) + + def __str__(self): + return str(self.to_path()) + + def append(self, item, force_shift=False): + """Accept multiple different formats for the data + + .. versionchanged:: 1.2 + ``force_shift`` parameter has been added + """ + if isinstance(item, list) and len(item) == 2 and isinstance(item[0], str): + item = PathCommand.letter_to_class(item[0])(*item[1]) + coordinate_shift = True + if isinstance(item, list) and len(item) == 3 and not force_shift: + coordinate_shift = False + is_quadratic = False + if isinstance(item, PathCommand): + if isinstance(item, Move): + if self._closed is False: + super().append([]) + item = [list(item.args), list(item.args), list(item.args)] + elif isinstance(item, ZoneClose) and self and self[-1]: + # This duplicates the first segment to 'close' the path, it's appended + # directly because we don't want to last coord to change for the final + # segment. + self[-1].append( + [self[-1][0][0][:], self[-1][0][1][:], self[-1][0][2][:]] + ) + # Then adds a new subpath for the next shape (if any) + self._closed = True + self._prev.assign(self._first) + return + elif isinstance(item, Arc): + # Arcs are made up of three curves (approximated) + for arc_curve in item.to_curves(self._prev, self._prev_prev): + x2, y2, x3, y3, x4, y4 = arc_curve.args + self.append([[x2, y2], [x3, y3], [x4, y4]], force_shift=True) + self._prev_prev.assign(x3, y3) + return + else: + is_quadratic = isinstance( + item, (Quadratic, TepidQuadratic, quadratic, tepidQuadratic) + ) + if isinstance(item, (Horz, Vert)): + item = item.to_line(self._prev) + prp = self._prev_prev + if is_quadratic: + self._prev_prev = list( + item.control_points(self._first, self._prev, prp) + )[-2:-1][0] + item = item.to_curve(self._prev, prp) + + if isinstance(item, Curve): + # Curves are cut into three tuples for the super path. + item = item.to_bez() + + if not isinstance(item, list): + raise ValueError(f"Unknown super curve item type: {item}") + + if len(item) != 3 or not all(len(bit) == 2 for bit in item): + # The item is already a subpath (usually from some other process) + if len(item[0]) == 3 and all(len(bit) == 2 for bit in item[0]): + super().append(self._clean(item)) + self._prev_prev = Vector2d(self[-1][-1][0]) + self._prev = Vector2d(self[-1][-1][1]) + return + raise ValueError(f"Unknown super curve list format: {item}") + + if self._closed: + # Closed means that the previous segment is closed so we need a new one + # We always append to the last open segment. CSP starts out closed. + self._closed = False + super().append([]) + + if coordinate_shift: + if self[-1]: + # The last tuple is replaced, it's the coords of where the next segment + # will land. + self[-1][-1][-1] = item[0][:] + # The last coord is duplicated, but is expected to be replaced + self[-1].append(item[1:] + copy.deepcopy(item)[-1:]) + else: + # Item is already a csp segment and has already been shifted. + self[-1].append(copy.deepcopy(item)) + + self._prev = Vector2d(self[-1][-1][1]) + if not is_quadratic: + self._prev_prev = Vector2d(self[-1][-1][0]) + + def _clean(self, lst): + """Recursively clean lists so they have the same type""" + if isinstance(lst, (tuple, list)): + return [self._clean(child) for child in lst] + return lst + + @property + def _first(self): + try: + return Vector2d(self[-1][0][0]) + except IndexError: + return Vector2d() + + def to_path(self, curves_only=False, rtol=1e-5, atol=1e-8): + """Convert the super path back to an svg path + + Arguments: see :func:`to_segments` for parameters""" + return Path(list(self.to_segments(curves_only, rtol, atol))) + + def to_segments(self, curves_only=False, rtol=1e-5, atol=1e-8): + """Generate a set of segments for this cubic super path + + Arguments: + curves_only (bool, optional): If False, curves that can be represented + by Lineto / ZoneClose commands, will be. Defaults to False. + rtol (float, optional): relative tolerance, passed to :func:`is_line` and + :func:`inkex.transforms.ImmutableVector2d.is_close` for checking if a + line can be replaced by a ZoneClose command. Defaults to 1e-5. + + .. versionadded:: 1.2 + atol: absolute tolerance, passed to :func:`is_line` and + :func:`inkex.transforms.ImmutableVector2d.is_close`. Defaults to 1e-8. + + .. versionadded:: 1.2""" + for subpath in self: + previous = [] + for segment in subpath: + if not previous: + yield Move(*segment[1][:]) + elif self.is_line(previous, segment, rtol, atol) and not curves_only: + if segment is subpath[-1] and Vector2d(segment[1]).is_close( + subpath[0][1], rtol, atol + ): + yield ZoneClose() + else: + yield Line(*segment[1][:]) + else: + yield Curve(*(previous[2][:] + segment[0][:] + segment[1][:])) + previous = segment + + def transform(self, transform): + """Apply a transformation matrix to this super path""" + return self.to_path().transform(transform).to_superpath() + + @staticmethod + def is_on(pt_a, pt_b, pt_c, tol=1e-8): + """Checks if point pt_a is on the line between points pt_b and pt_c + + .. versionadded:: 1.2""" + return CubicSuperPath.collinear(pt_a, pt_b, pt_c, tol) and ( + CubicSuperPath.within(pt_a[0], pt_b[0], pt_c[0]) + if pt_a[0] != pt_b[0] + else CubicSuperPath.within(pt_a[1], pt_b[1], pt_c[1]) + ) + + @staticmethod + def collinear(pt_a, pt_b, pt_c, tol=1e-8): + """Checks if points pt_a, pt_b, pt_c lie on the same line, + i.e. that the cross product (b-a) x (c-a) < tol + + .. versionadded:: 1.2""" + return ( + abs( + (pt_b[0] - pt_a[0]) * (pt_c[1] - pt_a[1]) + - (pt_c[0] - pt_a[0]) * (pt_b[1] - pt_a[1]) + ) + < tol + ) + + @staticmethod + def within(val_b, val_a, val_c): + """Checks if float val_b is between val_a and val_c + + .. versionadded:: 1.2""" + return val_a <= val_b <= val_c or val_c <= val_b <= val_a + + @staticmethod + def is_line(previous, segment, rtol=1e-5, atol=1e-8): + """Check whether csp segment (two points) can be expressed as a line has retracted handles or the handles + can be retracted without loss of information (i.e. both handles lie on the + line) + + .. versionchanged:: 1.2 + Previously, it was only checked if both control points have retracted + handles. Now it is also checked if the handles can be retracted without + (visible) loss of information (i.e. both handles lie on the line connecting + the nodes). + + Arguments: + previous: first node in superpath notation + segment: second node in superpath notation + rtol (float, optional): relative tolerance, passed to + :func:`inkex.transforms.ImmutableVector2d.is_close` for checking handle + retraction. Defaults to 1e-5. + + .. versionadded:: 1.2 + atol (float, optional): absolute tolerance, passed to + :func:`inkex.transforms.ImmutableVector2d.is_close` for checking handle + retraction and + :func:`inkex.paths.CubicSuperPath.is_on` for checking if all points + (nodes + handles) lie on a line. Defaults to 1e-8. + + .. versionadded:: 1.2 + """ + + retracted = Vector2d(previous[1]).is_close( + previous[2], rtol, atol + ) and Vector2d(segment[0]).is_close(segment[1], rtol, atol) + + if retracted: + return True + + # Can both handles be retracted without loss of information? + # Definitely the case if the handles lie on the same line as the two nodes and + # in the correct order + # E.g. cspbezsplitatlength outputs non-retracted handles when splitting a + # straight line + return CubicSuperPath.is_on( + segment[0], segment[1], previous[2], atol + ) and CubicSuperPath.is_on(previous[2], previous[1], segment[0], atol) + + +def arc_to_path(point, params): + """Approximates an arc with cubic bezier segments. + + Arguments: + point: Starting point (absolute coords) + params: Arcs parameters as per + https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + + Returns a list of triplets of points : + [control_point_before, node, control_point_after] + (first and last returned triplets are [p1, p1, *] and [*, p2, p2]) + """ + + # pylint: disable=invalid-name, too-many-locals + A = point[:] + rx, ry, teta, longflag, sweepflag, x2, y2 = params[:] + teta = teta * pi / 180.0 + B = [x2, y2] + # Degenerate ellipse + if rx == 0 or ry == 0 or A == B: + return [[A[:], A[:], A[:]], [B[:], B[:], B[:]]] + + # turn coordinates so that the ellipse morph into a *unit circle* (not 0-centered) + mat = matprod((rotmat(teta), [[1.0 / rx, 0.0], [0.0, 1.0 / ry]], rotmat(-teta))) + applymat(mat, A) + applymat(mat, B) + + k = [-(B[1] - A[1]), B[0] - A[0]] + d = k[0] * k[0] + k[1] * k[1] + k[0] /= sqrt(d) + k[1] /= sqrt(d) + d = sqrt(max(0, 1 - d / 4.0)) + # k is the unit normal to AB vector, pointing to center O + # d is distance from center to AB segment (distance from O to the midpoint of AB) + # for the last line, remember this is a unit circle, and kd vector is ortogonal to + # AB (Pythagorean thm) + + if longflag == sweepflag: + # top-right ellipse in SVG example + # https://www.w3.org/TR/SVG/images/paths/arcs02.svg + d *= -1 + + O = [(B[0] + A[0]) / 2.0 + d * k[0], (B[1] + A[1]) / 2.0 + d * k[1]] + OA = [A[0] - O[0], A[1] - O[1]] + OB = [B[0] - O[0], B[1] - O[1]] + start = acos(OA[0] / norm(OA)) + if OA[1] < 0: + start *= -1 + end = acos(OB[0] / norm(OB)) + if OB[1] < 0: + end *= -1 + # start and end are the angles from center of the circle to A and to B respectively + + if sweepflag and start > end: + end += 2 * pi + if (not sweepflag) and start < end: + end -= 2 * pi + + NbSectors = int(abs(start - end) * 2 / pi) + 1 + dTeta = (end - start) / NbSectors + v = 4 * tan(dTeta / 4.0) / 3.0 + # I would use v = tan(dTeta/2)*4*(sqrt(2)-1)/3 ? + p = [] + for i in range(0, NbSectors + 1, 1): + angle = start + i * dTeta + v1 = [ + O[0] + cos(angle) - (-v) * sin(angle), + O[1] + sin(angle) + (-v) * cos(angle), + ] + pt = [O[0] + cos(angle), O[1] + sin(angle)] + v2 = [O[0] + cos(angle) - v * sin(angle), O[1] + sin(angle) + v * cos(angle)] + p.append([v1, pt, v2]) + p[0][0] = p[0][1][:] + p[-1][2] = p[-1][1][:] + + # go back to the original coordinate system + mat = matprod((rotmat(teta), [[rx, 0], [0, ry]], rotmat(-teta))) + for pts in p: + applymat(mat, pts[0]) + applymat(mat, pts[1]) + applymat(mat, pts[2]) + return p + + +def matprod(mlist): + """Get the product of the mat""" + prod = mlist[0] + for mat in mlist[1:]: + a00 = prod[0][0] * mat[0][0] + prod[0][1] * mat[1][0] + a01 = prod[0][0] * mat[0][1] + prod[0][1] * mat[1][1] + a10 = prod[1][0] * mat[0][0] + prod[1][1] * mat[1][0] + a11 = prod[1][0] * mat[0][1] + prod[1][1] * mat[1][1] + prod = [[a00, a01], [a10, a11]] + return prod + + +def rotmat(teta): + """Rotate the mat""" + return [[cos(teta), -sin(teta)], [sin(teta), cos(teta)]] + + +def applymat(mat, point): + """Apply the given mat""" + x = mat[0][0] * point[0] + mat[0][1] * point[1] + y = mat[1][0] * point[0] + mat[1][1] * point[1] + point[0] = x + point[1] = y + + +def norm(point): + """Normalise""" + return sqrt(point[0] * point[0] + point[1] * point[1]) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/ports.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/ports.py new file mode 100644 index 0000000..7595231 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/ports.py @@ -0,0 +1,105 @@ +# coding=utf-8 +# +# Copyright (C) 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Common access to serial and other computer ports. +""" + +import os +import sys +import time +from .utils import DependencyError, AbortExtension + +try: + import serial + from serial.tools import list_ports +except ImportError: + serial = None + + +class Serial: + """ + Attempt to get access to the computer's serial port. + + with Serial(port_name, ...) as com: + com.write(...) + + Provides access to the debug/testing ports which are pretend ports + able to accept the same input but allow for debugging. + """ + + def __init__(self, port, baud=9600, timeout=0.1, **options): + self.test = port == "[test]" + if self.test: + import pty # This does not work on windows #pylint: disable=import-outside-toplevel + + self.controller, self.peripheral = pty.openpty() + port = os.ttyname(self.peripheral) + + self.has_serial() + self.com = serial.Serial() + self.com.port = port + self.com.baudrate = int(baud) + self.com.timeout = timeout + self.set_options(**options) + + def set_options(self, stop=1, size=8, flow=None, parity=None): + """Set further options on the serial port""" + size = {5: "five", 6: "six", 7: "seven", 8: "eight"}.get(size, size) + stop = {"onepointfive": 1.5}.get(stop.lower(), stop) + stop = {1: "one", 1.5: "one_point_five", 2: "two"}.get(stop, stop) + self.com.bytesize = getattr(serial, str(str(size).upper()) + "BITS") + self.com.stopbits = getattr(serial, "STOPBITS_" + str(stop).upper()) + self.com.parity = getattr(serial, "PARITY_" + str(parity).upper()) + # set flow control + self.com.xonxoff = flow == "xonxoff" + self.com.rtscts = flow in ("rtscts", "dsrdtrrtscts") + self.com.dsrdtr = flow == "dsrdtrrtscts" + + def __enter__(self): + try: + # try to establish connection + self.com.open() + except serial.SerialException as error: + raise AbortExtension( + "Could not open serial port. Please check your device" + " is running, connected and the settings are correct" + ) from error + return self.com + + def __exit__(self, exc, value, traceback): + if not traceback and self.test: + output = " " * 1024 + while len(output) == 1024: + time.sleep(0.01) + output = os.read(self.controller, 1024) + sys.stderr.write(output.decode("utf8")) + # self.com.read(2) + self.com.close() + + @staticmethod + def has_serial(): + """Late importing of pySerial module""" + if serial is None: + raise DependencyError("pySerial is required to open serial ports.") + + @staticmethod + def list_ports(): + """Return a list of available serial ports""" + Serial.has_serial() # Cause DependencyError error + return [hw.name for hw in list_ports.comports(True)] diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/properties.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/properties.py new file mode 100644 index 0000000..0ec0ab6 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/properties.py @@ -0,0 +1,850 @@ +# coding=utf-8 +# +# Copyright (C) 2021 Jonathan Neuhauser, jonathan.neuhauser@outlook.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Property management and parsing, CSS cascading, default value storage + +.. versionadded:: 1.2 + +.. data:: all_properties + + A list of all properties, their parser class, and additional information + such as whether they are inheritable or can be given as presentation attributes +""" + +from abc import ABC, abstractmethod + +import re +from typing import Tuple, Dict, Type, Union, List, Optional +from .interfaces.IElement import IBaseElement, ISVGDocumentElement + +from .units import parse_unit, convert_unit + +from .colors import Color, ColorError + + +class BaseStyleValue: + """A class encapsuling a single CSS declaration / presentation_attribute""" + + def __init__(self, declaration=None, attr_name=None, value=None, important=False): + self.attr_name: str + self.value: str + self.important: bool + if declaration is not None and ":" in declaration: + ( + self.attr_name, + self.value, + self.important, + ) = BaseStyleValue.parse_declaration(declaration) + elif attr_name is not None: + self.attr_name = attr_name.strip().lower() + if isinstance(value, str): + self.value = value.strip() + else: + # maybe its already parsed? then set it + self.value = self.unparse_value(value) + self.important = important + _ = self.parse_value() # check that we can parse this value + + @classmethod + def parse_declaration(cls, declaration: str) -> Tuple[str, str, bool]: + """Parse a single css declaration + + Args: + declaration (str): a css declaration such as: + ``fill: #000 !important;``. The trailing semicolon may be ommitted. + + Raises: + ValueError: Unable to parse the declaration + + Returns: + Tuple[str, str, bool]: a tuple with key, value and importance + """ + if declaration != "" and ":" in declaration: + declaration = declaration.replace(";", "") + (name, value) = declaration.split(":", 1) + # check whether this is an important declaration + important = False + if "!important" in value: + value = value.replace("!important", "") + important = True + return (name.strip().lower(), value.strip(), important) + raise ValueError("Invalid declaration") + + def parse_value(self, element=None): + """Get parsed property value with resolved urls, color, etc. + + Args: + element (BaseElement): the SVG element to which this style is applied to + currently used for resolving gradients / masks, could be used for + computing percentage attributes or calc() attributes [optional] + + Returns: + object: parsed property value + """ + if self.value == "inherit": + if self.attr_name in all_properties: + return self._parse_value(all_properties[self.attr_name][1], element) + return None + return self._parse_value(self.value, element) + + def _parse_value( # pylint: disable=unused-argument, no-self-use + self, value: str, element=None + ) -> object: + """internal parse method, to be overwritten by derived classes + + Args: + value (str): unparsed value + element (BaseElement): the SVG element to which this style is applied to + [optional] + + Returns: + object: the parsed value + """ + return value + + def unparse_value(self, value: object) -> str: + """ "Unparses" an object, i.e. converts an object back to an attribute. + If the result is invalid, i.e. can't be parsed, an exception is raised. + + Args: + value (object): the object to be unparsed + + Returns: + str: the attribute value + """ + result = self._unparse_value(value) + self._parse_value(result) # check if value can be parsed (value is valid) + return result + + def _unparse_value(self, value: object) -> str: # pylint: disable=no-self-use + return str(value) + + @property + def declaration(self) -> str: + """The css declaration corresponding to the StyleValue object + + Returns: + str: the css declaration, such as "fill: #000 !important;" + """ + return ( + self.attr_name + + ":" + + self.value + + (" !important" if self.important else "") + ) + + @classmethod + def factory( + cls, + declaration: Optional[str] = None, + attr_name: Optional[str] = None, + value: Optional[object] = None, + important: Optional[bool] = False, + ): + """Create an attribute + + Args: + declaration (str, optional): the CSS declaration to parse. Defaults to None. + attr_name (str, optional): the attribute name. Defaults to None. + value (object, optional): the attribute value. Defaults to None. + important (bool, optional): whether the attribute is marked !important. + Defaults to False. + + Raises: + Errors may also be raised on parsing, so make sure to handle them + + Returns: + BaseStyleValue: the parsed style + """ + if declaration is not None and ":" in declaration: + attr_name, value, important = BaseStyleValue.parse_declaration(declaration) + elif attr_name is not None and value is not None: + attr_name = attr_name.strip().lower() + if isinstance(value, str): + value = value.strip() + + if attr_name in all_properties: + valuetype = all_properties[attr_name][0] + return valuetype(declaration, attr_name, value, important) + return BaseStyleValue(declaration, attr_name, value, important) + + def __eq__(self, other): + if not (isinstance(other, BaseStyleValue)): + return False + if self.declaration != other.declaration: + return False + return True + + @staticmethod + def factory_errorhandled(element=None, declaration="", key="", value=""): + """Error handling for the factory method: if something goes wrong during + parsing, ignore the attribute + + Args: + element (BaseElement, optional): The element this declaration is affecting, + for finding gradients ect. Defaults to None. + declaration (str, optional): the CSS declaration to parse. Defaults to "". + key (str, optional): the attribute name. Defaults to "". + value (str, optional): the attribute value. Defaults to "". + + Returns: + BaseStyleValue: The parsed style + """ + try: + value = BaseStyleValue.factory( + declaration=declaration, attr_name=key, value=value + ) + key = value.attr_name + # Try to parse the attribute + _ = value.parse_value(element) + return (key, value) + except ValueError: + # something went wrong during parsing, e.g. a bad attribute format + # or an attribute referencing a missing gradient + # -> ignore this declaration + pass + except ColorError: + # The color parsing methods have their own error type + pass + + +class AlphaValue(BaseStyleValue): + """Stores an alpha value (such as opacity), which may be specified as + as percentage or absolute value. + + Reference: https://www.w3.org/TR/css-color/#typedef-alpha-value""" + + def _parse_value(self, value: str, element=None): + if value[-1] == "%": # percentage + parsed_value = float(value[:-1]) * 0.01 + else: + parsed_value = float(value) + if parsed_value < 0: + return 0 + if parsed_value > 1: + return 1 + return parsed_value + + def _unparse_value(self, value: object) -> str: + if isinstance(value, (float, int)): + if value < 0: + return "0" + if value > 1: + return "1" + return str(value) + raise ValueError("Value must be number") + + +class ColorValue(BaseStyleValue): + """Stores a color value + + Reference: https://drafts.csswg.org/css-color-3/#valuea-def-color""" + + def _parse_value(self, value: str, element=None): + if value == "currentColor": + if element is not None: + style = element.specified_style() + return style("color") + return None + return Color(value) + + +# https://www.w3.org/TR/css3-values/#url-value +# matches anything inside url() enclosed with single/double quotes +# (technically a fragment url) or no quotes at all +URLREGEX = r"url\(\s*('.*?'|\".*?\"|[^\"'].*?)\s*\)" + + +def match_url_and_return_element(string: str, svg): + """Parses a string containing an url, e.g. "url(#rect1234)", + looks up the element in the svg document and returns it. + + Args: + string (str): the string to parse + svg (SvgDocumentElement): document referenced in the URL + + Raises: + ValueError: if the string has invalid format + ValueError: if the referenced element is not found + + Returns: + BaseElement: the referenced element + """ + regex = re.compile(URLREGEX) + match = regex.match(string) + if match: + url = match.group(1) + paint_server = svg.getElementById(url) + return paint_server + raise ValueError("invalid URL format") + + +class URLNoneValue(BaseStyleValue): + """Stores a value that is either none or an url, such as markers or masks. + + Reference: https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties""" + + def _parse_value(self, value: str, element=None): + if value == "none": + return None + if value[0:4] == "url(": + if element is not None and self.element_has_root(element): + return match_url_and_return_element(value, element.root) + return None + raise ValueError("Invalid property value") + + def _unparse_value(self, value: object): + if isinstance(value, IBaseElement): + return f"url(#{value.get_id()})" + return super()._unparse_value(value) + + @staticmethod + def element_has_root(element) -> bool: + "Checks if an element has a root, i.e. if element.root will fail" + + return not ( + element.getparent() is None and not isinstance(element, ISVGDocumentElement) + ) + + +class PaintValue(ColorValue, URLNoneValue): + """Stores a paint value (such as fill and stroke), which may be specified + as color, or url. + + Reference: https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint""" + + def _parse_value(self, value: str, element=None): + if value == "none": + return None + if value in ["context-fill", "context-stroke"]: + return value + if value == "currentColor": + return super()._parse_value(value, element) + if value[0:4] == "url(": + # First part: fragment url + # second part: a fallback color if the url is not found. Colors start with + # a letter or a # + regex = re.compile(URLREGEX + r"\s*([#\w].*?)?$") + match = regex.match(value) + if match: + url = match.group(1) + if element is not None and self.element_has_root(element): + paint_server = element.root.getElementById(url) + else: + return None + if paint_server is not None: + return paint_server + if match.group(2) is None: + raise ValueError("Paint server not found") + return Color(match.group(2)) + return Color(value) + + def _unparse_value(self, value: object): + if value is None: + return "none" + return super()._unparse_value(value) + + +class EnumValue(BaseStyleValue): + """Stores a value that can only have a finite set of options""" + + def __init__(self, declaration=None, attr_name=None, value=None, important=False): + self.valueset = all_properties[attr_name][4] + super().__init__(declaration, attr_name, value, important) + + def _parse_value(self, value: str, element=None): + if value in self.valueset: + return value + raise ValueError( + f"Value '{value}' is invalid for the property {self.attr_name}. " + + f"Allowed values are: {self.valueset + ['inherit']}" + ) + + +class ShorthandValue(BaseStyleValue, ABC): + """Stores a value that sets other values (e.g. the font shorthand)""" + + def apply_shorthand(self, style): + """Applies a shorthand attribute to its style, respecting the + importance state of the individual attributes. + + Args: + style (Style): the style that the shorthand attribute is contained in, + and that the shorthand attribute will be applied on + """ + if self.attr_name not in style: + return + + dct = self.get_shorthand_changes() + importance = self.important + + # they are ordered in the order of adding the style elements. + current_keys = list(style.keys()) + for curkey in dct: + perform = False + if curkey not in current_keys: + # this is the easiest case, just set the element and the importance + perform = True + else: + if importance != style.get_importance(curkey): + # different importance, result independent of position + perform = importance + else: + # only apply if style overwrites previous with same importance + perform = current_keys.index(curkey) < current_keys.index( + self.attr_name + ) + + if perform: + style[curkey] = dct[curkey] + style.set_importance(curkey, importance) + style.pop(self.attr_name) + + @abstractmethod + def get_shorthand_changes(self) -> Dict[str, str]: + """calculates the value of affected attributes for this shorthand + + Returns: + Dict[str, str]: a dictionary containing the new values of the + affected attributes + """ + + +class FontValue(ShorthandValue): + """Logic for the shorthand font property""" + + def get_shorthand_changes(self): + keys = [ + "font-style", + "font-variant", + "font-weight", + "font-stretch", + "font-size", + "line-height", + "font-family", + ] + options = { + key: all_properties[key][4] + for key in keys + if isinstance(all_properties[key][4], list) + } + # Font stretch can be specified in percent, but for the + # shorthand, only a keyword value is allowed + options["font-stretch"] = ( + "normal", + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded", + ) + result = {key: all_properties[key][1] for key in keys} + + tokens = [i for i in self.value.split() if i != ""] + + if len(tokens) == 0: + return {} # shorthand not set, nothing to do + + while not (len(tokens) == 0): + cur = tokens[0] + if cur in options["font-style"]: + result["font-style"] = cur + elif cur in options["font-variant"]: + result["font-variant"] = cur + elif cur in options["font-weight"]: + result["font-weight"] = cur + elif cur in options["font-stretch"]: + result["font-stretch"] = cur + else: + if "/" in cur: + result["font-size"], result["line-height"] = cur.split("/") + else: + result["font-size"] = cur + result["font-family"] = " ".join(tokens[1:]) + break + tokens = tokens[1:] # remove first element + return result + + +class TextDecorationValue(ShorthandValue): + """Logic for the shorthand font property + + .. versionadded:: 1.3""" + + def get_shorthand_changes(self): + options = { + "text-decoration-" + key: all_properties["text-decoration-" + key][4] + for key in ("line", "style", "color") + if isinstance(all_properties["text-decoration-" + key][4], list) + } + result = { + "text-decoration-style": all_properties["text-decoration-style"][1], + "text-decoration-color": "currentcolor", + "text-decoration-line": [], + } + + tokens = [i for i in self.value.split() if i != ""] + + if len(tokens) == 0: + return {} # shorthand not set, nothing to do + + for cur in tokens: + if cur in ["underline", "overline", "line-through", "blink"]: + result["text-decoration-line"] += [cur] + elif cur in options["text-decoration-style"]: + result["text-decoration-style"] = cur + else: + result["text-decoration-color"] = cur + + if len(result["text-decoration-line"]) == 0: + result["text-decoration-line"] = all_properties["text-decoration-line"][4] + else: + # Text-decoration-line can have multiple values. + result["text-decoration-line"] = " ".join(result["text-decoration-line"]) + + return result + + +class MarkerShorthandValue(ShorthandValue, URLNoneValue): + """Logic for the marker shorthand property""" + + def get_shorthand_changes(self): + if self.value == "": + return {} # shorthand not set, nothing to do + return {k: self.value for k in ["marker-start", "marker-end", "marker-mid"]} + + def _parse_value(self, value: str, element=None): + # Make sure the parsing routine doesn't choke on an empty shorthand + if value == "": + return "" + return super()._parse_value(value, element) + + +class FontSizeValue(BaseStyleValue): + """Logic for the font-size property""" + + def _parse_value(self, value: str, element=None): + if element is None: + return value # no additional logic in this case + try: + return element.to_dimensionless(value) + except ValueError: # unable to parse font size, e.g. font-size:normal + return element.to_dimensionless("12") + + +class StrokeDasharrayValue(BaseStyleValue): + """Logic for the stroke-dasharray property""" + + def _parse_value(self, value: str, element=None): + dashes = re.findall(r"[^,\s]+", value) + if len(dashes) == 0 or value == "none": + return None # no dasharray applied + if not any(parse_unit(i) is None for i in dashes): + dashes = [convert_unit(i, "px") for i in dashes] + else: + return None + if any(i < 0 for i in dashes): + return None # one negative value makes the dasharray invalid + if len(dashes) % 2 == 1: + dashes = 2 * dashes + return dashes + + def _unparse_value(self, value: object) -> str: + if value is None: + return "none" + if isinstance(value, list): + return " ".join(map(str, value)) + return str(value) + + +# keys: attributes, right side: +# - Subclass of BaseStyleValue used for instantiating +# - default value +# - is presentation attribute +# - inherited +# - additional information, such as valid enum values +# For properties which have no special implementation yet: +# "(BaseStyleValue, , , None)" + +# Source for this list: https://www.w3.org/TR/SVG2/styling.html#PresentationAttributes + + +all_properties: Dict[ + str, Tuple[Type[BaseStyleValue], str, bool, bool, Union[List[str], None]] +] = { + "alignment-baseline": ( + EnumValue, + "baseline", + True, + False, + [ + "baseline", + "text-bottom", + "alphabetic", + "ideographic", + "middle", + "central", + "mathematical", + "text-top", + ], + ), + "baseline-shift": (BaseStyleValue, "0", True, False, None), + "clip": (BaseStyleValue, "auto", True, False, None), + "clip-path": (URLNoneValue, "none", True, False, None), + "clip-rule": (EnumValue, "nonzero", True, True, ["nonzero", "evenodd"]), + # only used for currentColor, which is not yet implemented + "color": (PaintValue, "black", True, True, None), + "color-interpolation": ( + EnumValue, + "sRGB", + True, + True, + ["sRGB", "auto", "linearRGB"], + ), + "color-interpolation-filters": ( + EnumValue, + "linearRGB", + True, + True, + ["auto", "sRGB", "linearRGB"], + ), + "color-rendering": ( + EnumValue, + "auto", + True, + True, + ["auto", "optimizeSpeed", "optimizeQuality"], + ), + "cursor": (BaseStyleValue, "auto", True, True, None), + "direction": (EnumValue, "ltr", True, True, ["ltr", "rtl"]), + "display": ( + EnumValue, + "inline", + True, + False, + [ + "inline", + "block", + "list-item", + "inline-block", + "table", + "inline-table", + "table-row-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-column-group", + "table-column", + "table-cell", + "table-caption", + "none", + ], + ), # every value except none is rendered normally + "dominant-baseline": ( + EnumValue, + "auto", + True, + True, + [ + "auto", + "text-bottom", + "alphabetic", + "ideographic", + "middle", + "central", + "mathematical", + "hanging", + "text-top", + ], + ), + "fill": ( + PaintValue, + "black", + True, + True, + None, + ), # the normal fill, not the one + "fill-opacity": (AlphaValue, "1", True, True, None), + "fill-rule": (EnumValue, "nonzero", True, True, ["nonzero", "evenodd"]), + "filter": (URLNoneValue, "none", True, False, None), + "flood-color": (PaintValue, "black", True, False, None), + "flood-opacity": (AlphaValue, "1", True, False, None), + "font": (FontValue, "", True, False, None), + "font-family": (BaseStyleValue, "sans-serif", True, True, None), + "font-size": (FontSizeValue, "medium", True, True, None), + "font-size-adjust": (BaseStyleValue, "none", True, True, None), + "font-stretch": (BaseStyleValue, "normal", True, True, None), + "font-style": (EnumValue, "normal", True, True, ["normal", "italic", "oblique"]), + # a lot more values and subproperties in SVG2 / CSS-Fonts3 + "font-variant": (EnumValue, "normal", True, True, ["normal", "small-caps"]), + "font-weight": ( + EnumValue, + "normal", + True, + True, + ["normal", "bold"] + [str(i) for i in range(100, 901, 100)], + ), + "glyph-orientation-horizontal": (BaseStyleValue, "0deg", True, True, None), + "glyph-orientation-vertical": (BaseStyleValue, "auto", True, True, None), + "inline-size": (BaseStyleValue, "0", False, False, None), + "image-rendering": ( + EnumValue, + "auto", + True, + True, + ["auto", "optimizeQuality", "optimizeSpeed"], + ), + "letter-spacing": (BaseStyleValue, "normal", True, True, None), + "lighting-color": (ColorValue, "normal", True, False, None), + "line-height": (BaseStyleValue, "normal", False, True, None), + "marker": (MarkerShorthandValue, "", True, True, None), + "marker-end": (URLNoneValue, "none", True, True, None), + "marker-mid": (URLNoneValue, "none", True, True, None), + "marker-start": (URLNoneValue, "none", True, True, None), + # is a shorthand for a lot of mask-related properties which Inkscape doesn't support + "mask": (URLNoneValue, "none", True, False, None), + "opacity": (AlphaValue, "1", True, False, None), + "overflow": ( + EnumValue, + "visible", + True, + False, + ["visible", "hidden", "scroll", "auto"], + ), + "paint-order": (BaseStyleValue, "normal", True, False, None), + "pointer-events": ( + EnumValue, + "visiblePainted", + True, + True, + [ + "bounding-box", + "visiblePainted", + "visibleFill", + "visibleStroke", + "visible", + "painted", + "fill", + "stroke", + "all", + "none", + ], + ), + "shape-inside": (URLNoneValue, "none", False, False, None), + "shape-rendering": ( + EnumValue, + "visiblePainted", + True, + True, + ["auto", "optimizeSpeed", "crispEdges", "geometricPrecision"], + ), + "stop-color": (ColorValue, "black", True, False, None), + "stop-opacity": (AlphaValue, "1", True, False, None), + "stroke": (PaintValue, "none", True, True, None), + "stroke-dasharray": (StrokeDasharrayValue, "none", True, True, None), + "stroke-dashoffset": (BaseStyleValue, "0", True, True, None), + "stroke-linecap": (EnumValue, "butt", True, True, ["butt", "round", "square"]), + "stroke-linejoin": ( + EnumValue, + "miter", + True, + True, + ["miter", "miter-clip", "round", "bevel", "arcs"], + ), + "stroke-miterlimit": (BaseStyleValue, "4", True, True, None), + "stroke-opacity": (AlphaValue, "1", True, True, None), + "stroke-width": (BaseStyleValue, "1", True, True, None), + "text-align": ( + BaseStyleValue, + "start", + True, + True, + None, + ), # only HTML property, but used by some unit tests + "text-anchor": (EnumValue, "start", True, True, ["start", "middle", "end"]), + # shorthand for text-decoration-line, *-style, *-color + "text-decoration": (TextDecorationValue, "", True, True, None), + # multiple enum values are allowed + "text-decoration-line": (BaseStyleValue, "none", False, False, None), + "text-decoration-style": ( + EnumValue, + "solid", + False, + False, + ["solid", "double", "dotted", "dashed", "wavy"], + ), + # This currently cannot be a ColorValue because currentcolor and other special + # colors are not supported by the Color class + "text-decoration-color": (BaseStyleValue, "currentcolor", False, False, None), + "text-overflow": (EnumValue, "clip", True, False, ["clip", "ellipsis"]), + "text-rendering": ( + EnumValue, + "auto", + True, + True, + ["auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision"], + ), + "unicode-bidi": ( + EnumValue, + "normal", + True, + False, + [ + "normal", + "embed", + "isolate", + "bidi-override", + "isolate-override", + "plaintext", + ], + ), + "vector-effect": (BaseStyleValue, "none", True, False, None), + "vertical-align": (BaseStyleValue, "baseline", False, False, None), + "visibility": (EnumValue, "visible", True, True, ["visible", "hidden", "collapse"]), + "white-space": ( + EnumValue, + "normal", + True, + True, + ["normal", "pre", "nowrap", "pre-wrap", "break-spaces", "pre-line"], + ), + "word-spacing": (BaseStyleValue, "normal", True, True, None), + # including obsolete SVG 1.1 values + "writing-mode": ( + EnumValue, + "horizontal-tb", + True, + True, + [ + "horizontal-tb", + "vertical-rl", + "vertical-lr", + "lr", + "lr-tb", + "rl", + "rl-tb", + "tb", + "tb-rl", + ], + ), + "-inkscape-font-specification": (BaseStyleValue, "sans-serif", False, True, None), +} diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/styles.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/styles.py new file mode 100644 index 0000000..8f6b04b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/styles.py @@ -0,0 +1,636 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# 2019-2020 Martin Owens +# 2021 Jonathan Neuhauser, jonathan.neuhauser@outlook.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Functions for handling styles and embedded css +""" + +import re +import sys +from collections import OrderedDict +from typing import MutableMapping, Union, Iterable, TYPE_CHECKING + +from .interfaces.IElement import IBaseElement + +from .colors import Color +from .properties import BaseStyleValue, all_properties, ShorthandValue +from .css import ConditionalRule + +from .utils import FragmentError + +if TYPE_CHECKING: + from .elements._svg import SvgDocumentElement + + +class Classes(list): + """A list of classes applied to an element (used in css and js)""" + + def __init__(self, classes=None, callback=None): + self.callback = None + if isinstance(classes, str): + classes = classes.split() + super().__init__(classes or ()) + self.callback = callback + + def __str__(self): + return " ".join(self) + + def _callback(self): + if self.callback is not None: + self.callback(self) + + def __setitem__(self, index, value): + super().__setitem__(index, value) + self._callback() + + def append(self, value): + value = str(value) + if value not in self: + super().append(value) + self._callback() + + def remove(self, value): + value = str(value) + if value in self: + super().remove(value) + self._callback() + + def toggle(self, value): + """If exists, remove it, if not, add it""" + value = str(value) + if value in self: + return self.remove(value) + return self.append(value) + + +class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): + """A list of style directives + + .. versionchanged:: 1.2 + The Style API now allows for access to parsed / processed styles via the + :func:`call` method. + + .. automethod:: __call__ + .. automethod:: __getitem__ + .. automethod:: __setitem__ + """ + + color_props = ("stroke", "fill", "stop-color", "flood-color", "lighting-color") + opacity_props = ("stroke-opacity", "fill-opacity", "opacity", "stop-opacity") + unit_props = "stroke-width" + """Dictionary of attributes with units. + + ..versionadded:: 1.2 + """ + associated_props = { + "fill": "fill-opacity", + "stroke": "stroke-opacity", + "stop-color": "stop-opacity", + } + """Dictionary of association between color and opacity attributes. + + .. versionadded:: 1.2 + """ + + def __init__(self, style=None, callback=None, element=None, **kw): + self.element = element + # This callback is set twice because this is 'pre-initial' data (no callback) + self.callback = None + # Either a string style or kwargs (with dashes as underscores). + style = style or [(k.replace("_", "-"), v) for k, v in kw.items()] + if isinstance(style, str): + style = self._parse_str(style) + # Order raw dictionaries so tests can be made reliable + if isinstance(style, dict) and not isinstance(style, OrderedDict): + style = [(name, style[name]) for name in sorted(style)] + # Should accept dict, Style, parsed string, list etc. + super().__init__(style) + # Now after the initial data, the callback makes sense. + self.callback = callback + + @staticmethod + def _parse_str(style: str, element=None) -> Iterable[BaseStyleValue]: + """Create a dictionary from the value of a CSS rule (such as an inline style or + from an embedded style sheet), including its !important state, parsing the value + if possible. + + Args: + style: the content of a CSS rule to parse + element: the element this style is working on (can be the root SVG, is used + for parsing gradients etc.) + + Yields: + :class:`~inkex.properties.BaseStyleValue`: the parsed attribute + """ + for declaration in style.split(";"): + if ":" in declaration: + result = BaseStyleValue.factory_errorhandled( + element, declaration=declaration.strip() + ) + if result is not None: + yield result + + @staticmethod + def parse_str(style: str, element=None): + """Parse a style passed as string""" + return Style(style, element=element) + + def __str__(self): + """Format an inline style attribute from a dictionary""" + return self.to_str() + + def to_str(self, sep=";"): + """Convert to string using a custom delimiter""" + return sep.join([self.get_store(key).declaration for key in self]) + + def __add__(self, other): + """Add two styles together to get a third, composing them""" + ret = self.copy() + ret.update(Style(other)) + return ret + + def __iadd__(self, other): + """Add style to this style, the same as ``style.update(dict)``""" + self.update(other) + return self + + def __sub__(self, other): + """Remove keys and return copy""" + ret = self.copy() + ret.__isub__(other) + return ret + + def __isub__(self, other): + """Remove keys from this style, list of keys or other style dictionary""" + for key in other: + self.pop(key, None) + return self + + def __ne__(self, other): + return not self.__eq__(other) + + def copy(self): + """Create a copy of the style. + + .. versionadded:: 1.2""" + ret = Style({}, element=self.element) + for key, value in super().items(): + ret[key] = value + return ret + + def update(self, other): + """Update, while respecting ``!important`` declarations.""" + if not isinstance(other, Style): + other = Style(other) + # only update + if isinstance(other, Style): + for key in other.keys(): + if not (self.get_importance(key) and not other.get_importance(key)): + self[key] = other.get_store(key) + + if self.callback is not None: + self.callback(self) + + def add_inherited(self, parent): + """Creates a new Style containing all parent styles with importance "!important" + and current styles with importance "!important" + + .. versionadded:: 1.2 + + Args: + parent: the parent style that will be merged into this one (will not be + altered) + + Returns: + Style: the merged Style object + """ + ret = self.copy() + ret.apply_shorthands() # parent should already have its shortcuts applied + + if not (isinstance(parent, Style)): + return ret + + for key in parent.keys(): + apply = False + if key in all_properties and all_properties[key][3]: + # only set parent value if value is not set or parent importance is + # higher + if key not in ret: + apply = True + elif self.get_importance(key) != parent.get_importance(key): + apply = parent.get_importance(key) + if key in ret and ret[key] == "inherit": + apply = True + if apply: + ret[key] = parent[key] + return ret + + def apply_shorthands(self): + """Apply all shorthands in this style.""" + for element in list(self.values()): + if isinstance(element, ShorthandValue): + element.apply_shorthand(self) + + def __delitem__(self, key): + super().__delitem__(key) + if self.callback is not None: + self.callback(self) + + def pop(self, key, default=None): + super().pop(key, default) + # On Python < 3.11, pop internally calls __delitem__. + # This does not happen in 3.11. To avoid + # calling the callback twice, we need to check the Python version. + if sys.version_info >= (3, 11): + if self.callback is not None: + self.callback(self) + + def __setitem__(self, key, value): + """Sets a style value. + + .. versionchanged:: 1.2 + ``value`` can now also be non-string objects such as a Gradient. + + Args: + key (str): the attribute name + value (Any): + + - a :class:`BaseStyleValue` + - a string with the value + - any other object. The :class:`~inkex.properties.BaseStyleValue` + subclass of the provided key will attempt to create a string out of + the passed value. + Raises: + ValueError: when ``value`` is a :class:`~inkex.properties.BaseStyleValue` + for a different attribute than `key` + Error: Other exceptions may be raised when converting non-string objects.""" + if not isinstance(value, BaseStyleValue) or value is None: + # try to convert the value using the factory + value = BaseStyleValue.factory(attr_name=key, value=value) + # check if the set attribute is valid + _ = value.parse_value(self.element) + elif key != value.attr_name: + raise ValueError( + """You're trying to save a value into a style attribute, but the + provided key is different from the attribute name given in the value""" + ) + super().__setitem__(key, value) + if self.callback is not None: + self.callback(self) + + def __getitem__(self, key): + """Returns the unparsed value of the element (minus a possible ``!important``) + + .. versionchanged:: 1.2 + ``!important`` is removed from the value. + """ + return self.get_store(key).value + + def get(self, key, default=None): + if key in self: + return self.__getitem__(key) + return default + + def get_store(self, key): + """Gets the :class:`~inkex.properties.BaseStyleValue` of this key, since the + other interfaces - :func:`__getitem__` and :func:`__call__` - return the + original and parsed value, respectively. + + .. versionadded:: 1.2 + + Args: + key (str): the attribute name + + Returns: + BaseStyleValue: the BaseStyleValue struct of this attribute + """ + return super().__getitem__(key) + + def __call__(self, key, element=None, default=None): + """Return the parsed value of a style. Optionally, an element can be passed + that will be used to find gradient definitions etc. + + .. versionadded:: 1.2""" + # check if there are shorthand properties defined. If so, apply them to a copy + copy = self + for value in super().values(): + if isinstance(value, ShorthandValue): + copy = self.copy() + copy.apply_shorthands() + if key in copy: + return copy.get_store(key).parse_value(element or self.element) + # style is not set, return the default value + if key in all_properties or default is not None: + defvalue = BaseStyleValue.factory( + attr_name=key, value=default or all_properties[key][1] + ) + return ( + defvalue.parse_value() + ) # default values are independent of the element + raise KeyError("Unknown attribute") + + def __eq__(self, other): + if not isinstance(other, Style): + other = Style(other) + if self.keys() != other.keys(): + return False + for arg in set(self) | set(other): + if self.get_store(arg) != other.get_store(arg): + return False + return True + + def items(self): + """The styles's parsed items + + .. versionadded:: 1.2""" + for key, value in super().items(): + yield key, value.value + + def get_importance(self, key, default=False): + """Returns whether the declaration with ``key`` is marked as ``!important`` + + .. versionadded:: 1.2""" + if key in self: + return super().__getitem__(key).important + return default + + def set_importance(self, key, importance): + """Sets the ``!important`` state of a declaration with key ``key`` + + .. versionadded:: 1.2""" + if key in self: + super().__getitem__(key).important = importance + else: + raise KeyError() + if self.callback is not None: + self.callback(self) + + def get_color(self, name="fill"): + """Get the color AND opacity as one Color object""" + color = Color(self.get(name, "none")) + return color.to_rgba(self.get(name + "-opacity", 1.0)) + + def set_color(self, color, name="fill"): + """Sets the given color AND opacity as rgba to the fill or stroke style + properties.""" + color = Color(color) + if color.space == "rgba" and name in Style.associated_props: + self[Style.associated_props[name]] = color.alpha + self[name] = color.to_rgb() + else: + self[name] = color + + def update_urls(self, old_id, new_id): + """Find urls in this style and replace them with the new id""" + for name, value in self.items(): + if value == f"url(#{old_id})": + self[name] = f"url(#{new_id})" + + def interpolate(self, other, fraction): + # type: (Style, Style, float) -> Style + """Interpolate all properties. + + .. versionadded:: 1.1""" + from .tween import StyleInterpolator + from inkex.elements import PathElement + + if self.element is None: + self.element = PathElement(style=str(self)) + if other.element is None: + other.element = PathElement(style=str(other)) + return StyleInterpolator(self.element, other.element).interpolate(fraction) + + @classmethod + def cascaded_style(cls, element): + """Returns the cascaded style of an element (all rules that apply the element + itself), based on the stylesheets, the presentation attributes and the inline + style using the respective specificity of the style + + see https://www.w3.org/TR/CSS22/cascade.html#cascading-order + + .. versionadded:: 1.2 + + Args: + element (BaseElement): the element that the cascaded style will be + computed for + + Returns: + Style: the cascaded style + """ + try: + styles = list(element.root.stylesheets.lookup_specificity(element.get_id())) + except FragmentError: + styles = [] + + # presentation attributes have specificity 0, + # see https://www.w3.org/TR/SVG/styling.html#PresentationAttributes + styles.append([element.presentation_style(), (0, 0, 0)]) + + # would be (1, 0, 0, 0), but then we'd have to extend every entry + styles.append([element.style, (float("inf"), 0, 0)]) + + # sort styles by specificity (ascending, so when overwriting it's correct) + styles = sorted(styles, key=lambda item: item[1]) + + result = styles[0][0].copy() + for style, _ in styles[1:]: + result.update(style) + result.element = element + return result + + @classmethod + def specified_style(cls, element): + """Returns the specified style of an element, i.e. the cascaded style + + inheritance, see https://www.w3.org/TR/CSS22/cascade.html#specified-value + + .. versionadded:: 1.2 + + Args: + element (BaseElement): the element that the specified style will be computed + for + + Returns: + Style: the specified style + """ + + # We currently dont treat the case where parent=absolute value and + # element=relative value, i.e. specified = relative * absolute. + cascaded = Style.cascaded_style(element) + + parent = element.getparent() + + if parent is not None and isinstance(parent, IBaseElement): + cascaded = Style.add_inherited(cascaded, parent.specified_style()) + cascaded.element = element + return cascaded # doesn't have a parent + + +class StyleSheets(list): + """ + Special mechanism which contains all the stylesheets for an svg document + while also caching lookups for specific elements. + + This caching is needed because data can't be attached to elements as they are + re-created on the fly by lxml so lookups have to be centralised. + """ + + def __init__(self, svg=None): + super().__init__() + self.svg = svg + + def lookup(self, element_id, svg=None): + """ + Find all styles for this element. + """ + # This is aweful, but required because we can't know for sure + # what might have changed in the xml tree. + if svg is None: + svg = self.svg + for sheet in self: + for style in sheet.lookup(element_id, svg=svg): + yield style + + def lookup_specificity(self, element_id, svg=None): + """ + Find all styles for this element and return the specificity of the match. + + .. versionadded:: 1.2 + """ + # This is aweful, but required because we can't know for sure + # what might have changed in the xml tree. + if svg is None: + svg = self.svg + for sheet in self: + for style in sheet.lookup_specificity(element_id, svg=svg): + yield style + + +class StyleSheet(list): + """ + A style sheet, usually the CDATA contents of a style tag, but also + a css file used with a css. Will yield multiple Style() classes. + """ + + comment_strip = re.compile(r"(\/\/.*?\n)|(\/\*.*?\*\/|@import .*;)") + + def __init__(self, content=None, callback=None): + super().__init__() + self.callback = None + # Remove comments + content = self.comment_strip.sub("", (content or "")) + # Parse rules + for block in content.split("}"): + if block: + self.append(block) + self.callback = callback + + def __str__(self): + return "\n" + "\n".join([str(style) for style in self]) + "\n" + + def _callback(self, style=None): # pylint: disable=unused-argument + if self.callback is not None: + self.callback(self) + + def add(self, rule, style): + """Append a rule and style combo to this stylesheet""" + self.append( + ConditionalStyle(rules=rule, style=str(style), callback=self._callback) + ) + + def append(self, other): + """Make sure callback is called when updating""" + if isinstance(other, str): + if "{" not in other: + return # Warning? + rules, style = other.strip("}").split("{", 1) + if rules.strip().startswith("@"): # ignore @font-face and @import + return + other = ConditionalStyle( + rules=rules, style=style.strip(), callback=self._callback + ) + super().append(other) + self._callback() + + def lookup(self, element_id, svg): + """Lookup the element_id against all the styles in this sheet""" + for style in self: + for elem in svg.xpath(style.to_xpath()): + if elem.get("id", None) == element_id: + yield style + + def lookup_specificity(self, element_id, svg): + """Lookup the element_id against all the styles in this sheet + and return the specificity of the match + + Args: + element_id (str): the id of the element that styles are being queried for + svg (SvgDocumentElement): The document that contains both element and the + styles + + Yields: + Tuple[ConditionalStyle, Tuple[int, int, int]]: all matched styles and the + specificity of the match + """ + for style in self: + for rule, spec in zip(style.to_xpaths(), style.get_specificities()): + for elem in svg.xpath(rule): + if elem.get("id", None) == element_id: + yield (style, spec) + + +class ConditionalStyle(Style): + """ + Just like a Style object, but includes one or more + conditional rules which places this style in a stylesheet + rather than being an attribute style. + """ + + def __init__(self, rules="*", style=None, callback=None, **kwargs): + super().__init__(style=style, callback=callback, **kwargs) + self.rules = [ConditionalRule(rule) for rule in rules.split(",")] + + def __str__(self): + """Return this style as a css entry with class""" + content = self.to_str(";\n ") + rules = ",\n".join(str(rule) for rule in self.rules) + if content: + return f"{rules} {{\n {content};\n}}" + return f"{rules} {{}}" + + def to_xpath(self): + """Convert all rules to an xpath""" + # This can be converted to cssselect.CSSSelector (lxml.cssselect) later if we + # have coverage problems. The main reason we're not is that cssselect is doing + # exactly this xpath transform and provides no extra functionality for reverse + # lookups. + return "|".join(self.to_xpaths()) + + def to_xpaths(self): + """Gets a list of xpaths for all rules of this ConditionalStyle + + .. versionadded:: 1.2""" + return [rule.to_xpath() for rule in self.rules] + + def get_specificities(self): + """Gets an iterator of the specificity of all rules in this ConditionalStyle + + .. versionadded:: 1.2""" + for rule in self.rules: + yield rule.get_specificity() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/__init__.py new file mode 100644 index 0000000..4c3d4fa --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/__init__.py @@ -0,0 +1,454 @@ +# coding=utf-8 +# +# Copyright (C) 2018-2019 Martin Owens +# 2019 Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Testing module. See :ref:`unittests` for details. +""" + +import os +import re +import sys +import shutil +import tempfile +import hashlib +import random +import uuid +import io +from typing import List, Union, Tuple, Type, TYPE_CHECKING + +from io import BytesIO, StringIO +import xml.etree.ElementTree as xml + +from unittest import TestCase as BaseCase +from inkex.base import InkscapeExtension + +from .. import Transform, load_svg, SvgDocumentElement +from ..utils import to_bytes +from .xmldiff import xmldiff +from .mock import MockCommandMixin, Capture + +if TYPE_CHECKING: + from .filters import Compare + +COMPARE_DELETE, COMPARE_CHECK, COMPARE_WRITE, COMPARE_OVERWRITE = range(4) + + +class NoExtension(InkscapeExtension): # pylint: disable=too-few-public-methods + """Test case must specify 'self.effect_class' to assertEffect.""" + + def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called + raise NotImplementedError(self.__doc__) + + def run(self, args=None, output=None): + """Fake run""" + + +class TestCase(MockCommandMixin, BaseCase): + """ + Base class for all effects tests, provides access to data_files and + test_without_parameters + """ + + effect_class = NoExtension # type: Type[InkscapeExtension] + effect_name = property(lambda self: self.effect_class.__module__) + + # If set to true, the output is not expected to be the stdout SVG document, but + # rather text or a message sent to the stderr, this is highly weird. But sometimes + # happens. + stderr_output = False + stdout_protect = True + stderr_protect = True + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self._temp_dir = None + self._effect = None + + def setUp(self): # pylint: disable=invalid-name + """Make sure every test is seeded the same way""" + self._effect = None + super().setUp() + random.seed(0x35F) + + def tearDown(self): + super().tearDown() + if self._temp_dir and os.path.isdir(self._temp_dir): + shutil.rmtree(self._temp_dir) + + @classmethod + def __file__(cls): + """Create a __file__ property which acts much like the module version""" + return os.path.abspath(sys.modules[cls.__module__].__file__) + + @classmethod + def _testdir(cls): + """Get's the folder where the test exists (so data can be found)""" + return os.path.dirname(cls.__file__()) + + @classmethod + def rootdir(cls): + """Return the full path to the extensions directory""" + return os.path.dirname(cls._testdir()) + + @classmethod + def datadir(cls): + """Get the data directory (can be over-ridden if needed)""" + return os.path.join(cls._testdir(), "data") + + @property + def tempdir(self): + """Generate a temporary location to store files""" + if self._temp_dir is None: + self._temp_dir = os.path.realpath(tempfile.mkdtemp(prefix="inkex-tests-")) + if not os.path.isdir(self._temp_dir): + raise IOError("The temporary directory has disappeared!") + return self._temp_dir + + def temp_file( + self, prefix="file-", template="{prefix}{name}{suffix}", suffix=".tmp" + ): + """Generate the filename of a temporary file""" + filename = template.format(prefix=prefix, suffix=suffix, name=uuid.uuid4().hex) + return os.path.join(self.tempdir, filename) + + @classmethod + def data_file(cls, filename, *parts, check_exists=True): + """Provide a data file from a filename, can accept directories as arguments. + + .. versionchanged:: 1.2 + ``check_exists`` parameter added""" + if os.path.isabs(filename): + # Absolute root was passed in, so we trust that (it might be a tempdir) + full_path = os.path.join(filename, *parts) + else: + # Otherwise we assume it's relative to the test data dir. + full_path = os.path.join(cls.datadir(), filename, *parts) + + if not os.path.isfile(full_path) and check_exists: + raise IOError(f"Can't find test data file: {full_path}") + return full_path + + @property + def empty_svg(self): + """Returns a common minimal svg file""" + return self.data_file("svg", "default-inkscape-SVG.svg") + + def assertAlmostTuple( + self, found, expected, precision=8, msg="" + ): # pylint: disable=invalid-name + """ + Floating point results may vary with computer architecture; use + assertAlmostEqual to allow a tolerance in the result. + """ + self.assertEqual(len(found), len(expected), msg) + for fon, exp in zip(found, expected): + self.assertAlmostEqual(fon, exp, precision, msg) + + def assertEffectEmpty(self, effect, **kwargs): # pylint: disable=invalid-name + """Assert calling effect without any arguments""" + self.assertEffect(effect=effect, **kwargs) + + def assertEffect(self, *filename, **kwargs): # pylint: disable=invalid-name + """Assert an effect, capturing the output to stdout. + + filename should point to a starting svg document, default is empty_svg + """ + if filename: + data_file = self.data_file(*filename) + else: + data_file = self.empty_svg + + os.environ["DOCUMENT_PATH"] = data_file + args = [data_file] + list(kwargs.pop("args", [])) + args += [f"--{kw[0]}={kw[1]}" for kw in kwargs.items()] + + effect = kwargs.pop("effect", self.effect_class)() + + # Output is redirected to this string io buffer + if self.stderr_output: + with Capture("stderr") as stderr: + effect.run(args, output=BytesIO()) + effect.test_output = stderr + else: + output = BytesIO() + with Capture( + "stdout", kwargs.get("stdout_protect", self.stdout_protect) + ) as stdout: + with Capture( + "stderr", kwargs.get("stderr_protect", self.stderr_protect) + ) as stderr: + effect.run(args, output=output) + self.assertEqual( + "", stdout.getvalue(), "Extra print statements detected" + ) + self.assertEqual( + "", stderr.getvalue(), "Extra error or warnings detected" + ) + effect.test_output = output + + if os.environ.get("FAIL_ON_DEPRECATION", False): + warnings = getattr(effect, "warned_about", set()) + effect.warned_about = set() # reset for next test + self.assertFalse(warnings, "Deprecated API is still being used!") + + return effect + + # pylint: disable=invalid-name + def assertDeepAlmostEqual(self, first, second, places=None, msg=None, delta=None): + """Asserts that two objects, possible nested lists, are almost equal.""" + if delta is None and places is None: + places = 7 + if isinstance(first, (list, tuple)): + assert len(first) == len(second) + for f, s in zip(first, second): + self.assertDeepAlmostEqual(f, s, places, msg, delta) + else: + self.assertAlmostEqual(first, second, places, msg, delta) + + def assertTransformEqual(self, lhs, rhs, places=7): + """Assert that two transform expressions evaluate to the same + transformation matrix. + + .. versionadded:: 1.1 + """ + self.assertAlmostTuple( + tuple(Transform(lhs).to_hexad()), tuple(Transform(rhs).to_hexad()), places + ) + + # pylint: enable=invalid-name + + @property + def effect(self): + """Generate an effect object""" + if self._effect is None: + self._effect = self.effect_class() + return self._effect + + def import_string(self, string, *args) -> SvgDocumentElement: + """Runs a string through an import extension, with optional arguments + provided as "--arg=value" arguments""" + stream = io.BytesIO(string.encode()) + reader = self.effect_class() + out = io.BytesIO() + reader.parse_arguments([*args]) + reader.options.input_file = stream + reader.options.output = out + reader.load_raw() + reader.save_raw(reader.effect()) + out.seek(0) + decoded = out.read().decode("utf-8") + document = load_svg(decoded) + return document + + +class InkscapeExtensionTestMixin: + """Automatically setup self.effect for each test and test with an empty svg""" + + def setUp(self): # pylint: disable=invalid-name + """Check if there is an effect_class set and create self.effect if it is""" + super().setUp() + if self.effect_class is None: + self.skipTest("self.effect_class is not defined for this this test") + + def test_default_settings(self): + """Extension works with empty svg file""" + self.effect.run([self.empty_svg]) + + +class ComparisonMixin: + """ + Add comparison tests to any existing test suite. + """ + + compare_file: Union[List[str], Tuple[str], str] = "svg/shapes.svg" + """This input svg file sent to the extension (if any)""" + + compare_filters = [] # type: List[Compare] + """The ways in which the output is filtered for comparision (see filters.py)""" + + compare_filter_save = False + """If true, the filtered output will be saved and only applied to the + extension output (and not to the reference file)""" + + comparisons = [ + (), + ("--id=p1", "--id=r3"), + ] + """A list of comparison runs, each entry will cause the extension to be run.""" + + compare_file_extension = "svg" + + @property + def _compare_file_extension(self): + """The default extension to use when outputting check files in COMPARE_CHECK + mode.""" + if self.stderr_output: + return "txt" + return self.compare_file_extension + + def test_all_comparisons(self): + """Testing all comparisons""" + if not isinstance(self.compare_file, (list, tuple)): + self._test_comparisons(self.compare_file) + else: + for compare_file in self.compare_file: + self._test_comparisons( + compare_file, addout=os.path.basename(compare_file) + ) + + def _test_comparisons(self, compare_file, addout=None): + for args in self.comparisons: + self.assertCompare( + compare_file, + self.get_compare_cmpfile(args, addout), + args, + ) + + def assertCompare( + self, infile, cmpfile, args, outfile=None + ): # pylint: disable=invalid-name + """ + Compare the output of a previous run against this one. + + Args: + infile: The filename of the pre-processed svg (or other type of file) + cmpfile: The filename of the data we expect to get, if not set + the filename will be generated from the effect name and kwargs. + args: All the arguments to be passed to the effect run + outfile: Optional, instead of returning a regular output, this extension + dumps it's output to this filename instead. + + """ + compare_mode = int(os.environ.get("EXPORT_COMPARE", COMPARE_DELETE)) + + effect = self.assertEffect(infile, args=args) + + if cmpfile is None: + cmpfile = self.get_compare_cmpfile(args) + + if not os.path.isfile(cmpfile) and compare_mode == COMPARE_DELETE: + raise IOError( + f"Comparison file {cmpfile} not found, set EXPORT_COMPARE=1 to create " + "it." + ) + + if outfile: + if not os.path.isabs(outfile): + outfile = os.path.join(self.tempdir, outfile) + self.assertTrue( + os.path.isfile(outfile), f"No output file created! {outfile}" + ) + with open(outfile, "rb") as fhl: + data_a = fhl.read() + else: + data_a = effect.test_output.getvalue() + + write_output = None + if compare_mode == COMPARE_CHECK: + _file = cmpfile[:-4] if cmpfile.endswith(".out") else cmpfile + write_output = f"{_file}.{self._compare_file_extension}" + elif ( + compare_mode == COMPARE_WRITE and not os.path.isfile(cmpfile) + ) or compare_mode == COMPARE_OVERWRITE: + write_output = cmpfile + + try: + if write_output and not os.path.isfile(cmpfile): + raise AssertionError(f"Check the output: {write_output}") + with open(cmpfile, "rb") as fhl: + data_b = self._apply_compare_filters(fhl.read(), False) + self._base_compare(data_a, data_b, compare_mode) + except AssertionError: + if write_output: + if isinstance(data_a, str): + data_a = data_a.encode("utf-8") + with open(write_output, "wb") as fhl: + fhl.write(self._apply_compare_filters(data_a, True)) + print(f"Written output: {write_output}") + # This only reruns if the original test failed. + # The idea here is to make sure the new output file is "stable" + # Because some tests can produce random changes and we don't + # want test authors to be too reassured by a simple write. + if write_output == cmpfile: + effect = self.assertEffect(infile, args=args) + self._base_compare(data_a, cmpfile, COMPARE_CHECK) + if not write_output == cmpfile: + raise + + def _base_compare(self, data_a, data_b, compare_mode): + data_a = self._apply_compare_filters(data_a) + + if ( + isinstance(data_a, bytes) + and isinstance(data_b, bytes) + and data_a.startswith(b"<") + and data_b.startswith(b"<") + ): + # Late importing + diff_xml, delta = xmldiff(data_a, data_b) + if not delta and compare_mode == COMPARE_DELETE: + print( + "The XML is different, you can save the output using the " + "EXPORT_COMPARE envionment variable. Set it to 1 to save a file " + "you can check, set it to 3 to overwrite this comparison, setting " + "the new data as the correct one.\n" + ) + diff = "SVG Differences\n\n" + if os.environ.get("XML_DIFF", False): + diff = "<- " + diff_xml + else: + for x, (value_a, value_b) in enumerate(delta): + try: + # Take advantage of better text diff in testcase's own asserts. + self.assertEqual(value_a, value_b) + except AssertionError as err: + diff += f" {x}. {str(err)}\n" + self.assertTrue(delta, diff) + else: + # compare any content (non svg) + self.assertEqual(data_a, data_b) + + def _apply_compare_filters(self, data, is_saving=None): + data = to_bytes(data) + # Applying filters flips depending if we are saving the filtered content + # to disk, or filtering during the test run. This is because some filters + # are destructive others are useful for diagnostics. + if is_saving is self.compare_filter_save or is_saving is None: + for cfilter in self.compare_filters: + data = cfilter(data) + return data + + def get_compare_cmpfile(self, args, addout=None): + """Generate an output file for the arguments given""" + if addout is not None: + args = list(args) + [str(addout)] + opstr = ( + "__".join(args) + .replace(self.tempdir, "TMP_DIR") + .replace(self.datadir(), "DAT_DIR") + ) + opstr = re.sub(r"[^\w-]", "__", opstr) + if opstr: + if len(opstr) > 127: + # avoid filename-too-long error + opstr = hashlib.md5(opstr.encode("latin1")).hexdigest() + opstr = "__" + opstr + return self.data_file( + "refs", f"{self.effect_name}{opstr}.out", check_exists=False + ) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/decorators.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/decorators.py new file mode 100644 index 0000000..f27e638 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/decorators.py @@ -0,0 +1,9 @@ +""" +Useful decorators for tests. +""" +import pytest +from inkex.command import is_inkscape_available + +requires_inkscape = pytest.mark.skipif( # pylint: disable=invalid-name + not is_inkscape_available(), reason="Test requires inkscape, but it's not available" +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/filters.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/filters.py new file mode 100644 index 0000000..281adfc --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/filters.py @@ -0,0 +1,180 @@ +# +# Copyright (C) 2019 Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=too-few-public-methods +# +""" +Comparison filters for use with the ComparisonMixin. + +Each filter should be initialised in the list of +filters that are being used. + +.. code-block:: python + + compare_filters = [ + CompareNumericFuzzy(), + CompareOrderIndependentLines(option=yes), + ] + +""" + +import re +from ..utils import to_bytes + + +class Compare: + """ + Comparison base class, this acts as a passthrough unless + the filter staticmethod is overwritten. + """ + + def __init__(self, **options): + self.options = options + + def __call__(self, content): + return self.filter(content) + + @staticmethod + def filter(contents): + """Replace this filter method with your own filtering""" + return contents + + +class CompareNumericFuzzy(Compare): + """ + Turn all numbers into shorter standard formats + + 1.2345678 -> 1.2346 + 1.2300 -> 1.23, 50.0000 -> 50.0 + 50.0 -> 50 + """ + + @staticmethod + def filter(contents): + func = lambda m: b"%.3f" % (float(m.group(0))) + contents = re.sub(rb"\d+\.\d+(e[+-]\d+)?", func, contents) + contents = re.sub(rb"(\d\.\d+?)0+\b", rb"\1", contents) + contents = re.sub(rb"(\d)\.0+(?=\D|\b)", rb"\1", contents) + return contents + + +class CompareWithoutIds(Compare): + """Remove all ids from the svg""" + + @staticmethod + def filter(contents): + return re.sub(rb' id="([^"]*)"', b"", contents) + + +class CompareWithPathSpace(Compare): + """Make sure that path segment commands have spaces around them""" + + @staticmethod + def filter(contents): + def func(match): + """We've found a path command, process it""" + new = re.sub(rb"\s*([LZMHVCSQTAatqscvhmzl])\s*", rb" \1 ", match.group(1)) + return b' d="' + new.replace(b",", b" ") + b'"' + + return re.sub(rb' d="([^"]*)"', func, contents) + + +class CompareSize(Compare): + """Compare the length of the contents instead of the contents""" + + @staticmethod + def filter(contents): + return len(contents) + + +class CompareOrderIndependentBytes(Compare): + """Take all the bytes and sort them""" + + @staticmethod + def filter(contents): + return b"".join([bytes(i) for i in sorted(contents)]) + + +class CompareOrderIndependentLines(Compare): + """Take all the lines and sort them""" + + @staticmethod + def filter(contents): + return b"\n".join(sorted(contents.splitlines())) + + +class CompareOrderIndependentStyle(Compare): + """Take all styles and sort the results""" + + @staticmethod + def filter(contents): + contents = CompareNumericFuzzy.filter(contents) + + def func(match): + """Search and replace function for sorting""" + sty = b";".join(sorted(match.group(1).split(b";"))) + return b'style="%s"' % (sty,) + + return re.sub(rb'style="([^"]*)"', func, contents) + + +class CompareOrderIndependentStyleAndPath(Compare): + """Take all styles and paths and sort them both""" + + @staticmethod + def filter(contents): + contents = CompareOrderIndependentStyle.filter(contents) + + def func(match): + """Search and replace function for sorting""" + path = b"X".join(sorted(re.split(rb"[A-Z]", match.group(1)))) + return b'd="%s"' % (path,) + + return re.sub(rb'\bd="([^"]*)"', func, contents) + + +class CompareOrderIndependentTags(Compare): + """Sorts all the XML tags""" + + @staticmethod + def filter(contents): + return b"\n".join(sorted(re.split(rb">\s*<", contents))) + + +class CompareReplacement(Compare): + """Replace pieces to make output more comparable + + .. versionadded:: 1.1""" + + def __init__(self, *replacements): + self.deltas = replacements + super().__init__() + + def filter(self, contents): + contents = to_bytes(contents) + for _from, _to in self.deltas: + contents = contents.replace(to_bytes(_from), to_bytes(_to)) + return contents + + +class WindowsTextCompat(CompareReplacement): + """Normalize newlines so tests comparing plain text work + + .. versionadded:: 1.2""" + + def __init__(self): + super().__init__(("\r\n", "\n")) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.rng b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.rng new file mode 100644 index 0000000..1cb77de --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.rng @@ -0,0 +1,577 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file + executable + extension + + + + + + + + + + + + + + + + + + + + python + perl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + path + extensions + inx + absolute + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + g + path + rect + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + header + url + + + + + + + default + preserve + + + + + + + + + + + + + + + + + + + + + + + + + + + + expand + + + + + + + + + + + + + + + + + + + + + + + + + + int + + + + + + + + + + + + + + full + + + + + + + + + + float + + + + + + + + + + + + + + + + + + + full + + + + + + + bool + + + + + + color + + + + + colorbutton + + + + + + + + + + + + string + + + + + + + + + + multiline + + + + + + + + + + + path + + + + + file + files + folder + folders + file_new + folder_new + + + + + + + + + + + + + optiongroup + + + + + combo + radio + + + + + + + + + + + + + + + + + + + + + + + + notebook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + yes + no + + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.schema b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.schema new file mode 100644 index 0000000..63cb97b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inkscape.extension.schema @@ -0,0 +1,9 @@ + + + + + + Warning: @value values should be unique for a given option. + + + diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inx.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inx.py new file mode 100644 index 0000000..20cc345 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/inx.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# coding=utf-8 +""" +Test elements extra logic from svg xml lxml custom classes. +""" +import os +from importlib import resources + +from lxml import etree + +from ..utils import PY3 +from ..inx import InxFile + +INTERNAL_ARGS = ("help", "output", "id", "selected-nodes") +ARG_TYPES = { + "Boolean": "bool", + "Color": "color", + "str": "string", + "int": "int", + "float": "float", +} + + +class InxMixin: + """Tools for Testing INX files, use as a mixin class: + + class MyTests(InxMixin, TestCase): + def test_inx_file(self): + self.assertInxIsGood("some_inx_file.inx") + """ + + def assertInxIsGood(self, inx_file): # pylint: disable=invalid-name + """Test the inx file for consistancy and correctness""" + self.assertTrue(PY3, "INX files can only be tested in python3") + + inx = InxFile(inx_file) + if "help" in inx.ident or inx.script.get("interpreter", None) != "python": + return + cls = inx.extension_class + # Check class can be matched in python file + self.assertTrue(cls, f"Can not find class for {inx.filename}") + # Check name is reasonable for the class + if not cls.multi_inx: + self.assertEqual( + cls.__name__, + inx.slug, + f"Name of extension class {cls.__module__}.{cls.__name__} " + f"is different from ident {inx.slug}", + ) + self.assertParams(inx, cls) + + def assertParams(self, inx, cls): # pylint: disable=invalid-name + """Confirm the params in the inx match the python script + + .. versionchanged:: 1.2 + Also checks that the default values are identical""" + params = {param.name: self.parse_param(param) for param in inx.params} + args = dict(self.introspect_arg_parser(cls().arg_parser)) + mismatch_a = list(set(params) ^ set(args) & set(params)) + mismatch_b = list(set(args) ^ set(params) & set(args)) + self.assertFalse( + mismatch_a, f"{inx.filename}: Inx params missing from arg parser" + ) + self.assertFalse( + mismatch_b, f"{inx.filename}: Script args missing from inx xml" + ) + + for param in args: + if params[param]["type"] and args[param]["type"]: + self.assertEqual( + params[param]["type"], + args[param]["type"], + f"Type is not the same for {inx.filename}:param:{param}", + ) + inxdefault = params[param]["default"] + argsdefault = args[param]["default"] + if inxdefault and argsdefault: + # for booleans, the inx is lowercase and the param is uppercase + if params[param]["type"] == "bool": + argsdefault = str(argsdefault).lower() + elif params[param]["type"] not in ["string", None, "color"] or args[ + param + ]["type"] in ["int", "float"]: + # try to parse the inx value to compare numbers to numbers + inxdefault = float(inxdefault) + if args[param]["type"] == "color" or callable(args[param]["default"]): + # skip color, method types + continue + self.assertEqual( + argsdefault, + inxdefault, + f"Default value is not the same for {inx.filename}:param:{param}", + ) + + def introspect_arg_parser(self, arg_parser): + """Pull apart the arg parser to find out what we have in it""" + for ( + action + ) in arg_parser._optionals._actions: # pylint: disable=protected-access + for opt in action.option_strings: + # Ignore params internal to inkscape (thus not in the inx) + if opt.startswith("--") and opt[2:] not in INTERNAL_ARGS: + yield (opt[2:], self.introspect_action(action)) + + @staticmethod + def introspect_action(action): + """Pull apart a single action to get at the juicy insides""" + return { + "type": ARG_TYPES.get((action.type or str).__name__, "string"), + "default": action.default, + "choices": action.choices, + "help": action.help, + } + + @staticmethod + def parse_param(param): + """Pull apart the param element in the inx file""" + if param.param_type in ("optiongroup", "notebook"): + options = param.options + return { + "type": None, + "choices": options, + "default": options and options[0] or None, + } + param_type = param.param_type + if param.param_type in ("path",): + param_type = "string" + return { + "type": param_type, + "default": param.text, + "choices": None, + } + + def assertInxSchemaValid(self, inx_file): # pylint: disable=invalid-name + """Validate inx file schema.""" + self.assertTrue(INX_SCHEMAS, "no schema files found") + with open(inx_file, "rb") as fp: + inx_doc = etree.parse(fp) + + for schema_name, schema in INX_SCHEMAS.items(): + with self.subTest(schema_file=schema_name): + schema.assert_(inx_doc) + + +def _load_inx_schemas(): + _SCHEMA_CLASSES = { + ".rng": etree.RelaxNG, + ".schema": etree.Schematron, # "pre-ISO-Schematron" + } + + for name in resources.contents(__package__): + _, ext = os.path.splitext(name) + schema_class = _SCHEMA_CLASSES.get(ext) + if schema_class is None: + continue + + with resources.open_binary(__package__, name) as fp: + schema_doc = etree.parse(fp) + yield name, schema_class(schema_doc) + + +INX_SCHEMAS = dict(_load_inx_schemas()) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/mock.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/mock.py new file mode 100644 index 0000000..de5e569 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/mock.py @@ -0,0 +1,463 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +# pylint: disable=protected-access,too-few-public-methods +""" +Any mocking utilities required by testing. Mocking is when you need the test +to exercise a piece of code, but that code may or does call on something +outside of the target code that either takes too long to run, isn't available +during the test running process or simply shouldn't be running at all. +""" + +import io +import os +import sys +import logging +import hashlib +import tempfile +from typing import List, Tuple, Any + +from email.mime.application import MIMEApplication +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.parser import Parser as EmailParser + +import inkex.command + + +FIXED_BOUNDARY = "--CALLDATA--//--CALLDATA--" + + +class Capture: + """Capture stdout or stderr. Used as `with Capture('stdout') as stream:`""" + + def __init__(self, io_name="stdout", swap=True): + self.io_name = io_name + self.original = getattr(sys, io_name) + self.stream = io.StringIO() + self.swap = swap + + def __enter__(self): + if self.swap: + setattr(sys, self.io_name, self.stream) + return self.stream + + def __exit__(self, exc, value, traceback): + if exc is not None and self.swap: + # Dump content back to original if there was an error. + self.original.write(self.stream.getvalue()) + setattr(sys, self.io_name, self.original) + + +class ManualVerbosity: + """Change the verbosity of the test suite manually""" + + result = property(lambda self: self.test._current_result) + + def __init__(self, test, okay=True, dots=False): + self.test = test + self.okay = okay + self.dots = dots + + def flip( + self, exc_type=None, exc_val=None, exc_tb=None + ): # pylint: disable=unused-argument + """Swap the stored verbosity with the original""" + self.okay, self.result.showAll = self.result.showAll, self.okay + self.dots, self.result.dots = self.result.dots, self.okay + + __enter__ = flip + __exit__ = flip + + +class MockMixin: + """ + Add mocking ability to any test base class, will set up mock on setUp + and remove it on tearDown. + + Mocks are stored in an array attached to the test class (not instance!) which + ensures that mocks can only ever be setUp once and can never be reset over + themselves. (just in case this looks weird at first glance) + + class SomeTest(MockingMixin, TestBase): + mocks = [(sys, 'exit', NoSystemExit("Nope!")] + """ + + mocks = [] # type: List[Tuple[Any, str, Any]] + + def setUpMock(self, owner, name, new): # pylint: disable=invalid-name + """Setup the mock here, taking name and function and returning (name, old)""" + old = getattr(owner, name) + if isinstance(new, str): + if hasattr(self, new): + new = getattr(self, new) + if isinstance(new, Exception): + + def _error_function(*args2, **kw2): # pylint: disable=unused-argument + raise type(new)(str(new)) + + setattr(owner, name, _error_function) + elif new is None or isinstance(new, (str, int, float, list, tuple)): + + def _value_function(*args, **kw): # pylint: disable=unused-argument + return new + + setattr(owner, name, _value_function) + else: + setattr(owner, name, new) + # When we start, mocks contains length 3 tuples, when we're finished, it + # contains length 4, this stops remocking and reunmocking from taking place. + return (owner, name, old, False) + + def setUp(self): # pylint: disable=invalid-name + """For each mock instruction, set it up and store the return""" + super().setUp() + for x, mock in enumerate(self.mocks): + if len(mock) == 4: + logging.error( + "Mock was already set up, so it wasn't cleared previously!" + ) + continue + self.mocks[x] = self.setUpMock(*mock) + + def tearDown(self): # pylint: disable=invalid-name + """For each returned stored, tear it down and restore mock instruction""" + super().tearDown() + try: + for x, (owner, name, old, _) in enumerate(self.mocks): + self.mocks[x] = (owner, name, getattr(owner, name)) + setattr(owner, name, old) + except ValueError: + logging.warning("Was never mocked, did something go wrong?") + + def old_call(self, name): + """Get the original caller""" + for arg in self.mocks: + if arg[1] == name: + return arg[2] + return lambda: None + + +class MockCommandMixin(MockMixin): + """ + Replace all the command functions with testable replacements. + + This stops the pipeline and people without the programs, running into problems. + """ + + mocks = [ + (inkex.command, "_call", "mock_call"), + (tempfile, "mkdtemp", "record_tempdir"), + ] + recorded_tempdirs = [] # type:List[str] + + def setUp(self): # pylint: disable=invalid-name + super().setUp() + # This is a the daftest thing I've ever seen, when in the middle + # of a mock, the 'self' variable magically turns from a FooTest + # into a TestCase, this makes it impossible to find the datadir. + from . import TestCase + + TestCase._mockdatadir = self.datadir() + + @classmethod + def cmddir(cls): + """Returns the location of all the mocked command results""" + from . import TestCase + + return os.path.join(TestCase._mockdatadir, "cmd") + + def record_tempdir(self, *args, **kwargs): + """Record any attempts to make tempdirs""" + newdir = self.old_call("mkdtemp")(*args, **kwargs) + self.recorded_tempdirs.append(os.path.realpath(newdir)) + return newdir + + def clean_paths(self, data, files): + """Clean a string of any files or tempdirs""" + + def replace(indata, replaced, replacement): + if isinstance(indata, str): + indata = indata.replace(replaced, replacement) + else: + indata = [i.replace(replaced, replacement) for i in indata] + return indata + + try: + for fdir in self.recorded_tempdirs: + data = replace(data, fdir + os.sep, "./") + data = replace(data, fdir, ".") + files = replace(files, fdir + os.sep, "./") + files = replace(files, fdir, ".") + for fname in files: + data = replace(data, fname, os.path.basename(fname)) + except (UnicodeDecodeError, TypeError): + pass + return data + + def get_all_tempfiles(self): + """Returns a set() of all files currently in any of the tempdirs""" + ret = set([]) + for fdir in self.recorded_tempdirs: + if not os.path.isdir(fdir): + continue + for fname in os.listdir(fdir): + if fname in (".", ".."): + continue + path = os.path.join(fdir, fname) + # We store the modified time so if a program modifies + # the input file in-place, it will look different. + ret.add(path + f";{os.path.getmtime(path)}") + + return ret + + def ignore_command_mock(self, program, arglst, path): + """Return true if the mock is ignored""" + if self and program and arglst: + env = os.environ.get("NO_MOCK_COMMANDS", 0) + if (not os.path.exists(path) and int(env) == 1) or int(env) == 2: + return True + return False + + def mock_call(self, program, *args, **kwargs): + """ + Replacement for the inkex.command.call() function, instead of calling + an external program, will compile all arguments into a hash and use the + hash to find a command result. + """ + # Remove stdin first because it needs to NOT be in the Arguments list. + stdin = kwargs.pop("stdin", None) + args = list(args) + + # We use email + msg = MIMEMultipart(boundary=FIXED_BOUNDARY) + msg["Program"] = MockCommandMixin.get_program_name(program) + + # Gather any output files and add any input files to msg, args and kwargs + # may be modified to strip out filename directories (which change) + inputs, outputs = self.add_call_files(msg, args, kwargs) + + arglst = inkex.command.to_args_sorted(program, *args, **kwargs)[1:] + arglst = self.clean_paths(arglst, inputs + outputs) + argstr = " ".join(arglst) + msg["Arguments"] = argstr.strip() + + if stdin is not None: + # The stdin is counted as the msg body + cleanin = ( + self.clean_paths(stdin, inputs + outputs) + .replace("\r\n", "\n") + .replace(".\\", "./") + ) + msg.attach(MIMEText(cleanin, "plain", "utf-8")) + + keystr = msg.as_string() + # On Windows, output is separated by CRLF + keystr = keystr.replace("\r\n", "\n") + # There is a difference between python2 and python3 output + keystr = keystr.replace("\n\n", "\n") + keystr = keystr.replace("\n ", " ") + if "verb" in keystr: + # Verbs seperated by colons cause diff in py2/3 + keystr = keystr.replace("; ", ";") + # Generate a unique key for this call based on _all_ it's inputs + key = hashlib.md5(keystr.encode("utf-8")).hexdigest() + + if self.ignore_command_mock( + program, arglst, self.get_call_filename(program, key, create=True) + ): + # Call original code. This is so programmers can run the test suite + # against the external programs too, to see how their fair. + if stdin is not None: + kwargs["stdin"] = stdin + + before = self.get_all_tempfiles() + stdout = self.old_call("_call")(program, *args, **kwargs) + outputs += list(self.get_all_tempfiles() - before) + # Remove the modified time from the call + outputs = [out.rsplit(";", 1)[0] for out in outputs] + + # After the program has run, we collect any file outputs and store + # them, then store any stdout or stderr created during the run. + # A developer can then use this to build new test cases. + reply = MIMEMultipart(boundary=FIXED_BOUNDARY) + reply["Program"] = MockCommandMixin.get_program_name(program) + reply["Arguments"] = argstr + self.save_call(program, key, stdout, outputs, reply) + self.save_key(program, key, keystr, "key") + return stdout + + try: + return self.load_call(program, key, outputs) + except IOError as err: + self.save_key(program, key, keystr, "bad-key") + raise IOError( + f"Problem loading call: {program}/{key} use the environment variable " + "NO_MOCK_COMMANDS=1 to call out to the external program and generate " + f"the mock call file for call {program} {argstr}." + ) from err + + def add_call_files(self, msg, args, kwargs): + """ + Gather all files, adding input files to the msg (for hashing) and + output files to the returned files list (for outputting in debug) + """ + # Gather all possible string arguments together. + loargs = sorted(kwargs.items(), key=lambda i: i[0]) + values = [] + for arg in args: + if isinstance(arg, (tuple, list)): + loargs.append(arg) + else: + values.append(str(arg)) + + for _, value in loargs: + if isinstance(value, (tuple, list)): + for val in value: + if val is not True: + values.append(str(val)) + elif value is not True: + values.append(str(value)) + + # See if any of the strings could be filenames, either going to be + # or are existing files on the disk. + files = [[], []] + for value in values: + if os.path.isfile(value): # Input file + files[0].append(value) + self.add_call_file(msg, value) + elif os.path.isdir(os.path.dirname(value)): # Output file + files[1].append(value) + return files + + def add_call_file(self, msg, filename): + """Add a single file to the given mime message""" + fname = os.path.basename(filename) + with open(filename, "rb") as fhl: + if filename.endswith(".svg"): + value = self.clean_paths(fhl.read().decode("utf8"), []) + else: + value = fhl.read() + try: + value = value.decode() + except UnicodeDecodeError: + pass # do not attempt to process binary files further + if isinstance(value, str): + value = value.replace("\r\n", "\n").replace(".\\", "./") + part = MIMEApplication(value, Name=fname) + # After the file is closed + part["Content-Disposition"] = "attachment" + part["Filename"] = fname + msg.attach(part) + + def get_call_filename(self, program, key, create=False): + """ + Get the filename for the call testing information. + """ + path = self.get_call_path(program, create=create) + fname = os.path.join(path, key + ".msg") + if not create and not os.path.isfile(fname): + raise IOError(f"Attempted to find call test data {key}") + return fname + + @staticmethod + def get_program_name(program): + """Takes a program and returns a program name""" + if program == inkex.command.INKSCAPE_EXECUTABLE_NAME: + return "inkscape" + return program + + def get_call_path(self, program, create=True): + """Get where this program would store it's test data""" + command_dir = os.path.join( + self.cmddir(), MockCommandMixin.get_program_name(program) + ) + if not os.path.isdir(command_dir): + if create: + os.makedirs(command_dir) + else: + raise IOError( + "A test is attempting to use an external program in a test:" + f" {program}; but there is not a command data directory which " + f"should contain the results of the command here: {command_dir}" + ) + return command_dir + + def load_call(self, program, key, files): + """ + Load the given call + """ + fname = self.get_call_filename(program, key, create=False) + with open(fname, "rb") as fhl: + msg = EmailParser().parsestr(fhl.read().decode("utf-8")) + + stdout = None + for part in msg.walk(): + if "attachment" in part.get("Content-Disposition", ""): + base_name = part["Filename"] + for out_file in files: + if out_file.endswith(base_name): + with open(out_file, "wb") as fhl: + fhl.write(part.get_payload(decode=True)) + part = None + if part is not None: + # Was not caught by any normal outputs, so we will + # save the file to EVERY tempdir in the hopes of + # hitting on of them. + for fdir in self.recorded_tempdirs: + if os.path.isdir(fdir): + with open(os.path.join(fdir, base_name), "wb") as fhl: + fhl.write(part.get_payload(decode=True)) + elif part.get_content_type() == "text/plain": + stdout = part.get_payload(decode=True) + + return stdout + + def save_call( + self, program, key, stdout, files, msg, ext="output" + ): # pylint: disable=too-many-arguments + """ + Saves the results from the call into a debug output file, the resulting files + should be a Mime msg file format with each attachment being one of the input + files as well as any stdin and arguments used in the call. + """ + if stdout is not None and stdout.strip(): + # The stdout is counted as the msg body here + msg.attach(MIMEText(stdout.decode("utf-8"), "plain", "utf-8")) + + for fname in set(files): + if os.path.isfile(fname): + # print("SAVING FILE INTO MSG: {}".format(fname)) + self.add_call_file(msg, fname) + else: + part = MIMEText("Missing File", "plain", "utf-8") + part.add_header("Filename", os.path.basename(fname)) + msg.attach(part) + fname = self.get_call_filename(program, key, create=True) + "." + ext + + with open(fname, "wb") as fhl: + fhl.write(msg.as_string().encode("utf-8")) + if int(os.environ.get("NO_MOCK_COMMANDS", 0)) == 1: + print(f"Saved mock call as {fname}, remove .{ext}") + + def save_key(self, program, key, keystr, ext="key"): + """Save the key file if we are debugging the key data""" + if os.environ.get("DEBUG_KEY"): + fname = self.get_call_filename(program, key, create=True) + "." + ext + with open(fname, "wb") as fhl: + fhl.write(keystr.encode("utf-8")) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/svg.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/svg.py new file mode 100644 index 0000000..c601c81 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/svg.py @@ -0,0 +1,55 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +SVG specific utilities for tests. +""" + +from lxml import etree + +from inkex import SVG_PARSER + + +def svg(svg_attrs=""): + """Returns xml etree based on a simple SVG element. + + svg_attrs: A string containing attributes to add to the + root element of a minimal SVG document. + """ + return etree.fromstring( + str.encode( + '' + f"" + ), + parser=SVG_PARSER, + ) + + +def svg_unit_scaled(width_unit): + """Same as svg, but takes a width unit (top-level transform) for the new document. + + The transform is the ratio between the SVG width and the viewBox width. + """ + return svg(f'width="1{width_unit}" viewBox="0 0 1 1"') + + +def svg_file(filename): + """Parse an svg file and return it's document root""" + with open(filename, "r", encoding="utf-8") as fhl: + doc = etree.parse(fhl, parser=SVG_PARSER) + return doc.getroot() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/test_inx_file.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/test_inx_file.py new file mode 100644 index 0000000..bce8e20 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/test_inx_file.py @@ -0,0 +1,63 @@ +"""Check .inx file(s). + +This is meant to be run an executable module, e.g. + + python -m inkex.tester.test_inx_file *.inx + +""" +import argparse +import sys +import unittest +from typing import List + +from .inx import InxMixin + + +class InxTestCase(InxMixin, unittest.TestCase): + inx_files: List[str] = [] + + def test_inx_file_parameters(self): + for inx_file in self.inx_files: + with self.subTest(inx_file=inx_file): + self.assertInxIsGood(inx_file) + + def test_inx_file_schema(self): + for inx_file in self.inx_files: + with self.subTest(inx_file=inx_file): + self.assertInxSchemaValid(inx_file) + + +def main(args=None): + """Check .inx file(s)""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "-v", + "--verbose", + dest="verbosity", + action="store_const", + const=2, + help="Verbose output", + ) + parser.add_argument( + "-q", + "--quiet", + dest="verbosity", + action="store_const", + const=0, + help="Quiet output", + ) + parser.add_argument( + "inx_files", metavar="INX-FILE", nargs="+", help="An .inx file to check" + ) + args = parser.parse_args(args, namespace=argparse.Namespace(verbosity=1)) + + InxTestCase.inx_files = args.inx_files + runner = unittest.TextTestRunner(verbosity=args.verbosity) + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(InxTestCase) + result = runner.run(suite) + sys.exit(not result.wasSuccessful()) + + +if __name__ == "__main__": + main() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/word.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/word.py new file mode 100644 index 0000000..bf395d7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/word.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# +# Unknown author +# +""" +Generate words for testing. +""" + +import string +import random + + +def word_generator(text_length): + """ + Generate a word of text_length size + """ + word = "" + + for _ in range(0, text_length): + word += random.choice( + string.ascii_lowercase + + string.ascii_uppercase + + string.digits + + string.punctuation + ) + + return word + + +def sentencecase(word): + """Make a word standace case""" + word_new = "" + lower_letters = list(string.ascii_lowercase) + first = True + for letter in word: + if letter in lower_letters and first is True: + word_new += letter.upper() + first = False + else: + word_new += letter + + return word_new diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/xmldiff.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/xmldiff.py new file mode 100644 index 0000000..1864530 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tester/xmldiff.py @@ -0,0 +1,124 @@ +# +# Copyright 2011 (c) Ian Bicking +# 2019 (c) Martin Owens +# +# Taken from http://formencode.org under the GPL compatible PSF License. +# Modified to produce more output as a diff. +# +""" +Allow two xml files/lxml etrees to be compared, returning their differences. +""" +import xml.etree.ElementTree as xml +from io import BytesIO + +from inkex.paths import Path + + +def text_compare(test1, test2): + """ + Compare two text strings while allowing for '*' to match + anything on either lhs or rhs. + """ + if not test1 and not test2: + return True + if test1 == "*" or test2 == "*": + return True + return (test1 or "").strip() == (test2 or "").strip() + + +class DeltaLogger(list): + """A record keeper of the delta between two svg files""" + + def append_tag(self, tag_a, tag_b): + """Record a tag difference""" + if tag_a: + tag_a = f"<{tag_a}.../>" + if tag_b: + tag_b = f"<{tag_b}.../>" + self.append((tag_a, tag_b)) + + def append_attr(self, attr, value_a, value_b): + """Record an attribute difference""" + + def _prep(val): + if val: + if attr == "d": + return [attr] + Path(val).to_arrays() + return (attr, val) + return val + + # Only append a difference if the preprocessed values are different. + # This solves the issue that -0 != 0 in path data. + prep_a = _prep(value_a) + prep_b = _prep(value_b) + if prep_a != prep_b: + self.append((prep_a, prep_b)) + + def append_text(self, text_a, text_b): + """Record a text difference""" + self.append((text_a, text_b)) + + def __bool__(self): + """Returns True if there's no log, i.e. the delta is clean""" + return not self.__len__() + + __nonzero__ = __bool__ + + def __repr__(self): + if self: + return "No differences detected" + return f"{len(self)} xml differences" + + +def to_xml(data): + """Convert string or bytes to xml parsed root node""" + if isinstance(data, str): + data = data.encode("utf8") + if isinstance(data, bytes): + return xml.parse(BytesIO(data)).getroot() + return data + + +def xmldiff(data1, data2): + """Create an xml difference, will modify the first xml structure with a diff""" + xml1, xml2 = to_xml(data1), to_xml(data2) + delta = DeltaLogger() + _xmldiff(xml1, xml2, delta) + return xml.tostring(xml1).decode("utf-8"), delta + + +def _xmldiff(xml1, xml2, delta): + if xml1.tag != xml2.tag: + xml1.tag = f"{xml1.tag}XXX{xml2.tag}" + delta.append_tag(xml1.tag, xml2.tag) + for name, value in xml1.attrib.items(): + if name not in xml2.attrib: + delta.append_attr(name, xml1.attrib[name], None) + xml1.attrib[name] += "XXX" + elif xml2.attrib.get(name) != value: + delta.append_attr(name, xml1.attrib.get(name), xml2.attrib.get(name)) + xml1.attrib[name] = f"{xml1.attrib.get(name)}XXX{xml2.attrib.get(name)}" + for name, value in xml2.attrib.items(): + if name not in xml1.attrib: + delta.append_attr(name, None, value) + xml1.attrib[name] = "XXX" + value + if not text_compare(xml1.text, xml2.text): + delta.append_text(xml1.text, xml2.text) + xml1.text = f"{xml1.text}XXX{xml2.text}" + if not text_compare(xml1.tail, xml2.tail): + delta.append_text(xml1.tail, xml2.tail) + xml1.tail = f"{xml1.tail}XXX{xml2.tail}" + + # Get children and pad with nulls + children_a = list(xml1) + children_b = list(xml2) + children_a += [None] * (len(children_b) - len(children_a)) + children_b += [None] * (len(children_a) - len(children_b)) + + for child_a, child_b in zip(children_a, children_b): + if child_a is None: # child_b exists + delta.append_tag(child_b.tag, None) + elif child_b is None: # child_a exists + delta.append_tag(None, child_a.tag) + else: + _xmldiff(child_a, child_b, delta) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/__init__.py new file mode 100644 index 0000000..3520752 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/__init__.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +This submodule gives all TextElements and FlowRoots the parsed_text property, +which is a cached instance of ParsedText. To use, simply run + + import text + +Following this, text properties will be available at el.parsed_text. For more +details, see parser.py. To check if it is working properly, run the +following on some text: + + el.parsed_text.Make_Highlights('char') + +Rectangles should appear surrounding the logical extents of each character. + +Before parsing is done, a character table must be generated to determine the properties +of all the characters present. This is done automatically by the first invocation of +.parsed_text, which automatically analyzes the whole document and adds it to the SVG. +If you are only parsing a few text elements, this can be sped up by calling +svg.make_char_table(els). On the rare occasions this fails, a command call may +be performed as a fallback. +""" + +# Give inkex an inkex.text submodule that refers to this directory +import sys +import os +import inspect +import inkex + +if not hasattr(inkex, "text"): + import importlib + + mydir = os.path.dirname(os.path.abspath(__file__)) + myloc, myname = os.path.split(mydir) + oldpath = sys.path + sys.path.append(myloc) + inkex.text = importlib.import_module(myname) + sys.modules["inkex.text"] = inkex.text + sys.path = oldpath + +# Gives inkex elements some additional cached attributes, for further speedups +import inkex.text.cache # pylint: disable=wrong-import-position + + +def add_cache(base, derived): + """ + Adds the cache capabilities from a derived class to a base class by dynamically + attaching methods, properties, and data descriptors that are unique to the derived + class to the base class. + """ + predfn = ( + lambda x: isinstance(x, property) + or inspect.isfunction(x) + or inspect.isdatadescriptor(x) + ) + base_m = dict(inspect.getmembers(base, predicate=predfn)) + for name, memb in inspect.getmembers(derived, predicate=predfn): + if memb not in base_m.values(): + setattr(base, name, memb) + + +add_cache(inkex.BaseElement, inkex.text.cache.BaseElementCache) +add_cache(inkex.SvgDocumentElement, inkex.text.cache.SvgDocumentElementCache) +add_cache(inkex.Style, inkex.text.cache.StyleCache) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/cache.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/cache.py new file mode 100644 index 0000000..48f0fce --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/cache.py @@ -0,0 +1,1302 @@ +# coding=utf-8 + +# Copyright (c) 2023 David Burghoff +# Martin Owens +# Sergei Izmailov +# Thomas Holder +# Jonathan Neuhauser + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +""" +A collection of functions that provide caching of certain Inkex properties. +Most provide the same functionality as the regular properties, +but with a 'c' in front of the name. For example, + Style: cstyle, cspecified_style, ccascaded_style + Transform: ctransform, ccomposed_transform + Miscellaneous: croot, cdefs, ctag +Most are invalidated by setting to None (except ctransform, which is set to identity). +xpath calls are avoided at all costs. + +Also gives SvgDocumentElements some dictionaries that are used to speed up +various lookups: + svg.iddict: elements by their ID + svg.cssdict: CSS styles by ID + +Lastly, several core Inkex functions are overwritten with versions that +use the cache. For example, getElementById uses svg.iddict to avoid xpath +calls. +""" + +import re +from typing import Optional, List +import inkex +from inkex import Style +from inkex import BaseElement, SvgDocumentElement +from inkex.text.parser import ParsedText, CharacterTable +from text.utils import shapetags, tags, ipx, list2 # pylint: disable=import-error +import lxml + +EBget = lxml.etree.ElementBase.get +EBset = lxml.etree.ElementBase.set + + +def get_link_fcn(elem, typestr, svg=None, llget=False): + """ + Function that references URLs, returning the referenced element + Returns None if it does not exist or is invalid + Accepts both elements and styles as inputs + """ + if llget: + urlv = EBget(elem, typestr) + # fine for 'clip-path' & 'mask' + else: + urlv = elem.get(typestr) + if urlv is not None: + if svg is None: + svg = elem.croot # need to specify svg for Styles but not BaseElements + if svg is None: + return None + if typestr == "xlink:href": + urlel = svg.getElementById(urlv[1:]) + elif urlv.startswith("url"): + urlel = svg.getElementById(urlv[5:-1]) + else: + urlel = None + return urlel + return None + + +BE_set_id = BaseElement.set_id + +# Fast empty Style initialization +if len(Style().__dict__) == 0: + + def empty_style(): + """Instantiates an empty Style object.""" + return Style.__new__(Style) +else: + bstyle_dict = Style().__dict__ + + def empty_style(): + """Instantiates an empty Style object.""" + ret = Style.__new__(Style) + ret.__dict__ = bstyle_dict + return ret + + +# pylint:disable=attribute-defined-outside-init +class BaseElementCache(BaseElement): + """Adds caching of style and transformation properties of base elements.""" + + get_link = get_link_fcn + + class CStyle(Style): + """ + Cached style attribute that invalidates the cached cascaded / specified + style whenever the style is changed. Always use this when setting styles. + """ + + # Delete key when value set to None + def __init__(self, val, elem): + self.elem = elem + self.init = True + super().__init__(val) + self.init = False + + def __setitem__(self, *args): + # Allows for multiple inputs using + # __setitem__(key1, value1, key2, value2, ...) + if self.init: + # OrderedDict sets items during initialization, use super() + super().__setitem__(args[0], args[1]) + else: + changedvalue = False + for i in range(0, len(args), 2): + key = args[i] + value = args[i + 1] + if value is None: + if key in self: + del self[key] + changedvalue = True + else: + if key not in self or self[key] != value: + super().__setitem__(key, value) + changedvalue = True + if changedvalue: + self.elem.cstyle = self + + + class CStyleDescriptor: + """Descriptor for caching and managing style changes.""" + + def __get__(self, elem, owner): + if not hasattr(elem, "_cstyle") and elem is not None: + elem._cstyle = BaseElementCache.CStyle(EBget(elem, "style"), elem) + return elem._cstyle + + def __set__(self, elem, value): + if value: + vstr = str(value) + EBset(elem, "style", vstr) + else: + if "style" in elem.attrib: + del elem.attrib["style"] + + if not isinstance(value, BaseElementCache.CStyle): + if isinstance(value, Style): + # Cast to CStyle without reinitializing + value.__class__ = BaseElementCache.CStyle + value.elem = elem + value.init = False + else: + value = BaseElementCache.CStyle(value, elem) + elem._cstyle = value + elem.ccascaded_style = None + elem.cspecified_style = None + + cstyle = CStyleDescriptor() + + # Cached specified style property + cstytags = shapetags | {SvgDocumentElement.ctag} + + def get_cspecified_style(self): + """Returns the cached specified style, calculating it if not cached.""" + if not (hasattr(self, "_cspecified_style")): + parent = self.getparent() + if parent is not None and parent.tag in BaseElementCache.cstytags: + ret = parent.cspecified_style + self.ccascaded_style + else: + ret = self.ccascaded_style + if "font" in ret: + ret = ret + BaseElementCache.font_shorthand(ret["font"]) + self._cspecified_style = ret + return self._cspecified_style + + def set_cspecified_style(self, svi): + """Invalidates the cached specified style.""" + if svi is None: + try: + delattr(self, "_cspecified_style") + for k in list(self): + k.cspecified_style = None # invalidate children + except AttributeError: + pass + + cspecified_style = property(get_cspecified_style, set_cspecified_style) + + shorthand_font_pattern = re.compile( + r'^(?:(italic|oblique|normal)\s+)?' # font-style + r'(?:(small-caps)\s+)?' # font-variant + r'(?:(bold|bolder|lighter|\d{3})\s+)?' # font-weight + r'(?:(ultra-condensed|extra-condensed|condensed|semi-condensed|normal|semi-expanded|expanded|extra-expanded|ultra-expanded)\s+)?' # font-stretch + r'([0-9]+(?:\.[0-9]+)?(?:px|pt|em|rem|%)?)' # font-size (with optional decimal) + r'(?:/([0-9]+(?:\.[0-9]+)?(?:px|pt|em|rem|%)?))?' # optional line-height + r'\s+' # one or more spaces + r'(.+)$' # the rest is font-family + ) + + @staticmethod + def font_shorthand(fontstr): + """ Parses a font shorthand into relevant properties """ + match = BaseElementCache.shorthand_font_pattern.match(fontstr) + if match: + font_style, font_variant, font_weight, font_stretch, font_size, line_height, font_family = match.groups() + font_data = { + "font-style": font_style, + "font-variant": font_variant, + "font-weight": font_weight, + "font-stretch": font_stretch, + "font-size": font_size, + "line-height": line_height, + "font-family": font_family.strip() if font_family else None, + } + ret = {k: v for k, v in font_data.items() if v is not None} + else: + ret = dict() # Invalid or unsupported font string + return ret + + # Cached cascaded style property + svgpres = { + "alignment-baseline", + "baseline-shift", + "clip", + "clip-path", + "clip-rule", + "color", + "color-interpolation", + "color-interpolation-filters", + "color-profile", + "color-rendering", + "cursor", + "direction", + "display", + "dominant-baseline", + "enable-background", + "fill", + "fill-opacity", + "fill-rule", + "filter", + "flood-color", + "flood-opacity", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "glyph-orientation-horizontal", + "glyph-orientation-vertical", + "image-rendering", + "kerning", + "letter-spacing", + "lighting-color", + "marker-end", + "marker-mid", + "marker-start", + "mask", + "opacity", + "overflow", + "pointer-events", + "shape-rendering", + "stop-color", + "stop-opacity", + "stroke", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "text-anchor", + "text-decoration", + "text-rendering", + "transform", + "transform-origin", + "unicode-bidi", + "vector-effect", + "visibility", + "word-spacing", + "writing-mode", + } + excludes = {"clip", "clip-path", "mask", "transform", "transform-origin"} + style_atts = svgpres - excludes + + # raw dict update + dict_update = Style.__bases__[0].update # type: ignore + + def get_cascaded_style(self): + """Returns the cached cascaded (CSS) style, calculating it if not cached.""" + if not (hasattr(self, "_ccascaded_style")): + # Local style (in "style" attribute) + locsty = self.cstyle + # CSS style (from stylesheet) + csssty = getattr(self.croot, "cssdict", dict()).get(self.get_id()) + # Attribute style (from attributes other than "style") + attsty = empty_style() + BaseElementCache.dict_update( + attsty, + ( + { + a: EBget(self, a) + for a in self.attrib + if a in BaseElementCache.style_atts + } + ), + ) + + if csssty is None: + ret = attsty + locsty + else: + # Any style specified locally takes priority, followed by CSS, + # followed by any attributes that the element has + ret = attsty.add3(csssty, locsty) + self._ccascaded_style = ret + return self._ccascaded_style + + def set_ccascaded_style(self, svi): + """Invalidates the cached cascaded style.""" + if svi is None: + try: + delattr(self, "_ccascaded_style") + except AttributeError: + pass + + ccascaded_style = property(get_cascaded_style, set_ccascaded_style) + + def get_ccomposed_transform(self): + """ + Cached composed_transform, which can be invalidated by changes to + transform of any ancestor. + """ + if not (hasattr(self, "_ccomposed_transform")): + myp = self.getparent() + if myp is None: + self._ccomposed_transform = self.ctransform + else: + self._ccomposed_transform = myp.ccomposed_transform @ self.ctransform + return self._ccomposed_transform + + def set_ccomposed_transform(self, svi): + """Invalidates the cached composed transform.""" + if svi is None and hasattr(self, "_ccomposed_transform"): + delattr(self, "_ccomposed_transform") # invalidate + for k in list2(self): + k.ccomposed_transform = None # invalidate descendants + + ccomposed_transform = property(get_ccomposed_transform, set_ccomposed_transform) + + def get_ctransform(self): + """ + Cached transform property + Note: Can be None + """ + if not (hasattr(self, "_ctransform")): + self._ctransform = self.transform + return self._ctransform + + def set_ctransform(self, newt): + """Sets and caches a new transform, invalidating composed transforms.""" + self.transform = newt + self._ctransform = newt + self.ccomposed_transform = None + + ctransform = property(get_ctransform, set_ctransform) + + rect_tag = inkex.Rectangle.ctag + round_tags = tags((inkex.Circle, inkex.Ellipse)) + line_tag = inkex.Line.ctag + path_tag = inkex.PathElement.ctag + + def get_path2(self): + """ + Cached get_path, modified to correctly calculate path for rectangles + and ellipses. Caches Path of an object (set elem.cpath to None to reset) + """ + if not hasattr(self, "_cpath"): + # mostly from inkex.elements._polygons + if self.tag == BaseElementCache.path_tag: + ret = inkex.Path(self.get("d")) + elif self.tag == BaseElementCache.rect_tag: + left = ipx(self.get("x", "0")) + top = ipx(self.get("y", "0")) + width = ipx(self.get("width", "0")) + height = ipx(self.get("height", "0")) + rx = ipx(self.get("rx", self.get("ry", "0"))) + ry = ipx(self.get("ry", self.get("rx", "0"))) + right = left + width + bottom = top + height + + # Calculate the path as the box around the rect + if rx or ry: + rx = min(rx if rx > 0 else ry, width / 2) + ry = min(ry if ry > 0 else rx, height / 2) + cpts = [left + rx, right - rx, top + ry, bottom - ry] + return inkex.Path( + f"M {cpts[0]},{top}" + f"L {cpts[1]},{top} " + f"A {rx},{ry} 0 0 1 {right},{cpts[2]}" + f"L {right},{cpts[3]} " + f"A {rx},{ry} 0 0 1 {cpts[1]},{bottom}" + f"L {cpts[0]},{bottom} " + f"A {rx},{ry} 0 0 1 {left},{cpts[3]}" + f"L {left},{cpts[2]} " + f"A {rx},{ry} 0 0 1 {cpts[0]},{top} z" + ) + + return inkex.Path(f"M {left},{top} h{width}v{height}h{-width} z") + + elif self.tag in BaseElementCache.round_tags: + cx = ipx(self.get("cx", "0")) + cy = ipx(self.get("cy", "0")) + if isinstance(self, (inkex.Ellipse)): + rx = ipx(self.get("rx", "0")) + ry = ipx(self.get("ry", "0")) + else: # circle + rx = ipx(self.get("r", "0")) + ry = ipx(self.get("r", "0")) + ret = inkex.Path( + ( + "M {cx},{y} " + "a {rx},{ry} 0 1 0 {rx}, {ry} " + "a {rx},{ry} 0 0 0 -{rx}, -{ry} z" + ).format(cx=cx, y=cy - ry, rx=rx, ry=ry) + ) + + elif self.tag == BaseElementCache.line_tag: # updated in v1.2 + x1 = ipx(self.get("x1", "0")) + y1 = ipx(self.get("y1", "0")) + x2 = ipx(self.get("x2", "0")) + y2 = ipx(self.get("y2", "0")) + ret = inkex.Path(f"M{x1},{y1} L{x2},{y2}") + else: + ret = self.get_path() + if isinstance(ret, str): + ret = inkex.Path(ret) + self._cpath = ret + + return self._cpath + + def set_cpath_fcn(self, svi): + """Invalidates the cached path.""" + if svi is None and hasattr(self, "_cpath"): + delattr(self, "_cpath") # invalidate + + cpath = property(get_path2, set_cpath_fcn) + cpath_support = ( + inkex.Rectangle, + inkex.Ellipse, + inkex.Circle, + inkex.Polygon, + inkex.Polyline, + inkex.Line, + inkex.PathElement, + ) + + otp_support = cpath_support + otp_support_tags = tags(cpath_support) + otp_support_prop = property(lambda x: BaseElementCache.otp_support_tags) + ptag = inkex.PathElement.ctag + + def object_to_path(self): + """ + Converts specified elements to paths if supported. Adjusts the 'd' + attribute and changes the element tag to a path. + Only operates on elements within the `otp_support_tags` list. + """ + if ( + self.tag in BaseElementCache.otp_support_tags + and not self.tag == BaseElementCache.ptag + ): + self.set("d", str(self.cpath)) # do this first so cpath is correct + self.tag = BaseElementCache.ptag + + # Cached root property + svgtag = SvgDocumentElement.ctag + + def get_croot(self): + """Returns the cached root of the SVG document.""" + try: + return self._croot + except AttributeError: + if self.getparent() is not None: + self._croot = self.getparent().croot + elif self.tag == BaseElementCache.svgtag: + self._croot = self + else: + self._croot = None + return self._croot + + def set_croot(self, svi): + """Sets the cached root of the SVG document.""" + self._croot = svi + + croot = property(get_croot, set_croot) + + # Version of set_random_id that uses cached root + def set_random_id( + self, + prefix: Optional[str] = None, + size: Optional[int] = None, + backlinks: bool = False, + blacklist: Optional[List[str]] = None, + ): + """Assigns a unique ID to the element, using the cached root.""" + prefix = str(self) if prefix is None else prefix + self.set_id( + self.croot.get_unique_id(prefix, size=size, blacklist=blacklist), + backlinks=backlinks, + ) + + def get_id(self, as_url=0): + """ + Version of get_id that uses the low-level get + """ + if "id" not in self.attrib: + self.set_random_id(self.TAG) + eid = EBget(self, "id") + if as_url > 0: + eid = "#" + eid + if as_url > 1: + eid = f"url({eid})" + return eid + + def set_id(self, new_id, backlinks=False): + """Set the id and update backlinks to xlink and style urls if needed""" + if self.croot is not None: + self.croot.iddict[new_id] = self + BE_set_id(self, new_id, backlinks=backlinks) + + comment_tag = lxml.etree.Comment("").tag + + def descendants2(self, return_tails=False): + """ + A version of descendants that also returns a list of elements whose tails + precede each element. (This is helpful for parsing text.) + """ + if not return_tails: + return [self] + [ + d + for d in self.iterdescendants() + if d.tag != BaseElementCache.comment_tag + ] + descendants = [self] + precedingtails = [[]] + endsat = [(self, None)] + for ddv in self.iterdescendants(): + if not (ddv.tag == BaseElementCache.comment_tag): + precedingtails.append([]) + while endsat[-1][1] == ddv: + precedingtails[-1].append(endsat.pop()[0]) + nsib = ddv.getnext() + if nsib is None: + endsat.append((ddv, endsat[-1][1])) + else: + endsat.append((ddv, nsib)) + descendants.append(ddv) + + precedingtails.append([]) + while len(endsat) > 0: + precedingtails[-1].append(endsat.pop()[0]) + return descendants, precedingtails + + def ancestors2(self, includeme=False, stopbefore=None, stopafter=None): + """Version of ancestors that can stop before/after encountering an element""" + anc = [] + cel = self if includeme else self.getparent() + while cel is not None and cel != stopbefore: + anc.append(cel) + if cel == stopafter: + break + cel = cel.getparent() + return anc + + ## Bookkeeping functions + # When BaseElements are deleted, created, or moved, the caches need to be + # updated or invalidated. These functions do that while preserving the original + # base functionality + + # Deletion + BE_delete = inkex.BaseElement.delete + + def delete(self, deleteup=False): + """Deletes the element and optionally cleans up empty parent groups.""" + svg = self.croot + for ddv in reversed(self.descendants2()): + did = ddv.get_id() + if svg is not None: + try: + svg.ids.remove(did) + except (KeyError, AttributeError): + pass + svg.iddict.remove(ddv) + ddv.croot = None + if hasattr(svg, "_cd2"): + svg.cdescendants2.delel(self) + + if deleteup: + # If set, remove empty ancestor groups + myp = self.getparent() + BaseElementCache.BE_delete(self) + if myp is not None and len(myp) == 0: + BaseElementCache.delete(myp, True) + else: + BaseElementCache.BE_delete(self) + + # Insertion + # BE_insert = inkex.BaseElement.insert + BE_insert = lxml.etree.ElementBase.insert + + def insert(self, index, elem): + """Inserts an element at a specified index, managing caching.""" + oldroot = elem.croot + newroot = self.croot + + BaseElementCache.BE_insert(self, index, elem) + elem.ccascaded_style = None + elem.cspecified_style = None + elem.ccomposed_transform = None + + if EBget(elem, "id") is None: + # Make sure all elements have an ID (most assigned here) + elem.croot = newroot + if newroot is not None: + newroot.iddict.add(elem) # generates an ID if needed + for k in list2(elem): + elem.append(k) # update children + elif oldroot != newroot: + # When the root is changing, remove from old dicts and add to new + css = None + if oldroot is not None: + oldroot.iddict.remove(elem) + css = oldroot.cssdict.pop(elem.get_id(), None) + elem.croot = newroot + if newroot is not None: + newroot.iddict.add(elem) # generates an ID if needed + if css is not None: + newroot.cssdict[elem.get_id()] = css + for k in list2(elem): + elem.append(k) # update children + + # Appending + # BE_append = inkex.BaseElement.append + BE_append = lxml.etree.ElementBase.append + + def append(self, elem): + """Appends an element, managing caching and ID conflicts.""" + oldroot = elem.croot + newroot = self.croot + + BaseElementCache.BE_append(self, elem) + elem.ccascaded_style = None + elem.cspecified_style = None + elem.ccomposed_transform = None + + if EBget(elem, "id") is None: + # Make sure all elements have an ID (most assigned here) + elem.croot = newroot + if newroot is not None: + newroot.iddict.add(elem) # generates an ID if needed + for k in list2(elem): + elem.append(k) # update children + elif oldroot != newroot: + # When the root is changing, remove from old dicts and add to new + css = None + if oldroot is not None: + oldroot.iddict.remove(elem) + css = oldroot.cssdict.pop(elem.get_id(), None) + elem.croot = newroot + if newroot is not None: + newroot.iddict.add(elem) # generates an ID if needed + if css is not None: + newroot.cssdict[elem.get_id()] = css + for k in list2(elem): + elem.append(k) # update children + + # addnext + # BE_addnext = inkex.BaseElement.addnext + BE_addnext = lxml.etree.ElementBase.addnext + + def addnext(self, elem): + """Adds an element next to the specified element, managing caching.""" + oldroot = elem.croot + newroot = self.croot + + BaseElementCache.BE_addnext(self, elem) + elem.ccascaded_style = None + elem.cspecified_style = None + elem.ccomposed_transform = None + + if EBget(elem, "id") is None: + # Make sure all elements have an ID (most assigned here) + elem.croot = newroot + if newroot is not None: + newroot.iddict.add(elem) # generates an ID if needed + for k in list2(elem): + elem.append(k) # update children + elif oldroot != newroot: + # When the root is changing, remove from old dicts and add to new + css = None + if oldroot is not None: + oldroot.iddict.remove(elem) + css = oldroot.cssdict.pop(elem.get_id(), None) + elem.croot = newroot + if newroot is not None: + newroot.iddict.add(elem) # generates an ID if needed + if css is not None: + newroot.cssdict[elem.get_id()] = css + for k in list2(elem): + elem.append(k) # update children + + # Duplication + clipmasktags = {inkex.addNS("mask", "svg"), inkex.ClipPath.ctag} + + def duplicate(self): + """Creates a duplicate of the element, managing ID and caching.""" + eltail = self.tail + if eltail is not None: + self.tail = None + dup = self.copy() + + # After copying, duplicates have the original ID. Remove prior to insertion + origids = dict() + for dupd in dup.descendants2(): + origids[dupd] = dupd.pop("id", None) + dupd.croot = self.croot # set now for speed + + self.addnext(dup) + if eltail is not None: + self.tail = eltail + # Fix tail bug: https://gitlab.com/inkscape/extensions/-/issues/480 + + css = self.croot.cssdict + newids = { + EBget(k, "id"): css.get(origids[k]) + for k in dup.descendants2() + if origids[k] in css + } + dup.croot.cssdict.update(newids) + + if dup.tag in BaseElementCache.clipmasktags: + # Clip duplications can cause weird issues if they are not appended + # to the end of Defs + dup.croot.cdefs.append(dup) + return dup + + def get_parsed_text(self): + """Add parsed_text property to text, which is used to get the + properties of text""" + if not (hasattr(self, "_parsed_text")): + self._parsed_text = ParsedText(self, self.croot.char_table) + return self._parsed_text + + def set_parsed_text(self, svi): + """Invalidates the cached parsed text.""" + if hasattr(self, "_parsed_text") and svi is None: + delattr(self, "_parsed_text") + + parsed_text = property(get_parsed_text, set_parsed_text) + + +class SvgDocumentElementCache(SvgDocumentElement): + """Adds caching for SVG document elements.""" + + def cdefs_func(self): + """Cached def directly under root""" + if not (hasattr(self, "_cdefs")): + for k in list(self): + if isinstance(k, (inkex.Defs)): + self._cdefs = k + return self._cdefs + defel = inkex.Defs() + self.insert(0, defel) + self._cdefs = defel + return self._cdefs + + cdefs = property(cdefs_func) + + styletag = inkex.addNS("style", "svg") + + def crootsty_func(self): + """Cached style element directly under root""" + if not hasattr(self, "_crootsty"): + rootstys = [ + sty for sty in list(self) if sty.tag == SvgDocumentElementCache.styletag + ] + if len(rootstys) == 0: + self._crootsty = inkex.StyleElement() + self.insert(0, self._crootsty) + else: + self._crootsty = rootstys[0] + return self._crootsty + + crootsty = property(crootsty_func) + + def get_ids(self): + """Version of get_ids that uses iddict""" + return set(self.iddict.keys()) + + def get_unique_id( + self, + prefix: str, + size: Optional[int] = None, + blacklist: Optional[List[str]] = None, + ): + """Version of get_unique_id that removes randomness by keeping a + running count""" + new_id = None + cnt = self.iddict.prefixcounter.get(prefix, 0) + if blacklist is None: + while new_id is None or new_id in self.iddict: + new_id = prefix + str(cnt) + cnt += 1 + else: + while new_id is None or new_id in self.iddict or new_id in blacklist: + new_id = prefix + str(cnt) + cnt += 1 + self.iddict.prefixcounter[prefix] = cnt + return new_id + + # Repeated getElementById lookups can be slow, so instead create a cached + # iddict property. When an element is created that may be needed later, + # it should be added using self.iddict.add. + urlpat = re.compile(r"^url\(#(.*)\)$|^#") + + def getElementById(self, eid: str, elm="*", literal=False): + """Returns an element by ID using cached data for efficiency.""" + if eid is not None and not literal: + eid = SvgDocumentElementCache.urlpat.sub(r"\1", eid.strip()) + return self.iddict.get(eid) + + class IDDict(dict): + """Keeps track of the IDs in a document""" + + def __init__(self, svg): + super().__init__() + self.svg = svg + self.prefixcounter = dict() + toassign = [] + for elem in svg.descendants2(): + if "id" in elem.attrib: + self[EBget(elem, "id")] = elem + else: + toassign.append(elem) + elem.croot = svg # do now to speed up later + for elem in toassign: + # Reduced version of get_unique_id_fcn + # Cannot call it since it relies on iddict + prefix = elem.TAG + new_id = None + cnt = self.prefixcounter.get(prefix, 0) + while new_id is None or new_id in self: + new_id = prefix + str(cnt) + cnt += 1 + self.prefixcounter[prefix] = cnt + # Reduced version of set_id + self[new_id] = elem + BE_set_id(elem, new_id) + + def add(self, elem): + """Add an element to the ID dict""" + elid = ( + elem.get_id() + ) # fine since elem should have a croot to get called here + if elid in self and not self[elid] == elem: + # Make a new id when there's a conflict + elem.set_random_id(elem.TAG) + elid = elem.get_id() + self[elid] = elem + + @property + def descendants(self): + """all svg descendants, not necessarily in order""" + return list(self.values()) + + def remove(self, elem): + """Remove from ID dict""" + elid = elem.get_id() + if elid in self: + del self[elid] + + @property + def iddict(self): + """Returns the ID dictionary that caches all elements by ID.""" + if not (hasattr(self, "_iddict")): + self._iddict = SvgDocumentElementCache.IDDict(self) + return self._iddict + + estyle = Style() # keep separate in case Style was overridden + + # Check if v1.4 or later + hasmatches = hasattr(inkex.styles.ConditionalStyle, "matches") + + class CSSDict(dict): + """A dict that keeps track of the CSS style for each element""" + + def __init__(self, svg): + self.svg = svg + + # For certain xpaths such as classes, we can avoid xpath calls + # by checking the class attributes on a document's descendants directly. + # This is much faster for large documents. + hasall = False + simpleclasses = dict() + simpleids = dict() + patcls = re.compile(r"\.([-\w]+)") + patid = re.compile(r"#(\w+)") + + for sheet in svg.stylesheets: + for style in sheet: + rules = tuple(str(r) for r in style.rules) + # inkex.utils.debug(rules) + if rules == ("*",): + hasall = True + elif all(patcls.match(r) for r in rules): + # all rules are classes + simpleclasses[rules] = [patcls.sub(r"\1", r) for r in rules] + elif all(patid.match(r) for r in rules): + # all rules are ids + simpleids[rules] = [patid.sub(r"\1", r) for r in rules] + + # Now, we make a dictionary of rules / xpaths we can do easily + knownrules = dict() + if hasall or len(simpleclasses) > 0: + dds = svg.iddict.descendants + if hasall: + knownrules[("*",)] = dds + cvs = [EBget(d, "class") for d in dds] + c2s = [(i, c) for (i, c) in enumerate(cvs) if c is not None] + cmatches = dict() + for i, clsval in c2s: + for c in clsval.split(" "): + cmatches[c] = cmatches.get(c, []) + [dds[i]] + kxp2 = { + rules: list( + dict.fromkeys( + [d for c in clslist if c in cmatches for d in cmatches[c]] + ) + ) + for rules, clslist in simpleclasses.items() + } + knownrules.update(kxp2) + + for rules, ids in simpleids.items(): + knownrules[rules] = [] + for idv in ids: + idel = svg.getElementById(idv) + if idel is not None: + knownrules[rules].append(idel) + + # Now run any necessary xpaths and get the element styles + super().__init__() + for sheet in svg.croot.stylesheets: + for style in sheet: + try: + # els = svg.xpath(style.to_xpath()) # original code + rules = tuple(str(r) for r in style.rules) + stylev = Style(style) + if rules in knownrules: + els = knownrules[rules] + else: + if SvgDocumentElementCache.hasmatches: + els = style.all_matches(svg) + else: + els = svg.xpath(style.to_xpath()) + + if len(stylev) > 0: + idvs = [ + EBget(elem, "id", None) + for elem in els + if "id" in elem.attrib + ] + newstys = { + elid: stylev + if elid not in self + else self[elid] + stylev + for elid in idvs + } + # Technically we should copy stylev, but as long as + # styles in cssdict are only used in + # get_cascaded_style, this is fine since that + # function adds (creating copies) + self.update(newstys) + except (lxml.etree.XPathEvalError,): + pass + + def dupe_entry(self, oldid, newid): + """Duplicate entry in cssdict""" + csssty = self.get(oldid) + if csssty is not None: + self[newid] = csssty + + def get_cssdict(self): + """Returns the CSS dictionary that caches styles by element ID.""" + try: + return self._cssdict + except AttributeError: + self._cssdict = SvgDocumentElementCache.CSSDict(self) + return self._cssdict + + cssdict = property(get_cssdict) + + def document_size(self): + """Calculates and caches the size of the SVG document in various units.""" + if not (hasattr(self, "_cdocsize")): + rvb = self.get_viewbox() + wstr = self.get("width") + hstr = self.get("height") + + if rvb == [0, 0, 0, 0]: + wval = ipx(wstr) if '%' not in wstr else 300 + hval = ipx(hstr) if '%' not in hstr else 150 + vbx = [0, 0, wval, hval] + else: + vbx = [float(v) for v in rvb] # just in case + + # Get document width and height in pixels + wvl, wun = ( + inkex.units.parse_unit(wstr) if wstr is not None else (vbx[2], "px") + ) + hvl, hun = ( + inkex.units.parse_unit(hstr) if hstr is not None else (vbx[3], "px") + ) + + def parse_preserve_aspect_ratio(par_str): + align, meet_or_slice = "xMidYMid", "meet" # defaults + valigns = [ + "xMinYMin", + "xMidYMin", + "xMaxYMin", + "xMinYMid", + "xMidYMid", + "xMaxYMid", + "xMinYMax", + "xMidYMax", + "xMaxYMax", + "none", + ] + vmoss = ["meet", "slice"] + if par_str: + values = par_str.split(" ") + if len(values) == 1: + if values[0] in valigns: + align = values[0] + elif values[0] in vmoss: + meet_or_slice = values[0] + elif len(values) == 2: + if values[0] in valigns and values[1] in vmoss: + align = values[0] + meet_or_slice = values[1] + return align, meet_or_slice + + align, meet_or_slice = parse_preserve_aspect_ratio( + self.get("preserveAspectRatio") + ) + + xfr = ( + inkex.units.convert_unit(str(wvl) + " " + wun, "px") / vbx[2] + if wun != "%" + else wvl / 100 + ) # width of uu in px pre-stretch + yfr = ( + inkex.units.convert_unit(str(hvl) + " " + hun, "px") / vbx[3] + if hun != "%" + else hvl / 100 + ) # height of uu in px pre-stretch + if align != "none": + mfr = min(xfr, yfr) if meet_or_slice == "meet" else max(xfr, yfr) + xmf = {"xMin": 0, "xMid": 0.5, "xMax": 1}[align[0:4]] + ymf = {"YMin": 0, "YMid": 0.5, "YMax": 1}[align[4:]] + vbx[0], vbx[2] = ( + vbx[0] + vbx[2] * (1 - xfr / mfr) * xmf, + vbx[2] / mfr * (xfr if wun != "%" else 1), + ) + vbx[1], vbx[3] = ( + vbx[1] + vbx[3] * (1 - yfr / mfr) * ymf, + vbx[3] / mfr * (yfr if hun != "%" else 1), + ) + if wun == "%": + wvl, wun = vbx[2] * mfr, "px" + if hun == "%": + hvl, hun = vbx[3] * mfr, "px" + else: + if wun == "%": + wvl, wun, vbx[2] = vbx[2], "px", vbx[2] / xfr + if hun == "%": + hvl, hun, vbx[3] = vbx[3], "px", vbx[3] / yfr + + wpx = inkex.units.convert_unit( + str(wvl) + " " + wun, "px" + ) # document width in px + hpx = inkex.units.convert_unit( + str(hvl) + " " + hun, "px" + ) # document height in px + uuw = wpx / vbx[2] # uu width in px (px/uu) + uuh = hpx / vbx[3] # uu height in px (px/uu) + uupx = uuw if abs(uuw - uuh) < 0.001 else None # uu size in px (px/uu) + # should match Scale in Document Properties + + # Get Pages + nvs = [elem for elem in list(self) if isinstance(elem, inkex.NamedView)] + pgs = [ + elem + for nv in nvs + for elem in list(nv) + if elem.tag == inkex.addNS("page", "inkscape") + ] + for pgv in pgs: + pgv.bbuu = [ + ipx(pgv.get("x")), + ipx(pgv.get("y")), + ipx(pgv.get("width")), + ipx(pgv.get("height")), + ] + pgv.bbpx = [ + pgv.bbuu[0] * xfr, + pgv.bbuu[1] * yfr, + pgv.bbuu[2] * xfr, + pgv.bbuu[3] * yfr, + ] + + class DocSize: + """Contains the properties relating to document size""" + + def __init__( + self, + effvb, + uuw, + uuh, + uupx, + wunit, + hunit, + wpx, + hpx, + xfr, + yfr, + pgs, + ): + self.effvb = effvb + self.uuw = uuw + self.uuh = uuh + self.uupx = uupx + self.wunit = wunit + self.hunit = hunit + self.wpx = wpx + self.hpx = hpx + self.rawxf = xfr + self.rawyf = yfr + self.pgs = pgs + + def uutopx(self, x): + """Converts a bounding box specified in uu to pixels""" + ret = [ + (x[0] - self.effvb[0]) * self.uuw, + (x[1] - self.effvb[1]) * self.uuh, + x[2] * self.uuw, + x[3] * self.uuh, + ] + return ret + + def pxtouu(self, x): + """Converts a bounding box specified in pixels to uu""" + ret = [ + x[0] / self.uuw + self.effvb[0], + x[1] / self.uuh + self.effvb[1], + x[2] / self.uuw, + x[3] / self.uuh, + ] + return ret + + def unittouu(self, x): + """Converts any unit into uu""" + return ( + inkex.units.convert_unit(x, "px") / self.uupx + if self.uupx is not None + else None + ) + + def uutopxpgs(self, x): + """Version that applies to Pages""" + return [ + x[0] * self.rawxf, + x[1] * self.rawyf, + x[2] * self.rawxf, + x[3] * self.rawyf, + ] + + def pxtouupgs(self, x): + """Version that applies to Pages""" + return [ + x[0] / self.rawxf, + x[1] / self.rawyf, + x[2] / self.rawxf, + x[3] / self.rawyf, + ] + + self._cdocsize = DocSize( + vbx, uuw, uuh, uupx, wun, hun, wpx, hpx, xfr, yfr, pgs + ) + return self._cdocsize + + def set_cdocsize(self, svi): + """Invalidates the cached document size.""" + if svi is None and hasattr(self, "_cdocsize"): # invalidate + delattr(self, "_cdocsize") + + cdocsize = property(document_size, set_cdocsize) + + def set_viewbox(self, newvb): + """Sets a new viewbox for the SVG, updating dimensions appropriately.""" + uuw, uuh, wunit, hunit = ( + self.cdocsize.uuw, + self.cdocsize.uuh, + self.cdocsize.wunit, + self.cdocsize.hunit, + ) + self.set( + "width", + str(inkex.units.convert_unit(str(newvb[2] * uuw) + "px", wunit)) + wunit, + ) + self.set( + "height", + str(inkex.units.convert_unit(str(newvb[3] * uuh) + "px", hunit)) + hunit, + ) + self.set("viewBox", " ".join([str(v) for v in newvb])) + self.cdocsize = None + + def standardize_viewbox(self): + """Converts viewbox to pixels, removing any non-uniform scaling appropriately""" + pgbbs = [pgv.bbpx for pgv in self.cdocsize.pgs] + self.set("viewBox", " ".join([str(v) for v in self.cdocsize.effvb])) + self.set("width", str(self.cdocsize.wpx)) + self.set("height", str(self.cdocsize.hpx)) + + # Update Pages appropriately + self.cdocsize = None + for i, pgv in enumerate(self.cdocsize.pgs): + newbbuu = self.cdocsize.pxtouupgs(pgbbs[i]) + pgv.set("x", str(newbbuu[0])) + pgv.set("y", str(newbbuu[1])) + pgv.set("width", str(newbbuu[2])) + pgv.set("height", str(newbbuu[3])) + + # Add char_table property to SVGs, which are used to collect all of the + # properties of fonts in the document. Alternatively, calling make_char_table + # on a subset of elements will cause only those elements to be included. + + def make_char_table(self, els=None): + """ + Can be called with els argument to examine list of elements only + (otherwise use entire SVG) + """ + ttags = tags((inkex.TextElement, inkex.FlowRoot)) + if els is None: + tels = [d for d in self.iddict.descendants if d.tag in ttags] + else: + tels = [d for d in els if d.tag in ttags] + if not (hasattr(self, "_char_table")) or any( + t not in getattr(self, "_char_table").els for t in tels + ): + self._char_table = CharacterTable(tels) + + def get_char_table(self): + """Returns the cached character table, creating it if necessary.""" + if not (hasattr(self, "_char_table")): + self.make_char_table() + return self._char_table + + def set_char_table(self, svi): + """Invalidates the cached character table.""" + if svi is None and hasattr(self, "_char_table"): + delattr(self, "_char_table") + + char_table = property(get_char_table, set_char_table) + + +class StyleCache(Style): + """Caches and manages style data with enhanced functionality.""" + + get_link = get_link_fcn + + def __hash__(self): + """Generates a hash based on the style items.""" + return hash(tuple(self.items())) # type: ignore + + # Adds three styles + def add3_fcn(self, y, z): + """Adds three style objects together.""" + return self + y + z + + add3 = add3_fcn if not hasattr(Style, "add3") else getattr(Style, "add3") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/font_properties.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/font_properties.py new file mode 100644 index 0000000..10f2a66 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/font_properties.py @@ -0,0 +1,1317 @@ +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +""" +Some functions for getting the properties of fonts and characters. +Three libraries are used: + fontconfig: Used for discovering fonts based on the CSS (SVG) style. This + uses Inkscape's libfontconfig, so it always matches what Inkscape + does. + fonttools: Gets font properties once discovered (from font's filename) + Pango: Used to render test characters (included with GTK) + Sets up a blank GTK window and renders Pango text, reusing + the same layout for all rendering. +""" + +import os +import warnings +import sys +import re +import ctypes +from unittest.mock import patch +from functools import lru_cache +import inkex +from inkex.text.utils import default_style_atts +from inkex import Style + +# The fontconfig library is used to select a font given its CSS specs +# This library should work starting with v1.0 + +# Due to the way fontconfig is structured, we have to patch +# ctypes's LoadLibrary to help it find libfontconfig +original_load_library = ctypes.cdll.LoadLibrary + + +def custom_load_library(name): + """ + Custom function to load a library based on the platform. + """ + if name in ["libfontconfig.so.1", "libfreetype.so.6"]: + libname = { + "linux": { + "libfreetype.so.6": "libfreetype.so.6", + "libfontconfig.so.1": "libfontconfig.so.1", + }, + "openbsd6": { + "libfreetype.so.6": "libfreetype.so.28", + "libfontconfig.so.1": "libfontconfig.so.11", + }, + "darwin": { + "libfreetype.so.6": "libfreetype.6.dylib", + "libfontconfig.so.1": "libfontconfig.1.dylib", + }, + "win32": { + "libfreetype.so.6": "libfreetype-6.dll", + "libfontconfig.so.1": "libfontconfig-1.dll", + }, + }[sys.platform][name] + + if "SI_FC_DIR" in os.environ: + fpath = os.path.abspath(os.path.join(os.environ["SI_FC_DIR"], libname)) + ret = original_load_library(fpath) + else: + try: + ret = original_load_library(libname) + except FileNotFoundError: + biloc = inkex.inkscape_system_info.binary_location # type: ignore + blocdir = os.path.dirname(biloc) + fpath = os.path.abspath(os.path.join(blocdir, libname)) + ret = original_load_library(fpath) + return ret + if name == "libc.so.6": + # Do not need to load, return a blank class consistent with fontconfig + libc = type("libc", (object,), {"free": staticmethod(lambda ptr: None)})() + libc.free.argtypes = (ctypes.c_void_p,) + return libc + return original_load_library(name) + + +with patch("ctypes.cdll.LoadLibrary", side_effect=custom_load_library): + import fontconfig as fc # pylint: disable=import-error + + FC = fc.FC + + +def isnumeric(strv): + """ + Check if a string can be converted to a number + """ + try: + float(strv) + return True + except ValueError: + return False + + +def interpolate_dict(dictv, x, defaultval): + """ + Interpolate an input over the numerical values of a dict + Supports both string and int inputs and outputs + If the dict's values are enums, pick the nearest value + """ + # Check for exact match first + if x in dictv: + return dictv[x] + + # Interpolate numerical values + if isnumeric(x): + input_value = int(x) + numerical_keys = sorted([int(k) for k in dictv.keys() if isnumeric(k)]) + for k, val in dictv.items(): + if isnumeric(k): + ktype = type(k) # int or str + vtype = type(val) # int or str + + if vtype not in [int, float, str]: # enums + # Pick the nearest entry + intd = {int(k): v for k, v in dictv.items() if isnumeric(k)} + return intd[min(intd.keys(), key=lambda x: abs(x - int(x)))] + break + if input_value <= numerical_keys[0]: + return dictv[ktype(numerical_keys[0])] + if input_value >= numerical_keys[-1]: + return dictv[ktype(numerical_keys[-1])] + for i in range(len(numerical_keys) - 1): + if numerical_keys[i] <= input_value <= numerical_keys[i + 1]: + lower_key = numerical_keys[i] + upper_key = numerical_keys[i + 1] + lower_value = float(dictv[ktype(lower_key)]) + upper_value = float(dictv[ktype(upper_key)]) + ratio = (input_value - lower_key) / (upper_key - lower_key) + interpolated_value = lower_value + ratio * (upper_value - lower_value) + if vtype in [int, float]: + return interpolated_value + return f"{interpolated_value:g}" + return defaultval + + +# Windows doesn't have the XDG_DATA_HOME directory set, which is +# needed for /etc/fonts.conf to find the user fonts directory: +# fonts +# Needed for fontconfig +# Set it based on the location of preferences.xml +if sys.platform == "win32": + if os.environ.get("XDG_DATA_HOME") is None: + os.environ["XDG_DATA_HOME"] = os.path.dirname( + inkex.inkscape_system_info.preferences # type: ignore + ) + + +class FontConfig: + """ + Class to handle FontConfig functionalities. + """ + + def __init__(self): + self.truefonts = dict() # css + self.truefontsfc = dict() # fontconfig + self.truefontsfn = dict() # fullnames + self.truefontsft = dict() # fonttools + self.fontcharsets = dict() + self.disable_lcctype() + self.conf = fc.Config.get_current() + self._font_list = None + self._font_list_css = None + + def disable_lcctype(self): + """ + Disables LC_CTYPE to suppress Mac warnings. + """ + self.lcctype = os.environ.get("LC_CTYPE") + if self.lcctype is not None and sys.platform == "darwin": + del os.environ["LC_CTYPE"] # suppress Mac warning + + def enable_lcctype(self): + """ + Enables LC_CTYPE if it was previously disabled. + """ + if self.lcctype is not None and sys.platform == "darwin": + os.environ["LC_CTYPE"] = self.lcctype + + def get_true_font(self, fontsty): + """Use fontconfig to get the true font that most text will be rendered as""" + if fontsty not in self.truefonts: + found = self.font_match(fontsty) + truefont = FontConfig.fcfont_to_css(found) + self.truefonts[fontsty] = truefont + self.truefontsfc[fontsty] = found + self.fontcharsets[truefont] = found.get(fc.PROP.CHARSET, 0)[0] + return self.truefonts[fontsty] + + def get_true_font_fullname(self, fontsty): + """Use fontconfig to get the Face name for a font style""" + if fontsty not in self.truefontsfn: + if fontsty not in self.truefontsfc: + self.get_true_font(fontsty) + self.truefontsfn[fontsty] = self.truefontsfc[fontsty].get( + fc.PROP.FULLNAME, 0 + )[0] + return self.truefontsfn[fontsty] + + def get_true_font_by_char(self, fontsty, chars): + """ + Sometimes, a font will not have every character and a different one is + substituted. (For example, many fonts do not have the ⎣ character.) + Gets the true font by character + """ + if fontsty not in self.truefonts: + # font_match is more reliable at matching Inkscape than font_sort + self.get_true_font(fontsty) + truefont = self.truefonts[fontsty] + cd1 = {k: truefont for k in chars if ord(k) in self.fontcharsets[truefont]} + + if len(cd1) < len(chars): + found = self.font_sort(fontsty) + for fnt in found: + truefont = FontConfig.fcfont_to_css(fnt) + if truefont not in self.fontcharsets: + self.fontcharsets[truefont] = fnt.get(fc.PROP.CHARSET, 0)[0] + cset = self.fontcharsets[truefont] + cd2 = {k: truefont for k in chars if ord(k) in cset and k not in cd1} + cd1.update(cd2) + if len(cd1) == len(chars): + break + if len(cd1) < len(chars): + cd1.update({c: None for c in chars if c not in cd1}) + return cd1 + + @lru_cache(maxsize=None) + def font_match(self, fontsty): + """ + fc fonts matching a given reduced font style + """ + pat = FontConfig.css_to_fcpattern(fontsty) + self.conf.substitute(pat, FC.MatchPattern) + pat.default_substitute() + ret, _ = self.conf.font_match(pat) + return ret + + @lru_cache(maxsize=None) + def font_sort(self, fontsty): + """ + List of fc fonts closest to a given reduced font style + """ + pat = FontConfig.css_to_fcpattern(fontsty) + self.conf.substitute(pat, FC.MatchPattern) + pat.default_substitute() + ret, _, _ = self.conf.font_sort(pat, trim=True, want_coverage=False) + return ret + + def get_fonttools_font(self, fontsty): + """ + Get a FontTools font instance based on the reduced style. + """ + if fontsty not in self.truefontsft: + if fontsty not in self.truefontsfc: + self.get_true_font(fontsty) + found = self.truefontsfc[fontsty] + self.truefontsft[fontsty] = FontToolsFontInstance(found) + return self.truefontsft[fontsty] + + @staticmethod + def css_to_fcpattern(sty): + """Convert a style dictionary to an fc search pattern""" + pat = fc.Pattern.name_parse( + re.escape(sty["font-family"].replace("'", "").replace('"', "")) + ) + pat.add( + fc.PROP.WIDTH, + C.CSSSTR_FCWDT.get(sty.get("font-stretch"), FC.WIDTH_NORMAL), + ) + pat.add( + fc.PROP.WEIGHT, + interpolate_dict(C.CSSWGT_FCWGT, sty.get("font-weight"), FC.WEIGHT_NORMAL), + ) + pat.add( + fc.PROP.SLANT, C.CSSSTY_FCSLN.get(sty.get("font-style"), FC.SLANT_ROMAN) + ) + return pat + + @staticmethod + def fcfont_to_css(fnt): + """Convert an fc font to a Style""" + # For CSS, enclose font family in single quotes + # Needed for fonts like Modern No. 20 with periods in the family + fcfam = fnt.get(fc.PROP.FAMILY, 0)[0] + fcwgt = fnt.get(fc.PROP.WEIGHT, 0)[0] + fcsln = fnt.get(fc.PROP.SLANT, 0)[0] + fcwdt = fnt.get(fc.PROP.WIDTH, 0)[0] + if any(isinstance(v, tuple) for v in [fcfam, fcwgt, fcsln, fcwdt]): + return None + return Style( + [ + ("font-family", "'" + fcfam.strip("'") + "'"), + ("font-weight", interpolate_dict(C.FCWGT_CSSWGT, fcwgt, None)), + ("font-style", C.FCSLN_CSSSTY[fcsln]), + ("font-stretch", nearest_val(C.FCWDT_CSSSTR, fcwdt)), + ] + ) + + @property + def font_list(self): + """ + Finds all fonts known to FontConfig + Note that this does not appear to be completely comprehensive currently + A few fonts are missing, such as HoloLens MDL2 Assets Bold on Windows + These fonts can still be found using get_true_font + """ + if self._font_list is None: + pattern = fc.Pattern.create() # blank pattern + properties = ["family", "weight", "slant", "width", "style", "file"] + # style is a nice name for the weight/slant/width combo + # e.g., Arial Narrow Bold + self._font_list = self.conf.font_list(pattern, properties) + self._font_list = sorted( + self._font_list, key=lambda x: x.get("family", 0)[0] + ) # Sort by family + return self._font_list + + @property + def font_list_css(self): + """Finds all fonts known to FontConfig in css form""" + if self._font_list_css is None: + self._font_list_css = [FontConfig.fcfont_to_css(f) for f in self.font_list] + return self._font_list_css + + +fcfg = FontConfig() +fontatt = ["font-family", "font-weight", "font-style", "font-stretch"] +dfltatt = [(k, default_style_atts[k]) for k in fontatt] + + +@lru_cache(maxsize=None) +def font_style(sty): + """ + Given a CSS style, return a style that has the four attributes + that matter for font selection + Note that Inkscape may not draw this font if it is not present on the system. + The family can have multiple comma-separated values, used for fallback + """ + sty2 = Style(dfltatt) + sty2.update({k: v for k, v in sty.items() if k in fontatt}) + sty2["font-family"] = ",".join( + ["'" + v.strip('"').strip("'") + "'" for v in sty2["font-family"].split(",")] + ) + return sty2 + + +@lru_cache(maxsize=None) +def true_style(sty): + """ + Given a CSS style, return a style with the actual font that + fontconfig selected. This is the actual font that Inkscape will draw. + """ + sty2 = font_style(sty) + tfnt = fcfg.get_true_font(sty2) + return tfnt + + +def nearest_val(dictv, inputval): + """Return the value of a dict whose key is closest to the input value""" + return dictv[min(dictv.keys(), key=lambda x: abs(x - inputval))] + + +# Attempt to import the Pango bindings for the gi repository +with warnings.catch_warnings(): + # Ignore ImportWarning for Gtk/Pango + warnings.simplefilter("ignore") + + HASPANGO = False + HASPANGOFT2 = False + pangoenv = os.environ.get("USEPANGO", "") + if pangoenv != "False": + try: + if sys.platform == "win32": + # Windows may not have all of the typelibs needed for PangoFT2 + # Add the typelibs subdirectory as a fallback option + bloc = inkex.inkscape_system_info.binary_location # type: ignore + girepo = os.path.join( + os.path.dirname(os.path.dirname(bloc)), + "lib", + "girepository-1.0", + ) # Inkscape's GI repository + if os.path.isdir(girepo): + tlibs = [ + "fontconfig-2.0.typelib", + "PangoFc-1.0.typelib", + "PangoFT2-1.0.typelib", + "freetype2-2.0.typelib", + ] + # If any typelibs are missing, try adding the typelibs subdirectory + if any( + not (os.path.exists(os.path.join(girepo, t))) for t in tlibs + ): + tlibsub = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "typelibs" + ) + for newpath in [girepo, tlibsub]: + # gi looks in the order specified in GI_TYPELIB_PATH + cval = os.environ.get("GI_TYPELIB_PATH", "") + if cval == "": + os.environ["GI_TYPELIB_PATH"] = newpath + elif newpath not in cval: + os.environ["GI_TYPELIB_PATH"] = ( + cval + os.pathsep + newpath + ) + + import gi + + gi.require_version("Gtk", "3.0") + from gi.repository import Pango + + try: + HASPANGO = hasattr(Pango, "Variant") and hasattr( + Pango.Variant, "NORMAL" + ) + except ValueError: + HASPANGO = False + except ImportError: + HASPANGO = False + + if HASPANGO: + try: + # May require some typelibs we do not have + gi.require_version("PangoFT2", "1.0") + from gi.repository import PangoFT2 + + HASPANGOFT2 = True + except ValueError: + HASPANGOFT2 = False + from gi.repository import Gdk + +if pangoenv in ["True", "False"]: + os.environ["HASPANGO"] = str(HASPANGO) + os.environ["HASPANGOFT2"] = str(HASPANGOFT2) + with open("env_vars.txt", "w") as f: + f.write(f"HASPANGO={os.environ['HASPANGO']}") + f.write(f"\nHASPANGOFT2={os.environ['HASPANGOFT2']}") + + +class PangoRenderer: + """ + Class to handle Pango rendering functionalities. + """ + + def __init__(self): + self.pangosize = 1024 * 4 + # size of text to render. 1024 is good + + with warnings.catch_warnings(): + # Ignore ImportWarning + warnings.filterwarnings("ignore", category=ImportWarning) + if HASPANGOFT2: + self.ctx = Pango.Context.new() + self.ctx.set_font_map(PangoFT2.FontMap.new()) + else: + self.ctx = Gdk.pango_context_get() + self.pangolayout = Pango.Layout(self.ctx) + self.pufd = Pango.units_from_double + self.putd = Pango.units_to_double + self.scale = Pango.SCALE + self._families = None + self._faces = None + self._face_descriptions = None + self._face_strings = None + self._face_css = None + + @staticmethod + def css_attribute_to_pango(sty, key): + """ + Convert CSS style attributes to Pango attributes. + """ + val = sty.get(key) + if key == "font-weight": + return C.CSSWGT_PWGT.get(val, Pango.Weight.NORMAL) + if key == "font-style": + return C.CSSSTY_PSTY.get(val, Pango.Style.NORMAL) + if key == "font-stretch": + return C.CSSSTR_PSTR.get(val, Pango.Stretch.NORMAL) + if key == "font-variant": + return C.CSSVAR_PVAR.get(val, Pango.Variant.NORMAL) + return None + + @staticmethod + def css_to_pango_description(sty): + """ + Convert CSS style to Pango FontDescription. + """ + fdesc = Pango.FontDescription(sty["font-family"].strip("'").strip('"') + ",") + # The comma above is very important for font-families like Rockwell Condensed. + # Without it, Pango will interpret it as the Condensed font-stretch of the + # Rockwell font-family, rather than the Rockwell Condensed font-family. + fdesc.set_weight( + interpolate_dict(C.CSSWGT_PWGT, sty.get("font-weight"), Pango.Weight.NORMAL) + ) + fdesc.set_variant( + C.CSSVAR_PVAR.get(sty.get("font-variant"), Pango.Variant.NORMAL) + ) + fdesc.set_style(C.CSSSTY_PSTY.get(sty.get("font-style"), Pango.Style.NORMAL)) + fdesc.set_stretch( + C.CSSSTR_PSTR.get(sty.get("font-stretch"), Pango.Stretch.NORMAL) + ) + return fdesc + + @staticmethod + def pango_to_fc(pstretch, pweight, pstyle): + """ + Convert Pango attributes to FontConfig attributes. + """ + fcwidth = C.PSTR_FCWDT[pstretch] + fcweight = C.PWGT_FCWGT[pweight] + fcslant = C.PSTY_FCSLN[pstyle] + return fcwidth, fcweight, fcslant + + @staticmethod + def pango_to_css(pdescription): + """ + Convert Pango FontDescription to CSS Style. + """ + fdesc = pdescription + cstr = [k for k, v in C.CSSSTR_PSTR.items() if v == fdesc.get_stretch()] + fwgt = fdesc.get_weight() + if fwgt not in C.CSSWGT_PWGT.values(): + cwgt = [str(int(fwgt))] + else: + cwgt = [ + k + for k, v in C.CSSWGT_PWGT.items() + if v == fdesc.get_weight() and isnumeric(k) + ] + csty = [k for k, v in C.CSSSTY_PSTY.items() if v == fdesc.get_style()] + + sty = (("font-family", fdesc.get_family()),) + if len(cwgt) > 0: + sty += (("font-weight", cwgt[0]),) + if len(csty) > 0: + sty += (("font-style", csty[0]),) + if len(cstr) > 0: + sty += (("font-stretch", cstr[0]),) + return Style(sty) + + @property + def families(self): + """List all font families available in Pango context.""" + if self._families is None: + families = self.ctx.get_font_map().list_families() + self._families = sorted( + families, key=lambda x: x.get_name() + ) # Sort families alphabetically + return self._families + + @property + def faces(self): + """List all font faces available in Pango context.""" + if self._faces is None: + self._faces = [fc for fm in self.families for fc in fm.list_faces()] + return self._faces + + @property + def face_descriptions(self): + """List all font face descriptions available in Pango context.""" + if self._face_descriptions is None: + self._face_descriptions = [fc.describe() for fc in self.faces] + return self._face_descriptions + + @property + def face_strings(self): + """List all font face strings available in Pango context.""" + if self._face_strings is None: + self._face_strings = [fd.to_string() for fd in self.face_descriptions] + return self._face_strings + + @property + def face_css(self): + """List all font face CSS styles available in Pango context.""" + if self._face_css is None: + self._face_css = [ + PangoRenderer.pango_to_css(fd) for fd in self.face_descriptions + ] + return self._face_css + + @staticmethod + def fc_match_pango(family, pstretch, pweight, pstyle): + """Look up a font by its Pango properties""" + pat = fc.Pattern.name_parse(re.escape(family.replace("'", "").replace('"', ""))) + fcwidth, fcweight, fcslant = PangoRenderer.pango_to_fc( + pstretch, pweight, pstyle + ) + pat.add(fc.PROP.WIDTH, fcwidth) + pat.add(fc.PROP.WEIGHT, fcweight) + pat.add(fc.PROP.SLANT, fcslant) + + fcfg.conf.substitute(pat, FC.MatchPattern) + pat.default_substitute() + found, _ = fcfg.conf.font_match(pat) + return found + + def set_text_style(self, sty): + """Set the text style for rendering based on the provided style""" + fdesc = PangoRenderer.css_to_pango_description(sty) + fdesc.set_absolute_size(self.pufd(self.pangosize)) + fnt = self.ctx.get_font_map().load_font(self.ctx, fdesc) + + success = fnt is not None + if success: + self.pangolayout.set_font_description(fdesc) + metrics = fnt.get_metrics() + metrics = [ + self.putd(v) / self.pangosize + for v in [ + metrics.get_height(), + metrics.get_ascent(), + metrics.get_descent(), + ] + ] + return success, metrics + return success, None + + def render_text(self, texttorender): + """Render text using Pango layout.""" + self.pangolayout.set_text(texttorender, -1) + + def process_extents(self, ext, ascent): + """ + Scale extents and return extents as standard bboxes + (0:logical, 1:ink, 2: ink relative to anchor/baseline) + """ + logr = ext.logical_rect + logr = [ + self.putd(v) / self.pangosize + for v in [logr.x, logr.y, logr.width, logr.height] + ] + inkr = ext.ink_rect + inkr = [ + self.putd(v) / self.pangosize + for v in [inkr.x, inkr.y, inkr.width, inkr.height] + ] + ir_rel = [inkr[0] - logr[0], inkr[1] - logr[1] - ascent, inkr[2], inkr[3]] + return logr, inkr, ir_rel + + def get_character_extents(self, ascent, needexts): + """ + Iterate through the layout to get the logical width of each character + If there is differential kerning applied, it is applied to the + width of the first character. For example, the 'V' in 'Voltage' + will be thinner due to the 'o' that follows. + Units: relative to font size + """ + loi = self.pangolayout.get_iter() + wds = [] + i = -1 + lastpos = True + unwrapper = 0 + moved = True + while moved: + cext = loi.get_cluster_extents() + i += 1 + if needexts[i] == "1": + ext = self.process_extents(cext, ascent) + if ext[0][0] < 0 and lastpos: + unwrapper += 2**32 / (self.scale * self.pangosize) + lastpos = ext[0][0] >= 0 + ext[0][0] += unwrapper # account for 32-bit overflow + ext[1][0] += unwrapper + wds.append(ext) + else: + wds.append(None) + moved = loi.next_char() + + numunknown = self.pangolayout.get_unknown_glyphs_count() + return wds, numunknown + + +# pylint:disable=import-outside-toplevel +class FontToolsFontInstance: + """ + Class to handle FontTools font instances. + Note that this is rarely used (only if Pango fails, or for getting the + ascent for text flows). The imports are fairly time-consuming, so + they are only performed conditionally. + """ + + def __init__(self, fcfont): + self.font = FontToolsFontInstance.font_from_fc(fcfont) + self.head = self.font["head"] + self.os2 = self.font["OS/2"] if "OS/2" in self.font else None + self.find_font_metrics() + self.cmap = None + self.hmtx = None + self.kern = None + self.gsub = None + self.glyf = None + self.glyph_names = None + self.ligatures = None + + @staticmethod + def font_from_fc(found): + """Find a FontTools font from a FontConfig font.""" + fname = found.get(fc.PROP.FILE, 0)[0] + + from fontTools.ttLib import TTFont, TTLibFileIsCollectionError # pylint: disable=no-name-in-module + import logging + + logging.getLogger("fontTools").setLevel(logging.ERROR) + try: + font = TTFont(fname) + + # If font has variants, get them + if "fvar" in font: + fcwgt = found.get(fc.PROP.WEIGHT, 0)[0] + fcsln = found.get(fc.PROP.SLANT, 0)[0] + fcwdt = found.get(fc.PROP.WIDTH, 0)[0] + location = dict() + for axis in font["fvar"].axes: + if axis.axisTag == "wght": + location["wght"] = nearest_val(C.FCWGT_OS2WGT, fcwgt) + elif axis.axisTag == "wdth": + location["wdth"] = fcwdt + if len(location) > 0: + from fontTools.varLib import mutator + + font = mutator.instantiateVariableFont(font, location) + + except TTLibFileIsCollectionError: + # is TT collection + fcfam = found.get(fc.PROP.FAMILY, 0)[0] + fcwgt = found.get(fc.PROP.WEIGHT, 0)[0] + fcsln = found.get(fc.PROP.SLANT, 0)[0] + fcwdt = found.get(fc.PROP.WIDTH, 0)[0] + + from fontTools.ttLib import TTCollection + + collection = TTCollection(fname) + num_fonts = len(collection) + collection.close() + num_match = [] + for i in range(num_fonts): + tfont = TTFont(fname, fontNumber=i) + font_weight = tfont["OS/2"].usWeightClass + font_width = tfont["OS/2"].usWidthClass + + subfamily = tfont["name"].getName(2, 3, 1, 1033) + subfamily = ( + subfamily.toUnicode() if subfamily is not None else "Unknown" + ) + font_italic = ( + (tfont["OS/2"].fsSelection & 1) != 0 + or "italic" in subfamily.lower() + or "oblique" in subfamily.lower() + ) + + # nameID=1: font family name + familymatch = any( + fcfam in n.toUnicode() for n in tfont["name"].names if n.nameID == 1 + ) + widthmatch = C.OS2WDT_FCWDT[font_width] == fcwdt + weightmatch = ( + interpolate_dict(C.OS2WGT_FCWGT, font_weight, None) == fcwgt + ) + slantmatch = ( + font_italic and fcsln in [FC.SLANT_ITALIC, FC.SLANT_OBLIQUE] + ) or (not font_italic and fcsln == FC.SLANT_ROMAN) + num_match.append( + sum([weightmatch, widthmatch, slantmatch, familymatch]) + ) + if num_match[-1] == 4: + font = tfont + break + if max(num_match) < 4: + # Did not find a perfect match + font = [ + TTFont(fname, fontNumber=i) + for i in range(num_fonts) + if num_match[i] == max(num_match) + ][0] + return font + + def find_font_metrics(self): + """ + A modified version of Inkscape's find_font_metrics + https://gitlab.com/inkscape/inkscape/-/blob/master/src/libnrtype/font-instance.cpp#L267 + Uses FontTools, which is Pythonic + """ + font = self.font + units_per_em = self.head.unitsPerEm + os2 = self.os2 + if os2: + self._ascent = abs(os2.sTypoAscender / units_per_em) + self._descent = abs(os2.sTypoDescender / units_per_em) + else: + self._ascent = abs(font["hhea"].ascent / units_per_em) + self._descent = abs(font["hhea"].descent / units_per_em) + self._ascent_max = abs(font["hhea"].ascent / units_per_em) + self._descent_max = abs(font["hhea"].descent / units_per_em) + self._design_units = units_per_em + emv = self._ascent + self._descent + if emv > 0.0: + self._ascent /= emv + self._descent /= emv + + if os2 and os2.version >= 0x0002 and os2.version != 0xFFFF: + self._xheight = abs(os2.sxHeight / units_per_em) + else: + glyph_set = font.getGlyphSet() + self._xheight = ( + abs(glyph_set["x"].height / units_per_em) + if "x" in glyph_set and glyph_set["x"].height is not None + else 0.5 + ) + self._baselines = [0] * 8 + self.sp_css_baseline_ideographic = 0 + self.sp_css_baseline_hanging = 1 + self.sp_css_baseline_mathematical = 2 + self.sp_css_baseline_central = 3 + self.sp_css_baseline_middle = 4 + self.sp_css_baseline_text_beforeedge = 5 + self.sp_css_baseline_text_after_edge = 6 + self.sp_css_baseline_alphabetic = 7 + self._baselines[self.sp_css_baseline_ideographic] = -self._descent + self._baselines[self.sp_css_baseline_hanging] = 0.8 * self._ascent + self._baselines[self.sp_css_baseline_mathematical] = 0.8 * self._xheight + self._baselines[self.sp_css_baseline_central] = 0.5 - self._descent + self._baselines[self.sp_css_baseline_middle] = 0.5 * self._xheight + self._baselines[self.sp_css_baseline_text_beforeedge] = self._ascent + self._baselines[self.sp_css_baseline_text_after_edge] = -self._descent + + # Get capital height + if os2 and hasattr(os2, "sCapHeight") and os2.sCapHeight not in [0, None]: + self.cap_height = os2.sCapHeight / units_per_em + elif "glyf" in font and "I" in font.getGlyphNames(): + glyf_table = font["glyf"] + i_glyph = glyf_table["I"] + self.cap_height = (i_glyph.yMax - 0 * i_glyph.yMin) / units_per_em + else: + self.cap_height = 1 + + def get_char_advances(self, chars, pchars): + """Get the advance width and bounding boxes of characters.""" + units_per_em = self.head.unitsPerEm + if self.cmap is None: + self.cmap = self.font.getBestCmap() + if self.hmtx is None: + self.hmtx = self.font["hmtx"] + if self.kern is None: + self.kern = self.font["kern"] if "kern" in self.font else None + if self.gsub is None: + self.gsub = self.font["GSUB"] if "GSUB" in self.font else None + if self.glyf is None: + self.glyf = self.font["glyf"] if "glyf" in self.font else None + if self.glyph_names is None: + self.glyph_names = self.font.getGlyphNames() + + if self.cmap is None: + # Certain symbol fonts don't have a cmap table + self.cmap = dict() + for table in self.font["cmap"].tables: + if table.isUnicode(): + for codepoint, name in table.cmap.items(): + self.cmap[codepoint] = name + self.cmap[codepoint - 15 * 4096] = name + + advs = dict() + bbs = dict() + for c in chars: + glyph1 = self.cmap.get(ord(c)) + if glyph1 is not None: + if glyph1 in self.hmtx.metrics: + advance_width, _ = self.hmtx.metrics[glyph1] + advs[c] = advance_width / units_per_em + + try: + glyph = self.glyf[glyph1] + bbx = [ + glyph.xMin, + -glyph.yMax, + glyph.xMax - glyph.xMin, + glyph.yMax - glyph.yMin, + ] + bbs[c] = [v / units_per_em for v in bbx] + except (AttributeError, TypeError): + bbs[c] = [0, 0, 0, 0] + else: + advs[c] = None + + # Get ligature table (made with LLM help) + # Reference: https://learn.microsoft.com/en-us/typography/opentype/spec/gsub + if self.ligatures is None: + if self.gsub: + gsub_table = self.gsub.table + self.ligatures = dict() + + # Helper function to check if a feature tag is in the list + def has_feature_tag(feature_tag, lookup_index): + for feature in gsub_table.FeatureList.FeatureRecord: + if feature.FeatureTag == feature_tag: + if lookup_index in feature.Feature.LookupListIndex: + return True + return False + + for lookup_index, lookup in enumerate(gsub_table.LookupList.Lookup): + for subtable in lookup.SubTable: + if lookup.LookupType == 7: # Extension substitutions + ext_subtable = subtable.ExtSubTable + lookup_type = ext_subtable.LookupType + else: + ext_subtable = subtable + lookup_type = lookup.LookupType + + if lookup_type == 4: # Ligature substitutions + # Check if the lookup is for discretionary ligatures + if has_feature_tag("liga", lookup_index): + for ( + first_glyph, + ligature_set, + ) in ext_subtable.ligatures.items(): + for ligature in ligature_set: + component_glyphs = [ + first_glyph + ] + ligature.Component + ligature_glyph = ligature.LigGlyph + self.ligatures[tuple(component_glyphs)] = ( + ligature_glyph + ) + + else: + self.ligatures = dict() + + dadvs = dict() + for c in pchars: + glyph2 = self.cmap.get(ord(c)) + for pchar in pchars[c]: + glyph1 = self.cmap.get(ord(pchar)) + kerning_value = None + if (glyph1, glyph2) in self.ligatures: + ligglyph = self.ligatures[(glyph1, glyph2)] + awlig, _ = self.hmtx.metrics[ligglyph] + aw1, _ = self.hmtx.metrics[glyph1] + aw2, _ = self.hmtx.metrics[glyph2] + kerning_value = awlig - aw1 - aw2 + else: + if self.kern is not None: + for subtable in self.kern.kernTables: + kerning_value = subtable.kernTable.get((glyph1, glyph2)) + if kerning_value is not None: + break + if kerning_value is None: + kerning_value = 0 + dadvs[(pchar, c)] = kerning_value / units_per_em + return advs, dadvs, bbs + + +# pylint:enable=import-outside-toplevel + + +class Conversions: + """ + Conversions between CSS, FontConfig, Pango, and OS2 font attributes + CSS: font-weight, font-style, font-stretch + FontConfig: weight, slant, width + Pango: weight, style, stretch + OS2: weight, width, + + Inkscape conventions in libnrtype/font-factory.cpp + https://gitlab.com/inkscape/inkscape/-/blob/master/src/libnrtype/font-factory.cpp + """ + + # CSS to fontconfig + # For weights, Inkscape ignores anything commented out below + # See ink_font_description_from_style in libnrtype/font-factory.cpp + # And yet Semi-Light seems to work anyway + CSSWGT_FCWGT = { + # 'thin' : FC.WEIGHT_THIN, + # 'ultralight': FC.WEIGHT_EXTRALIGHT, + # 'light' : FC.WEIGHT_LIGHT, + # 'semilight' : FC.WEIGHT_SEMILIGHT, + # 'book' : FC.WEIGHT_BOOK, + "normal": FC.WEIGHT_NORMAL, + # 'medium' : FC.WEIGHT_MEDIUM, + # 'semibold' : FC.WEIGHT_SEMIBOLD, + "bold": FC.WEIGHT_BOLD, + # 'ultrabold' : FC.WEIGHT_ULTRABOLD, + # 'heavy' : FC.WEIGHT_HEAVY, + # 'ultraheavy': FC.WEIGHT_ULTRABLACK, + "100": FC.WEIGHT_THIN, + "200": FC.WEIGHT_EXTRALIGHT, + "300": FC.WEIGHT_LIGHT, + "350": FC.WEIGHT_SEMILIGHT, + # '380' : FC.WEIGHT_BOOK, + "400": FC.WEIGHT_NORMAL, + "500": FC.WEIGHT_MEDIUM, + "600": FC.WEIGHT_SEMIBOLD, + "700": FC.WEIGHT_BOLD, + "800": FC.WEIGHT_ULTRABOLD, + "900": FC.WEIGHT_HEAVY, + # '1000' : FC.WEIGHT_ULTRABLACK + } + + CSSSTY_FCSLN = { + "normal": FC.SLANT_ROMAN, + "italic": FC.SLANT_ITALIC, + "oblique": FC.SLANT_OBLIQUE, + } + + CSSSTR_FCWDT = { + "ultra-condensed": FC.WIDTH_ULTRACONDENSED, + "extra-condensed": FC.WIDTH_EXTRACONDENSED, + "condensed": FC.WIDTH_CONDENSED, + "semi-condensed": FC.WIDTH_SEMICONDENSED, + "normal": FC.WIDTH_NORMAL, + "semi-expanded": FC.WIDTH_SEMIEXPANDED, + "expanded": FC.WIDTH_EXPANDED, + "extra-expanded": FC.WIDTH_EXTRAEXPANDED, + "ultra-expanded": FC.WIDTH_ULTRAEXPANDED, + } + + # Fontconfig to CSS + # Semi-Light, Book, and Ultra-Black are mapped to Light, Normal, Heavy + # See FontFactory::GetUIStyles in libnrtype/font-factory.cpp + # Despite this, Semi-Light seems to be valid + FCWGT_CSSWGT = { + FC.WEIGHT_THIN: "100", + FC.WEIGHT_EXTRALIGHT: "200", + FC.WEIGHT_LIGHT: "300", + FC.WEIGHT_SEMILIGHT: "350", + # FC.WEIGHT_BOOK : '380', + FC.WEIGHT_BOOK: "400", + FC.WEIGHT_NORMAL: "400", + FC.WEIGHT_MEDIUM: "500", + FC.WEIGHT_SEMIBOLD: "600", + FC.WEIGHT_BOLD: "700", + FC.WEIGHT_ULTRABOLD: "800", + FC.WEIGHT_HEAVY: "900", + # FC.WEIGHT_ULTRABLACK : '1000', + FC.WEIGHT_ULTRABLACK: "900", + } + FCSLN_CSSSTY = { + FC.SLANT_ROMAN: "normal", + FC.SLANT_ITALIC: "italic", + FC.SLANT_OBLIQUE: "oblique", + } + FCWDT_CSSSTR = { + FC.WIDTH_ULTRACONDENSED: "ultra-condensed", + FC.WIDTH_EXTRACONDENSED: "extra-condensed", + FC.WIDTH_CONDENSED: "condensed", + FC.WIDTH_SEMICONDENSED: "semi-condensed", + FC.WIDTH_NORMAL: "normal", + FC.WIDTH_SEMIEXPANDED: "semi-expanded", + FC.WIDTH_EXPANDED: "expanded", + FC.WIDTH_EXTRAEXPANDED: "extra-expanded", + FC.WIDTH_ULTRAEXPANDED: "ultra-expanded", + } + + # Pango style string description to CSS + # Needed for matching the name of styles shown in Inkscape + PWGTSTR_CSSWGT = { + "Ultra-Light": "200", + "Light": "300", + "Semi-Light": "350", + "Medium": "500", + "Semi-Bold": "600", + "Bold": "bold", + "Ultra-Bold": "800", + "Heavy": "900", + "Normal": "normal", + "Book": "380", + "Thin": "100", + "Ultra-Heavy": "1000", + } + PSTRSTR_CSSSTR = { + "Ultra-Condensed": "ultra-condensed", + "Extra-Condensed": "extra-condensed", + "Condensed": "condensed", + "Semi-Condensed": "semi-condensed", + "Normal": "normal", + "Semi-Expanded": "semi-expanded", + "Expanded": "expanded", + "Extra-Expanded": "extra-expanded", + "Ultra-Expanded": "ultra-expanded", + } + PSTYSTR_CSSSTY = { + "Italic": "italic", + "Oblique": "oblique", + "Normal": "normal", + } + + # FC to OS2 and OS2 to FC + FCWGT_OS2WGT = { + FC.WEIGHT_THIN: 100, + FC.WEIGHT_EXTRALIGHT: 200, + FC.WEIGHT_LIGHT: 300, + FC.WEIGHT_SEMILIGHT: 350, + FC.WEIGHT_BOOK: 380, + FC.WEIGHT_NORMAL: 400, + FC.WEIGHT_MEDIUM: 500, + FC.WEIGHT_SEMIBOLD: 600, + FC.WEIGHT_BOLD: 700, + FC.WEIGHT_ULTRABOLD: 800, + FC.WEIGHT_HEAVY: 900, + FC.WEIGHT_ULTRABLACK: 1000, + } + OS2WDT_FCWDT = { + 1: FC.WIDTH_ULTRACONDENSED, + 2: FC.WIDTH_EXTRACONDENSED, + 3: FC.WIDTH_CONDENSED, + 4: FC.WIDTH_SEMICONDENSED, + 5: FC.WIDTH_NORMAL, + 6: FC.WIDTH_SEMIEXPANDED, + 7: FC.WIDTH_EXPANDED, + 8: FC.WIDTH_EXTRAEXPANDED, + 9: FC.WIDTH_ULTRAEXPANDED, + } + OS2WGT_FCWGT = { + 100: FC.WEIGHT_THIN, + 200: FC.WEIGHT_EXTRALIGHT, + 300: FC.WEIGHT_LIGHT, + 350: FC.WEIGHT_SEMILIGHT, + 380: FC.WEIGHT_BOOK, + 400: FC.WEIGHT_NORMAL, + 500: FC.WEIGHT_MEDIUM, + 600: FC.WEIGHT_SEMIBOLD, + 700: FC.WEIGHT_BOLD, + 800: FC.WEIGHT_ULTRABOLD, + 900: FC.WEIGHT_HEAVY, + 1000: FC.WEIGHT_ULTRABLACK, + } + + if HASPANGO: + # Pango to fontconfig + PWGT_FCWGT = { + Pango.Weight.THIN: FC.WEIGHT_THIN, + Pango.Weight.ULTRALIGHT: FC.WEIGHT_ULTRALIGHT, + Pango.Weight.ULTRALIGHT: FC.WEIGHT_EXTRALIGHT, + Pango.Weight.LIGHT: FC.WEIGHT_LIGHT, + Pango.Weight.SEMILIGHT: FC.WEIGHT_DEMILIGHT, + Pango.Weight.SEMILIGHT: FC.WEIGHT_SEMILIGHT, + Pango.Weight.BOOK: FC.WEIGHT_BOOK, + Pango.Weight.NORMAL: FC.WEIGHT_REGULAR, + Pango.Weight.NORMAL: FC.WEIGHT_NORMAL, + Pango.Weight.MEDIUM: FC.WEIGHT_MEDIUM, + Pango.Weight.SEMIBOLD: FC.WEIGHT_DEMIBOLD, + Pango.Weight.SEMIBOLD: FC.WEIGHT_SEMIBOLD, + Pango.Weight.BOLD: FC.WEIGHT_BOLD, + Pango.Weight.ULTRABOLD: FC.WEIGHT_EXTRABOLD, + Pango.Weight.ULTRABOLD: FC.WEIGHT_ULTRABOLD, + Pango.Weight.HEAVY: FC.WEIGHT_BLACK, + Pango.Weight.HEAVY: FC.WEIGHT_HEAVY, + Pango.Weight.ULTRAHEAVY: FC.WEIGHT_EXTRABLACK, + Pango.Weight.ULTRAHEAVY: FC.WEIGHT_ULTRABLACK, + } + + PSTY_FCSLN = { + Pango.Style.NORMAL: FC.SLANT_ROMAN, + Pango.Style.ITALIC: FC.SLANT_ITALIC, + Pango.Style.OBLIQUE: FC.SLANT_OBLIQUE, + } + + PSTR_FCWDT = { + Pango.Stretch.ULTRA_CONDENSED: FC.WIDTH_ULTRACONDENSED, + Pango.Stretch.EXTRA_CONDENSED: FC.WIDTH_EXTRACONDENSED, + Pango.Stretch.CONDENSED: FC.WIDTH_CONDENSED, + Pango.Stretch.SEMI_CONDENSED: FC.WIDTH_SEMICONDENSED, + Pango.Stretch.NORMAL: FC.WIDTH_NORMAL, + Pango.Stretch.SEMI_EXPANDED: FC.WIDTH_SEMIEXPANDED, + Pango.Stretch.EXPANDED: FC.WIDTH_EXPANDED, + Pango.Stretch.EXTRA_EXPANDED: FC.WIDTH_EXTRAEXPANDED, + Pango.Stretch.ULTRA_EXPANDED: FC.WIDTH_ULTRAEXPANDED, + } + + # CSS to Pango + CSSVAR_PVAR = { + "normal": Pango.Variant.NORMAL, + "small-caps": Pango.Variant.SMALL_CAPS, + } + + CSSSTY_PSTY = { + "normal": Pango.Style.NORMAL, + "italic": Pango.Style.ITALIC, + "oblique": Pango.Style.OBLIQUE, + } + # For weights, Inkscape ignores anything commented out below + # See ink_font_description_from_style in libnrtype/font-factory.cpp + CSSWGT_PWGT = { + # 'thin' : Pango.Weight.THIN, + # 'ultralight' : Pango.Weight.ULTRALIGHT, + # 'light' : Pango.Weight.LIGHT, + "semilight": Pango.Weight.SEMILIGHT, + # 'book' : Pango.Weight.BOOK, + "normal": Pango.Weight.NORMAL, + # 'medium' : Pango.Weight.MEDIUM, + # 'semibold' : Pango.Weight.SEMIBOLD, + "bold": Pango.Weight.BOLD, + # 'ultrabold' : Pango.Weight.ULTRABOLD, + # 'heavy' : Pango.Weight.HEAVY, + # 'ultraheavy' : Pango.Weight.ULTRAHEAVY, + "100": Pango.Weight.THIN, + "200": Pango.Weight.ULTRALIGHT, + "300": Pango.Weight.LIGHT, + "350": Pango.Weight.SEMILIGHT, + # '380' : Pango.Weight.BOOK, + "400": Pango.Weight.NORMAL, + "500": Pango.Weight.MEDIUM, + "600": Pango.Weight.SEMIBOLD, + "700": Pango.Weight.BOLD, + "800": Pango.Weight.ULTRABOLD, + "900": Pango.Weight.HEAVY, + # '1000' : Pango.Weight.ULTRAHEAVY + } + CSSSTR_PSTR = { + "ultra-condensed": Pango.Stretch.ULTRA_CONDENSED, + "extra-condensed": Pango.Stretch.EXTRA_CONDENSED, + "condensed": Pango.Stretch.CONDENSED, + "semi-condensed": Pango.Stretch.SEMI_CONDENSED, + "normal": Pango.Stretch.NORMAL, + "semi-expanded": Pango.Stretch.SEMI_EXPANDED, + "expanded": Pango.Stretch.EXPANDED, + "extra-expanded": Pango.Stretch.EXTRA_EXPANDED, + "ultra-expanded": Pango.Stretch.ULTRA_EXPANDED, + } + + +C = Conversions # pylint: disable=invalid-name + + +def inkscape_spec_to_css(fstr, usepango=False): + """ + Look up a CSS style based on its Inkscape font specification, which is + similar (identical?) to the Pango description's string representation. + This function is meant to be forgiving, allowing a user to vary punctuation + and capitalization, and tries to find a match in the system's families. + It does NOT require Pango. + """ + + def clean_str(strin): + ret = re.sub(r"[^\w\s]", "", strin) + ret = re.sub(r"\s+", " ", ret) + return ret.strip().lower() + + cstr = clean_str(fstr) + if usepango and HASPANGO: + fullfams = [fm.get_name() for fm in PangoRenderer().families] + else: + fullfams = [f.get("family", 0)[0] for f in fcfg.font_list] + fullfams += ["Serif", "Sans", "System-ui", "Monospace"] # from Inkscape + + # Split the input into words and find the longest match to an installed + # family at the beginning or end of the string + fmnames = [clean_str(f) for f in fullfams] + words = cstr.split() + longest_match = "" + match_length = 0 + match_type = None # 'prefix' or 'suffix' + # Check for the longest prefix match + for i in range(1, len(words) + 1): + current_match = " ".join(words[:i]) + if current_match in fmnames and len(current_match) > len(longest_match): + longest_match = current_match + match_length = i + match_type = "prefix" + # Check for the longest suffix match + for i in range(1, len(words) + 1): + current_match = " ".join(words[-i:]) + if current_match in fmnames and len(current_match) > len(longest_match): + longest_match = current_match + match_length = i + match_type = "suffix" + if longest_match: + fam = fullfams[fmnames.index(longest_match)] + if match_type == "prefix": + stylews = words[match_length:] + elif match_type == "suffix": + stylews = words[:-match_length] + if not stylews: + stylews = None + else: + fam = None + stylews = words + + sty = {} + if fam: + sty["font-family"] = fam + if stylews: + pwgtstrs = {clean_str(k): v for k, v in C.PWGTSTR_CSSWGT.items()} + pstrstrs = {clean_str(k): v for k, v in C.PSTRSTR_CSSSTR.items()} + pstystrs = {clean_str(k): v for k, v in C.PSTYSTR_CSSSTY.items()} + for wrd in stylews: + understood = False + if wrd in pwgtstrs: + sty["font-weight"] = pwgtstrs[wrd] + understood = True + elif "weight" in wrd: + sty["font-weight"] = re.search(r"weight(\d+)", wrd).group(1) + understood = True + if wrd in pstystrs: + sty["font-style"] = pstystrs[wrd] + understood = True + if wrd in pstrstrs: + sty["font-stretch"] = pstrstrs[wrd] + understood = True + if not understood: + return None + sty = {key: sty[key] for key in fontatt if key in sty} # order + return sty diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/parser.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/parser.py new file mode 100644 index 0000000..2ad362e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/parser.py @@ -0,0 +1,4097 @@ +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +text.parser module parses text in a document according to the way Inkscape +handles it. In short, every TextElement or FlowRoot is parsed into a ParsedText. +Each ParsedText contains a collection of tlines, representing one line of text. +Each TLine contains a collection of tchars, representing a single character. +Characters are also grouped into tchunks, which represent chunks of characters +sharing an anchor (usually from manually-kerned text). + +These functions allow text metrics and bounding boxes to be calculated without +binary calls to Inkscape. It can calculate both the ink bounding box (i.e., where +characters' ink is) as well as the extent bounding box (i.e, its logical location). +The extent of a character is defined as extending between cursor positions in the +x-direction and between the baseline and capital height in the y-direction. + +Some examples: +- el.parsed_text.get_full_inkbbox(): gets the untransformed bounding box of the + whole element +- el.parsed_text.get_char_extents(): gets all characters' untransformed extents +- el.parse_text.lns[0].chrs[0].pts_ut: the untransformed points of the extent of the + first character of the first line +- el.parse_text.lns[0].chrs[0].pts_t : the transformed points of the extent of the + first character of the first line + +To check if things are working properly, you can run the make_highlights() function, +which draws rectangles that tell you where the extents / bboxes are. For example, +- el.parsed_text.make_highlights('char') : shows the extent of each character +- el.parsed_text.make_highlights('fullink') : shows the bbox of the whole element +""" + +import itertools +import math +from copy import copy +import threading +import lxml +import numpy as np +import inkex +from inkex import ( + TextElement, + Tspan, + Transform, + Style, + FlowRoot, + FlowRegion, + FlowPara, + FlowSpan, +) +from inkex.text.utils import ( + uniquetol, + composed_width, + composed_lineheight, + default_style_atts, + isrectangle, + ipx, + bbox, +) +from inkex.text.font_properties import ( + PangoRenderer, + HASPANGO, + fcfg, + font_style, + true_style, +) +from inkex.utils import debug + +DIFF_ADVANCES = True # generate a differential advances table for each font? +TEXTSIZE = 100 # size of rendered text +DEPATHOLOGIZE = True # clean up pathological atts not normally made by Inkscape + +EBget = lxml.etree.ElementBase.get +EBset = lxml.etree.ElementBase.set + +TEtag, TStag, FRtag = TextElement.ctag, Tspan.ctag, FlowRoot.ctag +TEFRtags = {TEtag, FRtag} +TEtags = {TEtag, TStag} +FRtags = {FRtag, FlowRegion.ctag, FlowPara.ctag, FlowSpan.ctag} + +LOCK = threading.Lock() + + +class ParsedTextList(list): + """ + A list of parsed text whose coordinates are computed + vectorially using numpy. Normally they are calculated as they are needed, + but this is inefficient when dealing with thousands of manually-kerned + characters (e.g., from PDFs). + """ + + def __init__(self, els): + """Initializes ParsedTextList with a list of elements.""" + super().__init__([el.parsed_text for el in els]) + + def precalcs(self): + """ + Calculate parsed_bb, parsed_pts_ut, parsed_pts_t for all chunks + simultaneously + """ + tws = [chk for ptxt in self for line in ptxt.lns for chk in line.chks] + nchrs = sum(chk.ncs for chk in tws) + + # Preallocate arrays + dadv, cwd, dxeff, dy, bshft, caph, unsp, anfr = np.zeros( + (8, nchrs), dtype=float + ) + fidx, lidx, widx = np.zeros((3, nchrs)).astype(int) + + unsp = np.array([chk.unrenderedspace for chk in tws], dtype=float) + anfr = np.array([chk.line.anchfrac for chk in tws], dtype=float) + chkx = np.array([chk.x for chk in tws], dtype=float) + chky = np.array([chk.y for chk in tws], dtype=float) + m00, m01, m02, m10, m11, m12 = ( + np.array([chk.transform.matrix[i][j] for chk in tws], dtype=float) + for i in (0, 1) + for j in (0, 1, 2) + ) + lx2, rx2, by2, ty2 = np.zeros((4, len(tws))).astype(float) + + # Collect values for dadv, cwd, dxeff, dy, bshft, and caph + idx = 0 + for j, chk in enumerate(tws): + if DIFF_ADVANCES: + for i in range(1, chk.ncs): + dadv[idx + i] = chk.chrs[i].dadvs(chk.chrs[i - 1].c, chk.chrs[i].c)*(chk.dxeff[i]==0) + + for i in range(chk.ncs): + cwd[idx + i] = chk.cwd[i] + dxeff[idx + i] = chk.dxeff[i] + dy[idx + i] = chk.dy[i] + bshft[idx + i] = chk.bshft[i] + caph[idx + i] = chk.caph[i] + fidx[idx : idx + chk.ncs] = idx + lidx[idx : idx + chk.ncs] = idx + chk.ncs - 1 + widx[idx : idx + chk.ncs] = j + idx += chk.ncs + + # Calculate wds, cstop, and cstrt + wds = cwd + dxeff + dadv + cstop = np.array(list(itertools.accumulate(wds)), dtype=float) + cstop += wds[fidx] - cstop[fidx] + cstrt = cstop - cwd + + # # Calculate adyl + adyl = np.array(list(itertools.accumulate(dy)), dtype=float) + adyl += dy[fidx] - adyl[fidx] + + # Calculate chkw, offx, lftx, and rgtx + offx = -anfr[widx] * (cstop[lidx] - unsp[widx] * cwd[lidx]) + lftx = chkx[widx] + cstrt + offx + rgtx = chkx[widx] + cstop + offx + btmy = chky[widx] + adyl - bshft + topy = btmy - caph + + idx_ncs = np.cumsum([0] + [chk.ncs for chk in tws]) + starts = idx_ncs[:-1] + subtract_ufunc = np.frompyfunc(lambda a, b: a - b, 2, 1) + lx_minus_dxeff = subtract_ufunc(lftx, dxeff) + lx2 = np.minimum.reduceat(lx_minus_dxeff, starts) + rx2 = lx2 + cstop[idx_ncs[1:] - 1] + by2 = np.maximum.reduceat(btmy, starts) + ty2 = np.minimum.reduceat(topy, starts) + + cpts_ut = [ + np.hstack((lftx[:, np.newaxis], btmy[:, np.newaxis])), + np.hstack((lftx[:, np.newaxis], topy[:, np.newaxis])), + np.hstack((rgtx[:, np.newaxis], topy[:, np.newaxis])), + np.hstack((rgtx[:, np.newaxis], btmy[:, np.newaxis])), + ] + mat = ((m00[widx], m01[widx], m02[widx]), (m10[widx], m11[widx], m12[widx])) + cpts_t = [ + np.vstack(vmult(mat, lftx, btmy)).T, + np.vstack(vmult(mat, lftx, topy)).T, + np.vstack(vmult(mat, rgtx, topy)).T, + np.vstack(vmult(mat, rgtx, btmy)).T, + ] + pts_ut = [ + np.vstack((lx2, by2)), + np.vstack((lx2, ty2)), + np.vstack((rx2, ty2)), + np.vstack((rx2, by2)), + ] + mat = ((m00, m01, m02), (m10, m11, m12)) + pts_t = [ + np.vstack(vmult(mat, lx2, by2)), + np.vstack(vmult(mat, lx2, ty2)), + np.vstack(vmult(mat, rx2, ty2)), + np.vstack(vmult(mat, rx2, by2)), + ] + + # pylint:disable=protected-access + # Split outputs into lists of column vectors + # maxerr = float('-inf') + for i, chk in enumerate(tws): + # Extract the slices of the relevant arrays for this TChunk + lxw = lftx[idx_ncs[i] : idx_ncs[i + 1], np.newaxis] + rxw = rgtx[idx_ncs[i] : idx_ncs[i + 1], np.newaxis] + byw = btmy[idx_ncs[i] : idx_ncs[i + 1], np.newaxis] + tyw = topy[idx_ncs[i] : idx_ncs[i + 1], np.newaxis] + + chk._charpos = (lxw, rxw, byw, tyw, lx2[i], rx2[i], by2[i], ty2[i]) + chk._cpts_ut = [cpv[idx_ncs[i] : idx_ncs[i + 1], :] for cpv in cpts_ut] + chk._cpts_t = [cpv[idx_ncs[i] : idx_ncs[i + 1], :] for cpv in cpts_t] + chk._pts_ut = [ + (float(pts_ut[0][0][i]), float(pts_ut[0][1][i])), + (float(pts_ut[1][0][i]), float(pts_ut[1][1][i])), + (float(pts_ut[2][0][i]), float(pts_ut[2][1][i])), + (float(pts_ut[3][0][i]), float(pts_ut[3][1][i])), + ] + chk._pts_t = [ + (float(pts_t[0][0][i]), float(pts_t[0][1][i])), + (float(pts_t[1][0][i]), float(pts_t[1][1][i])), + (float(pts_t[2][0][i]), float(pts_t[2][1][i])), + (float(pts_t[3][0][i]), float(pts_t[3][1][i])), + ] + + for j, c in enumerate(chk.chrs): + c.parsed_pts_ut = [ + (float(chk._cpts_ut[0][j][0]), float(chk._cpts_ut[0][j][1])), + (float(chk._cpts_ut[1][j][0]), float(chk._cpts_ut[1][j][1])), + (float(chk._cpts_ut[2][j][0]), float(chk._cpts_ut[2][j][1])), + (float(chk._cpts_ut[3][j][0]), float(chk._cpts_ut[3][j][1])), + ] + c.parsed_pts_t = [ + (float(chk._cpts_t[0][j][0]), float(chk._cpts_t[0][j][1])), + (float(chk._cpts_t[1][j][0]), float(chk._cpts_t[1][j][1])), + (float(chk._cpts_t[2][j][0]), float(chk._cpts_t[2][j][1])), + (float(chk._cpts_t[3][j][0]), float(chk._cpts_t[3][j][1])), + ] + # pylint:enable=protected-access + + def make_next_chain(self): + for pt in self: + pt.make_next_chain() + + +def vmult(mat, x, y): + """Multiplies mat times (x;y) in a way compatible with vectorization""" + return ( + mat[0][0] * x + mat[0][1] * y + mat[0][2], + mat[1][0] * x + mat[1][1] * y + mat[1][2], + ) + + +def vmultinv(mat, x, y): + """Performs inverse matrix multiplication with vectors.""" + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0] + inv_det = 1 / det + sxv = x - mat[0][2] + syv = y - mat[1][2] + return ( + (mat[1][1] * sxv - mat[0][1] * syv) * inv_det, + (mat[0][0] * syv - mat[1][0] * sxv) * inv_det, + ) + + +class ParsedText: + """A text element that has been parsed into a list of lines""" + + def __init__(self, elem, ctable): + """Initializes ParsedText with an element and a character table.""" + self.ctable = ctable + self.textel = elem + + sty = elem.cspecified_style + self.isflow = ( + elem.tag == FRtag + or (elem.croot is not None and sty.get_link("shape-inside", elem.croot) is not None) + or ipx(sty.get("inline-size")) + ) + self._tree = None + self.dchange, self.writtendx, self.writtendy = [None] * 3 + self.achange = False + if DEPATHOLOGIZE: + remove_position_overflows(elem) + if self.isflow: + self.parse_lines_flow() + else: + self.parse_lines() + self.finish_lines() + + tlvllns = [ + line for line in self.lns if line.tlvlno is not None and line.tlvlno > 0 + ] + # top-level lines after 1st + self.isinkscape = ( + all(line.sprl for line in tlvllns) + and len(tlvllns) > 0 + and all( + line.style.get("-inkscape-font-specification") is not None + for line in self.lns + ) + ) + # probably made in Inkscape + self.ismlinkscape = self.isinkscape and len(self.lns) > 1 + # multi-line Inkscape + + def duplicate(self): + """Duplicates a PT and its text without reparsing""" + ret = copy(self) + ret.textel = self.textel.duplicate() + ret.tree = None + cmemo = dict(zip(self.tree.dds, ret.tree.dds)) + + ret.lns = [] + for line in self.lns: + if len(line.chrs) == 0: + continue + ret_ln = line.copy(cmemo) + ret_ln.ptxt = ret + ret.lns.append(ret_ln) + + # nextw et al. could be from any line, update after copying + for ret_ln in ret.lns: + for ret_w in ret_ln.chks: + ret_w.nextw = cmemo.get(ret_w.nextw) + ret_w.prevw = cmemo.get(ret_w.prevw) + ret_w.prevsametspan = cmemo.get(ret_w.prevsametspan) + return ret + + def txt(self): + """Returns the text content of the parsed lines.""" + return [line.txt() for line in self.lns] + + def reparse(self): + """Reparses the text element.""" + self.__init__(self.textel, self.ctable) + + @property + def chrs(self): + """Returns a list of characters in the parsed lines.""" + return [c for line in self.lns for c in line.chrs] + + def parse_lines(self, srcsonly=False): + """ + Every text element in an SVG can be thought of as a group of lines. + A line is a collection of text that gets its position from a single + source element. This position may be directly set, continued from a + previous line, or inherited from a previous line. + """ + elem = self.textel + # First we get the tree structure of the text and do all our gets + dds, pts, pdict = self.tree.dds, self.tree.ptails, self.tree.pdict + + numd = len(dds) + kids = list(elem) + text = [ddv.text for ddv in dds] + ptail = [[tel.tail for tel in ptv] for ptv in pts] # preceding tails + + # Next we find the top-level sodipodi:role lines + xvs = [ParsedText.get_xy(ddv, "x") for ddv in dds] + yvs = [ParsedText.get_xy(ddv, "y") for ddv in dds] + dxvs = [ParsedText.get_xy(ddv, "dx") for ddv in dds] + dyvs = [ParsedText.get_xy(ddv, "dy") for ddv in dds] + nsprl = {ddv:ddv.get("sodipodi:role")=='line' for ddv in dds} + + # Find effective sprls (ones that are not disabled) + esprl = [False]*len(dds) + for i, ddv in enumerate(dds): + esprl[i] = nsprl[ddv] and len(xvs[i]) == 1 and len(yvs[i]) == 1 and dds[i] in kids + + # If I don't have text and any descendants have position, disables spr:l + if esprl[i] and (text[i] == "" or text[i] is None): + dstop = [j for j in range(len(pts)) if dds[i] in pts[j]][0] + # should exist + for ddi in range(i + 1, dstop): + if xvs[ddi][0] is not None or yvs[ddi][0] is not None: + if text[ddi] is not None and text[ddi] != "": + esprl[i] = False + + if DEPATHOLOGIZE and not srcsonly: + # Prune any sodipodi:roles that are inactive + for ii, d in enumerate(dds): + if not esprl[ii] and nsprl[d]: + d.set('sodipodi:role',None) + nsprl[d] = False + + # Figure out which effective sprls are top-level + types = [None] * len(dds) + for i,ddv in enumerate(dds): + if not esprl[i]: + types[i] = NORMAL + elif len(ptail[i]) > 0 and ptail[i][-1] is not None: + types[i] = PRECEDEDSPRL + elif dds[i] == kids[0] and text[0] is not None: # and len(text[0])>0: + # 2022.08.17: I am not sure if the len(text[0])==0 condition + # should be included. Inkscape prunes text='', so not relevant + # most of the time. It does seem to make a difference though. + types[i] = PRECEDEDSPRL + else: + types[i] = TLVLSPRL + + # Position has a property of bidirectional inheritance. A tspan can inherit + # position from its parent or its descendant unless there is text in between. + # Down-inheritance requires that text be present + # Aborts if a sprl is encountered + def inherits_from(iin): + jmax = iin + while ( + jmax < numd - 1 + and text[jmax] in ["", None] + and pdict[dds[jmax + 1]] == dds[jmax] + and not (esprl[jmax + 1]) + ): + jmax += 1 + if jmax < numd - 1 and text[jmax] in ["", None]: + jmax = iin + + jmin = iin + while ( + jmin > 0 + and text[jmin - 1] in ["", None] + and dds[jmin - 1] == pdict[dds[jmin]] + and not (esprl[jmin - 1]) + ): + jmin -= 1 + return jmin, jmax # includes endpoints + + def inherit_none(iin, xyt): + if xyt[iin][0] is None: + imin, imax = inherits_from(iin) + vld = [i for i in range(imin, imax + 1) if xyt[i][0] is not None] + if len(vld) > 0: + if any(i <= iin for i in vld): + vld = [i for i in vld if i <= iin] + # inherit up if possible + dist = [abs(i - iin) for i in vld] + j = [vld[i] for i in range(len(vld)) if dist[i] == min(dist)][0] + return xyt[j], dds[j] + return xyt[iin], dds[iin] + + # For positions that are None, inherit from ancestor/descendants if possible + ixs, iys = xvs[:], yvs[:] + xsrcs, ysrcs = dds[:], dds[:] + + for i in [i for i in range(0, len(dds)) if ixs[i][0] is None]: + ixs[i], xsrcs[i] = inherit_none(i, xvs) + for i in [i for i in range(0, len(dds)) if iys[i][0] is None]: + iys[i], ysrcs[i] = inherit_none(i, yvs) + + if ixs[0][0] is None: + ixs[0] = [0] # at least the parent needs a position + if iys[0][0] is None: + iys[0] = [0] + + # Finally, walk the text tree generating lines + lns = [] + if not srcsonly: + self.lns = [] + sprl_inherits = None + for ddi, typ, tel, sel, txt in self.tree.dgenerator(): + newsprl = typ == TYP_TEXT and types[ddi] == TLVLSPRL + + if (txt is not None and len(txt) > 0) or newsprl: + sty = sel.cspecified_style + ctf = sel.ccomposed_transform + fsz, scf, utfs = composed_width(sel, "font-size") + + if newsprl: + lht = max( + composed_lineheight(sel), composed_lineheight(sel.getparent()) + ) + + # Make a new line if we're sprl or if we have a new x or y + makeline = len(lns) == 0 + makeline |= typ == TYP_TEXT and ( + newsprl + or ( + types[ddi] == NORMAL + and (ixs[ddi][0] is not None or iys[ddi][0] is not None) + ) + ) + if makeline: + edi = ddi + if typ == TYP_TAIL: + edi = dds.index(sel) + xvl = ixs[edi] + xsrc = xsrcs[edi] + yvl = iys[edi] + ysrc = ysrcs[edi] + if newsprl: + if len(lns) == 0: + xvl = [ixs[0][0]] + xsrc = xsrcs[0] + yvl = [iys[0][0]] + ysrc = ysrcs[0] + else: + xvl = [sprl_inherits.x[0]] + xsrc = sprl_inherits.xsrc + yvl = [sprl_inherits.y[0] + lht / scf] + ysrc = sprl_inherits.ysrc + issprl = True + continuex = False + continuey = False + else: + continuex = False + issprl = False + if xvl[0] is None: + if len(lns) > 0: + xvl = lns[-1].x[:] + xsrc = lns[-1].xsrc + else: + xvl = ixs[0][:] + xsrc = xsrcs[0] + continuex = True + continuey = False + if yvl[0] is None: + if len(lns) > 0: + yvl = lns[-1].y[:] + ysrc = lns[-1].ysrc + else: + yvl = iys[0][:] + ysrc = ysrcs[0] + continuey = True + + if srcsonly: # quit and return srcs of first line + return xsrc, ysrc + + tlvlno = None + if ddi < numd and dds[ddi] in kids: + tlvlno = kids.index(dds[ddi]) + elif edi == 0: + tlvlno = 0 + + anch = sty.get("text-anchor") + if len(lns) > 0 and not nsprl[sel] and edi>0: + if lns[-1].anchor is not None: + anch = lns[ + -1 + ].anchor # non-spr lines inherit the previous line's anchor + if anch is None: + anch = "start" + txtdir = sty.get("direction") + if txtdir is not None: + if txtdir == "rtl": + if anch == "start": + anch = "end" + elif anch == "end": + anch = "start" + + sprlabove = [] + cel = dds[edi] + while cel != elem: + if nsprl[cel]: + sprlabove.append(cel) + cel = pdict[cel] + + lns.append( + TLine( + self, + xvl, + yvl, + xsrc, + ysrc, + issprl, + sprlabove, + anch, + ctf, + tlvlno, + sty, + continuex, + continuey, + ) + ) + + if newsprl or len(lns) == 1: + sprl_inherits = lns[-1] + + fsty = font_style(sty) + tsty = true_style(fsty) + if txt is not None: + if typ==TYP_TEXT: + dxv = dxvs[ddi] + dyv = dyvs[ddi] + if dxv[0] is None: + dxv = [0]*len(txt) + else: + dxv = dxv + [0]*(len(txt)-len(dxv)) + if dyv[0] is None: + dyv = [0]*len(txt) + else: + dyv = dyv + [0]*(len(txt)-len(dyv)) + else: + dxv = [0]*len(txt) + dyv = [0]*len(txt) + + + for j, c in enumerate(txt): + csty = self.font_picker(txt, j, fsty, tsty) + prop = self.ctable.get_prop(c, csty) + _ = TChar( + c, + fsz, + utfs, + prop, + sty, + csty, + CLoc(tel, typ, j), + lns[-1], dxv[j], dyv[j] + ) + + if j == 0: + lsp0 = lns[-1].chrs[-1].lsp + bshft0 = lns[-1].chrs[-1].bshft + else: + lns[-1].chrs[-1].lsp = lsp0 + lns[-1].chrs[-1].bshft = bshft0 + + self.lns = lns + + def finish_lines(self): + """Finalizes the parsed lines by calculating deltas and chunks.""" + if self.lns is not None: + self.writtendx = any(c.dx != 0 for c in self.chrs) + self.writtendy = any(c.dy != 0 for c in self.chrs) + + for line in self.lns: + line.ptxt = self + line.parse_chunks() + + for line in reversed(self.lns): + if len(line.chrs) == 0: + self.lns.remove(line) + # prune empty lines + + def make_next_chain(self): + """ For manual kerning removal, assign next and previous chunks """ + yvs = [ + line.y[0] for line in self.lns if line.y is not None and len(line.y) > 0 + ] + tol = 0.001 + for unqy in uniquetol(yvs, tol): + samey = [ + self.lns[i] + for i in range(len(self.lns)) + if abs(yvs[i] - unqy) < tol + ] + sameyws = [chk for line in samey for chk in line.chks] + xvs = [0.5*(chk.pts_ut[0][0] + chk.pts_ut[3][0]) for line in samey for chk in line.chks] + sws = [ + x for _, x in sorted(zip(xvs, sameyws), key=lambda pair: pair[0]) + ] # chunks sorted in ascending x + for i in range(1, len(sws)): + # Account for space import bug where space has same position as prior char + if sws[i - 1].txt==' ' and abs(sws[i - 1].pts_ut[0][0]-sws[i].pts_ut[0][0])<.01*sws[i - 1].spw: + sws[i-1:i+1] = [sws[i],sws[i-1]] + for i in range(1, len(sws)): + sws[i - 1].nextw = sws[i] + sws[i].prevw = sws[i - 1] + sws[i].prevsametspan = ( + sws[i - 1].chrs[-1].loc.sel == sws[i].chrs[0].loc.sel + ) + + def font_picker(self, txt, j, fsty, tsty): + """Determine what font Inkscape will render a character as""" + if txt[j] != " ": + # Most of the time we want the true style of the text. When this is + # determined by Pango measurement, this practically always matches + # Inkscape's font selection + return tsty + # If the character is a space and the surrounding characters are + # missing from the true style, Pango will replace the space with a + # space from the missing characters' font + lbc = next((txt[i] for i in range(j - 1, -1, -1) if txt[i].strip()), None) + fac = next((txt[i] for i in range(j + 1, len(txt)) if txt[i].strip()), None) + if ( + lbc is not None + and fac is not None + and self.ctable.cstys[fsty][lbc] == self.ctable.cstys[fsty][fac] + ): + return self.ctable.cstys[fsty][lbc] + if lbc is None and fac is not None: + return self.ctable.cstys[fsty][fac] + if fac is None and lbc is not None: + return self.ctable.cstys[fsty][lbc] + return tsty + # Good fonts to check: + # MS Outlook : missing most characters, but has a space + # Marlett : charset doesn't have a space character, but somehow Pango + # is finding one different from the one provided by + # fontconfig's font_sort fallback + + def strip_sodipodi_role_line(self): + """Strip every sodipodi:role line from an element without changing + positions""" + if any(ddv.get("sodipodi:role") == "line" for ddv in self.tree.dds): + # Store old positions + oxs = [c.pts_ut[0][0] for line in self.lns for c in line.chrs] + oys = [c.pts_ut[0][1] for line in self.lns for c in line.chrs] + odxs = [c.dx for line in self.lns for c in line.chrs] + odys = [c.dy for line in self.lns for c in line.chrs] + _ = [ddv.set("sodipodi:role", None) for ddv in self.tree.dds] + + pput = [c.parsed_pts_ut for line in self.lns for c in line.chrs] + ppt = [c.parsed_pts_t for line in self.lns for c in line.chrs] + + deleteempty(self.textel) + self.reparse() + ii = 0; + for line in self.lns: + for c in line.chrs: + c.parsed_pts_ut = pput[ii] + c.parsed_pts_t = ppt[ii] + ii += 1 + + # Correct the position of the first character + chrs = [c for line in self.lns for c in line.chrs] + for i, line in enumerate(self.lns): + myi = chrs.index(line.chrs[0]) + dx = oxs[myi] - chrs[myi].pts_ut[0][0] + dy = oys[myi] - chrs[myi].pts_ut[0][1] + if abs(dx) > 0.001 or abs(dy) > 0.001: + newxv = [x + dx for x in line.x] + newyv = [y + dy for y in line.y] + if line.continuex or line.continuey: + if line.chrs[0].loc.typ == TYP_TAIL: + # wrap in a trivial Tspan so we can set x and y + line.chrs[0].add_style( + {"baseline-shift": "0%"}, setdefault=False + ) + line.xsrc = line.chrs[0].loc.elem + line.ysrc = line.chrs[0].loc.elem + line.continuex = False + line.continuey = False + line.write_xy(newx=newxv, newy=newyv) + + # Fix non-first dxs + ndxs = [c.dx for line in self.lns for c in line.chrs] + ndys = [c.dy for line in self.lns for c in line.chrs] + for i, c in enumerate(chrs): + if c.lnindex > 0: + if abs(odxs[i] - ndxs[i]) > 0.001 or abs(odys[i] - ndys[i]) > 0.001: + c.dx = odxs[i] + c.dy = odys[i] + self.write_dxdy() + for line in self.lns: + for chk in line.chks: + chk.charpos = None + + def strip_text_baseline_shift(self): + """Remove baseline-shift if applied to the whole element""" + if "baseline-shift" in self.textel.cspecified_style: + if len(self.lns) > 0 and len(self.lns[0].chrs) > 0: + lny = self.lns[0].y[:] + bsv = self.lns[0].chrs[0].bshft + self.textel.cstyle["baseline-shift"] = None + self.reparse() + newy = [y - bsv for y in lny] + self.lns[0].write_xy(newy=newy) + + @staticmethod + def get_xy(elem, xyt): + """Gets the x or y coordinate values from an element.""" + val = EBget(elem, xyt) # fine for 'x','y','dx','dy' + if not val: + return [None] # None forces inheritance + return [None if x == "none" else ipx(x) for x in val.split()] + + def write_dxdy(self): + """ + After dx/dy has changed, call this to write them to the text element + For simplicity, this is best done at the ParsedText level all at once + """ + if self.dchange: + # Group characters by location + cs_loc = {(d,typ):[] for d in self.textel.descendants2() for typ in [TYP_TEXT,TYP_TAIL]} + for ln in self.lns: + for c in ln.chrs: + cs_loc[c.loc.elem,c.loc.typ].append(c) + + for (d, typ), cd in cs_loc.items(): + dx = [c.dx for c in cd] + dy = [c.dy for c in cd] + + dxset = trim_list(dx,0) + dyset = trim_list(dy,0) + + if typ==TYP_TAIL and (dxset is not None or dyset is not None): + d = wrap_string(d,typ) + self.tree = None # invalidate + typ = TYP_TEXT + for c in cd: + c.loc = CLoc(d,TYP_TEXT,c.loc.ind) + + if typ==TYP_TEXT: + xyset(d,"dx",dxset) + xyset(d,"dy",dyset) + + for chk in [chk for line in self.lns for chk in line.chks]: + chk.charpos = None + + self.writtendx = any(c.dx != 0 for c in self.chrs) + self.writtendy = any(c.dy != 0 for c in self.chrs) + self.dchange = False + + def write_axay(self): + """ + After dx/dy has changed, call this to write them to the text element + For simplicity, this is best done at the ParsedText level all at once + """ + if self.achange: + # Group characters by location + cs_loc = {(d,typ):[] for d in self.textel.descendants2() for typ in [TYP_TEXT,TYP_TAIL]} + for ln in self.lns: + for c in ln.chrs: + cs_loc[c.loc.elem,c.loc.typ].append(c) + + for (d, typ), cd in cs_loc.items(): + ax = [c.ax for c in cd] + ay = [c.ay for c in cd] + + axset = trim_list(ax,None) + ayset = trim_list(ay,None) + if ayset is not None and all(ayset[0]==ayv for ayv in ayset): + ayset = ayset[0:1] + + if typ==TYP_TAIL and (axset is not None or ayset is not None): + d = wrap_string(d,typ) + self.tree = None # invalidate + typ = TYP_TEXT + for c in cd: + c.loc = CLoc(d,TYP_TEXT,c.loc.ind) + + if typ==TYP_TEXT: + # When an x/y ends in Nones they can be trimmed off, but when + # they have internal Nones the string must be split + if (axset is not None and None in axset) or (ayset is not None and None in ayset): + span = inkex.Tspan if d.tag in TEtags else inkex.FlowSpan + sx = [i+1 for i,v in enumerate(axset[:-1]) if v is None and axset[i+1] is not None] + sy = [i+1 for i,v in enumerate(ayset[:-1]) if v is None and ayset[i+1] is not None] + sidx = sorted(sx+sy) + ranges = [(sidx[i-1] if i > 0 else 0, sidx[i] if i < len(sidx) else len(cd)) for i in range(len(sidx) + 1)] + txt = d.text + d.text = None + for k,(r1,r2) in enumerate(reversed(ranges)): + t = span() if k 0: + cel = line.chrs[0].loc.elem + while cel != elem and cel.getparent() != elem: + cel = cel.getparent() + tlvl = cel + + xyset(tlvl.getparent(), "x", line.x) + xyset(tlvl.getparent(), "y", line.y) + + tlvl.set("sodipodi:role", "line") + # reenable sodipodi so we can insert returns + + if self.writtendx or self.writtendy: + for i, chrv in enumerate(self.lns[0].chrs): + chrv.dx = olddx[i] + chrv.dy = olddy[i] + self.dchange = True + # may have deleted spr lines + + # For single lines, reset line-height to default + changed_styles = dict() + if len(self.lns) == 1: + for ddv in elem.descendants2(): + if "line-height" in ddv.cstyle: + changed_styles[ddv] = ddv.cstyle + del changed_styles[ddv]["line-height"] + + if len(self.chrs) > 0: + # Clear all fonts and only apply to relevant Tspans + + for ddv in elem.descendants2(): + sty = changed_styles.get(ddv, ddv.cstyle) + for key in [ + "font-family", + "font-stretch", + "font-weight", + "font-style", + "-inkscape-font-specification", + ]: + if key in sty: + del sty[key] + changed_styles[ddv] = sty + + for c in self.chrs: + sty = changed_styles.get(c.loc.sel, c.loc.sel.cstyle) + sty.update(c.fsty) + changed_styles[c.loc.sel] = sty + + # Put the first char's font at top since that's what Inkscape displays + # sty = Style(tuple(elem.cstyle.items())) + sty = changed_styles.get(elem, elem.cstyle) + sty.update(self.chrs[0].fsty) + changed_styles[elem] = sty + + # Try to set nominal font size to max value + # (value used by line-height, what Inkscape reports, etc.) + fs_origins = set() + for c in self.chrs: + cel = c.loc.sel + fs_origins.add(cel) + celstyle = changed_styles.get(cel, cel.cstyle) + while ( + ("font-size" in celstyle and "%" in str(celstyle["font-size"])) + or ("font-size" not in celstyle) + ) and cel is not elem: + cel = cel.getparent() + fs_origins.add(cel) + celstyle = changed_styles.get(cel, cel.cstyle) + if elem not in fs_origins and ( + len(self.lns) == 1 or all(not line.sprl for line in self.lns[1:]) + ): + sty = changed_styles.get(elem, elem.cstyle) + maxsize = max([c.utfs for c in self.chrs]) + if "font-size" not in sty or sty["font-size"] != maxsize: + sty["font-size"] = str(max([c.utfs for c in self.chrs])) + changed_styles[elem] = sty + for k, val in changed_styles.items(): + k.cstyle = val + + def split_off_chunks(self, chks): + """Splits off specified chunks of text into a new element.""" + return self.split_off_characters([c for chk in chks for c in chk.chrs]) + + def split_off_characters(self, chrs): + """Splits off specified characters into a new element.""" + npt = self.duplicate() + newtxt = npt.textel + + # Record position + cs1 = [c for ln in self.lns for c in ln.chrs] + cs2 = [c for ln in npt.lns for c in ln.chrs] + dmemo = dict(zip(cs1,cs2)) + + ps1 = {c:c.pts_ut[0] for c in cs1} + ps2 = {c:c.pts_ut[0] for c in cs2} + ds = {c:(c.dx,c.dy) for c in cs2} + fc = dmemo[chrs[0]] + + + # chars' line index + iln = self.lns.index(chrs[0].line) + ciis = [c.lnindex for c in chrs] # indexs of charsin line + + fusex = self.lns[iln].continuex or ciis[0] > 0 or ds[fc][0] != 0 + fusey = self.lns[iln].continuey or ciis[0] > 0 or ds[fc][1] != 0 + if fusex: + anfr = self.lns[iln].anchfrac + oldx = chrs[0].pts_ut[0][0] * (1 - anfr) + chrs[-1].pts_ut[3][0] * anfr + if fusey: + oldy = chrs[0].chk.y + ds[fc][1] + + for c in reversed(chrs): + c.delc(updatedelta=False) + + # Delete the other lines/chars in the copy + for il2 in reversed(range(len(npt.lns))): + if il2 != iln: + npt.lns[il2].dell() + else: + nln = npt.lns[il2] + for j in reversed(range(len(nln.chrs))): + if j not in ciis: + nln.chrs[j].delc(updatedelta=False) + + # Deletion of text can cause the srcs to be wrong. + # Reparse to find whereit is now + nln.xsrc, nln.ysrc = npt.parse_lines(srcsonly=True) + nln.write_xy(newx=nln.x, newy=nln.y) + nln.disablesodipodi(force=True) + if len(self.lns) > 0: + self.lns[0].xsrc, self.lns[0].ysrc = self.parse_lines(srcsonly=True) + self.lns[0].write_xy(newx=self.lns[0].x, newy=self.lns[0].y) + + if fusex: + nln.continuex = False + nln.write_xy(newx=[oldx]) + ds[fc] = (0,ds[fc][1]) + if fusey: + nln.continuey = False + nln.write_xy(newy=[oldy]) + ds[fc] = (ds[fc][0],0) + + + for ln in npt.lns: + for c in ln.chrs: + c.dx = ds[c][0] + c.dy = ds[c][1] + npt.write_dxdy() + + # In case there are some errors, correct with deltas + for pt, ps in [(self,ps1), (npt,ps2)]: + for ln in pt.lns: + for chk in ln.chks: + Deltax = [c.pts_ut[0][0] - ps[c][0] for c in chk.chrs] + Deltay = [c.pts_ut[0][1] - ps[c][1] for c in chk.chrs] + + if any(abs(d) > 0.001 for d in Deltax + Deltay): + for i in range(len(Deltax)): + if i == 0: + if ln.anchfrac == 0: + dx = Deltax[0] + dy = Deltay[0] + elif ln.anchfrac == 0.5: + dx = Deltax[0] + Deltax[-1] + dy = Deltay[0] + Deltay[-1] + else: + dx = 0 + dy = 0 + # right aligned: would not be able to compute + else: + dx = Deltax[i] - Deltax[i - 1] + dy = Deltay[i] - Deltay[i - 1] + chk.chrs[i].dx -= dx + chk.chrs[i].dy -= dy + pt.write_dxdy() + + newtxt._parsed_text = npt + return newtxt + + def delete_empty(self): + """Deletes empty elements from the doc. Generally this is done last""" + ocs_pts = [c.pts_t for c in self.chrs] + ocs_dx = [c.dx for c in self.chrs] + ocs_dy = [c.dy for c in self.chrs] + dltd = deleteempty(self.textel) + if dltd: + self.tree = None + self.reparse() + # Correct the position of the first character + chrs = [c for line in self.lns for c in line.chrs] + for i, line in enumerate(self.lns): + myi = chrs.index(line.chrs[0]) + dxt = ocs_pts[myi][0][0] - chrs[myi].pts_t[0][0] + dyt = ocs_pts[myi][0][1] - chrs[myi].pts_t[0][1] + + if abs(dxt) > 0.001 or abs(dyt) > 0.001: + oldpos = vmultinv(chrs[myi].line.transform.matrix, ocs_pts[myi][0][0], ocs_pts[myi][0][1]) + dltax = oldpos[0] - chrs[myi].pts_ut[0][0] + dltay = oldpos[1] - chrs[myi].pts_ut[0][1] + newxv = [x + dltax for x in line.x] + newyv = [y + dltay for y in line.y] + + if line.continuex or line.continuey: + if line.chrs[0].loc.typ == TYP_TAIL: + # wrap in a trivial Tspan so we can set x and y + line.chrs[0].add_style( + {"baseline-shift": "0%"}, setdefault=False + ) + line.xsrc = line.chrs[0].loc.elem + line.ysrc = line.chrs[0].loc.elem + line.continuex = False + line.continuey = False + line.write_xy(newx=newxv, newy=newyv) + + # Fix non-first dxs + ndxs = [c.dx for line in self.lns for c in line.chrs] + ndys = [c.dy for line in self.lns for c in line.chrs] + for i, c in enumerate(chrs): + if c.lnindex > 0: + if abs(ocs_dx[i] - ndxs[i]) > 0.001 or abs(ocs_dy[i] - ndys[i]) > 0.001: + c.dx = ocs_dx[i] + c.dy = ocs_dy[i] + self.write_dxdy() + for line in self.lns: + for chk in line.chks: + chk.charpos = None + + def fuse_fonts(self): + """ + For exporting, one usually wants to replace fonts with the actual font + that each character is rendered as. + + Other SVG renderers may only be able to use the font face name (i.e., + the fullname in fontconfig, which is the same name reported as Face in + Text and Font), so we make this the family name and add the true + family as a backup (with appropriate weight, width, and slant). + + For example, the ultra-bold variation of Arial has a face name of + Arial Black, while in Inkscape its CSS name is Arial Heavy. Therefore, + we would change its font-family to 'Arial Black','Arial'. + """ + # Collect all the fonts and how they are rendered + cstys = self.ctable.cstys + newfams = [] + for line in self.lns: + for c in line.chrs: + newfam = (c.fsty, c, c.loc.sel) + if c.c in cstys[c.fsty] and cstys[c.fsty][c.c] is not None: + csty = cstys[c.fsty][c.c] + fullname = fcfg.get_true_font_fullname(csty) + fontfam = csty["font-family"].strip("'") + fusefam = "'" + fullname + "','" + fontfam + "'" + fusesty = Style( + { + k: v if k != "font-family" else fusefam + for k, v in csty.items() + } + ) + if not c.fsty == fusesty: + newfam = (fusesty, c, c.loc.sel) + + newfams.append(newfam) + + # Make a dictionary whose keys are the elements whose styles we + # want to modify and whose values are (the new family, a list of characters) + # In descending order of the number of characters + torepl = {} + for csty, c, sel in newfams: + torepl.setdefault(sel, []).append((csty, c)) + for sel, value in torepl.items(): + count_dict = {} + for csty, c in value: + count_dict.setdefault(csty, []).append(c) + new_value = [] + for k, val in count_dict.items(): + new_tup = (k, val) + new_value.append(new_tup) + new_value.sort(key=lambda x: len(x[1]), reverse=True) + torepl[sel] = new_value + + # Replace fonts + for elem, rlst in torepl.items(): + # For the most common family, set the element style itself + if not font_style(elem.ccascaded_style) == rlst[0][0]: + elem.cstyle = elem.ccascaded_style + for k, val in rlst[0][0].items(): + elem.cstyle[k] = val + elem.cstyle["-inkscape-font-specification"] = None + for c in rlst[0][1]: + c.sty = elem.cstyle + # For less common, need to wrap in a new Tspan + for r in rlst[1:]: + for c in r[1]: + c.add_style( + { + "font-family": r[0]["font-family"], + "font-weight": r[0]["font-weight"], + "font-style": r[0]["font-style"], + "font-stretch": r[0]["font-stretch"], + "baseline-shift": "0%", + }, + setdefault=False, + ) + + def flow_to_text(self): + """Converts flowed text into normal text, returning the text els.""" + if self.isflow: + newtxts = [] + for line in reversed(self.lns): + nany = any(math.isnan(yvl) for yvl in line.y) + + anch = line.anchor + algn = {"start": "start", "middle": "center", "end": "end"}[anch] + + origx = None + if len(line.chrs) > 0: + origx = line.chrs[0].pts_ut[0][0] + + newtxt = self.split_off_characters(line.chrs) + if newtxt.tag == FRtag: + for ddv in newtxt.descendants2(): + if ddv.tag == FRtag: + ddv.tag = TextElement.ctag + elif isinstance(ddv, (FlowPara, FlowSpan)): + ddv.tag = TStag + elif isinstance(ddv, inkex.FlowRegion): + ddv.delete() + else: + newtxt.cstyle["shape-inside"] = None + newtxt.cstyle["inline-size"] = None + for k in list(newtxt): + k.cstyle["text-align"] = algn + k.cstyle["text-anchor"] = anch + + if nany: + newtxt.delete() + else: + deleteempty(newtxt) + npt = newtxt.parsed_text + if ( + origx is not None + and len(npt.lns) > 0 + and len(npt.lns[0].chrs) > 0 + ): + npt.reparse() + newx = [ + xvl + origx - npt.lns[0].chrs[0].pts_ut[0][0] + for xvl in npt.lns[0].x + ] + npt.lns[0].write_xy(newx) + newtxts.append(newtxt) + + self.textel.delete() + return newtxts + return [] + + # For debugging: make a rectange at all of the line's chunks' nominal extents + HIGHLIGHT_STYLE = "fill:#007575;fill-opacity:0.4675" # mimic selection + + def make_highlights(self, htype): + """Creates rectangles to highlight extents or bounding boxes.""" + if htype == "char": + exts = self.get_char_extents() + elif htype == "charink": + exts = self.get_char_inkbbox() + elif htype == "fullink": + exts = [self.get_full_inkbbox()] + elif htype == "chunk": + exts = self.get_chunk_extents() + elif htype == "line": + exts = self.get_line_extents() + elif htype == "chunkink": + exts = self.get_chunk_ink() + elif htype == "lineink": + exts = self.get_line_ink() + else: # 'all' + exts = [self.get_full_extent()] + for i, ext in enumerate(exts): + r = inkex.Rectangle() + r.set("x", ext.x1) + r.set("y", ext.y1) + r.set("height", ext.h) + r.set("width", ext.w) + r.set("transform", self.textel.ccomposed_transform) + sty = ( + ParsedText.HIGHLIGHT_STYLE + if i % 2 == 0 + else ParsedText.HIGHLIGHT_STYLE.replace("0.4675", "0.5675") + ) + r.set("style", sty) + self.textel.croot.append(r) + + def differential_to_absolute_kerning(self): + """ Converts any differential kerning to absolute kerning """ + if not self.isflow and any(c.dx!=0 for c in self.chrs): + for ln in self.lns: + ln.disablesodipodi() + for i,w in enumerate(ln.chks): + pts = [c.pts_ut for c in w.chrs] + for j, c in enumerate(w.chrs): + if c.dx != 0: + # Get last char in chunk without a dx + lc = next((j + idx for idx, c in enumerate(w.chrs[j+1:]) if c.dx != 0), len(w.chrs) - 1) + c.ax = pts[j][0][0] * (1 - ln.anchfrac) + pts[lc][3][0] * ln.anchfrac + c.dx = 0 + else: + c.ax = w.x if j==0 else None + + if c.dy !=0: + c.ay = pts[j][0][1] + c.dy = 0 + else: + c.ay = w.y if j==0 else None + self.write_dxdy() + self.write_axay() + + ptsut = [c.parsed_pts_ut for c in self.chrs] + ptst = [c.parsed_pts_t for c in self.chrs] + self.reparse() + for i, c in enumerate(self.chrs): + c.parsed_pts_ut = ptsut[i] + c.parsed_pts_t = ptst[i] + + # Bounding box functions + def get_char_inkbbox(self): + """Gets the untransformed bounding boxes of all characters' ink.""" + exts = [] + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + for chk in line.chks: + for c in chk.chrs: + pt1 = c.pts_ut_ink[0] + pt2 = c.pts_ut_ink[2] + if not math.isnan(pt1[1]): + exts.append(bbox((pt1, pt2))) + return exts + + def get_full_inkbbox(self): + """Gets the untransformed bounding box of the whole element.""" + ext = bbox(None) + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + for chk in line.chks: + for c in chk.chrs: + pt1 = c.pts_ut_ink[0] + pt2 = c.pts_ut_ink[2] + ext = ext.union(bbox((pt1, pt2))) + return ext + + # Extent functions + def get_char_extents(self): + """Gets the untransformed extent of each character.""" + exts = [] + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + for chk in line.chks: + for c in chk.chrs: + pt1 = c.pts_ut[0] + pt2 = c.pts_ut[2] + if not math.isnan(pt1[1]): + exts.append(bbox((pt1, pt2))) + return exts + + def get_chunk_extents(self): + """Gets the untransformed extent of each chunk.""" + exts = [] + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + for chk in line.chks: + pt1 = chk.pts_ut[0] + pt2 = chk.pts_ut[2] + if not math.isnan(pt1[1]): + exts.append(bbox((pt1, pt2))) + return exts + + def get_line_extents(self): + """Gets the untransformed extent of each line.""" + exts = [] + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + extln = bbox(None) + for chk in line.chks: + pt1 = chk.pts_ut[0] + pt2 = chk.pts_ut[2] + if not math.isnan(pt1[1]): + extln = extln.union(bbox((pt1, pt2))) + if not (extln.isnull): + exts.append(extln) + return exts + + def get_chunk_ink(self): + """Gets the untransformed extent of each chunk's ink.""" + exts = [] + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + for chk in line.chks: + ext = bbox(None) + for c in chk.chrs: + pt1 = c.pts_ut_ink[0] + pt2 = c.pts_ut_ink[2] + ext = ext.union(bbox((pt1, pt2))) + exts.append(ext) + return exts + + def get_line_ink(self): + """Gets the untransformed extent of each line's ink.""" + exts = [] + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + ext = bbox(None) + for c in line.chrs: + pt1 = c.pts_ut_ink[0] + pt2 = c.pts_ut_ink[2] + ext = ext.union(bbox((pt1, pt2))) + exts.append(ext) + return exts + + def get_full_extent(self, parsed=False): + """ + Gets the untransformed extent of the whole element. + parsed=True gets original prior to any mods + """ + ext = bbox(None) + if self.lns is not None and len(self.lns) > 0 and self.lns[0].xsrc is not None: + for line in self.lns: + for chk in line.chks: + for c in chk.chrs: + pts = ( + c.parsed_pts_ut + if parsed and c.parsed_pts_ut is not None + else c.pts_ut + ) + pt1 = pts[0] + pt2 = pts[2] + if not math.isnan(pt1[1]): + ext = ext.union(bbox((pt1, pt2))) + return ext + + @property + def tree(self): + """Returns the text tree of the element.""" + if self._tree is None: + self._tree = TextTree(self.textel) + return self._tree + + @tree.setter + def tree(self, val): + """Sets the text tree of the element.""" + if val is None: + self._tree = None + + def parse_lines_flow(self): + """ + Parses lines of flowed text. + For non-rectangular flow regions, uses svgpathtools to find where + lines are drawn (this is imported conditionally). + """ + self.lns = [] + sty = self.textel.cspecified_style + isflowroot = self.textel.tag == FRtag + isshapeins = ( + self.textel.tag == TEtag + and sty.get_link("shape-inside", self.textel.croot) is not None + ) + isz = ipx(sty.get("inline-size")) + isinlinesz = self.textel.tag == TEtag and isz + # Inkscape ignores 0 and invalid inline-size + + # Determine the flow region + otp_support = self.textel.otp_support_prop + if isshapeins: + frgn = sty.get_link("shape-inside", self.textel.croot) + dfr = frgn.duplicate() + + # Fuse transform to path + dfr.object_to_path() + dfr.set("d", str(dfr.cpath.transform(dfr.ctransform))) + dfr.cpath = None + dfr.ctransform = None + + # shape transform fused on path (not composed transform though) + self.textel.append(dfr) + region = dfr + # Find the bbox of the FlowRegion + elif isflowroot: + for ddv in self.textel.descendants2(): + if isinstance(ddv, FlowRegion): + pths = [p for p in ddv.descendants2() if p.tag in otp_support] + if len(pths) > 0: + region = pths[0] + elif isinlinesz: + # Make a Rectangle and treat it as the flow region + r = inkex.Rectangle() + _, ysrc = self.parse_lines(srcsonly=True) + iszx = self.textel.get("x") + iszy = self.textel.get("y", ysrc.get("y")) + + afr = get_anchorfrac(sty.get("text-anchor")) + r.set("x", -isz * afr) # pylint:disable=invalid-unary-operand-type + r.set("y", 0) + r.set("height", isz) + r.set("width", isz) + self.textel.croot.append(r) + region = r + + padding = ipx(self.textel.cspecified_style.get("shape-padding", "0")) + isrect = isrectangle(region, includingtransform=False) + + usesvt = not isrect and region.tag in otp_support and padding == 0 + if not isrect and not usesvt: + # Flow region we cannot yet handle, parse as normal text + # This works as long as the SVG1.1 fallback is being used + self.isflow = False + return self.parse_lines() + if usesvt: + region.object_to_path() + import svgpathtools as spt # pylint: disable=import-error, import-outside-toplevel + + sptregion = spt.parse_path(region.get("d")) + if not sptregion.isclosed(): + end = sptregion[-1].end + start = sptregion[0].start + sptregion.append(spt.Line(end, start)) + sptregion.closed = True + + bbx = region.cpath.bounding_box() + bbx = [bbx.left, bbx.top, bbx.width, bbx.height] + if not padding == 0: + bbx = [ + bbx[0] + padding, + bbx[1] + padding, + bbx[2] - 2 * padding, + bbx[3] - 2 * padding, + ] + + # Delete duplicate + if isshapeins: + dfr.delete() + elif isinlinesz: + r.delete() + + def height_above_below_baseline(elem): + lht = composed_lineheight(elem) + lsty = true_style(elem.cspecified_style) + fsz, scf, _ = composed_width(elem, "font-size") + + absp = (0.5000 * (lht / fsz - 1) + CharacterTable.flowy(lsty)) * ( + fsz / scf + ) # spacing above baseline + bbsp = (0.5000 * (lht / fsz - 1) + 1 - CharacterTable.flowy(lsty)) * ( + fsz / scf + ) # spacing below baseline + rawfs = fsz / scf + return absp, bbsp, rawfs + + # Get the properties of the FlowRoot + rabsp, rbbsp, rfs = height_above_below_baseline(self.textel) + rpct = ( + "line-height" in self.textel.cspecified_style + and "%" in self.textel.cspecified_style["line-height"] + ) + if rpct: + pctage = float(self.textel.cspecified_style["line-height"].strip("%")) + + # Group characters into lines + lns = [] + fparas = [ + k for k in list(self.textel) if isinstance(k, FlowPara) + ] # top-level FlowParas + for ddi, typ, tel, sel, txt in self.tree.dgenerator(): + if txt is not None and len(txt) > 0: + if isflowroot: + lnno = [ + i + for i, fpv in enumerate(fparas) + if fpv in tel.ancestors2(includeme=True) + ] + if len(lnno) == 0: + lnno = 0 + else: + lnno = lnno[0] + # tails of a FlowPara belong to the next line + if tel == fparas[lnno] and typ == TYP_TAIL: + lnno += 1 + else: + # Note: Tspans don't do anything in SVG2 flows + lnno = 0 + + # Determine above- and below-baseline lineheight + sty = sel.cspecified_style + ctf = sel.ccomposed_transform + fsz, scf, utfs = composed_width(sel, "font-size") + absp, bbsp, mrfs = height_above_below_baseline(sel) + lsty = true_style(sel.cspecified_style) + fabsp = max(rabsp, absp) + fbbsp = max(rbbsp, bbsp) + + # Inkscape has a bug when the FlowRoot has a line-height + # specified as a percentage and the FlowPara doesn't have one + # specified. Account for this + anc = sel.ancestors2(includeme=True, stopbefore=self.textel) + if rpct and all("line-height" not in a.cstyle for a in anc): + if rbbsp > bbsp: + fbbsp += (CharacterTable.flowy(lsty) - 0.5) * (rfs - mrfs) + if absp > rabsp: + fabsp -= (0.5 * (pctage / 100)) * (mrfs - rfs) + fbbsp -= ( + 0.5 * (pctage / 100) - (CharacterTable.flowy(lsty) - 0.5) + ) * (mrfs - rfs) + + if lnno >= len(lns): + algn = sty.get("text-align", "start") + anch = sty.get("text-anchor", "start") + if not algn == "start" and anch == "start": + anch = {"start": "start", "center": "middle", "end": "end"}[ + algn + ] + cln = TLine( + self, + [0], + [0], + self.textel, + self.textel, + False, + [], + anch, + ctf, + None, + sty, + False, + False, + ) + lns.append(cln) + cln.broken = False + cln.effabsp = fabsp + cln.effbbsp = fbbsp + else: + cln = lns[lnno] + + if typ==TYP_TEXT: + dxv = ParsedText.get_xy(self.tree.dds[ddi],'dx') + dyv = ParsedText.get_xy(self.tree.dds[ddi],'dy') + if dxv[0] is None: + dxv = [0]*len(txt) + else: + dxv = dxv + [0]*(len(txt)-len(dxv)) + if dyv[0] is None: + dyv = [0]*len(txt) + else: + dyv = dyv + [0]*(len(txt)-len(dyv)) + else: + dxv = [0]*len(txt) + dyv = [0]*len(txt) + + fsty = font_style(sty) + tsty = true_style(fsty) + for j, c in enumerate(txt): + csty = self.font_picker(txt, j, fsty, tsty) + prop = self.ctable.get_prop(c, csty) + tchr = TChar( + c, fsz, utfs, prop, sty, csty, CLoc(tel, typ, j), cln, dxv[j], dyv[j] + ) + tchr.lhs = (fabsp, fbbsp) + if j == 0: + lsp0 = tchr.lsp + bshft0 = tchr.bshft + else: + tchr.lsp = lsp0 + tchr.bshft = bshft0 + self.lns = lns + self.finish_lines() + self.fparaafter = False + + # Figure out where to break lines + # Currently only works for rectangular flows + i = 0 + breakcs = " -—!}|/?" + lncs = [line.chrs for line in lns] + blns = [] + lchr = None + y = 0 + while i < len(lncs): + if len(lncs[i]) > 0: + fchr = lncs[i][0] + + lht = None + getxintervals = True + while getxintervals: + getxintervals = False + if lht is None: + lht = fchr.line.effabsp + fchr.line.effbbsp + + # Determine x intervals where we can draw text + if not usesvt: + # For rectangles this is just the bounding box + xlims = [(bbx[0], bbx[2])] + else: + # For other flows this is where at least 90% of the line + # is unobstructed by the path + # Start by finding intervals where the y=10% and 90% points + # of the baseline intersect with the flow region + ln10 = spt.Line( + bbx[0] + (bbx[1] + y + lht * 0.1) * 1j, + bbx[0] + bbx[2] + (bbx[1] + y + lht * 0.1) * 1j, + ) + ln90 = spt.Line( + bbx[0] + (bbx[1] + y + lht * 0.9) * 1j, + bbx[0] + bbx[2] + (bbx[1] + y + lht * 0.9) * 1j, + ) + isc10 = sptregion.intersect(ln10) + isc90 = sptregion.intersect(ln90) + pts10 = sorted( + [ + sptregion.point(T1).real + for (T1, seg1, t1), (T2, seg2, t2) in isc10 + ] + ) + pts90 = sorted( + [ + sptregion.point(T1).real + for (T1, seg1, t1), (T2, seg2, t2) in isc90 + ] + ) + intervals = [] + for j in range(int(len(pts10) / 2)): + int10 = (pts10[2 * j], pts10[2 * j + 1]) + for k in range(int(len(pts90) / 2)): + int90 = (pts90[2 * k], pts90[2 * k + 1]) + intrsc = ( + max(int10[0], int90[0]), + min(int10[1], int90[1]), + ) + if intrsc[1] > intrsc[0]: + intervals.append(intrsc) + + # Use the tangent line at the 10 and 90% points to find the + # points where the line is at least 90% unobstructed + tol = lht * 1e-6 + + def tangent_line(pos): + pnt = sptregion.point(pos) + drv = sptregion.derivative(pos) + if not drv.real == 0: + slp = drv.imag / drv.real + intcp = pnt.imag - drv.imag / drv.real * pnt.real + else: + slp = drv.imag / tol + intcp = pnt.imag - drv.imag / tol * pnt.real + return slp, intcp + + def intersection_belowabove(pos, below=True): + pnt = sptregion.point(pos) + vrtl = spt.Line( + pnt.real + (bbx[1]) * 1j, + pnt.real + (bbx[1] + bbx[3]) * 1j, + ) + try: + intrsct = sptregion.intersect(vrtl) + except AssertionError: + return None, None + yvs = sorted( + [ + sptregion.point(T1b).imag + for (T1b, seg1b, t1b), (T2b, seg2b, t2b) in intrsct + ] + ) + if below: + yvs = [yvl for yvl in yvs if yvl > pnt.imag + tol] + ret = yvs[0] if len(yvs) > 0 else None + else: + yvs = [yvl for yvl in yvs if yvl < pnt.imag - tol] + ret = yvs[-1] if len(yvs) > 0 else None + rett = ( + [ + T1b + for (T1b, seg1b, t1b), (T2b, seg2b, t2b) in intrsct + if sptregion.point(T1b).imag == ret + ][0] + if ret is not None + else None + ) + return ret, rett + + def bounding_lines(p_top, p_btm): + if len(p_top) > 0: + p_top = p_top[0] + (mtop, btop) = tangent_line(p_top) + ry, r_pos = intersection_belowabove(p_top, below=True) + if ry is None or ry > bbx[1] + y + lht: + (mbtm, bbtm) = (0, bbx[1] + y + lht) + else: + (mbtm, bbtm) = tangent_line(r_pos) + else: + p_btm = p_btm[0] + (mbtm, bbtm) = tangent_line(p_btm) + ry, r_pos = intersection_belowabove(p_btm, below=False) + if ry is None or ry < bbx[1] + y: + (mtop, btop) = (0, bbx[1] + y) + else: + (mtop, btop) = tangent_line(r_pos) + return mtop, btop, mbtm, bbtm + + ints2 = [] + for inta, intb in intervals: + t10a = [ + T1 + for (T1, seg1, t1), (T2, seg2, t2) in isc10 + if sptregion.point(T1).real == inta + ] + t90a = [ + T1 + for (T1, seg1, t1), (T2, seg2, t2) in isc90 + if sptregion.point(T1).real == inta + ] + t10b = [ + T1 + for (T1, seg1, t1), (T2, seg2, t2) in isc10 + if sptregion.point(T1).real == intb + ] + t90b = [ + T1 + for (T1, seg1, t1), (T2, seg2, t2) in isc90 + if sptregion.point(T1).real == intb + ] + + mtopa, btopa, mbtma, bbtma = bounding_lines(t10a, t90a) + mtopb, btopb, mbtmb, bbtmb = bounding_lines(t10b, t90b) + + dya = (mbtma * inta + bbtma) - (mtopa * inta + btopa) + if dya < 0.9 * lht - tol: + xp9a = ( + (0.9 * lht - (bbtma - btopa)) / (mbtma - mtopa) + if mbtma != mtopa + else None + ) + xp9a = xp9a if xp9a >= inta else None + else: + xp9a = inta + + dyb = (mbtmb * intb + bbtmb) - (mtopb * intb + btopb) + if dyb < 0.9 * lht - tol: + xp9b = ( + (0.9 * lht - (bbtmb - btopb)) / (mbtmb - mtopb) + if mbtmb != mtopb + else None + ) + xp9b = xp9b if xp9b <= intb else None + else: + xp9b = intb + + if xp9a is not None and xp9b is not None and xp9b >= xp9a: + ints2.append((xp9a, xp9b)) + xlims = [(intv[0], intv[1] - intv[0]) for intv in ints2] + + # Intersection diagnostics + # for p in pts10: + # c = dh.new_element(inkex.Circle, self.textel) + # c.set('cx',str(p)) + # c.set('cy',str(bbx[1]+y+lht*0.1)) + # c.set('r',str(fchr.utfs/10)) + # c.set('style','fill:#000000;stroke:none') + # self.textel.croot.append(c) + # c.ctransform = self.textel.ccomposed_transform + # for p in pts90: + # c = dh.new_element(inkex.Circle, self.textel) + # c.set('cx',str(p)) + # c.set('cy',str(bbx[1]+y+lht*0.9)) + # c.set('r',str(fchr.utfs/10)) + # c.set('style','fill:#000000;stroke:none') + # self.textel.croot.append(c) + # c.ctransform = self.textel.ccomposed_transform + + # For every interval, find what text we can insert and where + # we should make breaks + breaks = [] + for xlim in xlims: + breakaft = None + hardbreak = False + strt = 0 if len(breaks) == 0 else breaks[-1] + 1 + csleft = lncs[i][strt:] + for j, c in enumerate(csleft): + if j == 0: + fcrun = c + if not isflowroot and c.c == "\n": + breakaft = j + hardbreak = True + break + if c.pts_ut[3][0] - fcrun.pts_ut[0][0] > xlim[1]: + spcs = [cv for cv in csleft[:j] if cv.c in breakcs] + # inkex.utils.debug('Break on '+str((c.c,j))) + if c.c == " ": + breakaft = j + # inkex.utils.debug('Break overflow space') + elif len(spcs) > 0: + breakaft = [ + k + for k, cv in enumerate(csleft) + if spcs[-1] == cv + ][0] + # inkex.utils.debug('Break chunk') + elif ( + xlim[1] > 4 * (c.line.effabsp + c.line.effbbsp) + and j > 0 + ): + # When the flowregion width is > 4*line height, + # allow intraword break + # https://gitlab.com/inkscape/inkscape/-/blob/master/src/libnrtype/Layout-TNG-Compute.cpp#L1989 + breakaft = j - 1 + # inkex.utils.debug('Break intraword') + else: + # Break whole line and hope that the next + # line is wider + breakaft = -1 + # inkex.utils.debug('Break whole line') + break + if breakaft is not None: + c.line.broken = True + breaks.append(breakaft + strt) + if hardbreak: + break + else: + break + if len(xlims) == 0: + breaks = [-1] + + # Use break points to split the line, pushing text after the + # last break to the next line + if not breaks: + splitcs = [lncs[i]] + else: + splitcs = [] + prev_index = -1 + for index in breaks: + splitcs.append(lncs[i][prev_index + 1 : index + 1]) + prev_index = index + splitcs.append(lncs[i][prev_index + 1 :]) + nextcs = None + if len(splitcs) > 1: + nextcs = splitcs.pop(-1) # push to next line + mycs = [item for sublist in splitcs[:-1] for item in sublist] + + allcs = [c for s in splitcs for c in s] + maxabsp = ( + max([c.lhs[0] for c in allcs]) + if len(allcs) > 0 + else fchr.line.effabsp + ) + maxbbsp = ( + max([c.lhs[1] for c in allcs]) + if len(allcs) > 0 + else fchr.line.effbbsp + ) + if not lht == maxabsp + maxbbsp: + lht = maxabsp + maxbbsp + getxintervals = True + + if nextcs is not None: + lncs = lncs[0:i] + [mycs, nextcs] + lncs[i + 1 :] + y += maxabsp + for j, chrs in enumerate(splitcs): + line = fchr.line + cln = TLine( + self, + [0], + [0], + self.textel, + self.textel, + False, + [], + line.anchor, + line.transform, + None, + line.style, + False, + False, + ) + cln.broken = line.broken + cln.effabsp = maxabsp + cln.effbbsp = maxbbsp + blns.append(cln) + for c in chrs: + lchr = TChar( + c.c, + c.tfs, + c.utfs, + c.prop, + c._sty, + c.tsty, + c.loc, + cln, c._dx, c._dy + ) + + if len(chrs) > 0: + anfr = cln.anchfrac + x = ( + xlims[j][0] * (1 - anfr) + + (xlims[j][0] + xlims[j][1]) * anfr + ) + if i == 0 and isinlinesz and iszy is not None: + y += ipx(iszy) - cln.effabsp + if isinlinesz and iszx is not None: + x += ipx(iszx) + cln.x = [x] + cln.y = [bbx[1] + y] + y += maxbbsp + + if y - 0.1 * (maxabsp + maxbbsp) > bbx[3] and not isinlinesz: + # When we reached the end of the flow region, add the remaining + # characters to a line that is given a NaN y position + cln.chrs = [c for lnc in lncs[i:] for c in lnc] + for j, c in enumerate(cln.chrs): + c.line = cln + c.lnindex = j + cln.y = [float("nan")] + break + + i += 1 + if i > 1000: + # inkex.utils.debug('Infinite loop') + break + + # Determine if any FlowParas follow the last character + # (Needed for unrenderedspace determination) + if lchr is not None: + dds = self.textel.descendants2() + j = dds.index(lchr.loc.elem) + if j < len(dds) - 1: + self.fparaafter = any(isinstance(ddv, FlowPara) for ddv in dds[j + 1 :]) + self.lns = blns + + +TYP_TEXT = 1 +TYP_TAIL = 0 + +NORMAL = 0 +PRECEDEDSPRL = 1 +TLVLSPRL = 2 + +class TextTree: + """ + Descendant tree class for text, with a generator for iterating through blocks of + text. Generator returns the current descendant index, typ (TYP_TAIL or TYP_TEXT), + descendant element, element from which it gets its style, and text string. + When starting at subel, only gets the tree subset corresponding to that element. + Note: If there is no text/tail, returns None for that block. + """ + + def __init__(self, elem): + """Initializes the text tree of an element.""" + dds, pts = elem.descendants2(True) + self.dds = dds + self.ptails = pts + self.pdict = {ddv: ddv.getparent() for ddv in dds} + + def dgenerator(self, subel=None): + """Yields descendants and their text in the tree.""" + if subel is None: + starti = 0 + stopi = len(self.dds) + subel = self.dds[0] + else: + starti = self.dds.index(subel) + stopi = [i for i, ptail in enumerate(self.ptails) if subel in ptail][0] + + for ddi, typ in TextTree.ttgenerator(len(self.dds), starti, stopi): + srcs = self.ptails[ddi] if typ == TYP_TAIL else [self.dds[ddi]] + for src in srcs: + if ( + typ == TYP_TAIL and src == subel + ): # finish at my own tail (do not yield it) + return + txt = src.tail if typ == TYP_TAIL else src.text + sel = ( + self.pdict[src] if typ == TYP_TAIL else src + ) # tails get style from parent + yield ddi, typ, src, sel, txt + + @staticmethod + def ttgenerator(numd, starti=0, stopi=None): + """ + A generator for crawling through a general text descendant tree + Returns the current descendant index and typ (TYP_TAIL for tail, + TYP_TEXT for text) + """ + ddi = starti + if stopi is None: + stopi = numd + typ = TYP_TAIL + while True: + if typ == TYP_TEXT: + ddi += 1 + typ = TYP_TAIL + else: + typ = TYP_TEXT + if ddi == stopi and typ == TYP_TEXT: + return + yield ddi, typ + + +def get_anchorfrac(anch): + """Gets the anchor fraction based on the text anchor.""" + anch_frac = {"start": 0, "middle": 0.5, "end": 1} + return anch_frac.get(anch, 0) + + +class TLine: + """ + A single line, which represents a list of characters. Typically a top-level + Tspan or TextElement. This is further subdivided into a list of chunks. + """ + + __slots__ = ( + "_xv", + "_yv", + "sprl", + "sprlabove", + "anchor", + "anchfrac", + "chrs", + "chks", + "transform", + "xsrc", + "ysrc", + "tlvlno", + "style", + "continuex", + "continuey", + "ptxt", + "splits", + "sws", + "effabsp", + "effbbsp", + "broken", + ) + + def __init__( + self, + ptxt, + x, + y, + xsrc, + ysrc, + sprl, + sprlabove, + anch, + xform, + tlvlno, + sty, + continuex, + continuey, + ): + """Initializes TLine with given parameters.""" + self._xv = x + self._yv = y + self.sprl = sprl + # is this line truly a sodipodi:role line + self.sprlabove = sprlabove + # nominal value of spr (sprl may actually be disabled) + self.anchor = anch + self.anchfrac = get_anchorfrac(anch) + self.chrs = [] + self.chks = [] + if xform is None: + self.transform = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) + else: + self.transform = xform + self.xsrc = xsrc + # element from which we derive our x value + self.ysrc = ysrc + # element from which we derive our x value + self.tlvlno = tlvlno + # which number Tspan I am if I'm top-level (otherwise None) + self.style = sty + # self.elem = elem; + self.continuex = continuex + # when enabled, x of a line is the endpoint of the previous line + self.continuey = continuey + # when enabled, y of a line is the endpoint of the previous line + self.ptxt = ptxt + # Flow-specific + self.broken = None # text is broken + self.effabsp = None # above-baseline space + self.effbbsp = None # below-baseline space + ptxt.lns.append(self) + + def copy(self, memo): + """Creates a copy of the TLine instance.""" + ret = TLine.__new__(TLine) + memo[self] = ret + + ret._xv = self._xv[:] + ret._yv = self._yv[:] + ret.sprl = self.sprl + ret.sprlabove = [memo[sa] for sa in self.sprlabove] + ret.anchor = self.anchor + ret.anchfrac = self.anchfrac + ret.chrs = [c.copy(memo) for c in self.chrs] + ret.chks = [chk.copy(memo) for chk in self.chks] + ret.transform = self.transform + ret.xsrc = memo[self.xsrc] + ret.ysrc = memo[self.ysrc] + ret.tlvlno = self.tlvlno + ret.style = self.style + ret.continuex = self.continuex + ret.continuey = self.continuey + ret.ptxt = self.ptxt + ret.broken = self.broken + ret.effabsp = self.effabsp # above-baseline space + ret.effbbsp = self.effbbsp # below-baseline space + + return ret + + @property + def angle(self): + """Returns the transform angle in degrees.""" + return math.atan2(self.transform.c, self.transform.d) * 180 / math.pi + + def get_x(self): + """Returns the x property value.""" + if not self.continuex: + return self._xv + if self.ptxt is None: + return [0] + i = self.ptxt.lns.index(self) + if i > 0 and len(self.ptxt.lns[i - 1].chks) > 0: + anfr = self.anchfrac + xanch = (1 + anfr) * self.ptxt.lns[i - 1].chks[-1].pts_ut[3][ + 0 + ] - anfr * self.ptxt.lns[i - 1].chks[-1].pts_ut[0][0] + return [xanch] + return [0] + + def set_x(self, xvi): + """Sets the x property value.""" + self._xv = xvi + + x = property(get_x, set_x) + + def get_y(self): + """Returns the y property value.""" + if not self.continuey: + return self._yv + if self.ptxt is None: + return [0] + i = self.ptxt.lns.index(self) + if i > 0 and len(self.ptxt.lns[i - 1].chks) > 0: + anfr = self.anchfrac + yanch = (1 + anfr) * self.ptxt.lns[i - 1].chks[-1].pts_ut[3][ + 1 + ] - anfr * self.ptxt.lns[i - 1].chks[-1].pts_ut[0][1] + return [yanch] + return [0] + + def set_y(self, yvi): + """Sets the y property value.""" + self._yv = yvi + + y = property(get_y, set_y) + # anchorfrac = property(lambda self: get_anchorfrac(self.anchor)) + + def insertc(self, c, i): + """Inserts a character below a specified position.""" + for ch2 in self.chrs[i:]: + ch2.lnindex += 1 + c.lnindex = i + self.chrs = self.chrs[0:i] + [c] + self.chrs[i:] + c.line = self + + def addw(self, chk): + """Adds a complete chunk to the line.""" + self.chks.append(chk) + + def parse_chunks(self): + """ + Parses the line into chunks based on characters. + Parsing a line into chunks is the final step that should be called once + the line parser has gotten all the characters. + """ + chk = None + self.chks = [] + for i in range(len(self.chrs)): + if i == 0: + chk = TChunk(i, self.x[0], self.y[0], self) + # open new chunk + elif (i < len(self.x) and self.x[i] is not None) or (i < len(self.y) and self.y[i] is not None): # None means keep the same chunk + self.addw(chk) # close previous chunk + chk = TChunk(i, self.x[min(i,len(self.x)-1)], self.y[min(i,len(self.y)-1)], self) + # open new chunk + else: + chk.addc(i) + # add to existing chunk + if chk is not None: + self.addw(chk) + + def dell(self): + """Deletes the whole line.""" + self.write_xy(self.x[:1]) + for _, c in enumerate(reversed(self.chrs)): + if c.loc.typ == TYP_TEXT: + c.loc.elem.text = ( + c.loc.elem.text[: c.loc.ind] + c.loc.elem.text[c.loc.ind + 1 :] + ) + else: + c.loc.elem.tail = ( + c.loc.elem.tail[: c.loc.ind] + c.loc.elem.tail[c.loc.ind + 1 :] + ) + self.ptxt.write_dxdy() + + if self in self.ptxt.lns: + self.ptxt.lns.remove(self) + + def txt(self): + """Returns the concatenated text of all characters.""" + return "".join([c.c for c in self.chrs]) + + def change_alignment(self, newanch): + """Changes the alignment of the line without affecting character position.""" + if newanch != self.anchor: + sibsrc = [ + line + for line in self.ptxt.lns + if line.xsrc == self.xsrc or line.ysrc == self.ysrc + ] + for line in reversed(sibsrc): + line.disablesodipodi() + # Disable sprl for all lines sharing our src, including us + # Note that it's impossible to change one line without affecting + # the others + + for chk in self.chks: + minx = min([chk.pts_ut[i][0] for i in range(4)]) + maxx = max([chk.pts_ut[i][0] for i in range(4)]) + + if chk.unrenderedspace and self.chrs[-1] in chk.chrs: + maxx -= chk.chrs[-1].cwd + + anfr = get_anchorfrac(newanch) + newx = (1 - anfr) * minx + anfr * maxx + + if len(chk.chrs) > 0: + newxv = self.x + newxv[chk.chrs[0].lnindex] = newx + self.write_xy(newxv) + alignd = {"start": "start", "middle": "center", "end": "end"} + chk.chrs[0].loc.elem.cstyle.__setitem__( + "text-anchor", newanch, "text-align", alignd[newanch] + ) + self.continuex = False + + self.anchor = newanch + self.anchfrac = anfr + chk.charpos = None # invalidate chunk positions + + def disablesodipodi(self, force=False): + """Disables sodipodi:role=line.""" + if len(self.sprlabove) > 0 or force: + if len(self.chrs) > 0: + newsrc = self.chrs[0].loc.elem # disabling snaps src to first char + if self.chrs[0].loc.typ == TYP_TAIL: + newsrc = self.chrs[0].loc.elem.getparent() + newsrc.set("sodipodi:role", None) + + self.sprlabove = [] + self.xsrc = newsrc + self.ysrc = newsrc + xyset(self.xsrc, "x", self.x) # fuse position to new source + xyset(self.ysrc, "y", self.y) + self.sprl = False + + def write_xy(self, newx=None, newy=None): + """ + Changes the position of the line in the document. + Update the line's position in the document, accounting for inheritance + Never change x/y directly, always call this function + """ + if newx is not None: + sibsrc = [line for line in self.ptxt.lns if line.xsrc == self.xsrc] + if len(sibsrc) > 1: + for line in reversed(sibsrc): + line.disablesodipodi() # Disable sprl when lines share an xsrc + + while len(newx) > 1 and newx[-1] is None: + newx.pop() + oldx = self._xv if not self.continuex else self.x + self._xv = newx + xyset(self.xsrc, "x", newx) + + if ( + len(oldx) > 1 and len(newx) == 1 and len(self.sprlabove) > 0 + ): # would re-enable sprl + self.disablesodipodi() + + if newy is not None: + sibsrc = [line for line in self.ptxt.lns if line.ysrc == self.ysrc] + if len(sibsrc) > 1: + for line in reversed(sibsrc): + line.disablesodipodi() # Disable sprl when lines share a ysrc + + while len(newy) > 1 and newy[-1] is None: + newx.pop() + oldy = self._yv if not self.continuey else self.y + self._yv = newy + xyset(self.ysrc, "y", newy) + + if ( + len(oldy) > 1 and len(newy) == 1 and len(self.sprlabove) > 0 + ): # would re-enable sprl + self.disablesodipodi() + + def split(self,i): + if i>=len(self.chrs): + return + self.ptxt.split_off_characters(self.chrs[i:]) + +class TChunk: + """Represents a chunk, a group of characters with the same assigned anchor.""" + + __slots__ = ( + "chrs", + "windex", + "iis", + "ncs", + "_xv", + "_yv", + # "scf", + "line", + "transform", + "nextw", + "prevw", + "prevsametspan", + "_pts_ut", + "_pts_t", + "_bb", + "_dxeff", + "_charpos", + "_cpts_ut", + "_cpts_t", + "txt", + "lsp", + "dxeff", + "cwd", + "dy", + "caph", + "bshft", + "mw", + "merges", + "mergetypes", + "merged", + "wtypes", + "bb_big", + ) + + def __init__(self, i, x, y, line): + """Initializes TChunk with given parameters.""" + c = line.chrs[i] + self.chrs = [c] + c.windex = 0 + self.iis = [i] + # character index in chunk + self.ncs = len(self.iis) + self._xv = x + self._yv = y + # all letters have the same scale + self.line = line + self.transform = line.transform + c.chk = self + self.nextw = self.prevw = self.prevsametspan = None + self._pts_ut = self._pts_t = self._bb = None + self._dxeff = self._charpos = None + self._cpts_ut = None + self._cpts_t = None + + # Character attribute lists + self.txt = c.c + self.lsp = [c.lsp] + self.dxeff = [c.dx, c.lsp] + # Effective dx (with letter-spacing). Note that letter-spacing adds space + # after the char, so dxeff ends up being longer than the number of chars by 1 + self.cwd = [c.cwd] + self.dy = [c.dy] + self.caph = [c.caph] + self.bshft = [c.bshft] + + def copy(self, memo): + """Creates a copy of the TChunk instance.""" + ret = TChunk.__new__(TChunk) + memo[self] = ret + + # pylint:disable=protected-access + ret.ncs = self.ncs + ret._xv = self._xv + ret._yv = self._yv + ret.transform = self.transform + ret.nextw = self.nextw + ret.prevw = self.prevw + ret.prevsametspan = self.prevsametspan + ret._pts_ut = self._pts_ut + ret._pts_t = self._pts_t + ret._bb = self._bb + ret._dxeff = self._dxeff + ret._charpos = self._charpos + ret._cpts_ut = self._cpts_ut + ret._cpts_t = self._cpts_t + ret.txt = self.txt + ret.lsp = self.lsp[:] + ret.dxeff = self.dxeff[:] + ret.cwd = self.cwd[:] + ret.dy = self.dy[:] + ret.caph = self.caph[:] + ret.bshft = self.bshft[:] + # pylint:enable=protected-access + + ret.chrs = list( + map(memo.get, self.chrs, self.chrs) + ) # faster than [memo.get(c) for c in self.chrs] + for ret_c in ret.chrs: + ret_c.chk = ret + ret.iis = self.iis[:] + ret.line = memo[self.line] + return ret + + def addc(self, i): + """Adds an existing character to a chunk based on line index.""" + c = self.line.chrs[i] + c.chk = None # avoid problems in character properties + self.chrs.append(c) + self.cchange() + self.iis.append(i) + self.ncs = len(self.iis) + c.windex = self.ncs - 1 + + self.txt += c.c + self.lsp.append(c.lsp) + self.dxeff[-1] += c.dx + self.dxeff.append(c.lsp) + self.cwd.append(c.cwd) + self.dy.append(c.dy) + self.caph.append(c.caph) + self.bshft.append(c.bshft) + c.chk = self + + def removec(self, c): + """Removes a character from a chunk based on chunk index.""" + i = c.windex + for ch2 in self.chrs[i + 1 :]: + ch2.windex -= 1 + c.windex = None + c.chk = None + self.chrs = del2(self.chrs, i) + self.iis.pop(i) + self.ncs = len(self.iis) + self.cchange() + + self.txt = del2(self.txt, i) + self.lsp.pop(i) + self.dxeff.pop(i) + self.dxeff[i] = (self.chrs[i].dx if i < self.ncs else 0) + ( + self.chrs[i - 1].lsp if i > 0 else 0 + ) + self.cwd.pop(i) + self.dy.pop(i) + self.caph.pop(i) + self.bshft.pop(i) + + if len(self.chrs) == 0: # chunk now empty + self.del_chk() + + def cchange(self): + """ + Callback for character addition/deletion. Used to invalidate cached + properties + """ + self.charpos = None + + if self.line.ptxt.writtendx or self.line.ptxt.writtendy: + self.line.ptxt.dchange = True + + @property + def x(self): + """Get effective x value""" + if self.line and self.ncs > 0: + lnx = self.line._xv if not self.line.continuex else self.line.x + # checking for continuex early eliminates most unnecessary calls + i = self.iis[0] # first char + return lnx[i] if i < len(lnx) else lnx[-1] + return 0 + + @property + def y(self): + """Get effective y value""" + if self.line and self.ncs > 0: + lny = self.line._yv if not self.line.continuey else self.line.y + # checking for continuex early eliminates most unnecessary calls + i = self.iis[0] # first char + return lny[i] if i < len(lny) else lny[-1] + return 0 + + @property + def scf(self): + """Get scale of first character""" + return self.chrs[0].scf if self.ncs>0 else None + + def del_chk(self,updatedelta=True): + """Deletes the chunk from the line.""" + for c in reversed(self.chrs): + c.delc(updatedelta=updatedelta) + if self in self.line.chks: + self.line.chks.remove(self) + + def appendc(self, ncv, ncprop, ndx, ndy, totail=None): + """Generates a new character and adds it to the end of the chunk.""" + # Add to document + lchr = self.chrs[-1] + # last character + if totail is None: + myi = lchr.loc.ind + 1 + # insert after last character + if lchr.loc.typ == TYP_TEXT: + lchr.loc.elem.text = ( + lchr.loc.elem.text[0:myi] + ncv + lchr.loc.elem.text[myi:] + ) + else: + lchr.loc.elem.tail = ( + lchr.loc.elem.tail[0:myi] + ncv + lchr.loc.elem.tail[myi:] + ) + else: + if totail.tail is None: + totail.tail = ncv + else: + totail.tail = ncv + totail.tail + + # Make new character as a copy of the last one of the current chunk + c = copy(lchr) + c.chk = None # so we don't have problems during style setting + c.c = ncv + c.prop = ncprop + c.cwd = ncprop.charw * c.utfs + c.dx = ndx + c.dy = ndy + + if totail is None: + c.loc = CLoc(c.loc.elem, c.loc.typ, c.loc.ind + 1) # updated location + else: + c.loc = CLoc(totail, TYP_TAIL, 0) + c.sty = c.loc.sel.cspecified_style + + # Add to line + myi = lchr.lnindex + 1 # insert after last character + if len(self.line.x) > 0: + newx = self.line.x[0:myi] + [None] + self.line.x[myi:] + newx = newx[0 : len(self.line.chrs) + 1] + self.line.write_xy(newx) + + self.line.insertc(c, myi) + for i in range(myi + 1, len(self.line.chrs)): + # need to increment index of subsequent objects with the same parent + cha = self.line.chrs[i] + if cha.loc.typ == c.loc.typ and cha.loc.elem == c.loc.elem: + cha.loc.ind += 1 + if cha.chk is not None: + cha.chk.iis[cha.windex] += 1 + # Add to chunk, recalculate properties + self.addc(myi) + + # Adding a character causes the chunk to move if it's center- or right-justified + # Need to fix this by adjusting position + deltax = -self.line.anchfrac * self.line.chrs[myi].cwd + if deltax != 0: + newx = self.line.x + newx[self.chrs[0].lnindex] -= deltax + self.line.write_xy(newx) + + self.line.ptxt.write_dxdy() + + def append_chk(self, nchk, stype, maxspaces=None): + """ + Adds a new chunk (possibly from another line) into the current one. + Equivalent to typing it in + """ + if len(nchk.chrs) > 0: + # Calculate the number of spaces we need to keep the position constant + # (still need to adjust for anchors) + _, br1, _, bl2 = self.get_ut_pts(nchk) + lchr = self.chrs[-1] + # last character + numsp = (bl2[0] - br1[0]) / (lchr.spw) + numsp = max(0, round(numsp)) + if maxspaces is not None: + numsp = min(numsp, maxspaces) + + for _ in range(numsp): + self.appendc( + " ", lchr.line.ptxt.ctable.get_prop(" ", lchr.tsty), -lchr.lsp, 0 + ) + + fchr = nchk.chrs[0] + prevc = self.chrs[-1] + for c in nchk.chrs: + mydx = (c != fchr) * c.dx - prevc.lsp * (c == fchr) + # use dx to remove lsp from the previous c + c.delc() + + ntype = copy(stype) + otype = c.sty.get("baseline-shift") + if otype in ["super", "sub"] and stype == "normal": + ntype = otype + + # Nested tspans should be appended to the end of tails + totail = None + if self.chrs[-1].loc.sel != self.chrs[0].loc.sel: + cel = self.chrs[-1].loc.sel + while ( + cel is not None + and cel.getparent() != self.chrs[0].loc.sel + and cel.getparent() != self.line.ptxt.textel + ): + cel = cel.getparent() + totail = cel + + self.appendc(c.c, c.prop, mydx, c.dy, totail=totail) + newc = self.chrs[-1] + + # Make sure items on tails have right font size + if totail is not None: + fsz, scf, utfs = composed_width(totail.getparent(), "font-size") + newc.utfs = utfs + newc.tfs = fsz + newc.cwd = newc.prop.charw * utfs + newc.caph = newc.prop.caph * utfs + newc.spw = newc.prop.spacew * utfs + newc.chk.cwd[newc.windex] = newc.cwd + newc.chk.caph[newc.windex] = newc.caph + + newc.parsed_pts_ut = [ + vmultinv(self.transform.matrix, p[0], p[1]) for p in c.parsed_pts_t + ] + newc.parsed_pts_t = c.parsed_pts_t + + # Update the style + newsty = None + if c.sty != newc.sty or ntype in ["super", "sub"] or abs(c.scf - newc.scf)>1e-4: + newsty = c.sty + if ntype in ["super", "sub"]: + # newsty = c.sty + # Nativize super/subscripts + newsty["baseline-shift"] = ( + "super" if ntype == "super" else "sub" + ) + newsty["font-size"] = "65%" + # Leave size unchanged + # nsz = round((c.spw*c.scf)/(newc.spw*newc.scf)*100) + # newsty['font-size'] = str(nsz)+'%'; + + # Leave baseline unchanged (works) + # shft = round(-(bl2.y-br1.y)/self.tfs*100*self.scf); + # newsty['baseline-shift']= str(shft)+'%'; + elif abs(c.scf - newc.scf)>1e-4: + # Prevent accidental font size changes when differently + # transformed + nsz = round((c.spw * c.scf) / (newc.spw * newc.scf) * 100) + # newsty['font-size'] = str(nsz)+'%'; + newsty["font-size"] = ( + f"{format(nsz, '.2f').rstrip('0').rstrip('.')}%" + ) + + if newsty is not None: + newc.add_style(newsty, newfs=True) + prevc = newc + + def get_ut_pts(self, chk2, current_pts=False): + """Gets the coordinates of another chunk in my coordinate system.""" + if current_pts: + c1uts = [c.pts_ut for c in self.chrs] + c2uts = [c.pts_ut for c in chk2.chrs] + c2ts = [c.pts_t for c in chk2.chrs] + else: + c1uts = [c.parsed_pts_ut for c in self.chrs] + c2uts = [c.parsed_pts_ut for c in chk2.chrs] + c2ts = [c.parsed_pts_t for c in chk2.chrs] + + minv = float("inf") + chri = None + + for i in range(len(chk2.chrs)): + if c2uts[i] is not None: + if c2uts[i][0][0] < minv: + minv = c2uts[i][0][0] + chri = i + + bl2 = vmultinv(self.transform.matrix, c2ts[chri][0][0], c2ts[chri][0][1]) + tl2 = vmultinv(self.transform.matrix, c2ts[chri][1][0], c2ts[chri][1][1]) + + maxv = float("-inf") + chri = None + for i in range(len(self.chrs)): + if c1uts[i] is not None: + if c1uts[i][3][0] > maxv: + maxv = c1uts[i][3][0] + chri = i + + tr1 = c1uts[chri][2] + br1 = c1uts[chri][3] + return tr1, br1, tl2, bl2 + + def fix_merged_position(self): + """ + Adjusts the position of merged text to account for small changes in + chunk position that occur. This depends on alignment, so it is + generally done after the final justification is set. + """ + gcs = [c for c in self.chrs if c.c != " "] + if len(gcs) > 0: + omaxx = max([c.parsed_pts_ut[3][0] for c in gcs]) + ominx = min([c.parsed_pts_ut[0][0] for c in gcs]) + newptsut = [c.pts_ut for c in gcs] + nmaxx = max([p[3][0] for p in newptsut]) + nminx = min([p[0][0] for p in newptsut]) + + anfr = self.line.anchfrac + deltaanch = (nminx * (1 - anfr) + nmaxx * anfr) - ( + ominx * (1 - anfr) + omaxx * anfr + ) + # how much the final anchor moved + if deltaanch != 0: + newx = self.line.x + newx[self.chrs[0].lnindex] -= deltaanch + self.line.write_xy(newx) + self.charpos = None + self.line.ptxt.write_dxdy() + + def get_fs(self): + """Returns the font size of the chunk.""" + return maxnone([c.tfs for c in self.chrs]) + + tfs = property(get_fs) + + @property + def spw(self): + """Returns the space width of the chunk.""" + return maxnone([c.spw for c in self.chrs]) + + def get_mch(self): + """Returns the maximum character height of the chunk.""" + return maxnone(self.caph) + + mch = property(get_mch) + + @property + def angle(self): + """Returns the angle of the chunk.""" + return self.line.angle + + @property + def unrenderedspace(self): + """ + Returns True if the last character of a multichar line is a space + and is not rendered. + """ + lastspc = ( + self.ncs > 1 + and self.chrs[-1] == self.line.chrs[-1] + and self.chrs[-1].c in [" ", "\u00a0"] + ) + if not self.line.ptxt.isflow: + return lastspc + if lastspc: + # For flows, is rendered when not the last line and line isn't broken + lastln = ( + self.line.ptxt.lns[-1] == self.line and not self.line.ptxt.fparaafter + ) + return lastln or self.line.broken + return False + + @property + def charpos(self): + """ + Returns the character positions relative to the left side of the chunk. + """ + if self._charpos is None: + dadv = [0] * self.ncs + if DIFF_ADVANCES: + for i in range(1, self.ncs): + dadv[i] = self.chrs[i].dadvs(self.chrs[i - 1].c, self.chrs[i].c)*(self.dxeff[i]==0) + # default to 0 for chars of different style + # any dx value overrides differential advances + + chks = [self.cwd[i] + self.dxeff[i] + dadv[i] for i in range(self.ncs)] + cstop = list(itertools.accumulate(chks)) + # cumulative width up to and including the ith char + cstrt = [cstop[i] - self.cwd[i] for i in range(self.ncs)] + + cstrt = np.array(cstrt, dtype=float) + cstop = np.array(cstop, dtype=float) + + chkw = cstop[-1] + offx = -self.line.anchfrac * ( + chkw - self.unrenderedspace * self.chrs[-1].cwd + ) + # offset of the left side of the chunk from the anchor + chkx = self.x + chky = self.y + + lftx = (chkx + cstrt + offx)[:, np.newaxis] + rgtx = (chkx + cstop + offx)[:, np.newaxis] + + adyl = list(itertools.accumulate(self.dy)) + btmy = np.array( + [chky + dy - bs for dy, bs in zip(adyl, self.bshft)], dtype=float + )[:, np.newaxis] + topy = np.array( + [ + chky + dy - bs - caph + for dy, caph, bs in zip(adyl, self.caph, self.bshft) + ], + dtype=float, + )[:, np.newaxis] + + lx2 = float(min(lftx - self.dxeff[0]).squeeze()) + rx2 = float(lx2 + chkw) + by2 = float(max(btmy).squeeze()) + ty2 = float(min(topy).squeeze()) + + self._charpos = (lftx, rgtx, btmy, topy, lx2, rx2, by2, ty2) + + return self._charpos + + @charpos.setter + def charpos(self, svi): + """Sets the character positions and invalidates dependent properties.""" + if svi is None: # invalidate self and dependees + self._charpos = None + self.pts_ut = None + self.cpts_ut = None + self.cpts_t = None + + @property + def pts_ut(self): + """Returns the untransformed bounding box of the chunk.""" + if self._pts_ut is None: + (_, _, _, _, lx2, rx2, by2, ty2) = self.charpos + self._pts_ut = [ + (lx2, by2), + (lx2, ty2), + (rx2, ty2), + (rx2, by2), + ] + return self._pts_ut + + @pts_ut.setter + def pts_ut(self, _): + """Sets the untransformed bounding box and invalidates dependent properties.""" + if _ is None and self._pts_ut is not None: # invalidate self and dependees + self._pts_ut = None + self.pts_t = None + # self.cpts_ut = None + + @property + def pts_t(self): + """Returns the transformed bounding box of the chunk.""" + if self._pts_t is None: + self._pts_t = [ + vmult(self.transform.matrix, p[0], p[1]) for p in self.pts_ut + ] + return self._pts_t + + @pts_t.setter + def pts_t(self, _): + """Sets the transformed bounding box and invalidates dependent properties.""" + if _ is None and self._pts_t is not None: # invalidate self and dependees + self._pts_t = None + self.bbx = None + # self.cpts_t = None + + @property + def bbx(self): + """Returns the bounding box of the chunk.""" + if self._bb is None: + ptt = self.pts_t + min_x = min(ptt[0][0], ptt[1][0], ptt[2][0], ptt[3][0]) + min_y = min(ptt[0][1], ptt[1][1], ptt[2][1], ptt[3][1]) + max_x = max(ptt[0][0], ptt[1][0], ptt[2][0], ptt[3][0]) + max_y = max(ptt[0][1], ptt[1][1], ptt[2][1], ptt[3][1]) + self._bb = bbox([min_x, min_y, max_x - min_x, max_y - min_y]) + return self._bb + + @bbx.setter + def bbx(self, bbi): + """Sets the bounding box and invalidates dependent properties.""" + if bbi is None: # invalidate + self._bb = None + + @property + def cpts_ut(self): + """Returns the untransformed bounding box of characters.""" + if self._cpts_ut is None: + (lftx, rgtx, btmy, topy, _, _, _, _) = self.charpos + n_rows = lftx.shape[0] + self._cpts_ut = [ + tuple((coord[i][0], val[i][0]) for i in range(n_rows)) + for coord, val in zip( + (lftx, lftx, rgtx, rgtx), (btmy, topy, topy, btmy) + ) + ] + + return self._cpts_ut + + @cpts_ut.setter + def cpts_ut(self, svi): + """Sets the untransformed bounding box of characters and invalidates + dependent properties.""" + if svi is None: + self._cpts_ut = None + + @property + def cpts_t(self): + """Returns the transformed bounding box of characters.""" + if self._cpts_t is None: + (lftx, rgtx, btmy, topy, _, _, _, _) = self.charpos + nch = len(lftx) + pts = np.array( + [ + (coord[i][0], val[i][0], 1) + for coord, val in zip( + (lftx, lftx, rgtx, rgtx), (btmy, topy, topy, btmy) + ) + for i in range(nch) + ], + dtype=float, + ) + mat = np.array(self.transform.matrix + ((0, 0, 1),), dtype=float) + + tps = np.dot(mat, pts.T).T + self._cpts_t = [ + tps[0:nch, 0:2], + tps[nch : 2 * nch, 0:2], + tps[2 * nch : 3 * nch, 0:2], + tps[3 * nch : 4 * nch, 0:2], + ] + + return self._cpts_t + + @cpts_t.setter + def cpts_t(self, svi): + """Sets the transformed bounding box of characters and invalidates + dependent properties.""" + if svi is None: + self._cpts_t = None + + +class TChar: + """Represents a single character and its style.""" + + def __init__(self, c, tfs, utfs, prop, sty, tsty, loc, line, dx, dy): + """Initializes TChar with given parameters.""" + self.c = c + self.tfs = tfs + # transformed font size (uu) + self.utfs = utfs + # untransformed font size + self.prop = prop + # properties of a 1 uu character + self.cwd = prop.charw * utfs + # ut character width + self._sty = sty + # actual style + self.tsty = tsty + # true font style + self.fsty = font_style(sty) + # font style + self.loc = loc + # true location: [parent, TYP_TEXT or TYP_TAIL, index] + self.caph = prop.caph * utfs + # cap height (height of flat capitals like T) + self.spw = prop.spacew * utfs + # space width for style + self.line = line + line.chrs.append(self) + self.lnindex = len(line.chrs) - 1 + # my line + self.chk = None + # my chunk (to be assigned) + self.windex = None # index in chunk + # 'normal','super', or 'sub' (to be assigned) + self._dx = dx + self._dy = dy + self._ax = None + self._ay = None + self.dadvs = lambda cL, cR: prop.dadvs.get((cL, cR), 0) * utfs + self.parsed_pts_t = None + self.parsed_pts_ut = None + # for merging later + self._lsp = TChar.lspfunc(self.sty) + self._bshft = TChar.bshftfunc(self.sty, self.loc.sel) + # letter spacing + self.lhs = None + # flow line-heights + + def copy(self, memo=None): + """Creates a copy of the TChar instance.""" + if memo is None: + memo = dict() + ret = TChar.__new__(TChar) + memo[self] = ret + ret.__dict__.update(self.__dict__) + ret.loc = CLoc( + memo.get(self.loc.elem, self.loc.elem), self.loc.typ, self.loc.ind + ) + ret.line = memo.get(self.line, self.line) + return ret + + @property + def dx(self): + """Returns the dx property.""" + return self._dx + + @dx.setter + def dx(self, dxi): + """Sets the dx property and invalidates dependent properties.""" + if self._dx != dxi: + self._dx = dxi + if self.chk is not None: + self.chk.charpos = None # invalidate + i = self.windex + chk = self.chk + chk.dxeff[i] = (chk.chrs[i].dx if i < chk.ncs else 0) + ( + chk.chrs[i - 1].lsp if i > 0 else 0 + ) + self.line.ptxt.dchange = True + + @property + def dy(self): + """Returns the dy property.""" + return self._dy + + @dy.setter + def dy(self, dxi): + """Sets the dy property and invalidates dependent properties.""" + if self._dy != dxi: + self._dy = dxi + if self.chk is not None: + self.chk.dy[self.windex] = dxi + self.line.ptxt.dchange = True + + @property + def ax(self): + """Returns the ax property.""" + return self._ax + + @ax.setter + def ax(self, axi): + """Sets the ax property and invalidates dependent properties.""" + if self._ax != axi: + self._ax = axi + self.line.ptxt.achange = True + + @property + def ay(self): + """Returns the ay property.""" + return self._ay + + @ay.setter + def ay(self, axi): + """Sets the dy property and invalidates dependent properties.""" + if self._ay != axi: + self._ay = axi + self.line.ptxt.achange = True + + @property + def sty(self): + """Returns the style of the character.""" + # Character style + return self._sty + + @sty.setter + def sty(self, styi): + """Sets the style of the character and updates dependent properties.""" + self._sty = styi + self.lsp = TChar.lspfunc(self._sty) + self.bshft = TChar.bshftfunc(self._sty, self.loc.sel) + + self.fsty = font_style(styi) + self.tsty = true_style(styi) + + @staticmethod + def lspfunc(styv): + """Returns the letter spacing for the given style.""" + if "letter-spacing" in styv: + lspv = styv.get("letter-spacing") + if "em" in lspv: # em is basically the font size + fs2 = styv.get("font-size") + if fs2 is None: + fs2 = "12px" + lspv = float(lspv.strip("em")) * ipx(fs2) + else: + lspv = ipx(lspv) or 0 + else: + lspv = 0 + return lspv + + @property + def lsp(self): + """Returns the letter spacing of the character.""" + return self._lsp + + @lsp.setter + def lsp(self, sval): + """Sets the letter spacing and invalidates dependent properties.""" + if sval != self._lsp: + self._lsp = sval + if self.chk is not None: + self.chk.charpos = None + i = self.windex + chk = self.chk + chk.lsp[i] = sval + chk.dxeff[i + 1] = (chk.chrs[i + 1].dx if i < chk.ncs - 1 else 0) + ( + chk.chrs[i].lsp if i < chk.ncs else 0 + ) + + @staticmethod + def bshftfunc(styv, strtel): + """Calculates the baseline shift for the given style.""" + if "baseline-shift" in styv: + # Find all ancestors with baseline-shift + bsancs = [] + cel = strtel + while cel is not None and "baseline-shift" in cel.cspecified_style: + bsancs.append(cel) + cel = cel.getparent() + + # Starting from the most distant ancestor, calculate relative + # baseline shifts (i.e., how much each element is raised/lowered + # from the last block of text) + relbs = [] + for att in reversed(bsancs): + if "baseline-shift" in att.cstyle: + relbs.append(TChar.get_baseline(att.cstyle, att.getparent())) + else: + # When an element has a baseline-shift from inheritance + # but no baseline-shift is specified, implicitly gets the + # sum of the previous values + relbs.append(sum(relbs)) + bshft = sum(relbs) + else: + bshft = 0 + return bshft + + # Character baseline shift + @property + def bshft(self): + """Returns the baseline shift of the character.""" + return self._bshft + + @bshft.setter + def bshft(self, sval): + """Sets the baseline shift and invalidates dependent properties.""" + if sval != self._bshft: + self._bshft = sval + if self.chk is not None: + self.chk.charpos = None + self.chk.bshft[self.windex] = sval + + @staticmethod + def get_baseline(styin, fsel): + """Gets the baseline shift value based on style and font size.""" + bshft = styin.get("baseline-shift", "0") + if bshft == "super": + bshft = "40%" + elif bshft == "sub": + bshft = "-20%" + if "%" in bshft: # relative to parent + fs2, sf2, _ = composed_width(fsel, "font-size") + bshft = fs2 / sf2 * float(bshft.strip("%")) / 100 + else: + bshft = ipx(bshft) or 0 + return bshft + + @property + def scf(self): + """Returns the scale from the utfs to tfs.""" + return self.tfs / self.utfs + + def __str__(self): + return str((self.c,self.loc.elem.get_id(), self.loc.typ, self.loc.ind)) + + def delc(self,updatedelta=True): + """Deletes the character from the document and its chunk/line.""" + # Deleting a character causes the chunk to move if it's center- or + # right-justified. Adjust position to fix + lncs = self.line.chrs + myi = self.lnindex # index in line + + # Differential kerning affect on character width + dko1 = dko2 = dkn = 0 + if myi < len(lncs) - 1: + dko2 = self.dadvs(lncs[myi].c, lncs[myi + 1].c) # old from right + if myi > 0: + dkn = self.dadvs(lncs[myi - 1].c, lncs[myi + 1].c) # new + if myi > 0: + dko1 = self.dadvs(lncs[myi - 1].c, lncs[myi].c) # old from left + tdk = dko1 + dko2 - dkn + + cwo = self.cwd + tdk + self._dx + self.lsp * (self.windex != 0) + if self.chk.unrenderedspace and self.chk.chrs[-1] == self: + if len(self.chk.chrs) > 1 and self.chk.chrs[-2].c != " ": + cwo = tdk + # Deletion will not affect position + # Weirdly dkerning from unrendered spaces still counts + + if self == self.chk.chrs[0]: # from beginning of line + deltax = (self.line.anchfrac - 1) * cwo + else: # assume end of line + deltax = self.line.anchfrac * cwo + + lnx = self.line.x + changedx = False + if deltax != 0: + chkidx = self.line.chks.index(self.chk) + if chkidx < len(lnx) and lnx[chkidx] is not None: + lnx[chkidx] -= deltax + changedx = True + + # Delete from document + if self.loc.typ == TYP_TEXT: + self.loc.elem.text = ( + self.loc.elem.text[: self.loc.ind] + + self.loc.elem.text[self.loc.ind + 1 :] + ) + else: + self.loc.elem.tail = ( + self.loc.elem.tail[: self.loc.ind] + + self.loc.elem.tail[self.loc.ind + 1 :] + ) + + if len(lnx) > 1 and myi < len(lnx): + if myi < len(lnx) - 1 and lnx[myi] is not None and lnx[myi + 1] is None: + newx = ( + lnx[: myi + 1] + lnx[myi + 2 :] + ) # next x is None, delete that instead + elif myi == len(lnx) - 1 and len(lncs) > len(lnx): + newx = lnx # last x, characters still follow + else: + newx = lnx[:myi] + lnx[myi + 1 :] + + lnx = newx[: len(lncs) - 1] + # we haven't deleted the char yet, so make it len-1 long + changedx = True + if changedx: + self.line.write_xy(lnx) + + # Delete from line + _ = [ + setattr(ca.loc, "ind", ca.loc.ind - 1) + for ca in lncs[myi + 1 :] + if ca.loc.typ == self.loc.typ and ca.loc.elem == self.loc.elem + ] + _ = [ + ca.chk.iis.__setitem__(ca.windex, ca.chk.iis[ca.windex] - 1) + for ca in lncs[myi + 1 :] + if ca.chk is not None + ] + _ = [setattr(c, "lnindex", c.lnindex - 1) for c in self.line.chrs[myi + 1 :]] + + lncs[myi].lnindex = None + self.line.chrs = lncs[:myi] + lncs[myi + 1 :] + + if len(self.line.chrs) == 0: # line now empty, can delete + self.line.dell() + + # Remove from chunk + self.chk.removec(self) + + # Update the dx/dy value in the ParsedText + if updatedelta: + self.line.ptxt.write_dxdy() + + def add_style(self, sty, setdefault=True, newfs=False): + """Adds a style to the character by wrapping it in a new Tspan.""" + span = Tspan if self.line.ptxt.textel.tag == TEtag else inkex.FlowSpan + t = span() + t.text = self.c + + prt = self.loc.elem + if self.loc.typ == TYP_TEXT: + tbefore = prt.text[0 : self.loc.ind] + tafter = prt.text[self.loc.ind + 1 :] + prt.text = tbefore + prt.insert(0, t) + t.tail = tafter + else: + tbefore = prt.tail[0 : self.loc.ind] + tafter = prt.tail[self.loc.ind + 1 :] + prt.tail = tbefore + grp = prt.getparent() + # parent is a Tspan, so insert it into the grandparent + grp.insert(grp.index(prt) + 1, t) + # after the parent + + t.tail = tafter + + self.line.ptxt.tree = None # invalidate + + myi = self.lnindex + for i in range( + myi + 1, len(self.line.chrs) + ): # for characters after, update location + cha = self.line.chrs[i] + if cha.loc.elem == self.loc.elem and cha.loc.typ == self.loc.typ: + cha.loc = CLoc(t, TYP_TAIL, i - myi - 1) + self.loc = CLoc(t, TYP_TEXT, 0) # update my own location + + # When the specified style has something new span doesn't, are inheriting and + # need to explicitly assign the default value + styset = Style(sty) + newspfd = t.cspecified_style + for att in newspfd: + if att not in styset and setdefault: + styset[att] = default_style_atts.get(att) + + t.cstyle = styset + self.sty = styset + if newfs: + fsz, scf, _ = composed_width(self.loc.sel, "font-size") + self.utfs = fsz / scf + self.tfs = fsz + self.cwd = self.prop.charw * self.utfs + self.chk.cwd[self.windex] = self.cwd + self.caph = self.prop.caph * self.utfs + self.chk.caph[self.windex] = self.caph + self.chk.charpos = None + + @property + def pts_ut(self): + """Returns the untransformed bounding box of the character.""" + myi = self.windex + cput = self.chk.cpts_ut + ret_pts_ut = [ + (cput[0][myi][0], cput[0][myi][1]), + (cput[1][myi][0], cput[1][myi][1]), + (cput[2][myi][0], cput[2][myi][1]), + (cput[3][myi][0], cput[3][myi][1]), + ] + return ret_pts_ut + + @property + def pts_t(self): + """Returns the transformed bounding box of the character.""" + myi = self.windex + cpt = self.chk.cpts_t + ret_pts_t = [ + (cpt[0][myi][0], cpt[0][myi][1]), + (cpt[1][myi][0], cpt[1][myi][1]), + (cpt[2][myi][0], cpt[2][myi][1]), + (cpt[3][myi][0], cpt[3][myi][1]), + ] + return ret_pts_t + + @property + def pts_ut_ink(self): + """Returns the untransformed ink bounding box of the character.""" + put = self.pts_ut + nwd = self.prop.inkbb[2] * self.utfs + nht = self.prop.inkbb[3] * self.utfs + x = put[0][0] + self.prop.inkbb[0] * self.utfs + y = put[0][1] + self.prop.inkbb[1] * self.utfs + nht + return [(x, y), (x, y - nht), (x + nwd, y - nht), (x + nwd, y)] + + +def del2(x, ind): + """deletes an index from a list""" + return x[:ind] + x[ind + 1 :] + + +def extendind(x, ind, val, default=None): + """indexes a matrix, extending it if necessary""" + if ind >= len(x): + x += [default] * (ind + 1 - len(x)) + x[ind] = val + return x + + +def sortnone(x): + """sorts an x skipping Nones""" + rem = list(range(len(x))) + minxrem = min([x[r] for r in rem if x[r] is not None]) + i = min([r for r in rem if x[r] == minxrem]) + sord = [i] + rem.remove(i) + while len(rem) > 0: + if i == len(x) - 1 or x[i + 1] is not None: + minxrem = min([x[r] for r in rem if x[r] is not None]) + i = min([r for r in rem if x[r] == minxrem]) + sord += [i] + else: + i += 1 + rem.remove(i) + return sord + + +class CProp: + """ + A class representing the properties of a single character + It is meant to be immutable...do not modify attributes + """ + + __slots__ = ("char", "charw", "spacew", "caph", "dadvs", "inkbb") + + def __init__(self, char, cwd, spw, caph, dadvs, inkbb): + """Initializes CProp with given parameters.""" + self.char = char + self.charw = cwd + # character width + self.spacew = spw + # space width + self.caph = caph + # cap height + self.dadvs = dadvs + # table of how much extra width a preceding character adds to me + self.inkbb = inkbb + + def __mul__(self, scl): + """Scales the character properties by a given factor.""" + dadv2 = {k: val * scl for k, val in self.dadvs.items()} + inkbb2 = [val * scl for val in self.inkbb] + return CProp( + self.char, + self.charw * scl, + self.spacew * scl, + self.caph * scl, + dadv2, + inkbb2, + ) + + @property + def __dict__(self): + """Returns a dictionary of character properties.""" + return { + "char": self.char, + "charw": self.charw, + "spacew": self.spacew, + "caph": self.caph, + "inkbb": self.inkbb, + } + + +class CLoc: + """Represents the location of a single character in the SVG.""" + + __slots__ = ("elem", "typ", "ind", "sel") + + def __init__(self, elem, typ, ind): + """Initializes CLoc with given parameters.""" + self.elem = elem + # the element it belongs to + self.typ = typ + # TYP_TEXT or TYP_TAIL + self.ind = ind + # its index + self.sel = elem if typ == TYP_TEXT else elem.getparent() + # where style comes from + + def copy(self, memo): + """Creates a copy of the CLoc instance.""" + ret = CLoc.__new__(CLoc) + memo[self] = ret + + ret.elem = memo[self.elem] + ret.typ = self.typ + ret.ind = self.ind + ret.sel = memo[self.sel] + return ret + + def __eq__(self, other): + """Checks equality with another CLoc instance.""" + return ( + self.elem == other.elem and self.typ == other.typ and self.ind == other.ind + ) + + def __hash__(self): + """Returns the hash value of the CLoc instance.""" + return hash((self.elem, self.typ, self.ind)) + + +class CharacterTable: + """Represents the properties of a collection of characters.""" + + def __init__(self, els): + """Initializes CharacterTable with a list of elements.""" + self.els = els + self.root = els[0].croot if len(els) > 0 else None + self.tstyset, self.pchrset, self.fstyset, self.cstys = ( + CharacterTable.collect_characters(els) + ) + + # HASPANGO = False; os.environ["HASPANGO"]='False' + if HASPANGO: + # Prefer to measure with Pango if we have it (faster, more accurate) + self.ctable = self.measure_characters() + else: + # Can also extract directly using fonttools, which is pure Python + self.ctable = self.extract_characters() + + self.mults = dict() + self._ftable = None + + @staticmethod + def collect_characters(els): + """Finds all the characters in a list of elements.""" + fstyset = dict() # set of characters in a given font style + txtfsty = [] + for elem in els: + tree = TextTree(elem) + for _, _, _, sel, txt in tree.dgenerator(): + if txt is not None and len(txt) > 0: + sty = sel.cspecified_style + fsty = font_style(sty) + fstyset.setdefault(fsty, set()).update(txt + " ") + txtfsty.append((txt, fsty)) + + cstys = dict() # cstys[fsty][c] looks up a character's true style + tstyset = dict() # set of characters in a given true style + for fsty, chrs in fstyset.items(): + tfbc = fcfg.get_true_font_by_char(fsty, chrs) + cstys[fsty] = tfbc + tstyset.setdefault(true_style(fsty), set()).update(chrs) + for c, csty in tfbc.items(): + cstys.setdefault(csty, dict()).__setitem__(c, csty) + tstyset.setdefault(csty, set()).update({c, " "}) + + pchrset = dict() + # set of characters that precede a char in a true style + # used to apply differential kerning + for txt, fsty in txtfsty: + for j in range(1, len(txt)): + csty = cstys[fsty][txt[j]] + pchrset.setdefault(csty, dict()) + if csty == cstys[fsty][txt[j - 1]]: + pchrset[csty].setdefault(txt[j], set()).update({txt[j - 1], " "}) + + return tstyset, pchrset, fstyset, cstys + + def extract_characters(self): + """ + Direct extraction of character metrics from the font file using fonttools + fonttools is pure Python, so this usually works + """ + badchars = {"\n", "\r"} + ret = dict() + for sty, chrs in self.tstyset.items(): + if sty is not None: + bdcs = {c for c in chrs if c in badchars} # unusual chars + gcs = {c for c in chrs if c not in badchars} + chrfs = fcfg.get_true_font_by_char(sty, gcs) + fntcs = dict() # font: corresponding characters + for k, val in chrfs.items(): + fntcs.setdefault(val, []).append(k) + + if None in fntcs: # unrendered characters are bad + bdcs.update(fntcs[None]) + gcs = gcs - set(fntcs[None]) + del fntcs[None] + + ret[sty] = dict() + for fnt, chs in fntcs.items(): + ftfnt = fcfg.get_fonttools_font(fnt) + if sty in self.pchrset: + pct2 = { + k: val for k, val in self.pchrset[sty].items() if k in chs + } + else: + pct2 = dict() + advs, dadv, inkbbs = ftfnt.get_char_advances(chs, pct2) + for c in chs: + cwd = advs[c] + caph = ftfnt.cap_height + # dr = 0 + inkbb = inkbbs[c] + ret[sty][c] = CProp(c, cwd, None, caph, dadv, inkbb) + spc = ret[sty][" "] + for c in ret[sty]: + ret[sty][c].spacew = spc.charw + ret[sty][c].caph = spc.caph + for bdc in bdcs: + ret[sty][bdc] = CProp( + bdc, 0, spc.charw, spc.caph, dict(), [0, 0, 0, 0] + ) + else: + ret[sty] = dict() + for c in self.tstyset[None]: + ret[sty][c] = CProp(c, 0, 0, 0, dict(), [0, 0, 0, 0]) + return ret + + def measure_characters(self): + """ + Uses Pango to measure character properties by rendering them on an unseen + context. Requires GTK Python bindings, generally present in Inkscape 1.1 and + later. If Pango is absent, extract_characters will be called instead. + + Generates prefixed, suffixed copies of each string, compares them to a blank + version without any character. This measures the logical advance, i.e., the + width including intercharacter space. Width corresponds to a character with + a composed font size of 1 uu. + """ + cnt = 0 + pstrings = dict() + + def make_string(c, sty): + nonlocal cnt + cnt += 1 + pstrings["text" + str(cnt)] = (c, sty) + return "text" + str(cnt) + + class StringInfo: + """Stores metadata on strings""" + + def __init__(self, strval, strid, dadv, bareid=None): + self.strval = strval + self.strid = strid + self.dadv = dadv + self.bareid = bareid + + badchars = {"\n": " ", "\r": " "} + + def effc(c): + return badchars.get(c, c) + + ixes = dict() + validtstyset = { + sty: chrs for sty, chrs in self.tstyset.items() if sty is not None + } + for sty in validtstyset: + chrs = [chr(c) for c in fcfg.fontcharsets[sty]] + if "=" in chrs: + bufc = "=" + elif "M" in chrs: + bufc = "M" + else: + bufc = min([ch for ch in chrs if ord(ch) >= ord("A")], key=ord) + prefix = "I" + bufc + suffix = bufc + "I" + # Use an equals sign to eliminate differential kerning effects + # (characters like '=' rarely have differential kerning), then I for + # capital height. + + empty = prefix + suffix + pistr = prefix + "pI" + suffix + # Add 'pI' as test characters. 'p' provides the font's descender + # (how much the tail descends), and 'I' gives cap height + # (how tall capital letters are). + ixes[sty] = (prefix, suffix, empty, pistr) + + ctbl = dict() + bareids = [] + for sty, chrs in validtstyset.items(): + prefix, suffix, empty, pistr = ixes[sty] + ctbl[sty] = dict() + for myc in chrs: + t = make_string(prefix + effc(myc) + suffix, sty) + tbare = make_string(effc(myc), sty) + bareids.append(tbare) + dadv = dict() + if DIFF_ADVANCES: + for pchr in chrs: + if ( + sty in self.pchrset + and myc in self.pchrset[sty] + and pchr in self.pchrset[sty][myc] + ): + tpc = make_string( + prefix + effc(pchr) + effc(myc) + suffix, sty + ) + # precede by all chars of the same style + dadv[pchr] = tpc + ctbl[sty][myc] = StringInfo(myc, t, dadv, tbare) + + ctbl[sty][pistr] = StringInfo(pistr, make_string(pistr, sty), dict()) + ctbl[sty][empty] = StringInfo(empty, make_string(empty, sty), dict()) + + # Pango querying doesn't multithread well + lock = threading.Lock() + lock.acquire() + try: + pngr = PangoRenderer() + nbb = dict() + for sty in ctbl: + joinch = " " + mystrs = [val[0] for k, val in pstrings.items() if val[1] == sty] + myids = [k for k, val in pstrings.items() if val[1] == sty] + + success, metrics = pngr.set_text_style(sty) + if not (success): + lock.release() + return self.extract_characters() + joinedstr = joinch.join(mystrs) + joinch + prefix + + # We need to render all the characters, but we don't + # need all of their extents. For most of them we just + # need the first character, unless the following string + # has length 1 (and may be differently differentially kerned) + modw = [ + any( + len(mystrs[i]) == 1 + for i in range(i, i + 2) + if 0 <= i < len(mystrs) + ) + for i in range(len(mystrs)) + ] + needexts = [ + "1" + if len(s) == 1 + else "1" + "0" * (len(s) - 2) + ("1" if modw[i] else "0") + for i, s in enumerate(mystrs) + ] + needexts2 = "0".join(needexts) + "1" + "1" * len(prefix) + pngr.render_text(joinedstr) + exts, _ = pngr.get_character_extents(metrics[1], needexts2) + + spw = exts[-len(prefix) - 1][0][2] + cnt = 0 + x = 0 + for i, mystr in enumerate(mystrs): + if modw[i]: + altw = ( + exts[cnt + len(mystr) - 1][0][0] + + exts[cnt + len(mystr) - 1][0][2] + - exts[cnt][0][0] + ) + else: + altw = ( + exts[cnt + len(mystr) + 1][0][0] - exts[cnt][0][0] - spw + ) + wdt = altw + + firstch = exts[cnt] + (xbr, ybr, wbr, hbr) = tuple(firstch[2]) + if myids[i] not in bareids: + xbr = x + wbr = wdt + # use logical width + + nbb[myids[i]] = [val * TEXTSIZE for val in [xbr, ybr, wbr, hbr]] + cnt += len(mystr) + len(joinch) + x += wdt + finally: + lock.release() + + dadv = dict() + for sty, chd in ctbl.items(): + prefix, suffix, empty, pistr = ixes[sty] + for i in chd: + chd[i].bbx = bbox(nbb[chd[i].strid]) + if DIFF_ADVANCES: + precwidth = dict() + for j in chd[i].dadv: + precwidth[j] = bbox(nbb[chd[i].dadv[j]]).w + # width including the preceding character and extra kerning + chd[i].precwidth = precwidth + + if DIFF_ADVANCES: + dadv[sty] = dict() + for i in chd: + mcw = chd[i].bbx.w - chd[empty].bbx.w # my character width + for j in chd[i].precwidth: + pcw = chd[j].bbx.w - chd[empty].bbx.w # preceding char width + bcw = chd[i].precwidth[j] - chd[empty].bbx.w # both char widths + dadv[sty][j, chd[i].strval] = bcw - pcw - mcw + # preceding char, then next char + + for sty, chd in ctbl.items(): + prefix, suffix, empty, pistr = ixes[sty] + blnkwd = chd[empty].bbx.w + spw = chd[" "].bbx.w - blnkwd # space width + caph = -chd[empty].bbx.y1 + # cap height is the top of I (relative to baseline) + + dadvscl = dict() + if DIFF_ADVANCES: + for k in dadv[sty]: + dadvscl[k] = dadv[sty][k] / TEXTSIZE + + for i in chd: + cwd = chd[i].bbx.w - blnkwd + # character width (full, including extra space on each side) + if chd[i].bareid in nbb: + inkbb = nbb[chd[i].bareid] + else: + # whitespace: make zero-width + inkbb = [chd[i].bbx.x1, chd[i].bbx.y1, 0, 0] + + if chd[i].strval in badchars: + cwd = 0 + chd[i] = CProp( + chd[i].strval, + cwd / TEXTSIZE, + spw / TEXTSIZE, + caph / TEXTSIZE, + dadvscl, + [val / TEXTSIZE for val in inkbb], + ) + if None in self.tstyset: + ctbl[None] = dict() + for c in self.tstyset[None]: + ctbl[None][c] = CProp(c, 0, 0, 0, dict(), [0, 0, 0, 0]) + return ctbl + + def __str__(self): + ret = "" + for sty in self.ctable: + ret += str(sty) + "\n" + val = None + for c, val in self.ctable[sty].items(): + ret += " " + c + " : " + str(vars(val)) + "\n" + if val is not None: + ret += " " + str(val.dadvs) + return ret + + @staticmethod + def flowy(sty): + """Returns the font's ascent value for the given style.""" + return fcfg.get_fonttools_font(sty)._ascent + + def get_prop(self, char, sty): + """Returns the properties of a character for a given style.""" + try: + return self.ctable[sty][char] + except KeyError as excp: + reset_msg = ( + "This probably means that new text was generated and the SVG's " + "character table is outdated. Reset it by setting" + ) + reset_action = "self.svg.char_table = None" + frequency_msg = ( + "(This can take a long time, so best practice is to do this as few " + "times as is possible.)" + ) + if sty not in self.ctable: + debug("No style matches found!") + debug(reset_msg) + debug(" " + reset_action) + debug(frequency_msg) + debug("\nCharacter: " + char) + debug("Style: " + str(sty)) + debug("Existing styles: " + str(list(self.ctable.keys()))) + else: + debug("No character matches!") + debug(reset_msg) + debug(" " + reset_action) + debug(frequency_msg) + debug("\nCharacter: " + char) + debug("Style: " + str(sty)) + debug("Existing chars: " + str(list(self.ctable[sty].keys()))) + raise KeyError from excp + + def get_prop_mult(self, char, sty, scl): + """Returns the scaled properties of a character for a given style.""" + try: + return self.mults[(char, sty, scl)] + except KeyError: + self.mults[(char, sty, scl)] = self.get_prop(char, sty) * scl + return self.mults[(char, sty, scl)] + + +def wstrip(txt): + """strip whitespaces""" + return txt.translate({ord(c): None for c in " \n\t\r"}) + + +def deleteempty(elem): + """ + Recursively delete empty elements + Tspans are deleted if they're totally empty, TextElements are deleted + if they contain only whitespace + """ + anydeleted = False + for k in list(elem): + dcsndt = deleteempty(k) + anydeleted |= dcsndt + txt = elem.text + tail = elem.tail + if ( + (txt is None or len(txt) == 0) + and (tail is None or len(tail) == 0) + and len(elem) == 0 + ): + elem.delete() + anydeleted = True + # delete anything empty + elif elem.tag == TEtag: + if all( + (dcsndt.text is None or len(wstrip(dcsndt.text)) == 0) + and (dcsndt.tail is None or len(wstrip(dcsndt.tail)) == 0) + for dcsndt in elem.descendants2() + ): + elem.delete() + anydeleted = True + # delete any text elements that are just white space + return anydeleted + + +def maxnone(x): + """Returns the maximum value of a list, or None if the list is empty.""" + if len(x) > 0: + return max(x) + return None + + +def xyset(elem, xyt, val): + """ + A fast setter for 'x', 'y', 'dx', and 'dy' that uses lxml's set directly and + converts arrays to a string + """ + if not (val): + elem.attrib.pop(xyt, None) # pylint: disable=no-member + else: + EBset(elem, xyt, ' '.join('%s' % v for v in val)) + +def remove_position_overflows(el): + """ + Normally Inkscape only produces multiple position attributes (x,y,dx,dy) on a + tspan corresponding to the number of characters. It does support more than this, + but that is a somewhat pathological case that makes editing difficult. + Removing this prior to parsing simplifies the logic needed. + """ + xyvs = {(d, patt) : ParsedText.get_xy(d, patt) for d in el.descendants2() for patt in ['x','y','dx','dy']} + anyoverflow = False + ttree = [v for v in TextTree(el).dgenerator()] + for ddi0, typ0, src0, sel0, txt0 in ttree: + if typ0==TYP_TEXT: + for patt in ['x','y','dx','dy']: + anyoverflow |= (len(xyvs[src0,patt])>1 and + ((txt0 is not None and len(xyvs[src0,patt])>len(txt0)) + or (txt0 is None))) + # inkex.utils.debug((anyoverflow, patt,src0.get_id(),typ0)) + + + if anyoverflow: + toplevels = list(el) + xvs, yvs, dxs, dys = [[] for _ in range(4)] + pos = {'x':xvs,'y':yvs,'dx':dxs,'dy':dys} + diffs = ['dx','dy'] + topidx = 0 + for ddi0, typ0, src0, sel0, txt0 in ttree: + if typ0==TYP_TEXT: + for patt in ['x','y','dx','dy']: + xyv = xyvs[src0, patt] + # Objects lower in the descendant list override ancestors + # dx and dy don't affect flows + if len(xyv) > 0 and xyv[0] is not None: + if patt in diffs or len(xyv)>1: + src0.set(patt,None) # leave individual x,y alone + cntd = 0 + cntl = 0 + for _, typ, src, _, txt in TextTree(el).dgenerator(subel=src0): + if ( + typ == TYP_TAIL + and src.get("sodipodi:role") == "line" + and src in toplevels + ): + cntd += 1 + # top-level Tspans have an implicit CR at + # the beginning of the tail + + if txt is not None: + srtd = cntd + stpd = min(len(xyv),cntd+len(txt)) + srtl = topidx+cntl + stpl = topidx+cntl+stpd-srtd + + if stpl>=len(pos[patt]): + pos[patt] += [None] * (stpl-len(pos[patt])+1) + pos[patt][srtl:stpl] = xyv[srtd:stpd] + cntd += len(txt) + cntl += len(txt) + if cntd >= len(xyv): + break + if txt0 is not None: + topidx += len(txt0) + + topidx = 0 + for ddi, typ, src, sel, txt in ttree: + if txt is not None: + wrapped_tail = False + for patt in ['x','y','dx','dy']: + vals = [v for v in pos[patt][topidx:topidx+len(txt)] if v is not None] + if len(vals)>0: + if typ==TYP_TAIL and not wrapped_tail: + # Tails need to be wrapped in a new Tspan + src = wrap_string(src,typ) + wrapped_tail = True + + if not(len(vals)==1 and patt not in diffs and wrapped_tail): + # wrapping a tail could mess with sprl if single x/y specified + xyset(src, patt, vals) + topidx += len(txt) + +def wrap_string(src,typ): + ''' Wrap a string in a new Tspan/FlowSpan''' + if typ==TYP_TAIL: + span = inkex.Tspan if src.tag in TEtags else inkex.FlowSpan + t = span() + t.text = src.tail + src.tail = None + src.getparent().insert(src.getparent().index(src) + 1, t) + else: + span = inkex.Tspan if src.tag in TEtags else inkex.FlowSpan + t = span() + t.text = src.text + src.text = None + src.insert(0, t) + return t + +def trim_list(lst,val): + """ Trims any values from the end of a list equal to val """ + trim = lst[:len(lst) - next((i for i, x in enumerate(reversed(lst)) if x != val), len(lst))] + return trim if len(trim)>0 else None \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFT2-1.0.typelib b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFT2-1.0.typelib new file mode 100644 index 0000000..e505499 Binary files /dev/null and b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFT2-1.0.typelib differ diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFc-1.0.typelib b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFc-1.0.typelib new file mode 100644 index 0000000..652ac77 Binary files /dev/null and b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/PangoFc-1.0.typelib differ diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/fontconfig-2.0.typelib b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/fontconfig-2.0.typelib new file mode 100644 index 0000000..acbd826 Binary files /dev/null and b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/fontconfig-2.0.typelib differ diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/freetype2-2.0.typelib b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/freetype2-2.0.typelib new file mode 100644 index 0000000..46dab5d Binary files /dev/null and b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/typelibs/freetype2-2.0.typelib differ diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/utils.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/utils.py new file mode 100644 index 0000000..dd7cb0a --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/text/utils.py @@ -0,0 +1,639 @@ +# coding=utf-8 +# +# Copyright (c) 2023 David Burghoff +# Martin Owens +# Sergei Izmailov +# Thomas Holder +# Jonathan Neuhauser +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +""" +Utilities for text parsing +""" + +import math +import re +import sys +import os +from functools import lru_cache +import lxml +from lxml import etree +import inkex +import inkex.command +from inkex.properties import all_properties +from inkex.units import CONVERSIONS, BOTH_MATCH + +# For style components that represent a size (stroke-width, font-size, etc), +# calculate the true size reported by Inkscape in user units, inheriting +# any styles/transforms/document scaling +flookup = {"small": "10px", "medium": "12px", "large": "14px"} + + +def composed_width(elem, comp): + """ + Gets the transformed size of a style component and the scale factor representing + the scale of the composed transform, accounting for relative sizes. + + Parameters: + elem (Element): The element whose style to compute. + comp (str): The component of the style to compute, such as 'stroke-width' + or 'font-size'. + + Returns: + tuple: A tuple containing the true size in user units, the scale factor, + and the untransformed size + """ + sty = elem.cspecified_style + ctf = elem.ccomposed_transform + satt = sty.get(comp) + + # Get default attribute if empty + if satt is None: + satt = default_style_atts[comp] + + if "%" in satt: # relative width, get ancestor width + cel = elem + while satt != cel.cstyle.get(comp) and satt != cel.get(comp): + cel = cel.getparent() + # figure out ancestor where % is coming from + + satt = float(satt.strip("%")) / 100 + tsz, scf, utsz = composed_width(cel.getparent(), comp) + + # Since relative widths have no untransformed width, we assign + # it to be a scaled version of the ancestor's ut width + return tsz * satt, scf, utsz * satt + utsz = ipx(satt) + if utsz is None: + utsz = ( + utsz + or ipx(flookup.get(satt) if comp == "font-size" else None) + or ipx(default_style_atts[comp]) + ) + scf = math.sqrt(abs(ctf.a * ctf.d - ctf.b * ctf.c)) # scale factor + return utsz * scf, scf, utsz + + +def composed_lineheight(elem): + """ + Get absolute line-height in user units based on an element's specified style. + + Parameters: + elem (Element): The element whose line-height to compute. + + Returns: + float: The computed line-height in user units. + """ + sty = elem.cspecified_style + satt = sty.get("line-height", default_style_atts["line-height"]) + if satt == "normal": + satt = 1.25 + elif "%" in satt: # relative width, get parent width + satt = float(satt.strip("%")) / 100 + else: + try: + # Lines have no unit, em treated the same + satt = float(satt.strip("em")) + except ValueError: + fsz, scf, _ = composed_width(elem, "font-size") + satt = ipx(satt) / (fsz / scf) + fsz, _, _ = composed_width(elem, "font-size") + return satt * fsz + + +def unique(lst): + """ + Returns a list of unique items from the given list while preserving order. + + Parameters: + lst (list): The list from which to remove duplicates. + + Returns: + list: A list of unique items in the order they appeared in the input list. + """ + return list(dict.fromkeys(lst)) + + +def uniquetol(x, tol): + """ + Like unique, but for numeric values and accepts a tolerance. + + Parameters: + x (list of float): List of numeric values from which to remove near-duplicates. + tol (float): The tolerance within which two numbers are considered the same. + + Returns: + list of float: A list of unique numbers within the specified tolerance. + """ + if not x: # Check if the input list is empty + return [] + x_sorted = sorted((y for y in x if y is not None)) # Sort, ignoring None values + ret = ( + [x_sorted[0]] if x_sorted else [] + ) # Start with the first value if there are any non-None values + for i in range(1, len(x_sorted)): + if abs(x_sorted[i] - ret[-1]) > tol: + ret.append(x_sorted[i]) + # If there were any None values in the original list, append None to the result list + if None in x: + ret.append(None) + return ret + + +# Adds ctag to the inkex classes, which holds each class's corresponding tag +# Checking the tag is usually much faster than instance checking, which can +# substantially speed up low-level functions. +# pylint:disable=protected-access +lt = dict(inkex.elements._parser.NodeBasedLookup.lookup_table) +shapetags = set() +for key, v in lt.items(): + for v2 in v: + v2.ctag = inkex.addNS(key[1], key[0]) if isinstance(key, tuple) else key + if issubclass(v2, inkex.ShapeElement): + shapetags.add(v2.ctag) +tags = lambda x: {v.ctag for v in x} # converts class tuple to set of tags +# pylint:enable=protected-access + +rectlike_tags = tags((inkex.PathElement, inkex.Rectangle, inkex.Line, inkex.Polyline)) +rect_tag = inkex.Rectangle.ctag +pel_tag = inkex.PathElement.ctag +usetag = inkex.Use.ctag + +PTH_CMDS = "".join(list(inkex.paths.PathCommand._letter_to_class.keys())) +pth_cmd_pat = re.compile("[" + re.escape(PTH_CMDS) + "]") +cnt_pth_cmds = lambda d: len(pth_cmd_pat.findall(d)) # count path commands + + +def isrectangle(elem, includingtransform=True): + """ + Determines if an element is rectangle-like, considering transformations if + specified. + + Parameters: + elem (Element): The element to check. + includingtransform (bool): Whether to consider transformations in the + determination. + + Returns: + tuple: A tuple containing a boolean indicating if the element is a rectangle and + the path if it is. + """ + + ret = True + if not includingtransform and elem.tag == rect_tag: + pth = elem.cpath + elif elem.tag in rectlike_tags: + if elem.tag == pel_tag and not (1 <= cnt_pth_cmds(elem.get("d", "")) <= 6): + # Allow up to 6 (initial M + 4 lines + redundant close path) + return False + pth = elem.cpath + + if includingtransform: + tmat = elem.ctransform.matrix + x, y = list( + zip( + *[ + ( + tmat[0][0] * pt.x + tmat[0][1] * pt.y + tmat[0][2], + tmat[1][0] * pt.x + tmat[1][1] * pt.y + tmat[1][2], + ) + for pt in pth.end_points + ] + ) + ) + else: + x, y = list(zip(*[(pt.x, pt.y) for pt in pth.end_points])) + + maxsz = max(max(x) - min(x), max(y) - min(y)) + tol = 1e-3 * maxsz + if len(uniquetol(x, tol)) != 2 or len(uniquetol(y, tol)) != 2: + ret = False + elif elem.tag == usetag: + useel = elem.get_link("xlink:href") + if useel is not None: + return isrectangle(useel) + ret = True + else: + ret = False + + if ret: + if ( + elem.get_link("mask", llget=True) is not None + or elem.cspecified_style.get_link("filter", elem.croot) is not None + ): + ret = False + elif elem.get_link("clip-path", llget=True) is not None and any( + not isrectangle(k) for k in list(elem.get_link("clip-path", llget=True)) + ): + ret = False + return ret + +class InkscapeSystemInfo: + """ + Discovers and caches Inkscape System info. + """ + + _UNSET = object() # Sentinel value for unset attributes + + def __init__(self): + """Initialize InkscapeSystemInfo.""" + self._language = self._UNSET + self._preferences = self._UNSET + self._binary_location = self._UNSET + self._binary_version = self._UNSET + + @property + def language(self): + """Get the language used by Inkscape.""" + if self._language is self._UNSET: + self._language = self.determine_language() + return self._language + + @property + def preferences(self): + """Get the preferences file location.""" + if self._preferences is self._UNSET: + self._preferences = self.find_preferences() + return self._preferences + + @property + def binary_location(self): + """Get the Inkscape binary location.""" + if self._binary_location is self._UNSET: + self._binary_location = self.get_binary_location() + return self._binary_location + + @property + def binary_version(self): + """Get the Inkscape binary version.""" + if self._binary_version is self._UNSET: + self._binary_version = self.get_binary_version() + return self._binary_version + + def get_binary_version(self): + """Gets the binary location by calling with --version (slow)""" + proc = subprocess_repeat([self.binary_location, "--version"]) + + match = re.search(r"Inkscape\s+(\S+)\s+\(", str(proc.stdout)) + return match.group(1) if match else None + + @staticmethod + def get_binary_location(): + """ + Gets the location of the Inkscape binary, checking the system + path if necessary. + """ + + program = inkex.command.INKSCAPE_EXECUTABLE_NAME + try: + return inkex.command.which(program) + except inkex.command.CommandNotFound as excp: + # Search the path as a backup (primarily for testing) + try: + from shutil import which as warlock + + for sysp in sys.path: + if sys.platform == "win32": + prog = warlock(program, path=os.environ["PATH"] + ";" + sysp) + if prog: + return prog + except ImportError as impe: + raise ImportError("Failed to import the 'which' function.") from impe + raise inkex.command.CommandNotFound( + f"Can not find the command: '{program}'" + ) from excp + + @staticmethod + def find_preferences(): + """Attempt to discover preferences.xml""" + prefspaths = [] + + # First check the location of the user extensions directory + mydir = os.path.dirname(os.path.abspath(__file__)) + file_path = mydir + while "extensions" in file_path and os.path.basename(file_path) != "extensions": + file_path = os.path.dirname(file_path) + prefspaths.append(os.path.join(os.path.dirname(file_path), "preferences.xml")) + + # Try some common default locations based on the home directory + home = os.path.expanduser("~") + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + if appdata is not None: + # https://wiki.inkscape.org/wiki/Preferences_subsystem + prefspaths.append( + os.path.join( + os.path.abspath(appdata), "inkscape", "preferences.xml" + ) + ) + # https://en.wikipedia.org/wiki/Environment_variable#Default_Values_on_Microsoft_Windows + prefspaths.append( + os.path.join(home, "AppData", "Roaming", "inkscape", "preferences.xml") + ) + # https://en.wikipedia.org/wiki/Environment_variable#Default_Values_on_Microsoft_Windows + # http://tavmjong.free.fr/INKSCAPE/MANUAL/html/Customize-Files.html + prefspaths.append( + os.path.join(home, "­Application Data", "­Inkscape", "preferences.xml") + ) + else: + if sys.platform == "darwin": + # test Mac + prefspaths.append( + os.path.join( + home, + "Library", + "Application Support", + "org.inkscape.Inkscape", + "config", + "inkscape", + "preferences.xml", + ) + ) + # test Linux + prefspaths.append( + os.path.join(home, ".config", "inkscape", "preferences.xml") + ) + # https://wiki.inkscape.org/wiki/Preferences_subsystem#Where_preferences_are_stored + prefspaths.append( + os.path.join(home, ".config", "Inkscape", "preferences.xml") + ) + # https://wiki.inkscape.org/wiki/Preferences_subsystem#Where_preferences_are_stored + # https://alpha.inkscape.org/vectors/www.inkscapeforum.com/viewtopicc8ae.html?t=1712 + prefspaths.append(os.path.join(home, ".inkscape", "preferences.xml")) + + # Try finding from snap location + file_path = mydir + while "snap" in file_path and os.path.basename(file_path) != "snap": + file_path = os.path.dirname(file_path) + prefspaths.append( + os.path.join( + os.path.dirname(file_path), ".config", "inkscape", "preferences.xml" + ) + ) + + # Iterate over potential paths and return the first existing one + for path in prefspaths: + if os.path.exists(path): + return path + + return None # failed + + @staticmethod + def determine_language(verbose=False): + """Try to find the language Inkscape is using""" + + def get_ui_language(prefspath): + proot = etree.parse(prefspath).getroot() + for k in proot: + if k.get("id") == "ui" and k.get("language") is not None: + return k.get("language") + return None + + def getlocale_mod(): + # pylint:disable=import-outside-toplevel + import warnings + import locale + # pylint:enable=import-outside-toplevel + + with warnings.catch_warnings(): + # temporary work-around for + # https://github.com/python/cpython/issues/82986 + # by continuing to use getdefaultlocale() even though it has been + # deprecated. + if sys.version_info.minor >= 13: + warnings.warn( + "This function may not behave as expected in" + " Python versions beyond 3.12", + FutureWarning, + ) + warnings.simplefilter("ignore", category=DeprecationWarning) + language_code = locale.getdefaultlocale()[0] + if language_code: + return language_code + return "en-US" + + # First, try to get the language from preferences.xml + pxml = InkscapeSystemInfo().find_preferences() + if verbose: + inkex.utils.debug("Found preferences.xml: " + str(pxml)) + if pxml is not None: + prefslang = get_ui_language(pxml) + if verbose: + inkex.utils.debug("preferences.xml language: " + str(prefslang)) + # If it can't be found or is set to use the system lang, use locale + if pxml is None or prefslang in ["", None]: + lcle = getlocale_mod() + prefslang = lcle.split("_")[0] + if verbose: + inkex.utils.debug("locale language: " + str(prefslang)) + return prefslang + + +inkex.inkscape_system_info = InkscapeSystemInfo() # type: ignore + + +def subprocess_repeat(argin,cwd=None): + """ + In the event of a timeout, repeats a subprocess call several times. + + Parameters: + argin (list): The command and arguments to run in the subprocess. + + Returns: + CompletedProcess: The result from the subprocess call. + """ + base_timeout = 60 + nattempts = 6 + import subprocess + + nfails = 0 + ntime = 0 + for i in range(nattempts): + timeout = base_timeout * 2**i + try: + os.environ["SELF_CALL"] = "true" # seems to be needed for 1.3 + proc = subprocess.run( + argin, + shell=False, + timeout=timeout, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, cwd=cwd + ) + break + except subprocess.TimeoutExpired: + nfails += 1 + ntime += timeout + if nfails == nattempts: + raise TimeoutError( + "\nThe call to the Inkscape binary timed out " + + str(nattempts) + + " times in " + + str(ntime) + + " seconds.\n\n" + + "This may be a temporary issue; try running the extension again." + ) + else: + return proc + + +# Get default style attributes +try: + default_style_atts = {a: v[1] for a, v in all_properties.items()} # type: ignore +except TypeError: + default_style_atts = { + a: "".join([str(t.value) for t in v.default_value]) + for a, v in all_properties.items() + } # type: ignore +default_style_atts["font-variant-ligatures"] = "normal" # missing + + +comment_tag = lxml.etree.Comment("").tag + + +def list2(elem): + """Returns non-comment children of an element.""" + return [k for k in list(elem) if not (k.tag == comment_tag)] + + +conv2 = {k: v / CONVERSIONS["px"] for k, v in CONVERSIONS.items()} + + +@lru_cache(maxsize=None) +def ipx(strin): + """ + Implicit pixel function + For many properties, a size specification of '1px' actually means '1uu' + Even if the size explicitly says '1mm' and the user units are mm, this will be + first converted to px and then interpreted to mean user units. (So '1mm' would + up being bigger than 1 mm). This returns the size as Inkscape will interpret it + (in uu). + No unit: Assumes 'px' + Invalid unit: Returns None + """ + try: + ret = BOTH_MATCH.match(strin) + value = float(ret.groups()[0]) + from_unit = ret.groups()[-1] or "px" + return value * conv2[from_unit] + except (AttributeError, TypeError): + return None + + +# pylint:disable=invalid-name +class bbox: + """ + A modified bounding box class. + """ + + __slots__ = ("isnull", "x1", "x2", "y1", "y2", "xc", "yc", "w", "h", "sbb") + + def __init__(self, bb): + """Initialize bbox.""" + if bb is not None: + self.isnull = False + if len(bb) == 2: # allow tuple of two points ((x1,y1),(x2,y2)) + self.sbb = [ + min(bb[0][0], bb[1][0]), + min(bb[0][1], bb[1][1]), + abs(bb[0][0] - bb[1][0]), + abs(bb[0][1] - bb[1][1]), + ] + else: + self.sbb = bb[:] # standard bbox + self.x1, self.y1, self.w, self.h = self.sbb + self.x2 = self.x1 + self.w + self.y2 = self.y1 + self.h + self.xc = (self.x1 + self.x2) / 2 + self.yc = (self.y1 + self.y2) / 2 + else: + self.isnull = True + + def copy(self): + """Copy the bounding box.""" + ret = bbox.__new__(bbox) + ret.isnull = self.isnull + if not self.isnull: + ret.x1 = self.x1 + ret.x2 = self.x2 + ret.y1 = self.y1 + ret.y2 = self.y2 + ret.xc = self.xc + ret.yc = self.yc + ret.w = self.w + ret.h = self.h + ret.sbb = self.sbb[:] + return ret + + def transform(self, xform): + """Transform the bounding box.""" + if not (self.isnull) and xform is not None: + tr1 = xform.apply_to_point([self.x1, self.y1]) + tr2 = xform.apply_to_point([self.x2, self.y2]) + tr3 = xform.apply_to_point([self.x1, self.y2]) + tr4 = xform.apply_to_point([self.x2, self.y1]) + return bbox( + [ + min(tr1[0], tr2[0], tr3[0], tr4[0]), + min(tr1[1], tr2[1], tr3[1], tr4[1]), + max(tr1[0], tr2[0], tr3[0], tr4[0]) + - min(tr1[0], tr2[0], tr3[0], tr4[0]), + max(tr1[1], tr2[1], tr3[1], tr4[1]) + - min(tr1[1], tr2[1], tr3[1], tr4[1]), + ] + ) + return bbox(None) + + def intersect(self, bb2): + """Check if bounding boxes intersect.""" + return (abs(self.xc - bb2.xc) * 2 < (self.w + bb2.w)) and ( + abs(self.yc - bb2.yc) * 2 < (self.h + bb2.h) + ) + + def union(self, bb2): + """Get the union of two bounding boxes.""" + if isinstance(bb2, list): + bb2 = bbox(bb2) + if not (self.isnull) and not bb2.isnull: + minx = min((self.x1, self.x2, bb2.x1, bb2.x2)) + maxx = max((self.x1, self.x2, bb2.x1, bb2.x2)) + miny = min((self.y1, self.y2, bb2.y1, bb2.y2)) + maxy = max((self.y1, self.y2, bb2.y1, bb2.y2)) + return bbox([minx, miny, maxx - minx, maxy - miny]) + if self.isnull and not bb2.isnull: + return bb2 + return self # bb2 is empty + + def intersection(self, bb2): + """Get the intersection of two bounding boxes.""" + if isinstance(bb2, list): + bb2 = bbox(bb2) + if not (self.isnull): + minx = max([self.x1, bb2.x1]) + maxx = min([self.x2, bb2.x2]) + miny = max([self.y1, bb2.y1]) + maxy = min([self.y2, bb2.y2]) + if maxx < minx or maxy < miny: + return bbox(None) + return bbox([minx, miny, maxx - minx, maxy - miny]) + return bbox(bb2.sbb) + + def __mul__(self, scl): + """Scale the bounding box.""" + return bbox([self.x1 * scl, self.y1 * scl, self.w * scl, self.h * scl]) + + +# pylint:enable=invalid-name diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/transforms.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/transforms.py new file mode 100644 index 0000000..846dff7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/transforms.py @@ -0,0 +1,1253 @@ +# coding=utf-8 +# +# Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr +# Copyright (C) 2010 Alvin Penner, penner@vaxxine.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# barraud@math.univ-lille1.fr +# +# This code defines several functions to make handling of transform +# attribute easier. +# +""" +Provide transformation parsing to extensions +""" +from __future__ import annotations +import re +from decimal import Decimal +from math import cos, radians, sin, sqrt, tan, fabs, atan2, hypot, pi, isfinite +from typing import ( + overload, + cast, + Callable, + Generator, + Iterator, + Tuple, + Union, + Optional, + List, +) + + +from .utils import strargs, KeyDict + + +VectorLike = Union[ + "ImmutableVector2d", Tuple[float, float] +] # pylint: disable=invalid-name +MatrixLike = Union[ + str, + Tuple[Tuple[float, float, float], Tuple[float, float, float]], + Tuple[float, float, float, float, float, float], + "Transform", +] +BoundingIntervalArgs = Union[ + "BoundingInterval", Tuple[float, float], float +] # pylint: disable=invalid-name + +# All the names that get added to the inkex API itself. +__all__ = ( + "BoundingBox", + "DirectedLineSegment", + "ImmutableVector2d", + "Transform", + "Vector2d", +) + + +# Old settings, supported because users click 'ok' without looking. +XAN = KeyDict({"l": "left", "r": "right", "m": "center_x"}) +YAN = KeyDict({"t": "top", "b": "bottom", "m": "center_y"}) +# Anchoring objects with given directions (see inx options) +CUSTOM_DIRECTION = {270: "tb", 90: "bt", 0: "lr", 360: "lr", 180: "rl"} +DIRECTION = ["tb", "bt", "lr", "rl", "ro", "ri"] + + +class ImmutableVector2d: + """Represents an immutable element of 2-dimensional Euclidean space""" + + _x = 0.0 + _y = 0.0 + + x = property(lambda self: self._x) + y = property(lambda self: self._y) + + @overload + def __init__(self): + # type: () -> None + pass + + @overload + def __init__(self, v, fallback=None): + # type: (Union[VectorLike, str], Optional[Union[VectorLike, str]]) -> None + pass + + @overload + def __init__(self, x, y): + # type: (float, float) -> None + pass + + def __init__(self, *args, fallback=None): + try: + if len(args) == 0: + x, y = 0.0, 0.0 + elif len(args) == 1: + x, y = self._parse(args[0]) + elif len(args) == 2: + x, y = map(float, args) + else: + raise ValueError("too many arguments") + except (ValueError, TypeError) as error: + if fallback is None: + raise ValueError("Cannot parse vector and no fallback given") from error + x, y = ImmutableVector2d(fallback) + self._x, self._y = float(x), float(y) + + @staticmethod + def _parse(point): + # type: (Union[VectorLike, str]) -> Tuple[float, float] + if isinstance(point, ImmutableVector2d): + x, y = point.x, point.y + elif isinstance(point, (tuple, list)) and len(point) == 2: + x, y = map(float, point) + elif isinstance(point, str) and point.count(",") == 1: + x, y = map(float, point.split(",")) + else: + raise ValueError(f"Can't parse {repr(point)}") + return x, y + + def __add__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(self.x + other.x, self.y + other.y) + + def __radd__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(self.x - other.x, self.y - other.y) + + def __rsub__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(-self.x + other.x, -self.y + other.y) + + def __neg__(self): + # type: () -> Vector2d + return Vector2d(-self.x, -self.y) + + def __pos__(self): + # type: () -> Vector2d + return Vector2d(self.x, self.y) + + def __floordiv__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x / float(factor), self.y / float(factor)) + + def __truediv__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x / float(factor), self.y / float(factor)) + + def __div__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x / float(factor), self.y / float(factor)) + + def __mul__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x * factor, self.y * factor) + + def __abs__(self): + # type: () -> float + return self.length + + def __rmul__(self, factor): + # type: (float) -> VectorLike + return Vector2d(self.x * factor, self.y * factor) + + def __repr__(self): + # type: () -> str + return f"Vector2d({self.x:.6g}, {self.y:.6g})" + + def __str__(self): + # type: () -> str + return f"{self.x:.6g}, {self.y:.6g}" + + def __iter__(self) -> Generator[float, None, None]: + yield self.x + yield self.y + + def __len__(self): + # type: () -> int + return 2 + + def __getitem__(self, item): + # type: (int) -> float + return (self.x, self.y)[item] + + def to_tuple(self) -> Tuple[float, float]: + """A tuple of the vector's components""" + return cast(Tuple[float, float], tuple(self)) + + def to_polar_tuple(self): + # type: () -> Tuple[float, Optional[float]] + """A tuple of the vector's magnitude and direction + + .. versionadded:: 1.1""" + return self.length, self.angle + + def dot(self, other: VectorLike) -> float: + """Multiply Vectors component-wise""" + other = Vector2d(other) + return self.x * other.x + self.y * other.y + + def cross(self, other): + # type: (VectorLike) -> float + """Z component of the cross product of the vectors extended into 3D + + .. versionadded:: 1.1""" + other = Vector2d(other) + return self.x * other.y - self.y * other.x + + def is_close( + self, + other: Union[VectorLike, str, Tuple[float, float]], + rtol: float = 1e-5, + atol: float = 1e-8, + ) -> float: + """Checks if two vectors are (almost) identical, up to both absolute and + relative tolerance.""" + other = Vector2d(other) + delta = (self - other).length + return delta < (atol + rtol * other.length) + + @property + def length(self) -> float: + """Returns the length of the vector""" + return sqrt(self.dot(self)) + + @property + def angle(self): + # type: () -> Optional[float] + """The angle of the vector when represented in polar coordinates + + .. versionadded:: 1.1""" + if self.x == 0 and self.y == 0: + return None + return atan2(self.y, self.x) + + +class Vector2d(ImmutableVector2d): + """Represents an element of 2-dimensional Euclidean space""" + + @staticmethod + def from_polar(radius, theta): + # type: (float, Optional[float]) -> Optional[Vector2d] + """Creates a Vector2d from polar coordinates + + None is returned when theta is None and radius is not zero. + + .. versionadded:: 1.1 + """ + if radius == 0.0: + return Vector2d(0.0, 0.0) + if theta is not None: + return Vector2d(radius * cos(theta), radius * sin(theta)) + # A vector with a radius but no direction is invalid + return None + + @ImmutableVector2d.x.setter + def x(self, value): + # type: (Union[float, int, str]) -> None + self._x = float(value) + + @ImmutableVector2d.y.setter + def y(self, value): + # type: (Union[float, int, str]) -> None + self._y = float(value) + + def __iadd__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + self.x += other.x + self.y += other.y + return self + + def __isub__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + self.x -= other.x + self.y -= other.y + return self + + def __imul__(self, factor): + # type: (float) -> Vector2d + self.x *= factor + self.y *= factor + return self + + def __idiv__(self, factor): + # type: (float) -> Vector2d + self.x /= factor + self.y /= factor + return self + + def __itruediv__(self, factor): + # type: (float) -> Vector2d + self.x /= factor + self.y /= factor + return self + + def __ifloordiv__(self, factor): + # type: (float) -> Vector2d + self.x /= factor + self.y /= factor + return self + + @overload + def assign(self, x, y): + # type: (float, float) -> VectorLike + pass + + @overload + def assign(self, other): + # type: (VectorLike, str) -> VectorLike + pass + + def assign(self, *args): + """Assigns a different vector in place""" + self.x, self.y = Vector2d(*args) + return self + + +class Transform: + """A transformation object which will always reduce to a matrix and can + then be used in combination with other transformations for reducing + finding a point and printing svg ready output. + + Use with svg transform attribute input: + + tr = Transform("scale(45, 32)") + + Use with triad matrix input (internal representation): + + tr = Transform(((1.0, 0.0, 0.0), (0.0, 1.0, 0.0))) + + Use with hexad matrix input (i.e. svg matrix(...)): + + tr = Transform((1.0, 0.0, 0.0, 1.0, 0.0, 0.0)) + + Once you have a transformation you can operate tr * tr to compose, + any of the above inputs are also valid operators for composing. + """ + + TRM = re.compile(r"(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?") + absolute_tolerance = 1e-5 # type: float + + def __init__( + self, + matrix=None, # type: Optional[MatrixLike] + callback=None, # type: Optional[Callable[[Transform], Transform]] + **extra, + ): + # type: (...) -> None + self.callback = None + self.matrix = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0)) + if matrix is not None: + self._set_matrix(matrix) + + self.add_kwargs(**extra) + # Set callback last, so it doesn't kick off just setting up the internal value + self.callback = callback + + def _set_matrix(self, matrix): + # type: (MatrixLike) -> None + """Parse a given string as an svg transformation instruction. + + .. versionadded:: 1.1""" + if isinstance(matrix, str): + for func, values in self.TRM.findall(matrix.strip()): + getattr(self, "add_" + func.lower())(*strargs(values)) + elif isinstance(matrix, Transform): + self.matrix = matrix.matrix + elif isinstance(matrix, (tuple, list)) and len(matrix) == 2: + row1 = matrix[0] + row2 = matrix[1] + if isinstance(row1, (tuple, list)) and isinstance(row2, (tuple, list)): + if len(row1) == 3 and len(row2) == 3: + row1 = cast(Tuple[float, float, float], tuple(map(float, row1))) + row2 = cast(Tuple[float, float, float], tuple(map(float, row2))) + self.matrix = row1, row2 + else: + raise ValueError( + f"Matrix '{matrix}' is not a valid transformation matrix" + ) + else: + raise ValueError( + f"Matrix '{matrix}' is not a valid transformation matrix" + ) + elif isinstance(matrix, (list, tuple)) and len(matrix) == 6: + tmatrix = cast( + Union[List[float], Tuple[float, float, float, float, float, float]], + matrix, + ) + row1 = (float(tmatrix[0]), float(tmatrix[2]), float(tmatrix[4])) + row2 = (float(tmatrix[1]), float(tmatrix[3]), float(tmatrix[5])) + self.matrix = row1, row2 + elif not isinstance(matrix, (list, tuple)): + raise ValueError(f"Invalid transform type: {type(matrix).__name__}") + else: + raise ValueError(f"Matrix '{matrix}' is not a valid transformation matrix") + + # These provide quick access to the svg matrix: + # + # [ a, c, e ] + # [ b, d, f ] + # + a = property(lambda self: self.matrix[0][0]) # pylint: disable=invalid-name + b = property(lambda self: self.matrix[1][0]) # pylint: disable=invalid-name + c = property(lambda self: self.matrix[0][1]) # pylint: disable=invalid-name + d = property(lambda self: self.matrix[1][1]) # pylint: disable=invalid-name + e = property(lambda self: self.matrix[0][2]) # pylint: disable=invalid-name + f = property(lambda self: self.matrix[1][2]) # pylint: disable=invalid-name + + def __bool__(self): + # type: () -> bool + return not self.__eq__(Transform()) + + __nonzero__ = __bool__ + + @overload + def add_matrix(self, a): + # type: (MatrixLike) -> Transform + pass + + @overload + def add_matrix( # pylint: disable=too-many-arguments + self, a: float, b: float, c: float, d: float, e: float, f: float + ) -> Transform: + pass + + @overload + def add_matrix(self, a, b): + # type: (Tuple[float, float, float], Tuple[float, float, float]) -> Transform + pass + + def add_matrix(self, *args): + """Add matrix in order they appear in the svg hexad""" + if len(args) == 1: + self.__imatmul__(Transform(args[0])) + elif len(args) == 2 or len(args) == 6: + self.__imatmul__(Transform(args)) + else: + raise ValueError(f"Invalid number of arguments {args}") + return self + + def add_kwargs(self, **kwargs): + """Add translations, scales, rotations etc using key word arguments""" + for key, value in reversed(list(kwargs.items())): + func = getattr(self, "add_" + key) + if isinstance(value, tuple): + func(*value) + elif value is not None: + func(value) + return self + + @overload + def add_translate(self, dr): + # type: (VectorLike) -> Transform + pass + + @overload + def add_translate(self, tr_x, tr_y=0.0): + # type: (float, Optional[float]) -> Transform + pass + + def add_translate(self, *args): + """Add translate to this transformation""" + if len(args) == 1 and isinstance(args[0], (int, float)): + tr_x, tr_y = args[0], 0.0 + else: + tr_x, tr_y = Vector2d(*args) + self.__imatmul__(((1.0, 0.0, tr_x), (0.0, 1.0, tr_y))) + return self + + def add_scale(self, sc_x, sc_y=None): + """Add scale to this transformation""" + sc_y = sc_x if sc_y is None else sc_y + self.__imatmul__(((sc_x, 0.0, 0.0), (0.0, sc_y, 0.0))) + return self + + @overload + def add_rotate(self, deg, center): + # type: (float, VectorLike) -> Transform + pass + + @overload + def add_rotate(self, deg, center_x, center_y): + # type: (float, float, float) -> Transform + pass + + @overload + def add_rotate(self, deg): + # type: (float) -> Transform + pass + + @overload + def add_rotate(self, deg, a): + # type: (float, Union[VectorLike, str]) -> Transform + pass + + @overload + def add_rotate(self, deg, a, b): + # type: (float, float, float) -> Transform + pass + + def add_rotate(self, deg, *args): + """Add rotation to this transformation""" + center_x, center_y = Vector2d(*args) + _cos, _sin = cos(radians(deg)), sin(radians(deg)) + self.__imatmul__(((_cos, -_sin, center_x), (_sin, _cos, center_y))) + self.__imatmul__(((1.0, 0.0, -center_x), (0.0, 1.0, -center_y))) + return self + + def add_skewx(self, deg): + # type: (float) -> Transform + """Add skew x to this transformation""" + self.__imatmul__(((1.0, tan(radians(deg)), 0.0), (0.0, 1.0, 0.0))) + return self + + def add_skewy(self, deg): + # type: (float) -> Transform + """Add skew y to this transformation""" + self.__imatmul__(((1.0, 0.0, 0.0), (tan(radians(deg)), 1.0, 0.0))) + return self + + def to_hexad(self): + # type: () -> Iterator[float] + """Returns the transform as a hexad matrix (used in svg)""" + return (val for lst in zip(*self.matrix) for val in lst) + + def is_translate(self, exactly=False): + # type: (bool) -> bool + """Returns True if this transformation is ONLY translate""" + tol = self.absolute_tolerance if not exactly else 0.0 + return ( + fabs(self.a - 1) <= tol + and abs(self.d - 1) <= tol + and fabs(self.b) <= tol + and fabs(self.c) <= tol + ) + + def is_scale(self, exactly=False): + # type: (bool) -> bool + """Returns True if this transformation is ONLY scale""" + tol = self.absolute_tolerance if not exactly else 0.0 + return ( + fabs(self.e) <= tol + and fabs(self.f) <= tol + and fabs(self.b) <= tol + and fabs(self.c) <= tol + ) + + def is_rotate(self, exactly=False): + # type: (bool) -> bool + """Returns True if this transformation is ONLY rotate""" + tol = self.absolute_tolerance if not exactly else 0.0 + return ( + self._is_URT(exactly=exactly) + and fabs(self.e) <= tol + and fabs(self.f) <= tol + and fabs(self.a**2 + self.b**2 - 1) <= tol + ) + + def rotation_degrees(self): + # type: () -> float + """Return the amount of rotation in this transform""" + if not self._is_URT(exactly=False): + raise ValueError( + "Rotation angle is undefined for non-uniformly scaled or skewed " + "matrices" + ) + return atan2(self.b, self.a) * 180 / pi + + def __str__(self): + # type: () -> str + """Format the given matrix into a string representation for svg""" + hexad = tuple(self.to_hexad()) + if self.is_translate(): + if not self: + return "" + return f"translate({self.e:.6g}, {self.f:.6g})" + if self.is_scale(): + return f"scale({self.a:.6g}, {self.d:.6g})" + if self.is_rotate(): + return f"rotate({self.rotation_degrees():.6g})" + return f"matrix({' '.join(f'{var:.6g}' for var in hexad)})" + + def __repr__(self) -> str: + """String representation of this object""" + return ( + f"{type(self).__name__}((" + f"({', '.join(f'{var:.6g}' for var in self.matrix[0])}), " + f"({', '.join(f'{var:.6g}' for var in self.matrix[1])})))" + ) + + def __eq__(self, matrix): + # typing this requires writing a proof for mypy that matrix is really + # MatrixLike + """Test if this transformation is equal to the given matrix""" + if isinstance(matrix, (str, tuple, list, Transform)): + val = all( + fabs(l - r) <= self.absolute_tolerance + for l, r in zip(self.to_hexad(), Transform(matrix).to_hexad()) + ) + else: + val = False + return val + + def __matmul__(self, matrix): + # type: (MatrixLike) -> Transform + """Combine this transform's internal matrix with the given matrix""" + # Conform the input to a known quantity (and convert if needed) + other = Transform(matrix) + # Return a transformation as the combined result + return Transform( + ( + self.a * other.a + self.c * other.b, + self.b * other.a + self.d * other.b, + self.a * other.c + self.c * other.d, + self.b * other.c + self.d * other.d, + self.a * other.e + self.c * other.f + self.e, + self.b * other.e + self.d * other.f + self.f, + ) + ) + + def __imatmul__(self, matrix): + # type: (MatrixLike) -> Transform + """In place multiplication of transform matrices""" + self.matrix = (self @ matrix).matrix + if self.callback is not None: + self.callback(self) + return self + + def __neg__(self): + # type: () -> Transform + """Returns an inverted transformation""" + det = (self.a * self.d) - (self.c * self.b) + # invert the rotation/scaling part + new_a = self.d / det + new_d = self.a / det + new_c = -self.c / det + new_b = -self.b / det + # invert the translational part + new_e = -(new_a * self.e + new_c * self.f) + new_f = -(new_b * self.e + new_d * self.f) + return Transform((new_a, new_b, new_c, new_d, new_e, new_f)) + + def apply_to_point(self, point): + # type: (VectorLike) -> Vector2d + """Transform a tuple (X, Y)""" + if isinstance(point, str): + raise ValueError(f"Will not transform string '{point}'") + point = Vector2d(point) + return Vector2d( + self.a * point.x + self.c * point.y + self.e, + self.b * point.x + self.d * point.y + self.f, + ) + + def _is_URT(self, exactly=False): + # type: (bool) -> bool + """ + Checks that transformation can be decomposed into product of + Uniform scale (U), Rotation around origin (R) and translation (T) + + :return: decomposition as U*R*T is possible + """ + tol = self.absolute_tolerance if not exactly else 0.0 + return (fabs(self.a - self.d) <= tol) and (fabs(self.b + self.c) <= tol) + + def interpolate(self, other, fraction): + # type: (Transform, float) -> Transform + """Interpolate with another Transform. + + .. versionadded:: 1.1 + """ + from .tween import TransformInterpolator + + return TransformInterpolator(self, other).interpolate(fraction) + + +class BoundingInterval: # pylint: disable=too-few-public-methods + """A pair of numbers that represent the minimum and maximum values.""" + + @overload + def __init__(self, other=None): + # type: (Optional[BoundingInterval]) -> None + pass + + @overload + def __init__(self, pair): + # type: (Tuple[float, float]) -> None + pass + + @overload + def __init__(self, value): + # type: (float) -> None + pass + + @overload + def __init__(self, x, y): + # type: (float, float) -> None + pass + + def __init__(self, x=None, y=None): + self.x: Union[int, float, Decimal] + self.y: Union[int, float, Decimal] + self.minimum: float + self.maximum: float + if y is not None: + if isinstance(x, (int, float, Decimal)) and isinstance( + y, (int, float, Decimal) + ): + self.minimum = float(x) + self.maximum = float(y) + else: + raise ValueError( + f"Not a number for scaling: {str((x, y))} " + f"({type(x).__name__},{type(y).__name__})" + ) + + else: + value = x + if value is None: + # identity for addition, zero for intersection + self.minimum, self.maximum = float("+inf"), float("-inf") + elif isinstance(value, BoundingInterval): + self.minimum = value.minimum + self.maximum = value.maximum + elif isinstance(value, (tuple, list)) and len(value) == 2: + self.minimum, self.maximum = min(value), max(value) + elif isinstance(value, (int, float, Decimal)): + self.minimum = self.maximum = float(value) + else: + raise ValueError( + f"Not a number for scaling: {str(value)} ({type(value).__name__})" + ) + + def __bool__(self): + # type: () -> bool + return isfinite(self.minimum) and isfinite(self.maximum) + + __nonzero__ = __bool__ + + def __neg__(self): + # type: () -> BoundingInterval + return BoundingInterval((-1 * self.maximum, -1 * self.minimum)) + + def __add__(self, other): + # type: (BoundingInterval) -> BoundingInterval + """Calculate the bounding interval that covers both given bounding intervals""" + new = BoundingInterval(self) + if other is not None: + new += other + return new + + def __iadd__(self, other): + # type: (BoundingInterval) -> BoundingInterval + other = BoundingInterval(other) + self.minimum = min((self.minimum, other.minimum)) + self.maximum = max((self.maximum, other.maximum)) + return self + + def __radd__(self, other): + # type: (BoundingInterval) -> BoundingInterval + if other is None: + return BoundingInterval(self) + return self + other + + def __and__(self, other: BoundingInterval) -> BoundingInterval: + """Calculate the bounding interval where both given bounding intervals + overlap""" + new = BoundingInterval(self) + if other is not None: + new &= other + return new + + def __iand__(self, other): + # type: (BoundingInterval) -> BoundingInterval + other = BoundingInterval(other) + self.minimum = max((self.minimum, other.minimum)) + self.maximum = min((self.maximum, other.maximum)) + if self.minimum > self.maximum: + self.minimum, self.maximum = float("+inf"), float("-inf") + return self + + def __rand__(self, other): + # type: (BoundingInterval) -> BoundingInterval + if other is None: + return BoundingInterval(self) + return self & other + + def __mul__(self, other: float) -> BoundingInterval: + new = BoundingInterval(self) + if other is not None: + new *= other + return new + + def __imul__(self, other: float) -> BoundingInterval: + self.minimum *= other + self.maximum *= other + return self + + def __iter__(self) -> Generator[float, None, None]: + yield self.minimum + yield self.maximum + + def __eq__(self, other) -> bool: + return tuple(self) == tuple(BoundingInterval(other)) + + def __contains__(self, value: float) -> bool: + return self.minimum <= value <= self.maximum + + def __repr__(self) -> str: + return f"BoundingInterval({self.minimum}, {self.maximum})" + + @property + def center(self): + # type: () -> float + """Pick the middle of the line""" + return self.minimum + ((self.maximum - self.minimum) / 2) + + @property + def size(self): + # type: () -> float + """Return the size difference minimum and maximum""" + return self.maximum - self.minimum + + +class BoundingBox: # pylint: disable=too-few-public-methods + """ + Some functions to compute a rough bbox of a given list of objects. + + BoundingBox(other) + BoundingBox(x, y) + BoundingBox((x1, x2), (y1, y2)) + """ + + width = property(lambda self: self.x.size) + height = property(lambda self: self.y.size) + top = property(lambda self: self.y.minimum) + left = property(lambda self: self.x.minimum) + bottom = property(lambda self: self.y.maximum) + right = property(lambda self: self.x.maximum) + center_x = property(lambda self: self.x.center) + center_y = property(lambda self: self.y.center) + diagonal_length = property( + lambda self: (self.width**2 + self.height**2) ** (0.5) + ) + + @overload + def __init__(self, other=None): + # type: (Optional[BoundingBox]) -> None + pass + + @overload + def __init__(self, x, y): + # type: (BoundingIntervalArgs, BoundingIntervalArgs) -> None + pass + + def __init__(self, x=None, y=None): + if y is None: + if x is None: + # identity for addition, zero for intersection + pass + elif isinstance(x, BoundingBox): + x, y = x.x, x.y + else: + raise ValueError( + f"Not a number for scaling: {str(x)} ({type(x).__name__})" + ) + self.x = BoundingInterval(x) + self.y = BoundingInterval(y) + + @staticmethod + def new_xywh(x: float, y: float, width: float, height: float) -> BoundingBox: + """Create a bounding box using x, y, width and height + + .. versionadded:: 1.2""" + return BoundingBox((x, x + width), (y, y + height)) + + def __bool__(self): + # type: () -> bool + return bool(self.x) and bool(self.y) + + __nonzero__ = __bool__ + + def __neg__(self): + # type: () -> BoundingBox + return BoundingBox(-self.x, -self.y) + + def __add__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + """Calculate the bounding box that covers both given bounding boxes""" + new = BoundingBox(self) + new += BoundingBox(other) + return new + + def __iadd__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + other = BoundingBox(other) + self.x += other.x + self.y += other.y + return self + + def __radd__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + return self + other + + def __and__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + """Calculate the bounding box where both given bounding boxes overlap""" + new = BoundingBox(self) + new &= BoundingBox(other) + return new + + def __iand__(self, other: Optional[BoundingBox]) -> BoundingBox: + other = BoundingBox(other) + self.x = self.x & other.x + self.y = self.y & other.y + if not self.x or not self.y: + self.x, self.y = BoundingInterval(), BoundingInterval() + return self + + def __rand__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + return self & other + + def __mul__(self, factor): + # type: (float) -> BoundingBox + new = BoundingBox(self) + new *= factor + return new + + def __imul__(self, factor): + # type: (float) -> BoundingBox + self.x *= factor + self.y *= factor + return self + + def __eq__(self, other): + # type (object) -> bool + if isinstance(other, BoundingBox): + return tuple(self) == tuple(other) + return False + + def __iter__(self) -> Generator[BoundingBox, None, None]: + yield self.x + yield self.y + + @property + def area(self): + """Return area of the bounding box + + .. versionadded:: 1.2""" + return self.width * self.height + + @property + def minimum(self): + # type: () -> Vector2d + """Return the minimum x,y coords""" + return Vector2d(self.x.minimum, self.y.minimum) + + @property + def maximum(self): + # type: () -> Vector2d + """Return the maximum x,y coords""" + return Vector2d(self.x.maximum, self.y.maximum) + + def __repr__(self): + # type: () -> str + return f"BoundingBox({tuple(self.x)},{tuple(self.y)})" + + @property + def center(self): + # type: () -> Vector2d + """Returns the middle of the bounding box""" + return Vector2d(self.x.center, self.y.center) + + @property + def size(self): + """Returns a vector containing width and height of the bounding box + + .. versionadded:: 1.2""" + return Vector2d(self.x.size, self.y.size) + + def get_anchor(self, xanchor, yanchor, direction=0, selbox=None): + # type: (str, str, Union[int, str], Optional[BoundingBox]) -> float + """Calls get_distance with the given anchor options""" + return self.anchor_distance( + getattr(self, XAN[xanchor]), + getattr(self, YAN[yanchor]), + direction=direction, + selbox=selbox, + ) + + @staticmethod + def anchor_distance( + x: float, + y: float, + direction: Union[int, str] = 0, + selbox: Optional[BoundingBox] = None, + ) -> float: + """Using the x,y returns a single sortable value based on direction and angle + + Args: + x (float): input x coordinate + y (float): input y coordinate + direction (Union[int, str], optional): int/float (custom angle), + tb/bt (top/bottom), lr/rl (left/right), ri/ro (radial). Defaults to 0. + selbox (Optional[BoundingBox], optional): The bounding box of the whole + selection for radial anchors. Defaults to None. + + Raises: + ValueError: if radial distance is requested without the optional selbox + parameter. + + Returns: + float: the anchor distance with respect to the direction. + """ + + rot = 0.0 + if isinstance(direction, (int, float)): # Angle + if direction not in CUSTOM_DIRECTION: + return hypot(x, y) * (cos(radians(-direction) - atan2(y, x))) + direction = CUSTOM_DIRECTION[direction] + + if direction in ("ro", "ri"): + if selbox is None: + raise ValueError( + "Radial distance not available without selection bounding box" + ) + rot = hypot(selbox.x.center - x, selbox.y.center - y) + + return [y, -y, x, -x, rot, -rot][DIRECTION.index(direction)] + + def resize(self, delta_x: float, delta_y: Optional[float] = None) -> BoundingBox: + """Enlarges / shrinks a bounding box by a constant value. If only delta_x + is given, each side is moved by the same amount; if delta_y is given, + different deltas are applied to horizontal and vertical intervals. + + .. versionadded:: 1.2""" + delta_y = delta_y or delta_x + return BoundingBox( + (self.x.minimum - delta_x, self.x.maximum + delta_x), + (self.y.minimum - delta_y, self.y.maximum + delta_y), + ) + + +class DirectedLineSegment: + """ + A directed line segment + + DirectedLineSegment(((x0, y0), (x1, y1))) + """ + + start = Vector2d() # start point of segment + end = Vector2d() # end point of segment + + x0 = property(lambda self: self.start.x) # pylint: disable=invalid-name + y0 = property(lambda self: self.start.y) # pylint: disable=invalid-name + x1 = property(lambda self: self.end.x) + y1 = property(lambda self: self.end.y) + dx = property(lambda self: self.vector.x) # pylint: disable=invalid-name + dy = property(lambda self: self.vector.y) # pylint: disable=invalid-name + + @overload + def __init__(self): + # type: () -> None + pass + + @overload + def __init__(self, other): + # type: (DirectedLineSegment) -> None + pass + + @overload + def __init__(self, start, end): + # type: (VectorLike, VectorLike) -> None + pass + + def __init__(self, *args): + if not args: # overload 0 + start, end = Vector2d(), Vector2d() + elif len(args) == 1: # overload 1 + (other,) = args + start, end = other.start, other.end + elif len(args) == 2: # overload 2 + start, end = args + else: + raise ValueError(f"DirectedLineSegment() can't be constructed from {args}") + + self.start = Vector2d(start) + self.end = Vector2d(end) + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, (tuple, DirectedLineSegment)): + return tuple(self) == tuple(other) + return False + + def __iter__(self): + # type: () -> Generator[DirectedLineSegment, None, None] + yield self.x0 + yield self.x1 + yield self.y0 + yield self.y1 + + @property + def vector(self): + # type: () -> Vector2d + """The vector of the directed line segment. + + The vector of the directed line segment represents the length + and direction of segment, but not the starting point. + + .. versionadded:: 1.1 + """ + return self.end - self.start + + @property + def length(self): + # type: () -> float + """Get the length of the line segment""" + return self.vector.length + + @property + def angle(self): + # type: () -> float + """Get the angle of the line created by this segment""" + return atan2(self.dy, self.dx) + + def distance_to_point(self, x, y): + # type: (float, float) -> Union[DirectedLineSegment, Optional[float]] + """Get the distance to the given point (x, y)""" + segment2 = DirectedLineSegment(self.start, (x, y)) + dot2 = segment2.dot(self) + if dot2 <= 0: + return DirectedLineSegment((x, y), self.start).length + if self.dot(self) <= dot2: + return DirectedLineSegment((x, y), self.end).length + return self.perp_distance(x, y) + + def perp_distance(self, x, y): + # type: (float, float) -> Optional[float] + """Perpendicular distance to the given point""" + if self.length == 0: + return None + return fabs((self.dx * (self.y0 - y)) - ((self.x0 - x) * self.dy)) / self.length + + def dot(self, other): + # type: (DirectedLineSegment) -> float + """Get the dot product with the segment with another""" + return self.vector.dot(other.vector) + + def point_at_ratio(self, ratio): + # type: (float) -> Tuple[float, float] + """Get the point at the given ratio along the line""" + return self.x0 + ratio * self.dx, self.y0 + ratio * self.dy + + def point_at_length(self, length): + # type: (float) -> Tuple[float, float] + """Get the point as the length along the line""" + return self.point_at_ratio(length / self.length) + + def parallel(self, x, y): + # type: (float, float) -> DirectedLineSegment + """Create parallel Segment""" + return DirectedLineSegment((x + self.dx, y + self.dy), (x, y)) + + def intersect(self, other): + # type: (DirectedLineSegment) -> Optional[Vector2d] + """Get the intersection between two segments""" + other = DirectedLineSegment(other) + denom = self.vector.cross(other.vector) + num = other.vector.cross(self.start - other.start) + + if denom != 0: + return Vector2d(self.point_at_ratio(num / denom)) + return None + + def __repr__(self): + # type: () -> str + return f"DirectedLineSegment(({self.start}), ({self.end}))" + + +def cubic_extrema(py0, py1, py2, py3): + # type: (float, float, float, float) -> Tuple[float, float] + """Returns the extreme value, given a set of bezier coordinates""" + + atol = 1e-9 + cmin, cmax = min(py0, py3), max(py0, py3) + pd1 = py1 - py0 + pd2 = py2 - py1 + pd3 = py3 - py2 + + def _is_bigger(point): + if 0 < point < 1: + pyx = ( + py0 * (1 - point) * (1 - point) * (1 - point) + + 3 * py1 * point * (1 - point) * (1 - point) + + 3 * py2 * point * point * (1 - point) + + py3 * point * point * point + ) + return min(cmin, pyx), max(cmax, pyx) + return cmin, cmax + + if fabs(pd1 - 2 * pd2 + pd3) > atol: + if pd2 * pd2 > pd1 * pd3: + pds = sqrt(pd2 * pd2 - pd1 * pd3) + cmin, cmax = _is_bigger((pd1 - pd2 + pds) / (pd1 - 2 * pd2 + pd3)) + cmin, cmax = _is_bigger((pd1 - pd2 - pds) / (pd1 - 2 * pd2 + pd3)) + + elif fabs(pd2 - pd1) > atol: + cmin, cmax = _is_bigger(-pd1 / (2 * (pd2 - pd1))) + + return cmin, cmax + + +def quadratic_extrema(py0, py1, py2): + # type: (float, float, float) -> Tuple[float, float] + """Returns the extreme value, given a set of quadratic bezier coordinates""" + atol = 1e-9 + cmin, cmax = min(py0, py2), max(py0, py2) + + def _is_bigger(point): + if 0 < point < 1: + pyx = ( + py0 * (1 - point) * (1 - point) + + 2 * py1 * point * (1 - point) + + py2 * point * point + ) + return min(cmin, pyx), max(cmax, pyx) + return cmin, cmax + + if fabs(py0 + py2 - 2 * py1) > atol: + cmin, cmax = _is_bigger((py0 - py1) / (py0 + py2 - 2 * py1)) + + return cmin, cmax diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/turtle.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/turtle.py new file mode 100644 index 0000000..40c9b60 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/turtle.py @@ -0,0 +1,255 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""A Python path turtle for Inkscape extensions""" + +import math +import random +from typing import List, Union + +from .paths import Line, Move, Path, PathCommand +from .elements import PathElement, Group, BaseElement +from .styles import Style + + +class PathTurtle: + """A Python path turtle + + .. versionchanged:: 1.2 + pTurtle has been renamed to PathTurtle.""" + + def __init__(self, home=(0, 0)): + self.__home = [home[0], home[1]] + self.__pos = self.__home[:] + self.__heading = -90 + self.__path = "" + self.__draw = True + self.__new = True + + def forward(self, mag: float): + """Move turtle forward by mag in the current direction.""" + self.setpos( + ( + self.__pos[0] + math.cos(math.radians(self.__heading)) * mag, + self.__pos[1] + math.sin(math.radians(self.__heading)) * mag, + ) + ) + + def backward(self, mag): + """Move turtle backward by mag in the current direction.""" + self.setpos( + ( + self.__pos[0] - math.cos(math.radians(self.__heading)) * mag, + self.__pos[1] - math.sin(math.radians(self.__heading)) * mag, + ) + ) + + def right(self, deg): + """Rotate turtle right by deg degrees. + + Changed in inkex 1.2: The turtle now rotates right (previously left) when + calling this method.""" + self.__heading += deg + + def left(self, deg): + """Rotate turtle left by deg degrees. + + Changed in inkex 1.2: The turtle now rotates left (previously right) when + calling this method.""" + self.__heading -= deg + + def penup(self): + """Enable non-drawing / moving mode""" + self.__draw = False + self.__new = False + + def pendown(self): + """Enable drawing mode""" + if not self.__draw: + self.__new = True + self.__draw = True + + def pentoggle(self): + """Switch between drawing and moving mode""" + if self.__draw: + self.penup() + else: + self.pendown() + + def home(self): + """Move to home position""" + self.setpos(self.__home) + + def clean(self): + """Delete current path""" + self.__path = "" + + def clear(self): + """Delete current path and move to home""" + self.clean() + self.home() + + def setpos(self, arg): + """Move/draw to position, depending on the current state""" + if self.__new: + self.__path += "M" + ",".join([str(i) for i in self.__pos]) + self.__new = False + self.__pos = arg + if self.__draw: + self.__path += "L" + ",".join([str(i) for i in self.__pos]) + + def getpos(self): + """Returns the current position""" + return self.__pos[:] + + def setheading(self, deg): + """Set the heading to deg degrees""" + self.__heading = deg + + def getheading(self): + """Returns the heading in degrees""" + return self.__heading + + def sethome(self, arg): + """Set home position""" + self.__home = list(arg) + + def getPath(self): + """Returns the current path""" + return self.__path + + def rtree(self, size, minimum, pt=False): + """Generates a random tree""" + if size < minimum: + return + self.fd(size) + turn = random.uniform(20, 40) + self.rt(turn) + self.rtree(size * random.uniform(0.5, 0.9), minimum, pt) + self.lt(turn) + turn = random.uniform(20, 40) + self.lt(turn) + self.rtree(size * random.uniform(0.5, 0.9), minimum, pt) + self.rt(turn) + if pt: + self.pu() + self.bk(size) + if pt: + self.pd() + + # pylint: disable=invalid-name + fd = forward + bk = backward + rt = right + lt = left + pu = penup + pd = pendown + + +pTurtle = PathTurtle # should be deprecated + + +class PathBuilder: + """This helper class can be used to construct a path and insert it into a + document. + + .. versionadded:: 1.2""" + + def __init__(self, style: Style): + """Initializes a PathDrawHelper object + + Args: + style (Style): Style of the path. + """ + self.current = Path() + self.style = style + + def add(self, command: Union[PathCommand, List[PathCommand]]): + """Add a Path command to the Helper + + Args: + command (Union[PathCommand, List[PathCommand]]): A (list of) PathCommand(s) + to be appended. + """ + self.current.append(command) + + def terminate(self): + """Terminates current subpath. This method does nothing by default and is + supposed to be overridden in subclasses.""" + + def append_next(self, sibling_before: BaseElement): + """Insert the resulting Path as :class:`inkex.elements._polygons.PathElement` + into the document tree. + + Args: + sibling_before (BaseElement): The element the resulting path will be + appended after. + """ + pth = PathElement() + pth.path = self.current + pth.style = self.style + sibling_before.addnext(pth) + + def Move_to(self, x, y): # pylint: disable=invalid-name + """Shorthand to insert an absolute move command: `M x y`. + + Args: + x (Float): x coordinate to move to + y (Float): y coordinate to move to + """ + self.add(Move(x, y)) + + def Line_to(self, x, y): # pylint: disable=invalid-name + """Shorthand to insert an absolute lineto command: `L x y`. + + Args: + x (Float): x coordinate to draw a line to + y (Float): y coordinate to draw a line to + """ + self.add(Line(x, y)) + + +class PathGroupBuilder(PathBuilder): + """This helper class can be used to construct a group of paths that all have the + same style. + + .. versionadded:: 1.2""" + + def __init__(self, style): + super().__init__(style) + self.result = Group() + + def terminate(self): + """Terminates the current Path, and appends it to the group if it is not + empty.""" + if len(self.current) > 1: + pth = PathElement() + pth.path = self.current.to_absolute() + pth.style = self.style + self.result.append(pth) + self.current = Path() + + def append_next(self, sibling_before: BaseElement): + """Insert the resulting Path as :class:`inkex.elements._groups.Group` into the + document tree. + + Args: + sibling_before (BaseElement): The element the resulting group will be + appended after. + """ + sibling_before.addnext(self.result) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tween.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tween.py new file mode 100644 index 0000000..c5dca76 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/tween.py @@ -0,0 +1,847 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# 2020 Jonathan Neuhauser, jonathan.neuhauser@outlook.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Module for interpolating attributes and styles + +.. versionchanged:: 1.2 + Rewritten in inkex 1.2 in an object-oriented structure to support more attributes. +""" +from bisect import bisect_left +import abc +import copy + +from .styles import Style +from .elements._filters import LinearGradient, RadialGradient, Stop +from .transforms import Transform +from .colors import Color +from .units import convert_unit, parse_unit, render_unit +from .bezier import bezlenapprx, cspbezsplit, cspbezsplitatlength, csplength +from .paths import Path, CubicSuperPath +from .elements import SvgDocumentElement +from .utils import FragmentError + + +try: + from typing import Tuple, TypeVar + + Value = TypeVar("Value") + Number = TypeVar("Number", int, float) +except ImportError: + pass + + +def interpcoord(coord_a: Number, coord_b: Number, time: float): + """Interpolate single coordinate by the amount of time""" + return ValueInterpolator(coord_a, coord_b).interpolate(time) + + +def interppoints(point1, point2, time): + # type: (Tuple[float, float], Tuple[float, float], float) -> Tuple[float, float] + """Interpolate coordinate points by amount of time""" + return ArrayInterpolator(point1, point2).interpolate(time) + + +class AttributeInterpolator(abc.ABC): + """Interpolate between attributes""" + + def __init__(self, start_value, end_value): + self.start_value = start_value + self.end_value = end_value + + @staticmethod + def best_style(node): + """Gets the best possible approximation to a node's style. For nodes inside the + element tree of an SVG file, stylesheets defined in the defs of that file can be + taken into account. This should be the case for input elements, but is not + required - in that case, only the local inline style is used. + + During the interpolation process, some nodes are created temporarily, such as + plain gradients of a single color to allow solid<->gradient interpolation. These + are not attached to the document tree and therefore have no root. Since the only + style relevant for them is the inline style, it is acceptable to fallback to it. + + Args: + node (BaseElement): The node to get the best approximated style of + + Returns: + Style: If the node is rooted, the CSS specified style. Else, the inline + style.""" + try: + return node.specified_style() + except FragmentError: + return node.style + + @staticmethod + def create_from_attribute(snode, enode, attribute, method=None): + """Creates an interpolator for an attribute. Currently, only path, transform and + style attributes are supported + + Args: + snode (BaseElement): start element + enode (BaseElement): end element + attribute (str): attribute name (for styles, starting with "style/") + method (AttributeInterpolator, optional): (currently only used for paths). + Specifies a method used to interpolate the attribute. Defaults to None. + + Raises: + ValueError: if an attribute is passed that is not a style, path or transform + attribute + + Returns: + AttributeInterpolator: an interpolator whose type depends on attribute. + """ + if attribute in Style.color_props: + return StyleInterpolator.create_from_fill_stroke(snode, enode, attribute) + if attribute == "d": + if method is None: + method = FirstNodesInterpolator + return method(snode.path, enode.path) + if attribute == "style": + return StyleInterpolator(snode, enode) + if attribute.startswith("style/"): + return StyleInterpolator.create(snode, enode, attribute[6:]) + if attribute == "transform": + return TransformInterpolator(snode.transform, enode.transform) + if method is not None: + return method(snode.get(attribute), enode.get(attribute)) + raise ValueError("only path and style attributes are supported") + + @abc.abstractmethod + def interpolate(self, time=0): + """Interpolation method, needs to be implemented by subclasses""" + return + + +class StyleInterpolator(AttributeInterpolator): + """Class to interpolate styles""" + + def __init__(self, start_value, end_value): + super().__init__(start_value, end_value) + self.interpolators = {} + # some keys are always processed in a certain order, these provide alternative + # interpolation routes if e.g. Color<->none is interpolated + all_keys = list( + dict.fromkeys( + ["fill", "stroke", "fill-opacity", "stroke-opacity", "stroke-width"] + + list(self.best_style(start_value).keys()) + + list(self.best_style(end_value).keys()) + ) + ) + for attr in all_keys: + sstyle = self.best_style(start_value) + estyle = self.best_style(end_value) + if attr not in sstyle and attr not in estyle: + continue + try: + interp = StyleInterpolator.create( + self.start_value, self.end_value, attr + ) + self.interpolators[attr] = interp + except ValueError: + # no interpolation method known for this attribute + pass + + @staticmethod + def create(snode, enode, attribute): + """Creates an Interpolator for a given style attribute, depending on its type: + + - Color properties (such as fill, stroke) -> :class:`ColorInterpolator`, + :class:`GradientInterpolator` ect. + - Unit properties -> :class:`UnitValueInterpolator` + - other properties -> :class:`ValueInterpolator` + + Args: + snode (BaseElement): start element + enode (BaseElement): end element + attribute (str): attribute to interpolate + + Raises: + ValueError: if the attribute is not in any of the lists + + Returns: + AttributeInterpolator: an interpolator object whose type depends on the + attribute. + """ + if attribute in Style.color_props: + return StyleInterpolator.create_from_fill_stroke(snode, enode, attribute) + + if attribute in Style.unit_props: + return UnitValueInterpolator( + AttributeInterpolator.best_style(snode)(attribute), + AttributeInterpolator.best_style(enode)(attribute), + ) + + if attribute in Style.opacity_props: + return ValueInterpolator( + AttributeInterpolator.best_style(snode)(attribute), + AttributeInterpolator.best_style(enode)(attribute), + ) + + raise ValueError("Unknown attribute") + + @staticmethod + def create_from_fill_stroke(snode, enode, attribute): + """Creates an Interpolator for a given color-like attribute + + Args: + snode (BaseElement): start element + enode (BaseElement): end element + attribute (str): attribute to interpolate + + Raises: + ValueError: if the attribute is not color-like + ValueError: if the attribute is unset on both start and end style + + Returns: + AttributeInterpolator: an interpolator object whose type depends on the + attribute. + """ + if attribute not in Style.color_props: + raise ValueError("attribute must be a color property") + + sstyle = AttributeInterpolator.best_style(snode) + estyle = AttributeInterpolator.best_style(enode) + + styles = [[snode, sstyle], [enode, estyle]] + for cur, curstyle in styles: + if curstyle(attribute) is None: + cur.style[attribute + "-opacity"] = 0.0 + if attribute == "stroke": + cur.style["stroke-width"] = 0.0 + + # check if style is none, unset or a color + if isinstance( + sstyle(attribute), (LinearGradient, RadialGradient) + ) or isinstance(estyle(attribute), (LinearGradient, RadialGradient)): + # if one of the two styles is a gradient, use gradient interpolation. + try: + return GradientInterpolator.create(snode, enode, attribute) + except ValueError: + # different gradient types, just duplicate the first + return TrivialInterpolator(sstyle(attribute)) + if sstyle(attribute) is None and estyle(attribute) is None: + return TrivialInterpolator("none") + return ColorInterpolator.create(sstyle, estyle, attribute) + + def interpolate(self, time=0): + """Interpolates a style using the interpolators set in self.interpolators + + Args: + time (int, optional): Interpolation position. If 0, start_value is returned, + if 1, end_value is returned. Defaults to 0. + + Returns: + inkex.Style: interpolated style + """ + style = Style() + for prop, interp in self.interpolators.items(): + style[prop] = interp.interpolate(time) + return style + + +class TrivialInterpolator(AttributeInterpolator): + """Trivial interpolator, returns value for every time""" + + def __init__(self, value): + super().__init__(value, value) + + def interpolate(self, time=0): + return self.start_value + + +class ValueInterpolator(AttributeInterpolator): + """Class for interpolation of a single value""" + + def __init__(self, start_value=0, end_value=0): + super().__init__(float(start_value), float(end_value)) + + def interpolate(self, time=0): + """(Linearly) interpolates a value + + Args: + time (int, optional): Interpolation position. If 0, start_value is returned, + if 1, end_value is returned. Defaults to 0. + + Returns: + int: interpolated value + """ + return self.start_value + ((self.end_value - self.start_value) * time) + + +class UnitValueInterpolator(ValueInterpolator): + """Class for interpolation of a value with unit""" + + def __init__(self, start_value=0, end_value=0): + start_val, start_unit = parse_unit(start_value) + end_val = convert_unit(end_value, start_unit) + super().__init__(start_val, end_val) + self.unit = start_unit + + def interpolate(self, time=0): + return render_unit(super().interpolate(time), self.unit) + + +class ArrayInterpolator(AttributeInterpolator): + """Interpolates array-like objects element-wise, e.g. color, transform, + coordinate""" + + def __init__(self, start_value, end_value): + super().__init__(start_value, end_value) + self.interpolators = [ + ValueInterpolator(cur, other) + for (cur, other) in zip(start_value, end_value) + ] + + def interpolate(self, time=0): + """Interpolates an array element-wise + + Args: + time (int, optional): [description]. Defaults to 0. + + Returns: + List: interpolated array + """ + return [interp.interpolate(time) for interp in self.interpolators] + + +class TransformInterpolator(ArrayInterpolator): + """Class for interpolation of transforms""" + + def __init__(self, start_value=Transform(), end_value=Transform()): + """Creates a transform interpolator. + + Args: + start_value (inkex.Transform, optional): start transform. Defaults to + inkex.Transform(). + end_value (inkex.Transform, optional): end transform. Defaults to + inkex.Transform(). + """ + super().__init__(start_value.to_hexad(), end_value.to_hexad()) + + def interpolate(self, time=0): + """Interpolates a transform by interpolating each item in the transform hexad + separately. + + Args: + time (int, optional): Interpolation position. If 0, start_value is returned, + if 1, end_value is returned. Defaults to 0. + + Returns: + Transform: interpolated transform + """ + return Transform(super().interpolate(time)) + + +class ColorInterpolator(ArrayInterpolator): + """Class for color interpolation""" + + @staticmethod + def create(sst, est, attribute): + """Creates a ColorInterpolator for either Fill or stroke, depending on the + attribute. + + Args: + sst (Style): Start style + est (Style): End style + attribute (string): either fill or stroke + + Raises: + ValueError: if none of the start or end style is a color. + + Returns: + ColorInterpolator: A ColorInterpolator object + """ + styles = [sst, est] + for cur, other in zip(styles, reversed(styles)): + if not isinstance(cur(attribute), Color) or cur(attribute) is None: + cur[attribute] = other(attribute) + this = ColorInterpolator( + Color(styles[0](attribute)), Color(styles[1](attribute)) + ) + if this is None: + raise ValueError("One of the two attribute needs to be a plain color") + return this + + def __init__(self, start_value=Color("#000000"), end_value=Color("#000000")): + super().__init__(start_value, end_value) + + def interpolate(self, time=0): + """Interpolates a color by interpolating its r, g, b, a channels separately. + + Args: + time (int, optional): Interpolation position. If 0, start_value is returned, + if 1, end_value is returned. Defaults to 0. + + Returns: + Color: interpolated color + """ + return Color(list(map(int, super().interpolate(time)))) + + +class GradientInterpolator(AttributeInterpolator): + """Base class for Gradient Interpolation""" + + def __init__(self, start_value, end_value, svg=None): + super().__init__(start_value, end_value) + self.svg = svg + # If one of the styles is empty, set it to the gradient of the other + if start_value is None: + self.start_value = end_value + if end_value is None: + self.end_value = start_value + self.transform_interpolator = TransformInterpolator( + self.start_value.gradientTransform, self.end_value.gradientTransform + ) + self.orientation_interpolator = { + attr: UnitValueInterpolator( + self.start_value.get(attr), self.end_value.get(attr) + ) + for attr in self.start_value.orientation_attributes + if self.start_value.get(attr) is not None + and self.end_value.get(attr) is not None + } + if not ( + self.start_value.href is not None + and self.start_value.href is self.end_value.href + ): + # the gradient link to different stops, interpolate between them + # add both start and end offsets, then take distict + newoffsets = sorted( + list(set(self.start_value.stop_offsets + self.end_value.stop_offsets)) + ) + + def func(start, end, time): + return StopInterpolator(start, end).interpolate(time) + + sstops = GradientInterpolator.interpolate_linear_list( + self.start_value.stop_offsets, + list(self.start_value.stops), + newoffsets, + func, + ) + ostops = GradientInterpolator.interpolate_linear_list( + self.end_value.stop_offsets, + list(self.end_value.stops), + newoffsets, + func, + ) + self.newstop_interpolator = [ + StopInterpolator(s1, s2) for s1, s2 in zip(sstops, ostops) + ] + else: + self.newstop_interpolator = None + + @staticmethod + def create(snode, enode, attribute): + """Creates a `GradientInterpolator` for either fill or stroke, depending on + attribute. + + Cases: (A, B) -> Interpolator + + - Linear Gradient, Linear Gradient -> LinearGradientInterpolator + - Color or None, Linear Gradient -> LinearGradientInterpolator + - Radial Gradient, Radial Gradient -> RadialGradientInterpolator + - Color or None, Radial Gradient -> RadialGradientInterpolator + - Radial Gradient, Linear Gradient -> ValueError + - Color or None, Color or None -> ValueError + + Args: + snode (BaseElement): start element + enode (BaseElement): end element + attribute (string): either fill or stroke + + Raises: + ValueError: if none of the styles are a gradient or if they are gradients + of different types + + Returns: + GradientInterpolator: an Interpolator object + """ + interpolator = None + gradienttype = None + # first find out which type of interpolator we need + sstyle = AttributeInterpolator.best_style(snode) + estyle = AttributeInterpolator.best_style(enode) + for cur in [sstyle, estyle]: + curgrad = None + if isinstance(cur(attribute), (LinearGradient, RadialGradient)): + curgrad = cur(attribute) + for gradtype, interp in [ + [LinearGradient, LinearGradientInterpolator], + [RadialGradient, RadialGradientInterpolator], + ]: + if curgrad is not None and isinstance(curgrad, gradtype): + if interpolator is None: + interpolator = interp + gradienttype = gradtype + if not (interp == interpolator): + raise ValueError("Gradient types don't match") + # If one of the styles is empty, set it to the gradient of the other, but with + # zero opacity (and stroke-width for strokes) + # If one of the styles is a plain color, replace it by a gradient with a single + # stop + iterator = [[snode, gradienttype(), enode], [enode, gradienttype(), snode]] + for index in [0, 1]: + curstyle = AttributeInterpolator.best_style(iterator[index][0]) + value = curstyle(attribute) + if value is None: + # if the attribute of one of the two ends is unset, set the opacity to + # zero. + iterator[index][0].style[attribute + "-opacity"] = 0.0 + if attribute == "stroke": + iterator[index][0].style["stroke-width"] = 0.0 + if isinstance(value, Color): + # if the attribute of one of the two ends is a color, convert it to a + # one-stop gradient. Type depends on the type of the other gradient. + interpolator.initialize_position( + iterator[index][1], iterator[index][0].bounding_box() + ) + stop = Stop() + stop.style = Style() + stop.style["stop-color"] = value + stop.offset = 0 + iterator[index][1].add(stop) + stop = Stop() + stop.style = Style() + stop.style["stop-color"] = value + stop.offset = 1 + iterator[index][1].add(stop) + else: + iterator[index][1] = value # is a gradient + if interpolator is None: + raise ValueError("None of the two styles is a gradient") + if interpolator in [LinearGradientInterpolator, RadialGradientInterpolator]: + return interpolator(iterator[0][1], iterator[1][1], snode) + return interpolator(iterator[0][1], iterator[1][1]) + + @staticmethod + def interpolate_linear_list(positions, values, newpositions, func): + """Interpolates a list of values given at n positions to the best approximation + at m newpositions. + + >>> + | + | x + | x + _________________ + pq q p q + (x denotes function values, p: positions, q: newpositions) + A function may be given to interpolate between given values. + + Args: + positions (list[number-like]): position of current function values + values (list[Type]): list of arbitrary type, + ``len(values) == len(positions)`` + newpositions (list[number-like]): position of interpolated values + func (Callable[[Type, Type, float], Type]): Function to interpolate between + values + + Returns: + list[Type]: interpolated function values at positions + """ + newvalues = [] + positions = list(map(float, positions)) + newpositions = list(map(float, newpositions)) + for pos in newpositions: + if len(positions) == 1: + newvalues.append(values[0]) + else: + # current run: + # idxl pos idxr + # p p | p + # q q + idxl = max(0, bisect_left(positions, pos) - 1) + idxr = min(len(positions) - 1, idxl + 1) + fraction = (pos - positions[idxl]) / (positions[idxr] - positions[idxl]) + vall = values[idxl] + valr = values[idxr] + newval = func(vall, valr, fraction) + newvalues.append(newval) + return newvalues + + @staticmethod + def append_to_doc(element, gradient): + """Splits a gradient into stops and orientation, appends it to the document's + defs and returns the href to the orientation gradient. + + Args: + element (BaseElement): an element inside the SVG that the gradient should be + added to + gradient (Gradient): the gradient to append to the document + + Returns: + Gradient: the orientation gradient, or the gradient object if + element has no root or is None + """ + stops, orientation = gradient.stops_and_orientation() + if element is None or ( + element.getparent() is None and not isinstance(element, SvgDocumentElement) + ): + return gradient + element.root.defs.add(orientation) + if len(stops) > 0: + element.root.defs.add(stops, orientation) + orientation.href = stops.get_id() + return orientation + + def interpolate(self, time=0): + """Interpolate with another gradient.""" + newgrad = self.start_value.copy() + # interpolate transforms + newgrad.gradientTransform = self.transform_interpolator.interpolate(time) + + # interpolate orientation + for attr in self.orientation_interpolator.keys(): + newgrad.set(attr, self.orientation_interpolator[attr].interpolate(time)) + + # interpolate stops + if self.newstop_interpolator is not None: + newgrad.remove_all(Stop) + newgrad.add( + *[interp.interpolate(time) for interp in self.newstop_interpolator] + ) + if self.svg is None: + return newgrad + return GradientInterpolator.append_to_doc(self.svg, newgrad) + + +class LinearGradientInterpolator(GradientInterpolator): + """Class for interpolation of linear gradients""" + + def __init__( + self, start_value=LinearGradient(), end_value=LinearGradient(), svg=None + ): + super().__init__(start_value, end_value, svg) + + @staticmethod + def initialize_position(grad, bbox): + """Initializes a linear gradient's position""" + grad.set("x1", bbox.left) + grad.set("x2", bbox.right) + grad.set("y1", bbox.center.y) + grad.set("y2", bbox.center.y) + + +class RadialGradientInterpolator(GradientInterpolator): + """Class to interpolate radial gradients""" + + def __init__( + self, start_value=RadialGradient(), end_value=RadialGradient(), svg=None + ): + super().__init__(start_value, end_value, svg) + + @staticmethod + def initialize_position(grad, bbox): + """Initializes a radial gradient's position""" + x, y = bbox.center + grad.set("cx", x) + grad.set("cy", y) + grad.set("fx", x) + grad.set("fy", y) + grad.set("r", bbox.right - bbox.center.x) + + +class StopInterpolator(AttributeInterpolator): + """Class to interpolate gradient stops""" + + def __init__(self, start_value, end_value): + super().__init__(start_value, end_value) + self.style_interpolator = StyleInterpolator(start_value, end_value) + self.position_interpolator = ValueInterpolator( + float(start_value.offset), float(end_value.offset) + ) + + def interpolate(self, time=0): + """Interpolates a gradient stop by interpolating style and offset separately + + Args: + time (int, optional): Interpolation position. If 0, start_value is returned, + if 1, end_value is returned. Defaults to 0. + + Returns: + Stop: interpolated gradient stop + """ + newstop = Stop() + newstop.style = self.style_interpolator.interpolate(time) + newstop.offset = self.position_interpolator.interpolate(time) + return newstop + + +class PathInterpolator(AttributeInterpolator): + """Base class for Path interpolation""" + + def __init__(self, start_value=Path(), end_value=Path()): + super().__init__(start_value.to_superpath(), end_value.to_superpath()) + self.processed_end_path = None + self.processed_start_path = None + + def truncate_subpaths(self): + """Truncates the longer path so that all subpaths in both paths have an equal + number of bezier commands""" + s = [[]] + e = [[]] + # loop through all subpaths as long as there are remaining ones + while self.start_value and self.end_value: + # if both subpaths contain a bezier command, append it to s and e + if self.start_value[0] and self.end_value[0]: + s[-1].append(self.start_value[0].pop(0)) + e[-1].append(self.end_value[0].pop(0)) + # if the subpath of start_value is empty, add the remaining empty list as + # new subpath of s and one more item of end_value as new subpath of e. + # Afterwards, the loop terminates + elif self.end_value[0]: + s.append(self.start_value.pop(0)) + e[-1].append(self.end_value[0][0]) + e.append([self.end_value[0].pop(0)]) + elif self.start_value[0]: + e.append(self.end_value.pop(0)) + s[-1].append(self.start_value[0][0]) + s.append([self.start_value[0].pop(0)]) + # if there are no commands left in both start_value or end_value, add empty + # list to both start_value and end_value + else: + s.append(self.start_value.pop(0)) + e.append(self.end_value.pop(0)) + self.processed_start_path = s + self.processed_end_path = e + + def interpolate(self, time=0): + # create an interpolated path for each interval + interp = [] + # process subpaths + for ssubpath, esubpath in zip( + self.processed_start_path, self.processed_end_path + ): + if not (ssubpath or esubpath): + break + # add a new subpath to the interpolated path + interp.append([]) + # process each bezier command in the subpaths (which now have equal length) + for sbezier, ebezier in zip(ssubpath, esubpath): + if not (sbezier or ebezier): + break + # add a new bezier command to the last subpath + interp[-1].append([]) + # process points + for point1, point2 in zip(sbezier, ebezier): + if not (point1 or point2): + break + # add a new point to the last bezier command + interp[-1][-1].append( + ArrayInterpolator(point1, point2).interpolate(time) + ) + # remove final subpath if empty. + if not interp[-1]: + del interp[-1] + return CubicSuperPath(interp) + + +class EqualSubsegmentsInterpolator(PathInterpolator): + """Interpolates the path by rediscretizing the subpaths first.""" + + @staticmethod + def get_subpath_lenghts(path): + """prepare lengths for interpolation""" + sp_lenghts, total = csplength(path) + t = 0 + lenghts = [] + for sp in sp_lenghts: + for l in sp: + t += l / total + lenghts.append(t) + lenghts.sort() + return sp_lenghts, total, lenghts + + @staticmethod + def process_path(path, other): + """Rediscretize path so that all subpaths have an equal number of segments, + so that there is a node at the path "times" where path or other have a node + + Args: + path (Path): the first path + other (Path): the second path + + Returns: + Array: the prepared path description for the intermediate path""" + sp_lenghts, total, _ = EqualSubsegmentsInterpolator.get_subpath_lenghts(path) + _, _, lenghts = EqualSubsegmentsInterpolator.get_subpath_lenghts(other) + t = 0 + s = [[]] + for sp in sp_lenghts: + if not path[0]: + s.append(path.pop(0)) + s[-1].append(path[0].pop(0)) + for l in sp: + pt = t + t += l / total + if lenghts and t > lenghts[0]: + while lenghts and lenghts[0] < t: + nt = (lenghts[0] - pt) / (t - pt) + bezes = cspbezsplitatlength(s[-1][-1][:], path[0][0][:], nt) + s[-1][-1:] = bezes[:2] + path[0][0] = bezes[2] + pt = lenghts.pop(0) + s[-1].append(path[0].pop(0)) + return s + + def __init__(self, start_path=Path(), end_path=Path()): + super().__init__(start_path, end_path) + # rediscretisize both paths + start_copy = copy.deepcopy(self.start_value) + # TODO find out why self.start_value.copy() doesn't work + self.start_value = EqualSubsegmentsInterpolator.process_path( + self.start_value, self.end_value + ) + self.end_value = EqualSubsegmentsInterpolator.process_path( + self.end_value, start_copy + ) + + self.truncate_subpaths() + + +class FirstNodesInterpolator(PathInterpolator): + """Interpolates a path by discarding the trailing nodes of the longer subpath""" + + def __init__(self, start_path=Path(), end_path=Path()): + super().__init__(start_path, end_path) + # which path has fewer segments? + lengthdiff = len(self.start_value) - len(self.end_value) + # swap shortest first + if lengthdiff > 0: + self.start_value, self.end_value = self.end_value, self.start_value + # subdivide the shorter path + for _ in range(abs(lengthdiff)): + maxlen = 0 + subpath = 0 + segment = 0 + for y, _ in enumerate(self.start_value): + for z in range(1, len(self.start_value[y])): + leng = bezlenapprx( + self.start_value[y][z - 1], self.start_value[y][z] + ) + if leng > maxlen: + maxlen = leng + subpath = y + segment = z + sp1, sp2 = self.start_value[subpath][segment - 1 : segment + 1] + self.start_value[subpath][segment - 1 : segment + 1] = cspbezsplit(sp1, sp2) + # if swapped, swap them back + if lengthdiff > 0: + self.start_value, self.end_value = self.end_value, self.start_value + self.truncate_subpaths() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/units.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/units.py new file mode 100644 index 0000000..fec0e87 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/units.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) Aaron Spike +# Aurélio A. Heckert +# Bulia Byak +# Nicolas Dufour, nicoduf@yahoo.fr +# Peter J. R. Moulder +# Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Convert to and from various units and find the closest matching unit. +""" + +import re + +# a dictionary of unit to user unit conversion factors +CONVERSIONS = { + "in": 96.0, + "pt": 1.3333333333333333, + "px": 1.0, + "mm": 3.779527559055118, + "cm": 37.79527559055118, + "m": 3779.527559055118, + "km": 3779527.559055118, + "Q": 0.94488188976378, + "pc": 16.0, + "yd": 3456.0, + "ft": 1152.0, + "": 1.0, # Default px +} + +# allowed unit types, including percentages, relative units, and others +# that are not suitable for direct conversion to a length. +# Note that this is _not_ an exhaustive list of allowed unit types. +UNITS = [ + "in", + "pt", + "px", + "mm", + "cm", + "m", + "km", + "Q", + "pc", + "yd", + "ft", + "", + "%", + "em", + "ex", + "ch", + "rem", + "vw", + "vh", + "vmin", + "vmax", + "deg", + "grad", + "rad", + "turn", + "s", + "ms", + "Hz", + "kHz", + "dpi", + "dpcm", + "dppx", +] + +UNIT_MATCH = re.compile(rf"({'|'.join(UNITS)})") +NUMBER_MATCH = re.compile(r"(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)") +BOTH_MATCH = re.compile(rf"^\s*{NUMBER_MATCH.pattern}\s*{UNIT_MATCH.pattern}\s*$") + + +def parse_unit(value, default_unit="px", default_value=None): + """ + Takes a value such as 55.32px and returns (55.32, 'px') + Returns default (None) if no match can be found + """ + ret = BOTH_MATCH.match(str(value)) + if ret: + return float(ret.groups()[0]), ret.groups()[-1] or default_unit + return (default_value, default_unit) if default_value is not None else None + + +def are_near_relative(point_a, point_b, eps=0.01): + """Return true if the points are near to eps""" + return (point_a - point_b <= point_a * eps) and ( + point_a - point_b >= -point_a * eps + ) + + +def discover_unit(value, viewbox, default="px"): + """Attempt to detect the unit being used based on the viewbox""" + # Default 100px when width can't be parsed + (value, unit) = parse_unit(value, default_value=100.0) + if unit not in CONVERSIONS: + return default + this_factor = CONVERSIONS[unit] * value / viewbox + + # try to find the svgunitfactor in the list of units known. If we don't find + # something, ... + for unit, unit_factor in CONVERSIONS.items(): + if unit != "": + # allow 1% error in factor + if are_near_relative(this_factor, unit_factor, eps=0.01): + return unit + return default + + +def convert_unit(value, to_unit, default="px"): + """Returns userunits given a string representation of units in another system + + Args: + value: string + to_unit: unit to convert to + default: if ``value`` contains no unit, what unit should be assumed. + + .. versionadded:: 1.1 + """ + value, from_unit = parse_unit(value, default_unit=default, default_value=0.0) + if from_unit in CONVERSIONS and to_unit in CONVERSIONS: + return ( + value * CONVERSIONS[from_unit] / CONVERSIONS.get(to_unit, CONVERSIONS["px"]) + ) + return 0.0 + + +def render_unit(value, unit): + """Checks and then renders a number with its unit""" + try: + if isinstance(value, str): + (value, unit) = parse_unit(value, default_unit=unit) + return f"{value:.6g}{ unit:s}" + except TypeError: + return "" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/utils.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/utils.py new file mode 100644 index 0000000..2d1dd94 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/inkex/utils.py @@ -0,0 +1,271 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Basic common utility functions for calculated things +""" +import os +import sys +import random +import re +import math +from argparse import ArgumentTypeError +from itertools import tee + +ABORT_STATUS = -5 + +(X, Y) = range(2) +PY3 = sys.version_info[0] == 3 + +# pylint: disable=line-too-long +# Taken from https://www.w3.org/Graphics/SVG/1.1/paths.html#PathDataBNF +DIGIT_REX_PART = r"[0-9]" +DIGIT_SEQUENCE_REX_PART = rf"(?:{DIGIT_REX_PART}+)" +INTEGER_CONSTANT_REX_PART = DIGIT_SEQUENCE_REX_PART +SIGN_REX_PART = r"[+-]" +EXPONENT_REX_PART = rf"(?:[eE]{SIGN_REX_PART}?{DIGIT_SEQUENCE_REX_PART})" +FRACTIONAL_CONSTANT_REX_PART = rf"(?:{DIGIT_SEQUENCE_REX_PART}?\.{DIGIT_SEQUENCE_REX_PART}|{DIGIT_SEQUENCE_REX_PART}\.)" +FLOATING_POINT_CONSTANT_REX_PART = rf"(?:{FRACTIONAL_CONSTANT_REX_PART}{EXPONENT_REX_PART}?|{DIGIT_SEQUENCE_REX_PART}{EXPONENT_REX_PART})" +NUMBER_REX = re.compile( + rf"(?:{SIGN_REX_PART}?{FLOATING_POINT_CONSTANT_REX_PART}|{SIGN_REX_PART}?{INTEGER_CONSTANT_REX_PART})" +) +# pylint: enable=line-too-long + + +def _pythonpath(): + for pth in os.environ.get("PYTHONPATH", "").split(":"): + if os.path.isdir(pth): + yield pth + + +def get_user_directory(): + """Return the user directory where extensions are stored. + + .. versionadded:: 1.1""" + if "INKSCAPE_PROFILE_DIR" in os.environ: + return os.path.abspath( + os.path.expanduser( + os.path.join(os.environ["INKSCAPE_PROFILE_DIR"], "extensions") + ) + ) + + home = os.path.expanduser("~") + for pth in _pythonpath(): + if pth.startswith(home): + return pth + return None + + +def get_inkscape_directory(): + """Return the system directory where inkscape's core is. + + .. versionadded:: 1.1""" + for pth in _pythonpath(): + if os.path.isdir(os.path.join(pth, "inkex")): + return pth + raise ValueError("Unable to determine the location of Inkscape") + + +class KeyDict(dict): + """ + A normal dictionary, except asking for anything not in the dictionary + always returns the key itself. This is used for translation dictionaries. + """ + + def __getitem__(self, key): + try: + return super().__getitem__(key) + except KeyError: + return key + + +def parse_percent(val: str): + """Parse strings that are either values (i.e., '3.14159') or percentages + (i.e. '75%') to a float. + + .. versionadded:: 1.2""" + val = val.strip() + if val.endswith("%"): + return float(val[:-1]) / 100 + return float(val) + + +def Boolean(value): + """ArgParser function to turn a boolean string into a python boolean""" + if value.upper() == "TRUE": + return True + if value.upper() == "FALSE": + return False + return None + + +def to_bytes(content): + """Ensures the content is bytes + + .. versionadded:: 1.1""" + if isinstance(content, bytes): + return content + return str(content).encode("utf8") + + +def debug(what): + """Print debug message if debugging is switched on""" + errormsg(what) + return what + + +def do_nothing(*args, **kwargs): # pylint: disable=unused-argument + """A blank function to do nothing + + .. versionadded:: 1.1""" + + +def errormsg(msg): + """Intended for end-user-visible error messages. + + (Currently just writes to stderr with an appended newline, but could do + something better in future: e.g. could add markup to distinguish error + messages from status messages or debugging output.) + + Note that this should always be combined with translation:: + + import inkex + ... + inkex.errormsg(_("This extension requires two selected paths.")) + """ + try: + sys.stderr.write(msg) + except TypeError: + sys.stderr.write(str(msg)) + except UnicodeEncodeError: + # Python 2: + # Fallback for cases where sys.stderr.encoding is not Unicode. + # Python 3: + # This will not work as write() does not accept byte strings, but AFAIK + # we should never reach this point as the default error handler is + # 'backslashreplace'. + + # This will be None by default if stderr is piped, so use ASCII as a + # last resort. + encoding = sys.stderr.encoding or "ascii" + sys.stderr.write(msg.encode(encoding, "backslashreplace")) + + # Write '\n' separately to avoid dealing with different string types. + sys.stderr.write("\n") + + +class AbortExtension(Exception): + """Raised to print a message to the user without backtrace""" + + +class DependencyError(NotImplementedError): + """Raised when we need an external python module that isn't available""" + + +class FragmentError(Exception): + """Raised when trying to do rooty things on an xml fragment""" + + +def to(kind): # pylint: disable=invalid-name + """ + Decorator which will turn a generator into a list, tuple or other object type. + """ + + def _inner(call): + def _outer(*args, **kw): + return kind(call(*args, **kw)) + + return _outer + + return _inner + + +def strargs(string, kind=float): + """Returns a list of floats from a string + + .. versionchanged:: 1.1 + also splits at -(minus) signs by adding a space in front of the - sign + + .. versionchanged:: 1.2 + Full support for the `SVG Path data BNF + `_ + """ + return [kind(val) for val in NUMBER_REX.findall(string)] + + +class classproperty: # pylint: disable=invalid-name, too-few-public-methods + """Combine classmethod and property decorators""" + + def __init__(self, func): + self.func = func + + def __get__(self, obj, owner): + return self.func(owner) + + +def filename_arg(name): + """Existing file to read or option used in script arguments""" + filename = os.path.abspath(os.path.expanduser(name)) + if not os.path.isfile(filename): + raise ArgumentTypeError(f"File not found: {name}") + return filename + + +def pairwise(iterable, start=True): + "Iterate over a list with overlapping pairs (see itertools recipes)" + first, then = tee(iterable) + starter = [(None, next(then, None))] + if not start: + starter = [] + return starter + list(zip(first, then)) + + +EVAL_GLOBALS = {} +EVAL_GLOBALS.update(random.__dict__) +EVAL_GLOBALS.update(math.__dict__) + + +def math_eval(function, variable="x"): + """Interpret a function string. All functions from math and random may be used. + + .. versionadded:: 1.1 + + Returns: + a lambda expression if sucessful; otherwise None. + """ + try: + if function != "": + return eval( + f"lambda {variable}: " + (function.strip('"') or "t"), EVAL_GLOBALS, {} + ) + # handle incomplete/invalid function gracefully + except SyntaxError: + pass + return None + + +def is_number(string): + """Checks if a value is a number + + .. versionadded:: 1.2""" + try: + float(string) + return True + except ValueError: + return False diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/README.md b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/README.md new file mode 100644 index 0000000..1f9e9fe --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/README.md @@ -0,0 +1 @@ +This directory is for contents of the site-packages associated with the packaged version of Inkex. \ No newline at end of file diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__init__.py new file mode 100644 index 0000000..fe58162 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__init__.py @@ -0,0 +1,115 @@ +######################## BEGIN LICENSE BLOCK ######################## +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import List, Union + +from .charsetgroupprober import CharSetGroupProber +from .charsetprober import CharSetProber +from .enums import InputState +from .resultdict import ResultDict +from .universaldetector import UniversalDetector +from .version import VERSION, __version__ + +__all__ = ["UniversalDetector", "detect", "detect_all", "__version__", "VERSION"] + + +def detect( + byte_str: Union[bytes, bytearray], should_rename_legacy: bool = False +) -> ResultDict: + """ + Detect the encoding of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + :type should_rename_legacy: ``bool`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError( + f"Expected object of type bytes or bytearray, got: {type(byte_str)}" + ) + byte_str = bytearray(byte_str) + detector = UniversalDetector(should_rename_legacy=should_rename_legacy) + detector.feed(byte_str) + return detector.close() + + +def detect_all( + byte_str: Union[bytes, bytearray], + ignore_threshold: bool = False, + should_rename_legacy: bool = False, +) -> List[ResultDict]: + """ + Detect all the possible encodings of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + :param ignore_threshold: Include encodings that are below + ``UniversalDetector.MINIMUM_THRESHOLD`` + in results. + :type ignore_threshold: ``bool`` + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + :type should_rename_legacy: ``bool`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError( + f"Expected object of type bytes or bytearray, got: {type(byte_str)}" + ) + byte_str = bytearray(byte_str) + + detector = UniversalDetector(should_rename_legacy=should_rename_legacy) + detector.feed(byte_str) + detector.close() + + if detector.input_state == InputState.HIGH_BYTE: + results: List[ResultDict] = [] + probers: List[CharSetProber] = [] + for prober in detector.charset_probers: + if isinstance(prober, CharSetGroupProber): + probers.extend(p for p in prober.probers) + else: + probers.append(prober) + for prober in probers: + if ignore_threshold or prober.get_confidence() > detector.MINIMUM_THRESHOLD: + charset_name = prober.charset_name or "" + lower_charset_name = charset_name.lower() + # Use Windows encoding name instead of ISO-8859 if we saw any + # extra Windows-specific bytes + if lower_charset_name.startswith("iso-8859") and detector.has_win_bytes: + charset_name = detector.ISO_WIN_MAP.get( + lower_charset_name, charset_name + ) + # Rename legacy encodings with superset encodings if asked + if should_rename_legacy: + charset_name = detector.LEGACY_MAP.get( + charset_name.lower(), charset_name + ) + results.append( + { + "encoding": charset_name, + "confidence": prober.get_confidence(), + "language": prober.language, + } + ) + if len(results) > 0: + return sorted(results, key=lambda result: -result["confidence"]) + + return [detector.result] diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__main__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__main__.py new file mode 100644 index 0000000..c19b0d2 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/__main__.py @@ -0,0 +1,6 @@ +"""Wrapper so people can run python -m chardet""" + +from .cli.chardetect import main + +if __name__ == "__main__": + main() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5freq.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5freq.py new file mode 100644 index 0000000..87d9f97 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5freq.py @@ -0,0 +1,386 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Big5 frequency table +# by Taiwan's Mandarin Promotion Council +# +# +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table +BIG5_TABLE_SIZE = 5376 +# fmt: off +BIG5_CHAR_TO_FREQ_ORDER = ( + 1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16 +3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32 +1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 + 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 +3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 +4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 +5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 + 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 + 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 + 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 +2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 +1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 +3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 + 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 +3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 +2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 + 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 +3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 +1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 +5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 + 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 +5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 +1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 + 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 + 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 +3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 +3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 + 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 +2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 +2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 + 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 + 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 +3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 +1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 +1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 +1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 +2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 + 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 +4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 +1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 +5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 +2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 + 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 + 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 + 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 + 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 +5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 + 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 +1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 + 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 + 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 +5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 +1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 + 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 +3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 +4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 +3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 + 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 + 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 +1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 +4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 +3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 +3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 +2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 +5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 +3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 +5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 +1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 +2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 +1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 + 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 +1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 +4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 +3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 + 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 + 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 + 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 +2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 +5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 +1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 +2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 +1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 +1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 +5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 +5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 +5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 +3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 +4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 +4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 +2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 +5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 +3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 + 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 +5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 +5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 +1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 +2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 +3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 +4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 +5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 +3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 +4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 +1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 +1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 +4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 +1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 + 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 +1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 +1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 +3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 + 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 +5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 +2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 +1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 +1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 +5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 + 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 +4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 + 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 +2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 + 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 +1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 +1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 + 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 +4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 +4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 +1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 +3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 +5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 +5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 +1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 +2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 +1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 +3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 +2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 +3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 +2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 +4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 +4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 +3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 + 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 +3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 + 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 +3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 +4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 +3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 +1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 +5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 + 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 +5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 +1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 + 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 +4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 +4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 + 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 +2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 +2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 +3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 +1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 +4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 +2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 +1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 +1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 +2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 +3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 +1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 +5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 +1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 +4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 +1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 + 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 +1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 +4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 +4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 +2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 +1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 +4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 + 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 +5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 +2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 +3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 +4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 + 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 +5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 +5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 +1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 +4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 +4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 +2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 +3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 +3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 +2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 +1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 +4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 +3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 +3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 +2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 +4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 +5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 +3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 +2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 +3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 +1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 +2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 +3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 +4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 +2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 +2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 +5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 +1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 +2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 +1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 +3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 +4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 +2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 +3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 +3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 +2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 +4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 +2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 +3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 +4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 +5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 +3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 + 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 +1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 +4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 +1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 +4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 +5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 + 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 +5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 +5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 +2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 +3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 +2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 +2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 + 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 +1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 +4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 +3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 +3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 + 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 +2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 + 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 +2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 +4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 +1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 +4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 +1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 +3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 + 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 +3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 +5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 +5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 +3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 +3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 +1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 +2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 +5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 +1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 +1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 +3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 + 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 +1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 +4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 +5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 +2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 +3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 + 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 +1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 +2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 +2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 +5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 +5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 +5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 +2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 +2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 +1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 +4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 +3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 +3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 +4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 +4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 +2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 +2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 +5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 +4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 +5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 +4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 + 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 + 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 +1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 +3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 +4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 +1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 +5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 +2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 +2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 +3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 +5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 +1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 +3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 +5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 +1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 +5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 +2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 +3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 +2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 +3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 +3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 +3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 +4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 + 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 +2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 +4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 +3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 +5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 +1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 +5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 + 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 +1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 + 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 +4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 +1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 +4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 +1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 + 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 +3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 +4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 +5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 + 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 +3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 + 890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360 +2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376 +) +# fmt: on diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5prober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5prober.py new file mode 100644 index 0000000..ef09c60 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/big5prober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import Big5DistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import BIG5_SM_MODEL + + +class Big5Prober(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) + self.distribution_analyzer = Big5DistributionAnalysis() + self.reset() + + @property + def charset_name(self) -> str: + return "Big5" + + @property + def language(self) -> str: + return "Chinese" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/chardistribution.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/chardistribution.py new file mode 100644 index 0000000..176cb99 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/chardistribution.py @@ -0,0 +1,261 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Tuple, Union + +from .big5freq import ( + BIG5_CHAR_TO_FREQ_ORDER, + BIG5_TABLE_SIZE, + BIG5_TYPICAL_DISTRIBUTION_RATIO, +) +from .euckrfreq import ( + EUCKR_CHAR_TO_FREQ_ORDER, + EUCKR_TABLE_SIZE, + EUCKR_TYPICAL_DISTRIBUTION_RATIO, +) +from .euctwfreq import ( + EUCTW_CHAR_TO_FREQ_ORDER, + EUCTW_TABLE_SIZE, + EUCTW_TYPICAL_DISTRIBUTION_RATIO, +) +from .gb2312freq import ( + GB2312_CHAR_TO_FREQ_ORDER, + GB2312_TABLE_SIZE, + GB2312_TYPICAL_DISTRIBUTION_RATIO, +) +from .jisfreq import ( + JIS_CHAR_TO_FREQ_ORDER, + JIS_TABLE_SIZE, + JIS_TYPICAL_DISTRIBUTION_RATIO, +) +from .johabfreq import JOHAB_TO_EUCKR_ORDER_TABLE + + +class CharDistributionAnalysis: + ENOUGH_DATA_THRESHOLD = 1024 + SURE_YES = 0.99 + SURE_NO = 0.01 + MINIMUM_DATA_THRESHOLD = 3 + + def __init__(self) -> None: + # Mapping table to get frequency order from char order (get from + # GetOrder()) + self._char_to_freq_order: Tuple[int, ...] = tuple() + self._table_size = 0 # Size of above table + # This is a constant value which varies from language to language, + # used in calculating confidence. See + # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html + # for further detail. + self.typical_distribution_ratio = 0.0 + self._done = False + self._total_chars = 0 + self._freq_chars = 0 + self.reset() + + def reset(self) -> None: + """reset analyser, clear any state""" + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + self._total_chars = 0 # Total characters encountered + # The number of characters whose frequency order is less than 512 + self._freq_chars = 0 + + def feed(self, char: Union[bytes, bytearray], char_len: int) -> None: + """feed a character with known length""" + if char_len == 2: + # we only care about 2-bytes character in our distribution analysis + order = self.get_order(char) + else: + order = -1 + if order >= 0: + self._total_chars += 1 + # order is valid + if order < self._table_size: + if 512 > self._char_to_freq_order[order]: + self._freq_chars += 1 + + def get_confidence(self) -> float: + """return confidence based on existing data""" + # if we didn't receive any character in our consideration range, + # return negative answer + if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: + return self.SURE_NO + + if self._total_chars != self._freq_chars: + r = self._freq_chars / ( + (self._total_chars - self._freq_chars) * self.typical_distribution_ratio + ) + if r < self.SURE_YES: + return r + + # normalize confidence (we don't want to be 100% sure) + return self.SURE_YES + + def got_enough_data(self) -> bool: + # It is not necessary to receive all data to draw conclusion. + # For charset detection, certain amount of data is enough + return self._total_chars > self.ENOUGH_DATA_THRESHOLD + + def get_order(self, _: Union[bytes, bytearray]) -> int: + # We do not handle characters based on the original encoding string, + # but convert this encoding string to a number, here called order. + # This allows multiple encodings of a language to share one frequency + # table. + return -1 + + +class EUCTWDistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = EUCTW_CHAR_TO_FREQ_ORDER + self._table_size = EUCTW_TABLE_SIZE + self.typical_distribution_ratio = EUCTW_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + # for euc-TW encoding, we are interested + # first byte range: 0xc4 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xC4: + return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 + return -1 + + +class EUCKRDistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER + self._table_size = EUCKR_TABLE_SIZE + self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + # for euc-KR encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xB0: + return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 + return -1 + + +class JOHABDistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER + self._table_size = EUCKR_TABLE_SIZE + self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + first_char = byte_str[0] + if 0x88 <= first_char < 0xD4: + code = first_char * 256 + byte_str[1] + return JOHAB_TO_EUCKR_ORDER_TABLE.get(code, -1) + return -1 + + +class GB2312DistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER + self._table_size = GB2312_TABLE_SIZE + self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + # for GB2312 encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if (first_char >= 0xB0) and (second_char >= 0xA1): + return 94 * (first_char - 0xB0) + second_char - 0xA1 + return -1 + + +class Big5DistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER + self._table_size = BIG5_TABLE_SIZE + self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + # for big5 encoding, we are interested + # first byte range: 0xa4 -- 0xfe + # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if first_char >= 0xA4: + if second_char >= 0xA1: + return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 + return 157 * (first_char - 0xA4) + second_char - 0x40 + return -1 + + +class SJISDistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER + self._table_size = JIS_TABLE_SIZE + self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + # for sjis encoding, we are interested + # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe + # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if 0x81 <= first_char <= 0x9F: + order = 188 * (first_char - 0x81) + elif 0xE0 <= first_char <= 0xEF: + order = 188 * (first_char - 0xE0 + 31) + else: + return -1 + order = order + second_char - 0x40 + if second_char > 0x7F: + order = -1 + return order + + +class EUCJPDistributionAnalysis(CharDistributionAnalysis): + def __init__(self) -> None: + super().__init__() + self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER + self._table_size = JIS_TABLE_SIZE + self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str: Union[bytes, bytearray]) -> int: + # for euc-JP encoding, we are interested + # first byte range: 0xa0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + char = byte_str[0] + if char >= 0xA0: + return 94 * (char - 0xA1) + byte_str[1] - 0xA1 + return -1 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetgroupprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetgroupprober.py new file mode 100644 index 0000000..6def56b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetgroupprober.py @@ -0,0 +1,106 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import List, Optional, Union + +from .charsetprober import CharSetProber +from .enums import LanguageFilter, ProbingState + + +class CharSetGroupProber(CharSetProber): + def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None: + super().__init__(lang_filter=lang_filter) + self._active_num = 0 + self.probers: List[CharSetProber] = [] + self._best_guess_prober: Optional[CharSetProber] = None + + def reset(self) -> None: + super().reset() + self._active_num = 0 + for prober in self.probers: + prober.reset() + prober.active = True + self._active_num += 1 + self._best_guess_prober = None + + @property + def charset_name(self) -> Optional[str]: + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.charset_name + + @property + def language(self) -> Optional[str]: + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.language + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + for prober in self.probers: + if not prober.active: + continue + state = prober.feed(byte_str) + if not state: + continue + if state == ProbingState.FOUND_IT: + self._best_guess_prober = prober + self._state = ProbingState.FOUND_IT + return self.state + if state == ProbingState.NOT_ME: + prober.active = False + self._active_num -= 1 + if self._active_num <= 0: + self._state = ProbingState.NOT_ME + return self.state + return self.state + + def get_confidence(self) -> float: + state = self.state + if state == ProbingState.FOUND_IT: + return 0.99 + if state == ProbingState.NOT_ME: + return 0.01 + best_conf = 0.0 + self._best_guess_prober = None + for prober in self.probers: + if not prober.active: + self.logger.debug("%s not active", prober.charset_name) + continue + conf = prober.get_confidence() + self.logger.debug( + "%s %s confidence = %s", prober.charset_name, prober.language, conf + ) + if best_conf < conf: + best_conf = conf + self._best_guess_prober = prober + if not self._best_guess_prober: + return 0.0 + return best_conf diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetprober.py new file mode 100644 index 0000000..a103ca1 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/charsetprober.py @@ -0,0 +1,147 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import logging +import re +from typing import Optional, Union + +from .enums import LanguageFilter, ProbingState + +INTERNATIONAL_WORDS_PATTERN = re.compile( + b"[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?" +) + + +class CharSetProber: + + SHORTCUT_THRESHOLD = 0.95 + + def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None: + self._state = ProbingState.DETECTING + self.active = True + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + + def reset(self) -> None: + self._state = ProbingState.DETECTING + + @property + def charset_name(self) -> Optional[str]: + return None + + @property + def language(self) -> Optional[str]: + raise NotImplementedError + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + raise NotImplementedError + + @property + def state(self) -> ProbingState: + return self._state + + def get_confidence(self) -> float: + return 0.0 + + @staticmethod + def filter_high_byte_only(buf: Union[bytes, bytearray]) -> bytes: + buf = re.sub(b"([\x00-\x7F])+", b" ", buf) + return buf + + @staticmethod + def filter_international_words(buf: Union[bytes, bytearray]) -> bytearray: + """ + We define three types of bytes: + alphabet: english alphabets [a-zA-Z] + international: international characters [\x80-\xFF] + marker: everything else [^a-zA-Z\x80-\xFF] + The input buffer can be thought to contain a series of words delimited + by markers. This function works to filter all words that contain at + least one international character. All contiguous sequences of markers + are replaced by a single space ascii character. + This filter applies to all scripts which do not use English characters. + """ + filtered = bytearray() + + # This regex expression filters out only words that have at-least one + # international character. The word may include one marker character at + # the end. + words = INTERNATIONAL_WORDS_PATTERN.findall(buf) + + for word in words: + filtered.extend(word[:-1]) + + # If the last character in the word is a marker, replace it with a + # space as markers shouldn't affect our analysis (they are used + # similarly across all languages and may thus have similar + # frequencies). + last_char = word[-1:] + if not last_char.isalpha() and last_char < b"\x80": + last_char = b" " + filtered.extend(last_char) + + return filtered + + @staticmethod + def remove_xml_tags(buf: Union[bytes, bytearray]) -> bytes: + """ + Returns a copy of ``buf`` that retains only the sequences of English + alphabet and high byte characters that are not between <> characters. + This filter can be applied to all scripts which contain both English + characters and extended ASCII characters, but is currently only used by + ``Latin1Prober``. + """ + filtered = bytearray() + in_tag = False + prev = 0 + buf = memoryview(buf).cast("c") + + for curr, buf_char in enumerate(buf): + # Check if we're coming out of or entering an XML tag + + # https://github.com/python/typeshed/issues/8182 + if buf_char == b">": # type: ignore[comparison-overlap] + prev = curr + 1 + in_tag = False + # https://github.com/python/typeshed/issues/8182 + elif buf_char == b"<": # type: ignore[comparison-overlap] + if curr > prev and not in_tag: + # Keep everything after last non-extended-ASCII, + # non-alphabetic character + filtered.extend(buf[prev:curr]) + # Output a space to delimit stretch we kept + filtered.extend(b" ") + in_tag = True + + # If we're not in a tag... + if not in_tag: + # Keep everything after last non-extended-ASCII, non-alphabetic + # character + filtered.extend(buf[prev:]) + + return filtered diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/chardetect.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/chardetect.py new file mode 100644 index 0000000..43f6e14 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cli/chardetect.py @@ -0,0 +1,112 @@ +""" +Script which takes one or more file paths and reports on their detected +encodings + +Example:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +If no paths are provided, it takes its input from stdin. + +""" + + +import argparse +import sys +from typing import Iterable, List, Optional + +from .. import __version__ +from ..universaldetector import UniversalDetector + + +def description_of( + lines: Iterable[bytes], + name: str = "stdin", + minimal: bool = False, + should_rename_legacy: bool = False, +) -> Optional[str]: + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + :param should_rename_legacy: Should we rename legacy encodings to + their more modern equivalents? + :type should_rename_legacy: ``bool`` + """ + u = UniversalDetector(should_rename_legacy=should_rename_legacy) + for line in lines: + line = bytearray(line) + u.feed(line) + # shortcut out of the loop to save reading further - particularly useful if we read a BOM. + if u.done: + break + u.close() + result = u.result + if minimal: + return result["encoding"] + if result["encoding"]: + return f'{name}: {result["encoding"]} with confidence {result["confidence"]}' + return f"{name}: no result" + + +def main(argv: Optional[List[str]] = None) -> None: + """ + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + """ + # Get command line arguments + parser = argparse.ArgumentParser( + description=( + "Takes one or more file paths and reports their detected encodings" + ) + ) + parser.add_argument( + "input", + help="File whose encoding we would like to determine. (default: stdin)", + type=argparse.FileType("rb"), + nargs="*", + default=[sys.stdin.buffer], + ) + parser.add_argument( + "--minimal", + help="Print only the encoding to standard output", + action="store_true", + ) + parser.add_argument( + "-l", + "--legacy", + help="Rename legacy encodings to more modern ones.", + action="store_true", + ) + parser.add_argument( + "--version", action="version", version=f"%(prog)s {__version__}" + ) + args = parser.parse_args(argv) + + for f in args.input: + if f.isatty(): + print( + "You are running chardetect interactively. Press " + "CTRL-D twice at the start of a blank line to signal the " + "end of your input. If you want help, run chardetect " + "--help\n", + file=sys.stderr, + ) + print( + description_of( + f, f.name, minimal=args.minimal, should_rename_legacy=args.legacy + ) + ) + + +if __name__ == "__main__": + main() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachine.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachine.py new file mode 100644 index 0000000..8ed4a87 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachine.py @@ -0,0 +1,90 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import logging + +from .codingstatemachinedict import CodingStateMachineDict +from .enums import MachineState + + +class CodingStateMachine: + """ + A state machine to verify a byte sequence for a particular encoding. For + each byte the detector receives, it will feed that byte to every active + state machine available, one byte at a time. The state machine changes its + state based on its previous state and the byte it receives. There are 3 + states in a state machine that are of interest to an auto-detector: + + START state: This is the state to start with, or a legal byte sequence + (i.e. a valid code point) for character has been identified. + + ME state: This indicates that the state machine identified a byte sequence + that is specific to the charset it is designed for and that + there is no other possible encoding which can contain this byte + sequence. This will to lead to an immediate positive answer for + the detector. + + ERROR state: This indicates the state machine identified an illegal byte + sequence for that encoding. This will lead to an immediate + negative answer for this encoding. Detector will exclude this + encoding from consideration from here on. + """ + + def __init__(self, sm: CodingStateMachineDict) -> None: + self._model = sm + self._curr_byte_pos = 0 + self._curr_char_len = 0 + self._curr_state = MachineState.START + self.active = True + self.logger = logging.getLogger(__name__) + self.reset() + + def reset(self) -> None: + self._curr_state = MachineState.START + + def next_state(self, c: int) -> int: + # for each byte we get its class + # if it is first byte, we also get byte length + byte_class = self._model["class_table"][c] + if self._curr_state == MachineState.START: + self._curr_byte_pos = 0 + self._curr_char_len = self._model["char_len_table"][byte_class] + # from byte's class and state_table, we get its next state + curr_state = self._curr_state * self._model["class_factor"] + byte_class + self._curr_state = self._model["state_table"][curr_state] + self._curr_byte_pos += 1 + return self._curr_state + + def get_current_charlen(self) -> int: + return self._curr_char_len + + def get_coding_state_machine(self) -> str: + return self._model["name"] + + @property + def language(self) -> str: + return self._model["language"] diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachinedict.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachinedict.py new file mode 100644 index 0000000..7a3c4c7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/codingstatemachinedict.py @@ -0,0 +1,19 @@ +from typing import TYPE_CHECKING, Tuple + +if TYPE_CHECKING: + # TypedDict was introduced in Python 3.8. + # + # TODO: Remove the else block and TYPE_CHECKING check when dropping support + # for Python 3.7. + from typing import TypedDict + + class CodingStateMachineDict(TypedDict, total=False): + class_table: Tuple[int, ...] + class_factor: int + state_table: Tuple[int, ...] + char_len_table: Tuple[int, ...] + name: str + language: str # Optional key + +else: + CodingStateMachineDict = dict diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cp949prober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cp949prober.py new file mode 100644 index 0000000..fa7307e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/cp949prober.py @@ -0,0 +1,49 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import EUCKRDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import CP949_SM_MODEL + + +class CP949Prober(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(CP949_SM_MODEL) + # NOTE: CP949 is a superset of EUC-KR, so the distribution should be + # not different. + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self) -> str: + return "CP949" + + @property + def language(self) -> str: + return "Korean" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/enums.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/enums.py new file mode 100644 index 0000000..5e3e198 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/enums.py @@ -0,0 +1,85 @@ +""" +All of the Enums that are used throughout the chardet package. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + +from enum import Enum, Flag + + +class InputState: + """ + This enum represents the different states a universal detector can be in. + """ + + PURE_ASCII = 0 + ESC_ASCII = 1 + HIGH_BYTE = 2 + + +class LanguageFilter(Flag): + """ + This enum represents the different language filters we can apply to a + ``UniversalDetector``. + """ + + NONE = 0x00 + CHINESE_SIMPLIFIED = 0x01 + CHINESE_TRADITIONAL = 0x02 + JAPANESE = 0x04 + KOREAN = 0x08 + NON_CJK = 0x10 + ALL = 0x1F + CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL + CJK = CHINESE | JAPANESE | KOREAN + + +class ProbingState(Enum): + """ + This enum represents the different states a prober can be in. + """ + + DETECTING = 0 + FOUND_IT = 1 + NOT_ME = 2 + + +class MachineState: + """ + This enum represents the different states a state machine can be in. + """ + + START = 0 + ERROR = 1 + ITS_ME = 2 + + +class SequenceLikelihood: + """ + This enum represents the likelihood of a character following the previous one. + """ + + NEGATIVE = 0 + UNLIKELY = 1 + LIKELY = 2 + POSITIVE = 3 + + @classmethod + def get_num_categories(cls) -> int: + """:returns: The number of likelihood categories in the enum.""" + return 4 + + +class CharacterCategory: + """ + This enum represents the different categories language models for + ``SingleByteCharsetProber`` put characters into. + + Anything less than CONTROL is considered a letter. + """ + + UNDEFINED = 255 + LINE_BREAK = 254 + SYMBOL = 253 + DIGIT = 252 + CONTROL = 251 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escprober.py new file mode 100644 index 0000000..fd71383 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escprober.py @@ -0,0 +1,102 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Optional, Union + +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .enums import LanguageFilter, MachineState, ProbingState +from .escsm import ( + HZ_SM_MODEL, + ISO2022CN_SM_MODEL, + ISO2022JP_SM_MODEL, + ISO2022KR_SM_MODEL, +) + + +class EscCharSetProber(CharSetProber): + """ + This CharSetProber uses a "code scheme" approach for detecting encodings, + whereby easily recognizable escape or shift sequences are relied on to + identify these encodings. + """ + + def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None: + super().__init__(lang_filter=lang_filter) + self.coding_sm = [] + if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: + self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) + self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) + if self.lang_filter & LanguageFilter.JAPANESE: + self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL)) + if self.lang_filter & LanguageFilter.KOREAN: + self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL)) + self.active_sm_count = 0 + self._detected_charset: Optional[str] = None + self._detected_language: Optional[str] = None + self._state = ProbingState.DETECTING + self.reset() + + def reset(self) -> None: + super().reset() + for coding_sm in self.coding_sm: + coding_sm.active = True + coding_sm.reset() + self.active_sm_count = len(self.coding_sm) + self._detected_charset = None + self._detected_language = None + + @property + def charset_name(self) -> Optional[str]: + return self._detected_charset + + @property + def language(self) -> Optional[str]: + return self._detected_language + + def get_confidence(self) -> float: + return 0.99 if self._detected_charset else 0.00 + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + for c in byte_str: + for coding_sm in self.coding_sm: + if not coding_sm.active: + continue + coding_state = coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + coding_sm.active = False + self.active_sm_count -= 1 + if self.active_sm_count <= 0: + self._state = ProbingState.NOT_ME + return self.state + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + self._detected_charset = coding_sm.get_coding_state_machine() + self._detected_language = coding_sm.language + return self.state + + return self.state diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escsm.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escsm.py new file mode 100644 index 0000000..11d4adf --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/escsm.py @@ -0,0 +1,261 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .codingstatemachinedict import CodingStateMachineDict +from .enums import MachineState + +# fmt: off +HZ_CLS = ( + 1, 0, 0, 0, 0, 0, 0, 0, # 00 - 07 + 0, 0, 0, 0, 0, 0, 0, 0, # 08 - 0f + 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17 + 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f + 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27 + 0, 0, 0, 0, 0, 0, 0, 0, # 28 - 2f + 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37 + 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f + 0, 0, 0, 0, 0, 0, 0, 0, # 40 - 47 + 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f + 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57 + 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f + 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67 + 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f + 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77 + 0, 0, 0, 4, 0, 5, 2, 0, # 78 - 7f + 1, 1, 1, 1, 1, 1, 1, 1, # 80 - 87 + 1, 1, 1, 1, 1, 1, 1, 1, # 88 - 8f + 1, 1, 1, 1, 1, 1, 1, 1, # 90 - 97 + 1, 1, 1, 1, 1, 1, 1, 1, # 98 - 9f + 1, 1, 1, 1, 1, 1, 1, 1, # a0 - a7 + 1, 1, 1, 1, 1, 1, 1, 1, # a8 - af + 1, 1, 1, 1, 1, 1, 1, 1, # b0 - b7 + 1, 1, 1, 1, 1, 1, 1, 1, # b8 - bf + 1, 1, 1, 1, 1, 1, 1, 1, # c0 - c7 + 1, 1, 1, 1, 1, 1, 1, 1, # c8 - cf + 1, 1, 1, 1, 1, 1, 1, 1, # d0 - d7 + 1, 1, 1, 1, 1, 1, 1, 1, # d8 - df + 1, 1, 1, 1, 1, 1, 1, 1, # e0 - e7 + 1, 1, 1, 1, 1, 1, 1, 1, # e8 - ef + 1, 1, 1, 1, 1, 1, 1, 1, # f0 - f7 + 1, 1, 1, 1, 1, 1, 1, 1, # f8 - ff +) + +HZ_ST = ( +MachineState.START, MachineState.ERROR, 3, MachineState.START, MachineState.START, MachineState.START, MachineState.ERROR, MachineState.ERROR, # 00-07 +MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 08-0f +MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.START, MachineState.START, 4, MachineState.ERROR, # 10-17 + 5, MachineState.ERROR, 6, MachineState.ERROR, 5, 5, 4, MachineState.ERROR, # 18-1f + 4, MachineState.ERROR, 4, 4, 4, MachineState.ERROR, 4, MachineState.ERROR, # 20-27 + 4, MachineState.ITS_ME, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 28-2f +) +# fmt: on + +HZ_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) + +HZ_SM_MODEL: CodingStateMachineDict = { + "class_table": HZ_CLS, + "class_factor": 6, + "state_table": HZ_ST, + "char_len_table": HZ_CHAR_LEN_TABLE, + "name": "HZ-GB-2312", + "language": "Chinese", +} + +# fmt: off +ISO2022CN_CLS = ( + 2, 0, 0, 0, 0, 0, 0, 0, # 00 - 07 + 0, 0, 0, 0, 0, 0, 0, 0, # 08 - 0f + 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17 + 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f + 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27 + 0, 3, 0, 0, 0, 0, 0, 0, # 28 - 2f + 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37 + 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f + 0, 0, 0, 4, 0, 0, 0, 0, # 40 - 47 + 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f + 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57 + 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f + 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67 + 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f + 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77 + 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f + 2, 2, 2, 2, 2, 2, 2, 2, # 80 - 87 + 2, 2, 2, 2, 2, 2, 2, 2, # 88 - 8f + 2, 2, 2, 2, 2, 2, 2, 2, # 90 - 97 + 2, 2, 2, 2, 2, 2, 2, 2, # 98 - 9f + 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7 + 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af + 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7 + 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf + 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7 + 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf + 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7 + 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df + 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7 + 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef + 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7 + 2, 2, 2, 2, 2, 2, 2, 2, # f8 - ff +) + +ISO2022CN_ST = ( + MachineState.START, 3, MachineState.ERROR, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 00-07 + MachineState.START, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 08-0f + MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 10-17 + MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 4, MachineState.ERROR, # 18-1f + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 20-27 + 5, 6, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 28-2f + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 30-37 + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.START, # 38-3f +) +# fmt: on + +ISO2022CN_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022CN_SM_MODEL: CodingStateMachineDict = { + "class_table": ISO2022CN_CLS, + "class_factor": 9, + "state_table": ISO2022CN_ST, + "char_len_table": ISO2022CN_CHAR_LEN_TABLE, + "name": "ISO-2022-CN", + "language": "Chinese", +} + +# fmt: off +ISO2022JP_CLS = ( + 2, 0, 0, 0, 0, 0, 0, 0, # 00 - 07 + 0, 0, 0, 0, 0, 0, 2, 2, # 08 - 0f + 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17 + 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f + 0, 0, 0, 0, 7, 0, 0, 0, # 20 - 27 + 3, 0, 0, 0, 0, 0, 0, 0, # 28 - 2f + 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37 + 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f + 6, 0, 4, 0, 8, 0, 0, 0, # 40 - 47 + 0, 9, 5, 0, 0, 0, 0, 0, # 48 - 4f + 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57 + 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f + 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67 + 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f + 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77 + 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f + 2, 2, 2, 2, 2, 2, 2, 2, # 80 - 87 + 2, 2, 2, 2, 2, 2, 2, 2, # 88 - 8f + 2, 2, 2, 2, 2, 2, 2, 2, # 90 - 97 + 2, 2, 2, 2, 2, 2, 2, 2, # 98 - 9f + 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7 + 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af + 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7 + 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf + 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7 + 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf + 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7 + 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df + 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7 + 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef + 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7 + 2, 2, 2, 2, 2, 2, 2, 2, # f8 - ff +) + +ISO2022JP_ST = ( + MachineState.START, 3, MachineState.ERROR, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 00-07 + MachineState.START, MachineState.START, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 08-0f + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 10-17 + MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, # 18-1f + MachineState.ERROR, 5, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 4, MachineState.ERROR, MachineState.ERROR, # 20-27 + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 6, MachineState.ITS_ME, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, # 28-2f + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, # 30-37 + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 38-3f + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.START, MachineState.START, # 40-47 +) +# fmt: on + +ISO2022JP_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022JP_SM_MODEL: CodingStateMachineDict = { + "class_table": ISO2022JP_CLS, + "class_factor": 10, + "state_table": ISO2022JP_ST, + "char_len_table": ISO2022JP_CHAR_LEN_TABLE, + "name": "ISO-2022-JP", + "language": "Japanese", +} + +# fmt: off +ISO2022KR_CLS = ( + 2, 0, 0, 0, 0, 0, 0, 0, # 00 - 07 + 0, 0, 0, 0, 0, 0, 0, 0, # 08 - 0f + 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17 + 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f + 0, 0, 0, 0, 3, 0, 0, 0, # 20 - 27 + 0, 4, 0, 0, 0, 0, 0, 0, # 28 - 2f + 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37 + 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f + 0, 0, 0, 5, 0, 0, 0, 0, # 40 - 47 + 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f + 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57 + 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f + 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67 + 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f + 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77 + 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f + 2, 2, 2, 2, 2, 2, 2, 2, # 80 - 87 + 2, 2, 2, 2, 2, 2, 2, 2, # 88 - 8f + 2, 2, 2, 2, 2, 2, 2, 2, # 90 - 97 + 2, 2, 2, 2, 2, 2, 2, 2, # 98 - 9f + 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7 + 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af + 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7 + 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf + 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7 + 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf + 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7 + 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df + 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7 + 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef + 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7 + 2, 2, 2, 2, 2, 2, 2, 2, # f8 - ff +) + +ISO2022KR_ST = ( + MachineState.START, 3, MachineState.ERROR, MachineState.START, MachineState.START, MachineState.START, MachineState.ERROR, MachineState.ERROR, # 00-07 + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 08-0f + MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 4, MachineState.ERROR, MachineState.ERROR, # 10-17 + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 5, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 18-1f + MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 20-27 +) +# fmt: on + +ISO2022KR_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) + +ISO2022KR_SM_MODEL: CodingStateMachineDict = { + "class_table": ISO2022KR_CLS, + "class_factor": 6, + "state_table": ISO2022KR_ST, + "char_len_table": ISO2022KR_CHAR_LEN_TABLE, + "name": "ISO-2022-KR", + "language": "Korean", +} diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/eucjpprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/eucjpprober.py new file mode 100644 index 0000000..39487f4 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/eucjpprober.py @@ -0,0 +1,102 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Union + +from .chardistribution import EUCJPDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .enums import MachineState, ProbingState +from .jpcntx import EUCJPContextAnalysis +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import EUCJP_SM_MODEL + + +class EUCJPProber(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL) + self.distribution_analyzer = EUCJPDistributionAnalysis() + self.context_analyzer = EUCJPContextAnalysis() + self.reset() + + def reset(self) -> None: + super().reset() + self.context_analyzer.reset() + + @property + def charset_name(self) -> str: + return "EUC-JP" + + @property + def language(self) -> str: + return "Japanese" + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + assert self.coding_sm is not None + assert self.distribution_analyzer is not None + + for i, byte in enumerate(byte_str): + # PY3K: byte_str is a byte array, so byte is an int, not a byte + coding_state = self.coding_sm.next_state(byte) + if coding_state == MachineState.ERROR: + self.logger.debug( + "%s %s prober hit error at byte %s", + self.charset_name, + self.language, + i, + ) + self._state = ProbingState.NOT_ME + break + if coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + if coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte + self.context_analyzer.feed(self._last_char, char_len) + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.context_analyzer.feed(byte_str[i - 1 : i + 1], char_len) + self.distribution_analyzer.feed(byte_str[i - 1 : i + 1], char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if self.context_analyzer.got_enough_data() and ( + self.get_confidence() > self.SHORTCUT_THRESHOLD + ): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self) -> float: + assert self.distribution_analyzer is not None + + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrfreq.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrfreq.py new file mode 100644 index 0000000..7dc3b10 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrfreq.py @@ -0,0 +1,196 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology + +# 128 --> 0.79 +# 256 --> 0.92 +# 512 --> 0.986 +# 1024 --> 0.99944 +# 2048 --> 0.99999 +# +# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 +# Random Distribution Ration = 512 / (2350-512) = 0.279. +# +# Typical Distribution Ratio + +EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 + +EUCKR_TABLE_SIZE = 2352 + +# Char to FreqOrder table , +# fmt: off +EUCKR_CHAR_TO_FREQ_ORDER = ( + 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, +1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, +1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, + 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, + 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, + 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, +1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, + 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, + 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, +1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, +1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, +1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, +1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, +1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, + 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, +1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, +1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, +1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, +1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, + 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, +1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, + 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, + 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, +1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, + 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, +1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, + 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, + 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, +1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, +1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, +1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, +1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, + 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, +1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, + 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, + 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, +1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, +1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, +1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, +1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, +1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, +1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, + 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, + 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, + 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, +1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, + 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, +1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, + 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, + 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, +2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, + 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, + 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, +2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, +2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, +2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, + 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, + 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, +2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, + 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, +1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, +2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, +1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, +2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, +2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, +1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, + 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, +2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, +2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, + 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, + 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, +2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, +1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, +2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, +2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, +2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, +2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, +2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, +2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, +1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, +2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, +2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, +2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, +2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, +2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, +1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, +1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, +2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, +1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, +2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, +1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, + 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, +2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, + 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, +2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, + 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, +2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, +2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, + 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, +2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, +1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, + 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, +1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, +2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, +1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, +2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, + 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, +2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, +1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, +2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, +1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, +2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, +1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, + 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, +2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, +2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, + 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, + 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, +1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, +1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, + 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, +2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, +2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, + 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, + 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, + 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, +2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, + 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, + 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, +2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, +2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, + 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, +2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, +1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, + 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, +2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, +2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, +2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, + 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, + 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, + 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, +2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, +2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, +2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, +1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, +2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042, + 670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256 +) +# fmt: on diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrprober.py new file mode 100644 index 0000000..1fc5de0 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euckrprober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import EUCKRDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import EUCKR_SM_MODEL + + +class EUCKRProber(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self) -> str: + return "EUC-KR" + + @property + def language(self) -> str: + return "Korean" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwfreq.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwfreq.py new file mode 100644 index 0000000..4900ccc --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwfreq.py @@ -0,0 +1,388 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# EUCTW frequency table +# Converted from big5 work +# by Taiwan's Mandarin Promotion Council +# + +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table +EUCTW_TABLE_SIZE = 5376 + +# fmt: off +EUCTW_CHAR_TO_FREQ_ORDER = ( + 1, 1800, 1506, 255, 1431, 198, 9, 82, 6, 7310, 177, 202, 3615, 1256, 2808, 110, # 2742 + 3735, 33, 3241, 261, 76, 44, 2113, 16, 2931, 2184, 1176, 659, 3868, 26, 3404, 2643, # 2758 + 1198, 3869, 3313, 4060, 410, 2211, 302, 590, 361, 1963, 8, 204, 58, 4296, 7311, 1931, # 2774 + 63, 7312, 7313, 317, 1614, 75, 222, 159, 4061, 2412, 1480, 7314, 3500, 3068, 224, 2809, # 2790 + 3616, 3, 10, 3870, 1471, 29, 2774, 1135, 2852, 1939, 873, 130, 3242, 1123, 312, 7315, # 2806 + 4297, 2051, 507, 252, 682, 7316, 142, 1914, 124, 206, 2932, 34, 3501, 3173, 64, 604, # 2822 + 7317, 2494, 1976, 1977, 155, 1990, 645, 641, 1606, 7318, 3405, 337, 72, 406, 7319, 80, # 2838 + 630, 238, 3174, 1509, 263, 939, 1092, 2644, 756, 1440, 1094, 3406, 449, 69, 2969, 591, # 2854 + 179, 2095, 471, 115, 2034, 1843, 60, 50, 2970, 134, 806, 1868, 734, 2035, 3407, 180, # 2870 + 995, 1607, 156, 537, 2893, 688, 7320, 319, 1305, 779, 2144, 514, 2374, 298, 4298, 359, # 2886 + 2495, 90, 2707, 1338, 663, 11, 906, 1099, 2545, 20, 2436, 182, 532, 1716, 7321, 732, # 2902 + 1376, 4062, 1311, 1420, 3175, 25, 2312, 1056, 113, 399, 382, 1949, 242, 3408, 2467, 529, # 2918 + 3243, 475, 1447, 3617, 7322, 117, 21, 656, 810, 1297, 2295, 2329, 3502, 7323, 126, 4063, # 2934 + 706, 456, 150, 613, 4299, 71, 1118, 2036, 4064, 145, 3069, 85, 835, 486, 2114, 1246, # 2950 + 1426, 428, 727, 1285, 1015, 800, 106, 623, 303, 1281, 7324, 2127, 2354, 347, 3736, 221, # 2966 + 3503, 3110, 7325, 1955, 1153, 4065, 83, 296, 1199, 3070, 192, 624, 93, 7326, 822, 1897, # 2982 + 2810, 3111, 795, 2064, 991, 1554, 1542, 1592, 27, 43, 2853, 859, 139, 1456, 860, 4300, # 2998 + 437, 712, 3871, 164, 2392, 3112, 695, 211, 3017, 2096, 195, 3872, 1608, 3504, 3505, 3618, # 3014 + 3873, 234, 811, 2971, 2097, 3874, 2229, 1441, 3506, 1615, 2375, 668, 2076, 1638, 305, 228, # 3030 + 1664, 4301, 467, 415, 7327, 262, 2098, 1593, 239, 108, 300, 200, 1033, 512, 1247, 2077, # 3046 + 7328, 7329, 2173, 3176, 3619, 2673, 593, 845, 1062, 3244, 88, 1723, 2037, 3875, 1950, 212, # 3062 + 266, 152, 149, 468, 1898, 4066, 4302, 77, 187, 7330, 3018, 37, 5, 2972, 7331, 3876, # 3078 + 7332, 7333, 39, 2517, 4303, 2894, 3177, 2078, 55, 148, 74, 4304, 545, 483, 1474, 1029, # 3094 + 1665, 217, 1869, 1531, 3113, 1104, 2645, 4067, 24, 172, 3507, 900, 3877, 3508, 3509, 4305, # 3110 + 32, 1408, 2811, 1312, 329, 487, 2355, 2247, 2708, 784, 2674, 4, 3019, 3314, 1427, 1788, # 3126 + 188, 109, 499, 7334, 3620, 1717, 1789, 888, 1217, 3020, 4306, 7335, 3510, 7336, 3315, 1520, # 3142 + 3621, 3878, 196, 1034, 775, 7337, 7338, 929, 1815, 249, 439, 38, 7339, 1063, 7340, 794, # 3158 + 3879, 1435, 2296, 46, 178, 3245, 2065, 7341, 2376, 7342, 214, 1709, 4307, 804, 35, 707, # 3174 + 324, 3622, 1601, 2546, 140, 459, 4068, 7343, 7344, 1365, 839, 272, 978, 2257, 2572, 3409, # 3190 + 2128, 1363, 3623, 1423, 697, 100, 3071, 48, 70, 1231, 495, 3114, 2193, 7345, 1294, 7346, # 3206 + 2079, 462, 586, 1042, 3246, 853, 256, 988, 185, 2377, 3410, 1698, 434, 1084, 7347, 3411, # 3222 + 314, 2615, 2775, 4308, 2330, 2331, 569, 2280, 637, 1816, 2518, 757, 1162, 1878, 1616, 3412, # 3238 + 287, 1577, 2115, 768, 4309, 1671, 2854, 3511, 2519, 1321, 3737, 909, 2413, 7348, 4069, 933, # 3254 + 3738, 7349, 2052, 2356, 1222, 4310, 765, 2414, 1322, 786, 4311, 7350, 1919, 1462, 1677, 2895, # 3270 + 1699, 7351, 4312, 1424, 2437, 3115, 3624, 2590, 3316, 1774, 1940, 3413, 3880, 4070, 309, 1369, # 3286 + 1130, 2812, 364, 2230, 1653, 1299, 3881, 3512, 3882, 3883, 2646, 525, 1085, 3021, 902, 2000, # 3302 + 1475, 964, 4313, 421, 1844, 1415, 1057, 2281, 940, 1364, 3116, 376, 4314, 4315, 1381, 7, # 3318 + 2520, 983, 2378, 336, 1710, 2675, 1845, 321, 3414, 559, 1131, 3022, 2742, 1808, 1132, 1313, # 3334 + 265, 1481, 1857, 7352, 352, 1203, 2813, 3247, 167, 1089, 420, 2814, 776, 792, 1724, 3513, # 3350 + 4071, 2438, 3248, 7353, 4072, 7354, 446, 229, 333, 2743, 901, 3739, 1200, 1557, 4316, 2647, # 3366 + 1920, 395, 2744, 2676, 3740, 4073, 1835, 125, 916, 3178, 2616, 4317, 7355, 7356, 3741, 7357, # 3382 + 7358, 7359, 4318, 3117, 3625, 1133, 2547, 1757, 3415, 1510, 2313, 1409, 3514, 7360, 2145, 438, # 3398 + 2591, 2896, 2379, 3317, 1068, 958, 3023, 461, 311, 2855, 2677, 4074, 1915, 3179, 4075, 1978, # 3414 + 383, 750, 2745, 2617, 4076, 274, 539, 385, 1278, 1442, 7361, 1154, 1964, 384, 561, 210, # 3430 + 98, 1295, 2548, 3515, 7362, 1711, 2415, 1482, 3416, 3884, 2897, 1257, 129, 7363, 3742, 642, # 3446 + 523, 2776, 2777, 2648, 7364, 141, 2231, 1333, 68, 176, 441, 876, 907, 4077, 603, 2592, # 3462 + 710, 171, 3417, 404, 549, 18, 3118, 2393, 1410, 3626, 1666, 7365, 3516, 4319, 2898, 4320, # 3478 + 7366, 2973, 368, 7367, 146, 366, 99, 871, 3627, 1543, 748, 807, 1586, 1185, 22, 2258, # 3494 + 379, 3743, 3180, 7368, 3181, 505, 1941, 2618, 1991, 1382, 2314, 7369, 380, 2357, 218, 702, # 3510 + 1817, 1248, 3418, 3024, 3517, 3318, 3249, 7370, 2974, 3628, 930, 3250, 3744, 7371, 59, 7372, # 3526 + 585, 601, 4078, 497, 3419, 1112, 1314, 4321, 1801, 7373, 1223, 1472, 2174, 7374, 749, 1836, # 3542 + 690, 1899, 3745, 1772, 3885, 1476, 429, 1043, 1790, 2232, 2116, 917, 4079, 447, 1086, 1629, # 3558 + 7375, 556, 7376, 7377, 2020, 1654, 844, 1090, 105, 550, 966, 1758, 2815, 1008, 1782, 686, # 3574 + 1095, 7378, 2282, 793, 1602, 7379, 3518, 2593, 4322, 4080, 2933, 2297, 4323, 3746, 980, 2496, # 3590 + 544, 353, 527, 4324, 908, 2678, 2899, 7380, 381, 2619, 1942, 1348, 7381, 1341, 1252, 560, # 3606 + 3072, 7382, 3420, 2856, 7383, 2053, 973, 886, 2080, 143, 4325, 7384, 7385, 157, 3886, 496, # 3622 + 4081, 57, 840, 540, 2038, 4326, 4327, 3421, 2117, 1445, 970, 2259, 1748, 1965, 2081, 4082, # 3638 + 3119, 1234, 1775, 3251, 2816, 3629, 773, 1206, 2129, 1066, 2039, 1326, 3887, 1738, 1725, 4083, # 3654 + 279, 3120, 51, 1544, 2594, 423, 1578, 2130, 2066, 173, 4328, 1879, 7386, 7387, 1583, 264, # 3670 + 610, 3630, 4329, 2439, 280, 154, 7388, 7389, 7390, 1739, 338, 1282, 3073, 693, 2857, 1411, # 3686 + 1074, 3747, 2440, 7391, 4330, 7392, 7393, 1240, 952, 2394, 7394, 2900, 1538, 2679, 685, 1483, # 3702 + 4084, 2468, 1436, 953, 4085, 2054, 4331, 671, 2395, 79, 4086, 2441, 3252, 608, 567, 2680, # 3718 + 3422, 4087, 4088, 1691, 393, 1261, 1791, 2396, 7395, 4332, 7396, 7397, 7398, 7399, 1383, 1672, # 3734 + 3748, 3182, 1464, 522, 1119, 661, 1150, 216, 675, 4333, 3888, 1432, 3519, 609, 4334, 2681, # 3750 + 2397, 7400, 7401, 7402, 4089, 3025, 0, 7403, 2469, 315, 231, 2442, 301, 3319, 4335, 2380, # 3766 + 7404, 233, 4090, 3631, 1818, 4336, 4337, 7405, 96, 1776, 1315, 2082, 7406, 257, 7407, 1809, # 3782 + 3632, 2709, 1139, 1819, 4091, 2021, 1124, 2163, 2778, 1777, 2649, 7408, 3074, 363, 1655, 3183, # 3798 + 7409, 2975, 7410, 7411, 7412, 3889, 1567, 3890, 718, 103, 3184, 849, 1443, 341, 3320, 2934, # 3814 + 1484, 7413, 1712, 127, 67, 339, 4092, 2398, 679, 1412, 821, 7414, 7415, 834, 738, 351, # 3830 + 2976, 2146, 846, 235, 1497, 1880, 418, 1992, 3749, 2710, 186, 1100, 2147, 2746, 3520, 1545, # 3846 + 1355, 2935, 2858, 1377, 583, 3891, 4093, 2573, 2977, 7416, 1298, 3633, 1078, 2549, 3634, 2358, # 3862 + 78, 3750, 3751, 267, 1289, 2099, 2001, 1594, 4094, 348, 369, 1274, 2194, 2175, 1837, 4338, # 3878 + 1820, 2817, 3635, 2747, 2283, 2002, 4339, 2936, 2748, 144, 3321, 882, 4340, 3892, 2749, 3423, # 3894 + 4341, 2901, 7417, 4095, 1726, 320, 7418, 3893, 3026, 788, 2978, 7419, 2818, 1773, 1327, 2859, # 3910 + 3894, 2819, 7420, 1306, 4342, 2003, 1700, 3752, 3521, 2359, 2650, 787, 2022, 506, 824, 3636, # 3926 + 534, 323, 4343, 1044, 3322, 2023, 1900, 946, 3424, 7421, 1778, 1500, 1678, 7422, 1881, 4344, # 3942 + 165, 243, 4345, 3637, 2521, 123, 683, 4096, 764, 4346, 36, 3895, 1792, 589, 2902, 816, # 3958 + 626, 1667, 3027, 2233, 1639, 1555, 1622, 3753, 3896, 7423, 3897, 2860, 1370, 1228, 1932, 891, # 3974 + 2083, 2903, 304, 4097, 7424, 292, 2979, 2711, 3522, 691, 2100, 4098, 1115, 4347, 118, 662, # 3990 + 7425, 611, 1156, 854, 2381, 1316, 2861, 2, 386, 515, 2904, 7426, 7427, 3253, 868, 2234, # 4006 + 1486, 855, 2651, 785, 2212, 3028, 7428, 1040, 3185, 3523, 7429, 3121, 448, 7430, 1525, 7431, # 4022 + 2164, 4348, 7432, 3754, 7433, 4099, 2820, 3524, 3122, 503, 818, 3898, 3123, 1568, 814, 676, # 4038 + 1444, 306, 1749, 7434, 3755, 1416, 1030, 197, 1428, 805, 2821, 1501, 4349, 7435, 7436, 7437, # 4054 + 1993, 7438, 4350, 7439, 7440, 2195, 13, 2779, 3638, 2980, 3124, 1229, 1916, 7441, 3756, 2131, # 4070 + 7442, 4100, 4351, 2399, 3525, 7443, 2213, 1511, 1727, 1120, 7444, 7445, 646, 3757, 2443, 307, # 4086 + 7446, 7447, 1595, 3186, 7448, 7449, 7450, 3639, 1113, 1356, 3899, 1465, 2522, 2523, 7451, 519, # 4102 + 7452, 128, 2132, 92, 2284, 1979, 7453, 3900, 1512, 342, 3125, 2196, 7454, 2780, 2214, 1980, # 4118 + 3323, 7455, 290, 1656, 1317, 789, 827, 2360, 7456, 3758, 4352, 562, 581, 3901, 7457, 401, # 4134 + 4353, 2248, 94, 4354, 1399, 2781, 7458, 1463, 2024, 4355, 3187, 1943, 7459, 828, 1105, 4101, # 4150 + 1262, 1394, 7460, 4102, 605, 4356, 7461, 1783, 2862, 7462, 2822, 819, 2101, 578, 2197, 2937, # 4166 + 7463, 1502, 436, 3254, 4103, 3255, 2823, 3902, 2905, 3425, 3426, 7464, 2712, 2315, 7465, 7466, # 4182 + 2332, 2067, 23, 4357, 193, 826, 3759, 2102, 699, 1630, 4104, 3075, 390, 1793, 1064, 3526, # 4198 + 7467, 1579, 3076, 3077, 1400, 7468, 4105, 1838, 1640, 2863, 7469, 4358, 4359, 137, 4106, 598, # 4214 + 3078, 1966, 780, 104, 974, 2938, 7470, 278, 899, 253, 402, 572, 504, 493, 1339, 7471, # 4230 + 3903, 1275, 4360, 2574, 2550, 7472, 3640, 3029, 3079, 2249, 565, 1334, 2713, 863, 41, 7473, # 4246 + 7474, 4361, 7475, 1657, 2333, 19, 463, 2750, 4107, 606, 7476, 2981, 3256, 1087, 2084, 1323, # 4262 + 2652, 2982, 7477, 1631, 1623, 1750, 4108, 2682, 7478, 2864, 791, 2714, 2653, 2334, 232, 2416, # 4278 + 7479, 2983, 1498, 7480, 2654, 2620, 755, 1366, 3641, 3257, 3126, 2025, 1609, 119, 1917, 3427, # 4294 + 862, 1026, 4109, 7481, 3904, 3760, 4362, 3905, 4363, 2260, 1951, 2470, 7482, 1125, 817, 4110, # 4310 + 4111, 3906, 1513, 1766, 2040, 1487, 4112, 3030, 3258, 2824, 3761, 3127, 7483, 7484, 1507, 7485, # 4326 + 2683, 733, 40, 1632, 1106, 2865, 345, 4113, 841, 2524, 230, 4364, 2984, 1846, 3259, 3428, # 4342 + 7486, 1263, 986, 3429, 7487, 735, 879, 254, 1137, 857, 622, 1300, 1180, 1388, 1562, 3907, # 4358 + 3908, 2939, 967, 2751, 2655, 1349, 592, 2133, 1692, 3324, 2985, 1994, 4114, 1679, 3909, 1901, # 4374 + 2185, 7488, 739, 3642, 2715, 1296, 1290, 7489, 4115, 2198, 2199, 1921, 1563, 2595, 2551, 1870, # 4390 + 2752, 2986, 7490, 435, 7491, 343, 1108, 596, 17, 1751, 4365, 2235, 3430, 3643, 7492, 4366, # 4406 + 294, 3527, 2940, 1693, 477, 979, 281, 2041, 3528, 643, 2042, 3644, 2621, 2782, 2261, 1031, # 4422 + 2335, 2134, 2298, 3529, 4367, 367, 1249, 2552, 7493, 3530, 7494, 4368, 1283, 3325, 2004, 240, # 4438 + 1762, 3326, 4369, 4370, 836, 1069, 3128, 474, 7495, 2148, 2525, 268, 3531, 7496, 3188, 1521, # 4454 + 1284, 7497, 1658, 1546, 4116, 7498, 3532, 3533, 7499, 4117, 3327, 2684, 1685, 4118, 961, 1673, # 4470 + 2622, 190, 2005, 2200, 3762, 4371, 4372, 7500, 570, 2497, 3645, 1490, 7501, 4373, 2623, 3260, # 4486 + 1956, 4374, 584, 1514, 396, 1045, 1944, 7502, 4375, 1967, 2444, 7503, 7504, 4376, 3910, 619, # 4502 + 7505, 3129, 3261, 215, 2006, 2783, 2553, 3189, 4377, 3190, 4378, 763, 4119, 3763, 4379, 7506, # 4518 + 7507, 1957, 1767, 2941, 3328, 3646, 1174, 452, 1477, 4380, 3329, 3130, 7508, 2825, 1253, 2382, # 4534 + 2186, 1091, 2285, 4120, 492, 7509, 638, 1169, 1824, 2135, 1752, 3911, 648, 926, 1021, 1324, # 4550 + 4381, 520, 4382, 997, 847, 1007, 892, 4383, 3764, 2262, 1871, 3647, 7510, 2400, 1784, 4384, # 4566 + 1952, 2942, 3080, 3191, 1728, 4121, 2043, 3648, 4385, 2007, 1701, 3131, 1551, 30, 2263, 4122, # 4582 + 7511, 2026, 4386, 3534, 7512, 501, 7513, 4123, 594, 3431, 2165, 1821, 3535, 3432, 3536, 3192, # 4598 + 829, 2826, 4124, 7514, 1680, 3132, 1225, 4125, 7515, 3262, 4387, 4126, 3133, 2336, 7516, 4388, # 4614 + 4127, 7517, 3912, 3913, 7518, 1847, 2383, 2596, 3330, 7519, 4389, 374, 3914, 652, 4128, 4129, # 4630 + 375, 1140, 798, 7520, 7521, 7522, 2361, 4390, 2264, 546, 1659, 138, 3031, 2445, 4391, 7523, # 4646 + 2250, 612, 1848, 910, 796, 3765, 1740, 1371, 825, 3766, 3767, 7524, 2906, 2554, 7525, 692, # 4662 + 444, 3032, 2624, 801, 4392, 4130, 7526, 1491, 244, 1053, 3033, 4131, 4132, 340, 7527, 3915, # 4678 + 1041, 2987, 293, 1168, 87, 1357, 7528, 1539, 959, 7529, 2236, 721, 694, 4133, 3768, 219, # 4694 + 1478, 644, 1417, 3331, 2656, 1413, 1401, 1335, 1389, 3916, 7530, 7531, 2988, 2362, 3134, 1825, # 4710 + 730, 1515, 184, 2827, 66, 4393, 7532, 1660, 2943, 246, 3332, 378, 1457, 226, 3433, 975, # 4726 + 3917, 2944, 1264, 3537, 674, 696, 7533, 163, 7534, 1141, 2417, 2166, 713, 3538, 3333, 4394, # 4742 + 3918, 7535, 7536, 1186, 15, 7537, 1079, 1070, 7538, 1522, 3193, 3539, 276, 1050, 2716, 758, # 4758 + 1126, 653, 2945, 3263, 7539, 2337, 889, 3540, 3919, 3081, 2989, 903, 1250, 4395, 3920, 3434, # 4774 + 3541, 1342, 1681, 1718, 766, 3264, 286, 89, 2946, 3649, 7540, 1713, 7541, 2597, 3334, 2990, # 4790 + 7542, 2947, 2215, 3194, 2866, 7543, 4396, 2498, 2526, 181, 387, 1075, 3921, 731, 2187, 3335, # 4806 + 7544, 3265, 310, 313, 3435, 2299, 770, 4134, 54, 3034, 189, 4397, 3082, 3769, 3922, 7545, # 4822 + 1230, 1617, 1849, 355, 3542, 4135, 4398, 3336, 111, 4136, 3650, 1350, 3135, 3436, 3035, 4137, # 4838 + 2149, 3266, 3543, 7546, 2784, 3923, 3924, 2991, 722, 2008, 7547, 1071, 247, 1207, 2338, 2471, # 4854 + 1378, 4399, 2009, 864, 1437, 1214, 4400, 373, 3770, 1142, 2216, 667, 4401, 442, 2753, 2555, # 4870 + 3771, 3925, 1968, 4138, 3267, 1839, 837, 170, 1107, 934, 1336, 1882, 7548, 7549, 2118, 4139, # 4886 + 2828, 743, 1569, 7550, 4402, 4140, 582, 2384, 1418, 3437, 7551, 1802, 7552, 357, 1395, 1729, # 4902 + 3651, 3268, 2418, 1564, 2237, 7553, 3083, 3772, 1633, 4403, 1114, 2085, 4141, 1532, 7554, 482, # 4918 + 2446, 4404, 7555, 7556, 1492, 833, 1466, 7557, 2717, 3544, 1641, 2829, 7558, 1526, 1272, 3652, # 4934 + 4142, 1686, 1794, 416, 2556, 1902, 1953, 1803, 7559, 3773, 2785, 3774, 1159, 2316, 7560, 2867, # 4950 + 4405, 1610, 1584, 3036, 2419, 2754, 443, 3269, 1163, 3136, 7561, 7562, 3926, 7563, 4143, 2499, # 4966 + 3037, 4406, 3927, 3137, 2103, 1647, 3545, 2010, 1872, 4144, 7564, 4145, 431, 3438, 7565, 250, # 4982 + 97, 81, 4146, 7566, 1648, 1850, 1558, 160, 848, 7567, 866, 740, 1694, 7568, 2201, 2830, # 4998 + 3195, 4147, 4407, 3653, 1687, 950, 2472, 426, 469, 3196, 3654, 3655, 3928, 7569, 7570, 1188, # 5014 + 424, 1995, 861, 3546, 4148, 3775, 2202, 2685, 168, 1235, 3547, 4149, 7571, 2086, 1674, 4408, # 5030 + 3337, 3270, 220, 2557, 1009, 7572, 3776, 670, 2992, 332, 1208, 717, 7573, 7574, 3548, 2447, # 5046 + 3929, 3338, 7575, 513, 7576, 1209, 2868, 3339, 3138, 4409, 1080, 7577, 7578, 7579, 7580, 2527, # 5062 + 3656, 3549, 815, 1587, 3930, 3931, 7581, 3550, 3439, 3777, 1254, 4410, 1328, 3038, 1390, 3932, # 5078 + 1741, 3933, 3778, 3934, 7582, 236, 3779, 2448, 3271, 7583, 7584, 3657, 3780, 1273, 3781, 4411, # 5094 + 7585, 308, 7586, 4412, 245, 4413, 1851, 2473, 1307, 2575, 430, 715, 2136, 2449, 7587, 270, # 5110 + 199, 2869, 3935, 7588, 3551, 2718, 1753, 761, 1754, 725, 1661, 1840, 4414, 3440, 3658, 7589, # 5126 + 7590, 587, 14, 3272, 227, 2598, 326, 480, 2265, 943, 2755, 3552, 291, 650, 1883, 7591, # 5142 + 1702, 1226, 102, 1547, 62, 3441, 904, 4415, 3442, 1164, 4150, 7592, 7593, 1224, 1548, 2756, # 5158 + 391, 498, 1493, 7594, 1386, 1419, 7595, 2055, 1177, 4416, 813, 880, 1081, 2363, 566, 1145, # 5174 + 4417, 2286, 1001, 1035, 2558, 2599, 2238, 394, 1286, 7596, 7597, 2068, 7598, 86, 1494, 1730, # 5190 + 3936, 491, 1588, 745, 897, 2948, 843, 3340, 3937, 2757, 2870, 3273, 1768, 998, 2217, 2069, # 5206 + 397, 1826, 1195, 1969, 3659, 2993, 3341, 284, 7599, 3782, 2500, 2137, 2119, 1903, 7600, 3938, # 5222 + 2150, 3939, 4151, 1036, 3443, 1904, 114, 2559, 4152, 209, 1527, 7601, 7602, 2949, 2831, 2625, # 5238 + 2385, 2719, 3139, 812, 2560, 7603, 3274, 7604, 1559, 737, 1884, 3660, 1210, 885, 28, 2686, # 5254 + 3553, 3783, 7605, 4153, 1004, 1779, 4418, 7606, 346, 1981, 2218, 2687, 4419, 3784, 1742, 797, # 5270 + 1642, 3940, 1933, 1072, 1384, 2151, 896, 3941, 3275, 3661, 3197, 2871, 3554, 7607, 2561, 1958, # 5286 + 4420, 2450, 1785, 7608, 7609, 7610, 3942, 4154, 1005, 1308, 3662, 4155, 2720, 4421, 4422, 1528, # 5302 + 2600, 161, 1178, 4156, 1982, 987, 4423, 1101, 4157, 631, 3943, 1157, 3198, 2420, 1343, 1241, # 5318 + 1016, 2239, 2562, 372, 877, 2339, 2501, 1160, 555, 1934, 911, 3944, 7611, 466, 1170, 169, # 5334 + 1051, 2907, 2688, 3663, 2474, 2994, 1182, 2011, 2563, 1251, 2626, 7612, 992, 2340, 3444, 1540, # 5350 + 2721, 1201, 2070, 2401, 1996, 2475, 7613, 4424, 528, 1922, 2188, 1503, 1873, 1570, 2364, 3342, # 5366 + 3276, 7614, 557, 1073, 7615, 1827, 3445, 2087, 2266, 3140, 3039, 3084, 767, 3085, 2786, 4425, # 5382 + 1006, 4158, 4426, 2341, 1267, 2176, 3664, 3199, 778, 3945, 3200, 2722, 1597, 2657, 7616, 4427, # 5398 + 7617, 3446, 7618, 7619, 7620, 3277, 2689, 1433, 3278, 131, 95, 1504, 3946, 723, 4159, 3141, # 5414 + 1841, 3555, 2758, 2189, 3947, 2027, 2104, 3665, 7621, 2995, 3948, 1218, 7622, 3343, 3201, 3949, # 5430 + 4160, 2576, 248, 1634, 3785, 912, 7623, 2832, 3666, 3040, 3786, 654, 53, 7624, 2996, 7625, # 5446 + 1688, 4428, 777, 3447, 1032, 3950, 1425, 7626, 191, 820, 2120, 2833, 971, 4429, 931, 3202, # 5462 + 135, 664, 783, 3787, 1997, 772, 2908, 1935, 3951, 3788, 4430, 2909, 3203, 282, 2723, 640, # 5478 + 1372, 3448, 1127, 922, 325, 3344, 7627, 7628, 711, 2044, 7629, 7630, 3952, 2219, 2787, 1936, # 5494 + 3953, 3345, 2220, 2251, 3789, 2300, 7631, 4431, 3790, 1258, 3279, 3954, 3204, 2138, 2950, 3955, # 5510 + 3956, 7632, 2221, 258, 3205, 4432, 101, 1227, 7633, 3280, 1755, 7634, 1391, 3281, 7635, 2910, # 5526 + 2056, 893, 7636, 7637, 7638, 1402, 4161, 2342, 7639, 7640, 3206, 3556, 7641, 7642, 878, 1325, # 5542 + 1780, 2788, 4433, 259, 1385, 2577, 744, 1183, 2267, 4434, 7643, 3957, 2502, 7644, 684, 1024, # 5558 + 4162, 7645, 472, 3557, 3449, 1165, 3282, 3958, 3959, 322, 2152, 881, 455, 1695, 1152, 1340, # 5574 + 660, 554, 2153, 4435, 1058, 4436, 4163, 830, 1065, 3346, 3960, 4437, 1923, 7646, 1703, 1918, # 5590 + 7647, 932, 2268, 122, 7648, 4438, 947, 677, 7649, 3791, 2627, 297, 1905, 1924, 2269, 4439, # 5606 + 2317, 3283, 7650, 7651, 4164, 7652, 4165, 84, 4166, 112, 989, 7653, 547, 1059, 3961, 701, # 5622 + 3558, 1019, 7654, 4167, 7655, 3450, 942, 639, 457, 2301, 2451, 993, 2951, 407, 851, 494, # 5638 + 4440, 3347, 927, 7656, 1237, 7657, 2421, 3348, 573, 4168, 680, 921, 2911, 1279, 1874, 285, # 5654 + 790, 1448, 1983, 719, 2167, 7658, 7659, 4441, 3962, 3963, 1649, 7660, 1541, 563, 7661, 1077, # 5670 + 7662, 3349, 3041, 3451, 511, 2997, 3964, 3965, 3667, 3966, 1268, 2564, 3350, 3207, 4442, 4443, # 5686 + 7663, 535, 1048, 1276, 1189, 2912, 2028, 3142, 1438, 1373, 2834, 2952, 1134, 2012, 7664, 4169, # 5702 + 1238, 2578, 3086, 1259, 7665, 700, 7666, 2953, 3143, 3668, 4170, 7667, 4171, 1146, 1875, 1906, # 5718 + 4444, 2601, 3967, 781, 2422, 132, 1589, 203, 147, 273, 2789, 2402, 898, 1786, 2154, 3968, # 5734 + 3969, 7668, 3792, 2790, 7669, 7670, 4445, 4446, 7671, 3208, 7672, 1635, 3793, 965, 7673, 1804, # 5750 + 2690, 1516, 3559, 1121, 1082, 1329, 3284, 3970, 1449, 3794, 65, 1128, 2835, 2913, 2759, 1590, # 5766 + 3795, 7674, 7675, 12, 2658, 45, 976, 2579, 3144, 4447, 517, 2528, 1013, 1037, 3209, 7676, # 5782 + 3796, 2836, 7677, 3797, 7678, 3452, 7679, 2602, 614, 1998, 2318, 3798, 3087, 2724, 2628, 7680, # 5798 + 2580, 4172, 599, 1269, 7681, 1810, 3669, 7682, 2691, 3088, 759, 1060, 489, 1805, 3351, 3285, # 5814 + 1358, 7683, 7684, 2386, 1387, 1215, 2629, 2252, 490, 7685, 7686, 4173, 1759, 2387, 2343, 7687, # 5830 + 4448, 3799, 1907, 3971, 2630, 1806, 3210, 4449, 3453, 3286, 2760, 2344, 874, 7688, 7689, 3454, # 5846 + 3670, 1858, 91, 2914, 3671, 3042, 3800, 4450, 7690, 3145, 3972, 2659, 7691, 3455, 1202, 1403, # 5862 + 3801, 2954, 2529, 1517, 2503, 4451, 3456, 2504, 7692, 4452, 7693, 2692, 1885, 1495, 1731, 3973, # 5878 + 2365, 4453, 7694, 2029, 7695, 7696, 3974, 2693, 1216, 237, 2581, 4174, 2319, 3975, 3802, 4454, # 5894 + 4455, 2694, 3560, 3457, 445, 4456, 7697, 7698, 7699, 7700, 2761, 61, 3976, 3672, 1822, 3977, # 5910 + 7701, 687, 2045, 935, 925, 405, 2660, 703, 1096, 1859, 2725, 4457, 3978, 1876, 1367, 2695, # 5926 + 3352, 918, 2105, 1781, 2476, 334, 3287, 1611, 1093, 4458, 564, 3146, 3458, 3673, 3353, 945, # 5942 + 2631, 2057, 4459, 7702, 1925, 872, 4175, 7703, 3459, 2696, 3089, 349, 4176, 3674, 3979, 4460, # 5958 + 3803, 4177, 3675, 2155, 3980, 4461, 4462, 4178, 4463, 2403, 2046, 782, 3981, 400, 251, 4179, # 5974 + 1624, 7704, 7705, 277, 3676, 299, 1265, 476, 1191, 3804, 2121, 4180, 4181, 1109, 205, 7706, # 5990 + 2582, 1000, 2156, 3561, 1860, 7707, 7708, 7709, 4464, 7710, 4465, 2565, 107, 2477, 2157, 3982, # 6006 + 3460, 3147, 7711, 1533, 541, 1301, 158, 753, 4182, 2872, 3562, 7712, 1696, 370, 1088, 4183, # 6022 + 4466, 3563, 579, 327, 440, 162, 2240, 269, 1937, 1374, 3461, 968, 3043, 56, 1396, 3090, # 6038 + 2106, 3288, 3354, 7713, 1926, 2158, 4467, 2998, 7714, 3564, 7715, 7716, 3677, 4468, 2478, 7717, # 6054 + 2791, 7718, 1650, 4469, 7719, 2603, 7720, 7721, 3983, 2661, 3355, 1149, 3356, 3984, 3805, 3985, # 6070 + 7722, 1076, 49, 7723, 951, 3211, 3289, 3290, 450, 2837, 920, 7724, 1811, 2792, 2366, 4184, # 6086 + 1908, 1138, 2367, 3806, 3462, 7725, 3212, 4470, 1909, 1147, 1518, 2423, 4471, 3807, 7726, 4472, # 6102 + 2388, 2604, 260, 1795, 3213, 7727, 7728, 3808, 3291, 708, 7729, 3565, 1704, 7730, 3566, 1351, # 6118 + 1618, 3357, 2999, 1886, 944, 4185, 3358, 4186, 3044, 3359, 4187, 7731, 3678, 422, 413, 1714, # 6134 + 3292, 500, 2058, 2345, 4188, 2479, 7732, 1344, 1910, 954, 7733, 1668, 7734, 7735, 3986, 2404, # 6150 + 4189, 3567, 3809, 4190, 7736, 2302, 1318, 2505, 3091, 133, 3092, 2873, 4473, 629, 31, 2838, # 6166 + 2697, 3810, 4474, 850, 949, 4475, 3987, 2955, 1732, 2088, 4191, 1496, 1852, 7737, 3988, 620, # 6182 + 3214, 981, 1242, 3679, 3360, 1619, 3680, 1643, 3293, 2139, 2452, 1970, 1719, 3463, 2168, 7738, # 6198 + 3215, 7739, 7740, 3361, 1828, 7741, 1277, 4476, 1565, 2047, 7742, 1636, 3568, 3093, 7743, 869, # 6214 + 2839, 655, 3811, 3812, 3094, 3989, 3000, 3813, 1310, 3569, 4477, 7744, 7745, 7746, 1733, 558, # 6230 + 4478, 3681, 335, 1549, 3045, 1756, 4192, 3682, 1945, 3464, 1829, 1291, 1192, 470, 2726, 2107, # 6246 + 2793, 913, 1054, 3990, 7747, 1027, 7748, 3046, 3991, 4479, 982, 2662, 3362, 3148, 3465, 3216, # 6262 + 3217, 1946, 2794, 7749, 571, 4480, 7750, 1830, 7751, 3570, 2583, 1523, 2424, 7752, 2089, 984, # 6278 + 4481, 3683, 1959, 7753, 3684, 852, 923, 2795, 3466, 3685, 969, 1519, 999, 2048, 2320, 1705, # 6294 + 7754, 3095, 615, 1662, 151, 597, 3992, 2405, 2321, 1049, 275, 4482, 3686, 4193, 568, 3687, # 6310 + 3571, 2480, 4194, 3688, 7755, 2425, 2270, 409, 3218, 7756, 1566, 2874, 3467, 1002, 769, 2840, # 6326 + 194, 2090, 3149, 3689, 2222, 3294, 4195, 628, 1505, 7757, 7758, 1763, 2177, 3001, 3993, 521, # 6342 + 1161, 2584, 1787, 2203, 2406, 4483, 3994, 1625, 4196, 4197, 412, 42, 3096, 464, 7759, 2632, # 6358 + 4484, 3363, 1760, 1571, 2875, 3468, 2530, 1219, 2204, 3814, 2633, 2140, 2368, 4485, 4486, 3295, # 6374 + 1651, 3364, 3572, 7760, 7761, 3573, 2481, 3469, 7762, 3690, 7763, 7764, 2271, 2091, 460, 7765, # 6390 + 4487, 7766, 3002, 962, 588, 3574, 289, 3219, 2634, 1116, 52, 7767, 3047, 1796, 7768, 7769, # 6406 + 7770, 1467, 7771, 1598, 1143, 3691, 4198, 1984, 1734, 1067, 4488, 1280, 3365, 465, 4489, 1572, # 6422 + 510, 7772, 1927, 2241, 1812, 1644, 3575, 7773, 4490, 3692, 7774, 7775, 2663, 1573, 1534, 7776, # 6438 + 7777, 4199, 536, 1807, 1761, 3470, 3815, 3150, 2635, 7778, 7779, 7780, 4491, 3471, 2915, 1911, # 6454 + 2796, 7781, 3296, 1122, 377, 3220, 7782, 360, 7783, 7784, 4200, 1529, 551, 7785, 2059, 3693, # 6470 + 1769, 2426, 7786, 2916, 4201, 3297, 3097, 2322, 2108, 2030, 4492, 1404, 136, 1468, 1479, 672, # 6486 + 1171, 3221, 2303, 271, 3151, 7787, 2762, 7788, 2049, 678, 2727, 865, 1947, 4493, 7789, 2013, # 6502 + 3995, 2956, 7790, 2728, 2223, 1397, 3048, 3694, 4494, 4495, 1735, 2917, 3366, 3576, 7791, 3816, # 6518 + 509, 2841, 2453, 2876, 3817, 7792, 7793, 3152, 3153, 4496, 4202, 2531, 4497, 2304, 1166, 1010, # 6534 + 552, 681, 1887, 7794, 7795, 2957, 2958, 3996, 1287, 1596, 1861, 3154, 358, 453, 736, 175, # 6550 + 478, 1117, 905, 1167, 1097, 7796, 1853, 1530, 7797, 1706, 7798, 2178, 3472, 2287, 3695, 3473, # 6566 + 3577, 4203, 2092, 4204, 7799, 3367, 1193, 2482, 4205, 1458, 2190, 2205, 1862, 1888, 1421, 3298, # 6582 + 2918, 3049, 2179, 3474, 595, 2122, 7800, 3997, 7801, 7802, 4206, 1707, 2636, 223, 3696, 1359, # 6598 + 751, 3098, 183, 3475, 7803, 2797, 3003, 419, 2369, 633, 704, 3818, 2389, 241, 7804, 7805, # 6614 + 7806, 838, 3004, 3697, 2272, 2763, 2454, 3819, 1938, 2050, 3998, 1309, 3099, 2242, 1181, 7807, # 6630 + 1136, 2206, 3820, 2370, 1446, 4207, 2305, 4498, 7808, 7809, 4208, 1055, 2605, 484, 3698, 7810, # 6646 + 3999, 625, 4209, 2273, 3368, 1499, 4210, 4000, 7811, 4001, 4211, 3222, 2274, 2275, 3476, 7812, # 6662 + 7813, 2764, 808, 2606, 3699, 3369, 4002, 4212, 3100, 2532, 526, 3370, 3821, 4213, 955, 7814, # 6678 + 1620, 4214, 2637, 2427, 7815, 1429, 3700, 1669, 1831, 994, 928, 7816, 3578, 1260, 7817, 7818, # 6694 + 7819, 1948, 2288, 741, 2919, 1626, 4215, 2729, 2455, 867, 1184, 362, 3371, 1392, 7820, 7821, # 6710 + 4003, 4216, 1770, 1736, 3223, 2920, 4499, 4500, 1928, 2698, 1459, 1158, 7822, 3050, 3372, 2877, # 6726 + 1292, 1929, 2506, 2842, 3701, 1985, 1187, 2071, 2014, 2607, 4217, 7823, 2566, 2507, 2169, 3702, # 6742 + 2483, 3299, 7824, 3703, 4501, 7825, 7826, 666, 1003, 3005, 1022, 3579, 4218, 7827, 4502, 1813, # 6758 + 2253, 574, 3822, 1603, 295, 1535, 705, 3823, 4219, 283, 858, 417, 7828, 7829, 3224, 4503, # 6774 + 4504, 3051, 1220, 1889, 1046, 2276, 2456, 4004, 1393, 1599, 689, 2567, 388, 4220, 7830, 2484, # 6790 + 802, 7831, 2798, 3824, 2060, 1405, 2254, 7832, 4505, 3825, 2109, 1052, 1345, 3225, 1585, 7833, # 6806 + 809, 7834, 7835, 7836, 575, 2730, 3477, 956, 1552, 1469, 1144, 2323, 7837, 2324, 1560, 2457, # 6822 + 3580, 3226, 4005, 616, 2207, 3155, 2180, 2289, 7838, 1832, 7839, 3478, 4506, 7840, 1319, 3704, # 6838 + 3705, 1211, 3581, 1023, 3227, 1293, 2799, 7841, 7842, 7843, 3826, 607, 2306, 3827, 762, 2878, # 6854 + 1439, 4221, 1360, 7844, 1485, 3052, 7845, 4507, 1038, 4222, 1450, 2061, 2638, 4223, 1379, 4508, # 6870 + 2585, 7846, 7847, 4224, 1352, 1414, 2325, 2921, 1172, 7848, 7849, 3828, 3829, 7850, 1797, 1451, # 6886 + 7851, 7852, 7853, 7854, 2922, 4006, 4007, 2485, 2346, 411, 4008, 4009, 3582, 3300, 3101, 4509, # 6902 + 1561, 2664, 1452, 4010, 1375, 7855, 7856, 47, 2959, 316, 7857, 1406, 1591, 2923, 3156, 7858, # 6918 + 1025, 2141, 3102, 3157, 354, 2731, 884, 2224, 4225, 2407, 508, 3706, 726, 3583, 996, 2428, # 6934 + 3584, 729, 7859, 392, 2191, 1453, 4011, 4510, 3707, 7860, 7861, 2458, 3585, 2608, 1675, 2800, # 6950 + 919, 2347, 2960, 2348, 1270, 4511, 4012, 73, 7862, 7863, 647, 7864, 3228, 2843, 2255, 1550, # 6966 + 1346, 3006, 7865, 1332, 883, 3479, 7866, 7867, 7868, 7869, 3301, 2765, 7870, 1212, 831, 1347, # 6982 + 4226, 4512, 2326, 3830, 1863, 3053, 720, 3831, 4513, 4514, 3832, 7871, 4227, 7872, 7873, 4515, # 6998 + 7874, 7875, 1798, 4516, 3708, 2609, 4517, 3586, 1645, 2371, 7876, 7877, 2924, 669, 2208, 2665, # 7014 + 2429, 7878, 2879, 7879, 7880, 1028, 3229, 7881, 4228, 2408, 7882, 2256, 1353, 7883, 7884, 4518, # 7030 + 3158, 518, 7885, 4013, 7886, 4229, 1960, 7887, 2142, 4230, 7888, 7889, 3007, 2349, 2350, 3833, # 7046 + 516, 1833, 1454, 4014, 2699, 4231, 4519, 2225, 2610, 1971, 1129, 3587, 7890, 2766, 7891, 2961, # 7062 + 1422, 577, 1470, 3008, 1524, 3373, 7892, 7893, 432, 4232, 3054, 3480, 7894, 2586, 1455, 2508, # 7078 + 2226, 1972, 1175, 7895, 1020, 2732, 4015, 3481, 4520, 7896, 2733, 7897, 1743, 1361, 3055, 3482, # 7094 + 2639, 4016, 4233, 4521, 2290, 895, 924, 4234, 2170, 331, 2243, 3056, 166, 1627, 3057, 1098, # 7110 + 7898, 1232, 2880, 2227, 3374, 4522, 657, 403, 1196, 2372, 542, 3709, 3375, 1600, 4235, 3483, # 7126 + 7899, 4523, 2767, 3230, 576, 530, 1362, 7900, 4524, 2533, 2666, 3710, 4017, 7901, 842, 3834, # 7142 + 7902, 2801, 2031, 1014, 4018, 213, 2700, 3376, 665, 621, 4236, 7903, 3711, 2925, 2430, 7904, # 7158 + 2431, 3302, 3588, 3377, 7905, 4237, 2534, 4238, 4525, 3589, 1682, 4239, 3484, 1380, 7906, 724, # 7174 + 2277, 600, 1670, 7907, 1337, 1233, 4526, 3103, 2244, 7908, 1621, 4527, 7909, 651, 4240, 7910, # 7190 + 1612, 4241, 2611, 7911, 2844, 7912, 2734, 2307, 3058, 7913, 716, 2459, 3059, 174, 1255, 2701, # 7206 + 4019, 3590, 548, 1320, 1398, 728, 4020, 1574, 7914, 1890, 1197, 3060, 4021, 7915, 3061, 3062, # 7222 + 3712, 3591, 3713, 747, 7916, 635, 4242, 4528, 7917, 7918, 7919, 4243, 7920, 7921, 4529, 7922, # 7238 + 3378, 4530, 2432, 451, 7923, 3714, 2535, 2072, 4244, 2735, 4245, 4022, 7924, 1764, 4531, 7925, # 7254 + 4246, 350, 7926, 2278, 2390, 2486, 7927, 4247, 4023, 2245, 1434, 4024, 488, 4532, 458, 4248, # 7270 + 4025, 3715, 771, 1330, 2391, 3835, 2568, 3159, 2159, 2409, 1553, 2667, 3160, 4249, 7928, 2487, # 7286 + 2881, 2612, 1720, 2702, 4250, 3379, 4533, 7929, 2536, 4251, 7930, 3231, 4252, 2768, 7931, 2015, # 7302 + 2736, 7932, 1155, 1017, 3716, 3836, 7933, 3303, 2308, 201, 1864, 4253, 1430, 7934, 4026, 7935, # 7318 + 7936, 7937, 7938, 7939, 4254, 1604, 7940, 414, 1865, 371, 2587, 4534, 4535, 3485, 2016, 3104, # 7334 + 4536, 1708, 960, 4255, 887, 389, 2171, 1536, 1663, 1721, 7941, 2228, 4027, 2351, 2926, 1580, # 7350 + 7942, 7943, 7944, 1744, 7945, 2537, 4537, 4538, 7946, 4539, 7947, 2073, 7948, 7949, 3592, 3380, # 7366 + 2882, 4256, 7950, 4257, 2640, 3381, 2802, 673, 2703, 2460, 709, 3486, 4028, 3593, 4258, 7951, # 7382 + 1148, 502, 634, 7952, 7953, 1204, 4540, 3594, 1575, 4541, 2613, 3717, 7954, 3718, 3105, 948, # 7398 + 3232, 121, 1745, 3837, 1110, 7955, 4259, 3063, 2509, 3009, 4029, 3719, 1151, 1771, 3838, 1488, # 7414 + 4030, 1986, 7956, 2433, 3487, 7957, 7958, 2093, 7959, 4260, 3839, 1213, 1407, 2803, 531, 2737, # 7430 + 2538, 3233, 1011, 1537, 7960, 2769, 4261, 3106, 1061, 7961, 3720, 3721, 1866, 2883, 7962, 2017, # 7446 + 120, 4262, 4263, 2062, 3595, 3234, 2309, 3840, 2668, 3382, 1954, 4542, 7963, 7964, 3488, 1047, # 7462 + 2704, 1266, 7965, 1368, 4543, 2845, 649, 3383, 3841, 2539, 2738, 1102, 2846, 2669, 7966, 7967, # 7478 + 1999, 7968, 1111, 3596, 2962, 7969, 2488, 3842, 3597, 2804, 1854, 3384, 3722, 7970, 7971, 3385, # 7494 + 2410, 2884, 3304, 3235, 3598, 7972, 2569, 7973, 3599, 2805, 4031, 1460, 856, 7974, 3600, 7975, # 7510 + 2885, 2963, 7976, 2886, 3843, 7977, 4264, 632, 2510, 875, 3844, 1697, 3845, 2291, 7978, 7979, # 7526 + 4544, 3010, 1239, 580, 4545, 4265, 7980, 914, 936, 2074, 1190, 4032, 1039, 2123, 7981, 7982, # 7542 + 7983, 3386, 1473, 7984, 1354, 4266, 3846, 7985, 2172, 3064, 4033, 915, 3305, 4267, 4268, 3306, # 7558 + 1605, 1834, 7986, 2739, 398, 3601, 4269, 3847, 4034, 328, 1912, 2847, 4035, 3848, 1331, 4270, # 7574 + 3011, 937, 4271, 7987, 3602, 4036, 4037, 3387, 2160, 4546, 3388, 524, 742, 538, 3065, 1012, # 7590 + 7988, 7989, 3849, 2461, 7990, 658, 1103, 225, 3850, 7991, 7992, 4547, 7993, 4548, 7994, 3236, # 7606 + 1243, 7995, 4038, 963, 2246, 4549, 7996, 2705, 3603, 3161, 7997, 7998, 2588, 2327, 7999, 4550, # 7622 + 8000, 8001, 8002, 3489, 3307, 957, 3389, 2540, 2032, 1930, 2927, 2462, 870, 2018, 3604, 1746, # 7638 + 2770, 2771, 2434, 2463, 8003, 3851, 8004, 3723, 3107, 3724, 3490, 3390, 3725, 8005, 1179, 3066, # 7654 + 8006, 3162, 2373, 4272, 3726, 2541, 3163, 3108, 2740, 4039, 8007, 3391, 1556, 2542, 2292, 977, # 7670 + 2887, 2033, 4040, 1205, 3392, 8008, 1765, 3393, 3164, 2124, 1271, 1689, 714, 4551, 3491, 8009, # 7686 + 2328, 3852, 533, 4273, 3605, 2181, 617, 8010, 2464, 3308, 3492, 2310, 8011, 8012, 3165, 8013, # 7702 + 8014, 3853, 1987, 618, 427, 2641, 3493, 3394, 8015, 8016, 1244, 1690, 8017, 2806, 4274, 4552, # 7718 + 8018, 3494, 8019, 8020, 2279, 1576, 473, 3606, 4275, 3395, 972, 8021, 3607, 8022, 3067, 8023, # 7734 + 8024, 4553, 4554, 8025, 3727, 4041, 4042, 8026, 153, 4555, 356, 8027, 1891, 2888, 4276, 2143, # 7750 + 408, 803, 2352, 8028, 3854, 8029, 4277, 1646, 2570, 2511, 4556, 4557, 3855, 8030, 3856, 4278, # 7766 + 8031, 2411, 3396, 752, 8032, 8033, 1961, 2964, 8034, 746, 3012, 2465, 8035, 4279, 3728, 698, # 7782 + 4558, 1892, 4280, 3608, 2543, 4559, 3609, 3857, 8036, 3166, 3397, 8037, 1823, 1302, 4043, 2706, # 7798 + 3858, 1973, 4281, 8038, 4282, 3167, 823, 1303, 1288, 1236, 2848, 3495, 4044, 3398, 774, 3859, # 7814 + 8039, 1581, 4560, 1304, 2849, 3860, 4561, 8040, 2435, 2161, 1083, 3237, 4283, 4045, 4284, 344, # 7830 + 1173, 288, 2311, 454, 1683, 8041, 8042, 1461, 4562, 4046, 2589, 8043, 8044, 4563, 985, 894, # 7846 + 8045, 3399, 3168, 8046, 1913, 2928, 3729, 1988, 8047, 2110, 1974, 8048, 4047, 8049, 2571, 1194, # 7862 + 425, 8050, 4564, 3169, 1245, 3730, 4285, 8051, 8052, 2850, 8053, 636, 4565, 1855, 3861, 760, # 7878 + 1799, 8054, 4286, 2209, 1508, 4566, 4048, 1893, 1684, 2293, 8055, 8056, 8057, 4287, 4288, 2210, # 7894 + 479, 8058, 8059, 832, 8060, 4049, 2489, 8061, 2965, 2490, 3731, 990, 3109, 627, 1814, 2642, # 7910 + 4289, 1582, 4290, 2125, 2111, 3496, 4567, 8062, 799, 4291, 3170, 8063, 4568, 2112, 1737, 3013, # 7926 + 1018, 543, 754, 4292, 3309, 1676, 4569, 4570, 4050, 8064, 1489, 8065, 3497, 8066, 2614, 2889, # 7942 + 4051, 8067, 8068, 2966, 8069, 8070, 8071, 8072, 3171, 4571, 4572, 2182, 1722, 8073, 3238, 3239, # 7958 + 1842, 3610, 1715, 481, 365, 1975, 1856, 8074, 8075, 1962, 2491, 4573, 8076, 2126, 3611, 3240, # 7974 + 433, 1894, 2063, 2075, 8077, 602, 2741, 8078, 8079, 8080, 8081, 8082, 3014, 1628, 3400, 8083, # 7990 + 3172, 4574, 4052, 2890, 4575, 2512, 8084, 2544, 2772, 8085, 8086, 8087, 3310, 4576, 2891, 8088, # 8006 + 4577, 8089, 2851, 4578, 4579, 1221, 2967, 4053, 2513, 8090, 8091, 8092, 1867, 1989, 8093, 8094, # 8022 + 8095, 1895, 8096, 8097, 4580, 1896, 4054, 318, 8098, 2094, 4055, 4293, 8099, 8100, 485, 8101, # 8038 + 938, 3862, 553, 2670, 116, 8102, 3863, 3612, 8103, 3498, 2671, 2773, 3401, 3311, 2807, 8104, # 8054 + 3613, 2929, 4056, 1747, 2930, 2968, 8105, 8106, 207, 8107, 8108, 2672, 4581, 2514, 8109, 3015, # 8070 + 890, 3614, 3864, 8110, 1877, 3732, 3402, 8111, 2183, 2353, 3403, 1652, 8112, 8113, 8114, 941, # 8086 + 2294, 208, 3499, 4057, 2019, 330, 4294, 3865, 2892, 2492, 3733, 4295, 8115, 8116, 8117, 8118, # 8102 +) +# fmt: on diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwprober.py new file mode 100644 index 0000000..a37ab18 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/euctwprober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import EUCTWDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import EUCTW_SM_MODEL + + +class EUCTWProber(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) + self.distribution_analyzer = EUCTWDistributionAnalysis() + self.reset() + + @property + def charset_name(self) -> str: + return "EUC-TW" + + @property + def language(self) -> str: + return "Taiwan" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312freq.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312freq.py new file mode 100644 index 0000000..b32bfc7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312freq.py @@ -0,0 +1,284 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# GB2312 most frequently used character table +# +# Char to FreqOrder table , from hz6763 + +# 512 --> 0.79 -- 0.79 +# 1024 --> 0.92 -- 0.13 +# 2048 --> 0.98 -- 0.06 +# 6768 --> 1.00 -- 0.02 +# +# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 +# Random Distribution Ration = 512 / (3755 - 512) = 0.157 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR + +GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 + +GB2312_TABLE_SIZE = 3760 + +# fmt: off +GB2312_CHAR_TO_FREQ_ORDER = ( +1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205, +2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842, +2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, + 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, +1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, +1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, + 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, +1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, +2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, +3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, + 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, +1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, + 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, +2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, + 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, +2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, +1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, +3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, + 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, +1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, + 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, +2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, +1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, +3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, +1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, +2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, +1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, + 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, +3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, +3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, + 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, +3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, + 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, +1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, +3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, +2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, +1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, + 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, +1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, +4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, + 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, +3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, +3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, + 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, +1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, +2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, +1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, +1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, + 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, +3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, +3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, +4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, + 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, +3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, +1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, +1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, +4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, + 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, + 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, +3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, +1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, + 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, +1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, +2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, + 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, + 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, + 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, +3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, +4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, +3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, + 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, +2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, +2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, +2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, + 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, +2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, + 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, + 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, + 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, +3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, +2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, +2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, +1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, + 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, +2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, + 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, + 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, +1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, +1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, + 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, + 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, +1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, +2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, +3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, +2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, +2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, +2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, +3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, +1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, +1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, +2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, +1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, +3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, +1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, +1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, +3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, + 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, +2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, +1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, +4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, +1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, +1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, +3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, +1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, + 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, + 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, +1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, + 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, +1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, +1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, + 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, +3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, +4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, +3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, +2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, +2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, +1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, +3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, +2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, +1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, +1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, + 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, +2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, +2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, +3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, +4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, +3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, + 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, +3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, +2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, +1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, + 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, + 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, +3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, +4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, +2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, +1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, +1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, + 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, +1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, +3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, + 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, + 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, +1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, + 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, +1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, + 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, +2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, + 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, +2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, +2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, +1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, +1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, +2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, + 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, +1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, +1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, +2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, +2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, +3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, +1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, +4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, + 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, + 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, +3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, +1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, + 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, +3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, +1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, +4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, +1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, +2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, +1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, + 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, +1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, +3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, + 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, +2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, + 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, +1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, +1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, +1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, +3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, +2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, +3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, +3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, +3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, + 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, +2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, + 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, +2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, + 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, +1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, + 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, + 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, +1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, +3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, +3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, +1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, +1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, +3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, +2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, +2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, +1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, +3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, + 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, +4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, +1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, +2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, +3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, +3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, +1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, + 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, + 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, +2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, + 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, +1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, + 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, +1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, +1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, +1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, +1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, +1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, + 381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189, + 852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, #last 512 +) +# fmt: on diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312prober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312prober.py new file mode 100644 index 0000000..d423e73 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/gb2312prober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import GB2312DistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import GB2312_SM_MODEL + + +class GB2312Prober(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) + self.distribution_analyzer = GB2312DistributionAnalysis() + self.reset() + + @property + def charset_name(self) -> str: + return "GB2312" + + @property + def language(self) -> str: + return "Chinese" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/hebrewprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/hebrewprober.py new file mode 100644 index 0000000..785d005 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/hebrewprober.py @@ -0,0 +1,316 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Shy Shalom +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Optional, Union + +from .charsetprober import CharSetProber +from .enums import ProbingState +from .sbcharsetprober import SingleByteCharSetProber + +# This prober doesn't actually recognize a language or a charset. +# It is a helper prober for the use of the Hebrew model probers + +### General ideas of the Hebrew charset recognition ### +# +# Four main charsets exist in Hebrew: +# "ISO-8859-8" - Visual Hebrew +# "windows-1255" - Logical Hebrew +# "ISO-8859-8-I" - Logical Hebrew +# "x-mac-hebrew" - ?? Logical Hebrew ?? +# +# Both "ISO" charsets use a completely identical set of code points, whereas +# "windows-1255" and "x-mac-hebrew" are two different proper supersets of +# these code points. windows-1255 defines additional characters in the range +# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific +# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. +# x-mac-hebrew defines similar additional code points but with a different +# mapping. +# +# As far as an average Hebrew text with no diacritics is concerned, all four +# charsets are identical with respect to code points. Meaning that for the +# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters +# (including final letters). +# +# The dominant difference between these charsets is their directionality. +# "Visual" directionality means that the text is ordered as if the renderer is +# not aware of a BIDI rendering algorithm. The renderer sees the text and +# draws it from left to right. The text itself when ordered naturally is read +# backwards. A buffer of Visual Hebrew generally looks like so: +# "[last word of first line spelled backwards] [whole line ordered backwards +# and spelled backwards] [first word of first line spelled backwards] +# [end of line] [last word of second line] ... etc' " +# adding punctuation marks, numbers and English text to visual text is +# naturally also "visual" and from left to right. +# +# "Logical" directionality means the text is ordered "naturally" according to +# the order it is read. It is the responsibility of the renderer to display +# the text from right to left. A BIDI algorithm is used to place general +# punctuation marks, numbers and English text in the text. +# +# Texts in x-mac-hebrew are almost impossible to find on the Internet. From +# what little evidence I could find, it seems that its general directionality +# is Logical. +# +# To sum up all of the above, the Hebrew probing mechanism knows about two +# charsets: +# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are +# backwards while line order is natural. For charset recognition purposes +# the line order is unimportant (In fact, for this implementation, even +# word order is unimportant). +# Logical Hebrew - "windows-1255" - normal, naturally ordered text. +# +# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be +# specifically identified. +# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew +# that contain special punctuation marks or diacritics is displayed with +# some unconverted characters showing as question marks. This problem might +# be corrected using another model prober for x-mac-hebrew. Due to the fact +# that x-mac-hebrew texts are so rare, writing another model prober isn't +# worth the effort and performance hit. +# +#### The Prober #### +# +# The prober is divided between two SBCharSetProbers and a HebrewProber, +# all of which are managed, created, fed data, inquired and deleted by the +# SBCSGroupProber. The two SBCharSetProbers identify that the text is in +# fact some kind of Hebrew, Logical or Visual. The final decision about which +# one is it is made by the HebrewProber by combining final-letter scores +# with the scores of the two SBCharSetProbers to produce a final answer. +# +# The SBCSGroupProber is responsible for stripping the original text of HTML +# tags, English characters, numbers, low-ASCII punctuation characters, spaces +# and new lines. It reduces any sequence of such characters to a single space. +# The buffer fed to each prober in the SBCS group prober is pure text in +# high-ASCII. +# The two SBCharSetProbers (model probers) share the same language model: +# Win1255Model. +# The first SBCharSetProber uses the model normally as any other +# SBCharSetProber does, to recognize windows-1255, upon which this model was +# built. The second SBCharSetProber is told to make the pair-of-letter +# lookup in the language model backwards. This in practice exactly simulates +# a visual Hebrew model using the windows-1255 logical Hebrew model. +# +# The HebrewProber is not using any language model. All it does is look for +# final-letter evidence suggesting the text is either logical Hebrew or visual +# Hebrew. Disjointed from the model probers, the results of the HebrewProber +# alone are meaningless. HebrewProber always returns 0.00 as confidence +# since it never identifies a charset by itself. Instead, the pointer to the +# HebrewProber is passed to the model probers as a helper "Name Prober". +# When the Group prober receives a positive identification from any prober, +# it asks for the name of the charset identified. If the prober queried is a +# Hebrew model prober, the model prober forwards the call to the +# HebrewProber to make the final decision. In the HebrewProber, the +# decision is made according to the final-letters scores maintained and Both +# model probers scores. The answer is returned in the form of the name of the +# charset identified, either "windows-1255" or "ISO-8859-8". + + +class HebrewProber(CharSetProber): + SPACE = 0x20 + # windows-1255 / ISO-8859-8 code points of interest + FINAL_KAF = 0xEA + NORMAL_KAF = 0xEB + FINAL_MEM = 0xED + NORMAL_MEM = 0xEE + FINAL_NUN = 0xEF + NORMAL_NUN = 0xF0 + FINAL_PE = 0xF3 + NORMAL_PE = 0xF4 + FINAL_TSADI = 0xF5 + NORMAL_TSADI = 0xF6 + + # Minimum Visual vs Logical final letter score difference. + # If the difference is below this, don't rely solely on the final letter score + # distance. + MIN_FINAL_CHAR_DISTANCE = 5 + + # Minimum Visual vs Logical model score difference. + # If the difference is below this, don't rely at all on the model score + # distance. + MIN_MODEL_DISTANCE = 0.01 + + VISUAL_HEBREW_NAME = "ISO-8859-8" + LOGICAL_HEBREW_NAME = "windows-1255" + + def __init__(self) -> None: + super().__init__() + self._final_char_logical_score = 0 + self._final_char_visual_score = 0 + self._prev = self.SPACE + self._before_prev = self.SPACE + self._logical_prober: Optional[SingleByteCharSetProber] = None + self._visual_prober: Optional[SingleByteCharSetProber] = None + self.reset() + + def reset(self) -> None: + self._final_char_logical_score = 0 + self._final_char_visual_score = 0 + # The two last characters seen in the previous buffer, + # mPrev and mBeforePrev are initialized to space in order to simulate + # a word delimiter at the beginning of the data + self._prev = self.SPACE + self._before_prev = self.SPACE + # These probers are owned by the group prober. + + def set_model_probers( + self, + logical_prober: SingleByteCharSetProber, + visual_prober: SingleByteCharSetProber, + ) -> None: + self._logical_prober = logical_prober + self._visual_prober = visual_prober + + def is_final(self, c: int) -> bool: + return c in [ + self.FINAL_KAF, + self.FINAL_MEM, + self.FINAL_NUN, + self.FINAL_PE, + self.FINAL_TSADI, + ] + + def is_non_final(self, c: int) -> bool: + # The normal Tsadi is not a good Non-Final letter due to words like + # 'lechotet' (to chat) containing an apostrophe after the tsadi. This + # apostrophe is converted to a space in FilterWithoutEnglishLetters + # causing the Non-Final tsadi to appear at an end of a word even + # though this is not the case in the original text. + # The letters Pe and Kaf rarely display a related behavior of not being + # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' + # for example legally end with a Non-Final Pe or Kaf. However, the + # benefit of these letters as Non-Final letters outweighs the damage + # since these words are quite rare. + return c in [self.NORMAL_KAF, self.NORMAL_MEM, self.NORMAL_NUN, self.NORMAL_PE] + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + # Final letter analysis for logical-visual decision. + # Look for evidence that the received buffer is either logical Hebrew + # or visual Hebrew. + # The following cases are checked: + # 1) A word longer than 1 letter, ending with a final letter. This is + # an indication that the text is laid out "naturally" since the + # final letter really appears at the end. +1 for logical score. + # 2) A word longer than 1 letter, ending with a Non-Final letter. In + # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, + # should not end with the Non-Final form of that letter. Exceptions + # to this rule are mentioned above in isNonFinal(). This is an + # indication that the text is laid out backwards. +1 for visual + # score + # 3) A word longer than 1 letter, starting with a final letter. Final + # letters should not appear at the beginning of a word. This is an + # indication that the text is laid out backwards. +1 for visual + # score. + # + # The visual score and logical score are accumulated throughout the + # text and are finally checked against each other in GetCharSetName(). + # No checking for final letters in the middle of words is done since + # that case is not an indication for either Logical or Visual text. + # + # We automatically filter out all 7-bit characters (replace them with + # spaces) so the word boundary detection works properly. [MAP] + + if self.state == ProbingState.NOT_ME: + # Both model probers say it's not them. No reason to continue. + return ProbingState.NOT_ME + + byte_str = self.filter_high_byte_only(byte_str) + + for cur in byte_str: + if cur == self.SPACE: + # We stand on a space - a word just ended + if self._before_prev != self.SPACE: + # next-to-last char was not a space so self._prev is not a + # 1 letter word + if self.is_final(self._prev): + # case (1) [-2:not space][-1:final letter][cur:space] + self._final_char_logical_score += 1 + elif self.is_non_final(self._prev): + # case (2) [-2:not space][-1:Non-Final letter][ + # cur:space] + self._final_char_visual_score += 1 + else: + # Not standing on a space + if ( + (self._before_prev == self.SPACE) + and (self.is_final(self._prev)) + and (cur != self.SPACE) + ): + # case (3) [-2:space][-1:final letter][cur:not space] + self._final_char_visual_score += 1 + self._before_prev = self._prev + self._prev = cur + + # Forever detecting, till the end or until both model probers return + # ProbingState.NOT_ME (handled above) + return ProbingState.DETECTING + + @property + def charset_name(self) -> str: + assert self._logical_prober is not None + assert self._visual_prober is not None + + # Make the decision: is it Logical or Visual? + # If the final letter score distance is dominant enough, rely on it. + finalsub = self._final_char_logical_score - self._final_char_visual_score + if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # It's not dominant enough, try to rely on the model scores instead. + modelsub = ( + self._logical_prober.get_confidence() - self._visual_prober.get_confidence() + ) + if modelsub > self.MIN_MODEL_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if modelsub < -self.MIN_MODEL_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # Still no good, back to final letter distance, maybe it'll save the + # day. + if finalsub < 0.0: + return self.VISUAL_HEBREW_NAME + + # (finalsub > 0 - Logical) or (don't know what to do) default to + # Logical. + return self.LOGICAL_HEBREW_NAME + + @property + def language(self) -> str: + return "Hebrew" + + @property + def state(self) -> ProbingState: + assert self._logical_prober is not None + assert self._visual_prober is not None + + # Remain active as long as any of the model probers are active. + if (self._logical_prober.state == ProbingState.NOT_ME) and ( + self._visual_prober.state == ProbingState.NOT_ME + ): + return ProbingState.NOT_ME + return ProbingState.DETECTING diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jisfreq.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jisfreq.py new file mode 100644 index 0000000..3293576 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jisfreq.py @@ -0,0 +1,325 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology +# +# Japanese frequency table, applied to both S-JIS and EUC-JP +# They are sorted in order. + +# 128 --> 0.77094 +# 256 --> 0.85710 +# 512 --> 0.92635 +# 1024 --> 0.97130 +# 2048 --> 0.99431 +# +# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 +# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 +# +# Typical Distribution Ratio, 25% of IDR + +JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 + +# Char to FreqOrder table , +JIS_TABLE_SIZE = 4368 + +# fmt: off +JIS_CHAR_TO_FREQ_ORDER = ( + 40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16 +3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32 +1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 +2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 +2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 +5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 +1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 +5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 +5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 +5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 +5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 +5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 +5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 +1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 +1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 +1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 +2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 +3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 +3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 + 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 + 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 +1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 + 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 +5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 + 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 + 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 + 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 + 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 + 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 +5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 +5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 +5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 +4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 +5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 +5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 +5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 +5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 +5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 +5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 +5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 +5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 +5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 +3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 +5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 +5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 +5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 +5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 +5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 +5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 +5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 +5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 +5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 +5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 +5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 +5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 +5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 +5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 +5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 +5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 +5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 +5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 +5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 +5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 +5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 +5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 +5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 +5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 +5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 +5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 +5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 +5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 +5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 +5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 +5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 +5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 +5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 +5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 +5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 +5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 +6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 +6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 +6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 +6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 +6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 +6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 +6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 +6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 +4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 + 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 + 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 +1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 +1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 + 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 +3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 +3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 + 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 +3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 +3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 + 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 +2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 + 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 +3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 +1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 + 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 +1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 + 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 +2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 +2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 +2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 +2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 +1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 +1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 +1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 +1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 +2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 +1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 +2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 +1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 +1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 +1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 +1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 +1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 +1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 + 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 + 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 +1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 +2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 +2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 +2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 +3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 +3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 + 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 +3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 +1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 + 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 +2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 +1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 + 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 +3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 +4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 +2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 +1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 +2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 +1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 + 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 + 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 +1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 +2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 +2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 +2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 +3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 +1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 +2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 + 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 + 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 + 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 +1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 +2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 + 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 +1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 +1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 + 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 +1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 +1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 +1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 + 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 +2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 + 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 +2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 +3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 +2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 +1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 +6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 +1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 +2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 +1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 + 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 + 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 +3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 +3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 +1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 +1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 +1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 +1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 + 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 + 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 +2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 + 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 +3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 +2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 + 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 +1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 +2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 + 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 +1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 + 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 +4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 +2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 +1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 + 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 +1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 +2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 + 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 +6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 +1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 +1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 +2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 +3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 + 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 +3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 +1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 + 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 +1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 + 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 +3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 + 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 +2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 + 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 +4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 +2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 +1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 +1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 +1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 + 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 +1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 +3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 +1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 +3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 + 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 + 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 + 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 +2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 +1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 + 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 +1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 + 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 +1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 + 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 + 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 + 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 +1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 +1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 +2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 +4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 + 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 +1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 + 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 +1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 +3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 +1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 +2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 +2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 +1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 +1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 +2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 + 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 +2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 +1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 +1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 +1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 +1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 +3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 +2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 +2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 + 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 +3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 +3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 +1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 +2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 +1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352 +2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512 +) +# fmt: on diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabfreq.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabfreq.py new file mode 100644 index 0000000..c129699 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabfreq.py @@ -0,0 +1,2382 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# The frequency data itself is the same as euc-kr. +# This is just a mapping table to euc-kr. + +JOHAB_TO_EUCKR_ORDER_TABLE = { + 0x8861: 0, + 0x8862: 1, + 0x8865: 2, + 0x8868: 3, + 0x8869: 4, + 0x886A: 5, + 0x886B: 6, + 0x8871: 7, + 0x8873: 8, + 0x8874: 9, + 0x8875: 10, + 0x8876: 11, + 0x8877: 12, + 0x8878: 13, + 0x8879: 14, + 0x887B: 15, + 0x887C: 16, + 0x887D: 17, + 0x8881: 18, + 0x8882: 19, + 0x8885: 20, + 0x8889: 21, + 0x8891: 22, + 0x8893: 23, + 0x8895: 24, + 0x8896: 25, + 0x8897: 26, + 0x88A1: 27, + 0x88A2: 28, + 0x88A5: 29, + 0x88A9: 30, + 0x88B5: 31, + 0x88B7: 32, + 0x88C1: 33, + 0x88C5: 34, + 0x88C9: 35, + 0x88E1: 36, + 0x88E2: 37, + 0x88E5: 38, + 0x88E8: 39, + 0x88E9: 40, + 0x88EB: 41, + 0x88F1: 42, + 0x88F3: 43, + 0x88F5: 44, + 0x88F6: 45, + 0x88F7: 46, + 0x88F8: 47, + 0x88FB: 48, + 0x88FC: 49, + 0x88FD: 50, + 0x8941: 51, + 0x8945: 52, + 0x8949: 53, + 0x8951: 54, + 0x8953: 55, + 0x8955: 56, + 0x8956: 57, + 0x8957: 58, + 0x8961: 59, + 0x8962: 60, + 0x8963: 61, + 0x8965: 62, + 0x8968: 63, + 0x8969: 64, + 0x8971: 65, + 0x8973: 66, + 0x8975: 67, + 0x8976: 68, + 0x8977: 69, + 0x897B: 70, + 0x8981: 71, + 0x8985: 72, + 0x8989: 73, + 0x8993: 74, + 0x8995: 75, + 0x89A1: 76, + 0x89A2: 77, + 0x89A5: 78, + 0x89A8: 79, + 0x89A9: 80, + 0x89AB: 81, + 0x89AD: 82, + 0x89B0: 83, + 0x89B1: 84, + 0x89B3: 85, + 0x89B5: 86, + 0x89B7: 87, + 0x89B8: 88, + 0x89C1: 89, + 0x89C2: 90, + 0x89C5: 91, + 0x89C9: 92, + 0x89CB: 93, + 0x89D1: 94, + 0x89D3: 95, + 0x89D5: 96, + 0x89D7: 97, + 0x89E1: 98, + 0x89E5: 99, + 0x89E9: 100, + 0x89F3: 101, + 0x89F6: 102, + 0x89F7: 103, + 0x8A41: 104, + 0x8A42: 105, + 0x8A45: 106, + 0x8A49: 107, + 0x8A51: 108, + 0x8A53: 109, + 0x8A55: 110, + 0x8A57: 111, + 0x8A61: 112, + 0x8A65: 113, + 0x8A69: 114, + 0x8A73: 115, + 0x8A75: 116, + 0x8A81: 117, + 0x8A82: 118, + 0x8A85: 119, + 0x8A88: 120, + 0x8A89: 121, + 0x8A8A: 122, + 0x8A8B: 123, + 0x8A90: 124, + 0x8A91: 125, + 0x8A93: 126, + 0x8A95: 127, + 0x8A97: 128, + 0x8A98: 129, + 0x8AA1: 130, + 0x8AA2: 131, + 0x8AA5: 132, + 0x8AA9: 133, + 0x8AB6: 134, + 0x8AB7: 135, + 0x8AC1: 136, + 0x8AD5: 137, + 0x8AE1: 138, + 0x8AE2: 139, + 0x8AE5: 140, + 0x8AE9: 141, + 0x8AF1: 142, + 0x8AF3: 143, + 0x8AF5: 144, + 0x8B41: 145, + 0x8B45: 146, + 0x8B49: 147, + 0x8B61: 148, + 0x8B62: 149, + 0x8B65: 150, + 0x8B68: 151, + 0x8B69: 152, + 0x8B6A: 153, + 0x8B71: 154, + 0x8B73: 155, + 0x8B75: 156, + 0x8B77: 157, + 0x8B81: 158, + 0x8BA1: 159, + 0x8BA2: 160, + 0x8BA5: 161, + 0x8BA8: 162, + 0x8BA9: 163, + 0x8BAB: 164, + 0x8BB1: 165, + 0x8BB3: 166, + 0x8BB5: 167, + 0x8BB7: 168, + 0x8BB8: 169, + 0x8BBC: 170, + 0x8C61: 171, + 0x8C62: 172, + 0x8C63: 173, + 0x8C65: 174, + 0x8C69: 175, + 0x8C6B: 176, + 0x8C71: 177, + 0x8C73: 178, + 0x8C75: 179, + 0x8C76: 180, + 0x8C77: 181, + 0x8C7B: 182, + 0x8C81: 183, + 0x8C82: 184, + 0x8C85: 185, + 0x8C89: 186, + 0x8C91: 187, + 0x8C93: 188, + 0x8C95: 189, + 0x8C96: 190, + 0x8C97: 191, + 0x8CA1: 192, + 0x8CA2: 193, + 0x8CA9: 194, + 0x8CE1: 195, + 0x8CE2: 196, + 0x8CE3: 197, + 0x8CE5: 198, + 0x8CE9: 199, + 0x8CF1: 200, + 0x8CF3: 201, + 0x8CF5: 202, + 0x8CF6: 203, + 0x8CF7: 204, + 0x8D41: 205, + 0x8D42: 206, + 0x8D45: 207, + 0x8D51: 208, + 0x8D55: 209, + 0x8D57: 210, + 0x8D61: 211, + 0x8D65: 212, + 0x8D69: 213, + 0x8D75: 214, + 0x8D76: 215, + 0x8D7B: 216, + 0x8D81: 217, + 0x8DA1: 218, + 0x8DA2: 219, + 0x8DA5: 220, + 0x8DA7: 221, + 0x8DA9: 222, + 0x8DB1: 223, + 0x8DB3: 224, + 0x8DB5: 225, + 0x8DB7: 226, + 0x8DB8: 227, + 0x8DB9: 228, + 0x8DC1: 229, + 0x8DC2: 230, + 0x8DC9: 231, + 0x8DD6: 232, + 0x8DD7: 233, + 0x8DE1: 234, + 0x8DE2: 235, + 0x8DF7: 236, + 0x8E41: 237, + 0x8E45: 238, + 0x8E49: 239, + 0x8E51: 240, + 0x8E53: 241, + 0x8E57: 242, + 0x8E61: 243, + 0x8E81: 244, + 0x8E82: 245, + 0x8E85: 246, + 0x8E89: 247, + 0x8E90: 248, + 0x8E91: 249, + 0x8E93: 250, + 0x8E95: 251, + 0x8E97: 252, + 0x8E98: 253, + 0x8EA1: 254, + 0x8EA9: 255, + 0x8EB6: 256, + 0x8EB7: 257, + 0x8EC1: 258, + 0x8EC2: 259, + 0x8EC5: 260, + 0x8EC9: 261, + 0x8ED1: 262, + 0x8ED3: 263, + 0x8ED6: 264, + 0x8EE1: 265, + 0x8EE5: 266, + 0x8EE9: 267, + 0x8EF1: 268, + 0x8EF3: 269, + 0x8F41: 270, + 0x8F61: 271, + 0x8F62: 272, + 0x8F65: 273, + 0x8F67: 274, + 0x8F69: 275, + 0x8F6B: 276, + 0x8F70: 277, + 0x8F71: 278, + 0x8F73: 279, + 0x8F75: 280, + 0x8F77: 281, + 0x8F7B: 282, + 0x8FA1: 283, + 0x8FA2: 284, + 0x8FA5: 285, + 0x8FA9: 286, + 0x8FB1: 287, + 0x8FB3: 288, + 0x8FB5: 289, + 0x8FB7: 290, + 0x9061: 291, + 0x9062: 292, + 0x9063: 293, + 0x9065: 294, + 0x9068: 295, + 0x9069: 296, + 0x906A: 297, + 0x906B: 298, + 0x9071: 299, + 0x9073: 300, + 0x9075: 301, + 0x9076: 302, + 0x9077: 303, + 0x9078: 304, + 0x9079: 305, + 0x907B: 306, + 0x907D: 307, + 0x9081: 308, + 0x9082: 309, + 0x9085: 310, + 0x9089: 311, + 0x9091: 312, + 0x9093: 313, + 0x9095: 314, + 0x9096: 315, + 0x9097: 316, + 0x90A1: 317, + 0x90A2: 318, + 0x90A5: 319, + 0x90A9: 320, + 0x90B1: 321, + 0x90B7: 322, + 0x90E1: 323, + 0x90E2: 324, + 0x90E4: 325, + 0x90E5: 326, + 0x90E9: 327, + 0x90EB: 328, + 0x90EC: 329, + 0x90F1: 330, + 0x90F3: 331, + 0x90F5: 332, + 0x90F6: 333, + 0x90F7: 334, + 0x90FD: 335, + 0x9141: 336, + 0x9142: 337, + 0x9145: 338, + 0x9149: 339, + 0x9151: 340, + 0x9153: 341, + 0x9155: 342, + 0x9156: 343, + 0x9157: 344, + 0x9161: 345, + 0x9162: 346, + 0x9165: 347, + 0x9169: 348, + 0x9171: 349, + 0x9173: 350, + 0x9176: 351, + 0x9177: 352, + 0x917A: 353, + 0x9181: 354, + 0x9185: 355, + 0x91A1: 356, + 0x91A2: 357, + 0x91A5: 358, + 0x91A9: 359, + 0x91AB: 360, + 0x91B1: 361, + 0x91B3: 362, + 0x91B5: 363, + 0x91B7: 364, + 0x91BC: 365, + 0x91BD: 366, + 0x91C1: 367, + 0x91C5: 368, + 0x91C9: 369, + 0x91D6: 370, + 0x9241: 371, + 0x9245: 372, + 0x9249: 373, + 0x9251: 374, + 0x9253: 375, + 0x9255: 376, + 0x9261: 377, + 0x9262: 378, + 0x9265: 379, + 0x9269: 380, + 0x9273: 381, + 0x9275: 382, + 0x9277: 383, + 0x9281: 384, + 0x9282: 385, + 0x9285: 386, + 0x9288: 387, + 0x9289: 388, + 0x9291: 389, + 0x9293: 390, + 0x9295: 391, + 0x9297: 392, + 0x92A1: 393, + 0x92B6: 394, + 0x92C1: 395, + 0x92E1: 396, + 0x92E5: 397, + 0x92E9: 398, + 0x92F1: 399, + 0x92F3: 400, + 0x9341: 401, + 0x9342: 402, + 0x9349: 403, + 0x9351: 404, + 0x9353: 405, + 0x9357: 406, + 0x9361: 407, + 0x9362: 408, + 0x9365: 409, + 0x9369: 410, + 0x936A: 411, + 0x936B: 412, + 0x9371: 413, + 0x9373: 414, + 0x9375: 415, + 0x9377: 416, + 0x9378: 417, + 0x937C: 418, + 0x9381: 419, + 0x9385: 420, + 0x9389: 421, + 0x93A1: 422, + 0x93A2: 423, + 0x93A5: 424, + 0x93A9: 425, + 0x93AB: 426, + 0x93B1: 427, + 0x93B3: 428, + 0x93B5: 429, + 0x93B7: 430, + 0x93BC: 431, + 0x9461: 432, + 0x9462: 433, + 0x9463: 434, + 0x9465: 435, + 0x9468: 436, + 0x9469: 437, + 0x946A: 438, + 0x946B: 439, + 0x946C: 440, + 0x9470: 441, + 0x9471: 442, + 0x9473: 443, + 0x9475: 444, + 0x9476: 445, + 0x9477: 446, + 0x9478: 447, + 0x9479: 448, + 0x947D: 449, + 0x9481: 450, + 0x9482: 451, + 0x9485: 452, + 0x9489: 453, + 0x9491: 454, + 0x9493: 455, + 0x9495: 456, + 0x9496: 457, + 0x9497: 458, + 0x94A1: 459, + 0x94E1: 460, + 0x94E2: 461, + 0x94E3: 462, + 0x94E5: 463, + 0x94E8: 464, + 0x94E9: 465, + 0x94EB: 466, + 0x94EC: 467, + 0x94F1: 468, + 0x94F3: 469, + 0x94F5: 470, + 0x94F7: 471, + 0x94F9: 472, + 0x94FC: 473, + 0x9541: 474, + 0x9542: 475, + 0x9545: 476, + 0x9549: 477, + 0x9551: 478, + 0x9553: 479, + 0x9555: 480, + 0x9556: 481, + 0x9557: 482, + 0x9561: 483, + 0x9565: 484, + 0x9569: 485, + 0x9576: 486, + 0x9577: 487, + 0x9581: 488, + 0x9585: 489, + 0x95A1: 490, + 0x95A2: 491, + 0x95A5: 492, + 0x95A8: 493, + 0x95A9: 494, + 0x95AB: 495, + 0x95AD: 496, + 0x95B1: 497, + 0x95B3: 498, + 0x95B5: 499, + 0x95B7: 500, + 0x95B9: 501, + 0x95BB: 502, + 0x95C1: 503, + 0x95C5: 504, + 0x95C9: 505, + 0x95E1: 506, + 0x95F6: 507, + 0x9641: 508, + 0x9645: 509, + 0x9649: 510, + 0x9651: 511, + 0x9653: 512, + 0x9655: 513, + 0x9661: 514, + 0x9681: 515, + 0x9682: 516, + 0x9685: 517, + 0x9689: 518, + 0x9691: 519, + 0x9693: 520, + 0x9695: 521, + 0x9697: 522, + 0x96A1: 523, + 0x96B6: 524, + 0x96C1: 525, + 0x96D7: 526, + 0x96E1: 527, + 0x96E5: 528, + 0x96E9: 529, + 0x96F3: 530, + 0x96F5: 531, + 0x96F7: 532, + 0x9741: 533, + 0x9745: 534, + 0x9749: 535, + 0x9751: 536, + 0x9757: 537, + 0x9761: 538, + 0x9762: 539, + 0x9765: 540, + 0x9768: 541, + 0x9769: 542, + 0x976B: 543, + 0x9771: 544, + 0x9773: 545, + 0x9775: 546, + 0x9777: 547, + 0x9781: 548, + 0x97A1: 549, + 0x97A2: 550, + 0x97A5: 551, + 0x97A8: 552, + 0x97A9: 553, + 0x97B1: 554, + 0x97B3: 555, + 0x97B5: 556, + 0x97B6: 557, + 0x97B7: 558, + 0x97B8: 559, + 0x9861: 560, + 0x9862: 561, + 0x9865: 562, + 0x9869: 563, + 0x9871: 564, + 0x9873: 565, + 0x9875: 566, + 0x9876: 567, + 0x9877: 568, + 0x987D: 569, + 0x9881: 570, + 0x9882: 571, + 0x9885: 572, + 0x9889: 573, + 0x9891: 574, + 0x9893: 575, + 0x9895: 576, + 0x9896: 577, + 0x9897: 578, + 0x98E1: 579, + 0x98E2: 580, + 0x98E5: 581, + 0x98E9: 582, + 0x98EB: 583, + 0x98EC: 584, + 0x98F1: 585, + 0x98F3: 586, + 0x98F5: 587, + 0x98F6: 588, + 0x98F7: 589, + 0x98FD: 590, + 0x9941: 591, + 0x9942: 592, + 0x9945: 593, + 0x9949: 594, + 0x9951: 595, + 0x9953: 596, + 0x9955: 597, + 0x9956: 598, + 0x9957: 599, + 0x9961: 600, + 0x9976: 601, + 0x99A1: 602, + 0x99A2: 603, + 0x99A5: 604, + 0x99A9: 605, + 0x99B7: 606, + 0x99C1: 607, + 0x99C9: 608, + 0x99E1: 609, + 0x9A41: 610, + 0x9A45: 611, + 0x9A81: 612, + 0x9A82: 613, + 0x9A85: 614, + 0x9A89: 615, + 0x9A90: 616, + 0x9A91: 617, + 0x9A97: 618, + 0x9AC1: 619, + 0x9AE1: 620, + 0x9AE5: 621, + 0x9AE9: 622, + 0x9AF1: 623, + 0x9AF3: 624, + 0x9AF7: 625, + 0x9B61: 626, + 0x9B62: 627, + 0x9B65: 628, + 0x9B68: 629, + 0x9B69: 630, + 0x9B71: 631, + 0x9B73: 632, + 0x9B75: 633, + 0x9B81: 634, + 0x9B85: 635, + 0x9B89: 636, + 0x9B91: 637, + 0x9B93: 638, + 0x9BA1: 639, + 0x9BA5: 640, + 0x9BA9: 641, + 0x9BB1: 642, + 0x9BB3: 643, + 0x9BB5: 644, + 0x9BB7: 645, + 0x9C61: 646, + 0x9C62: 647, + 0x9C65: 648, + 0x9C69: 649, + 0x9C71: 650, + 0x9C73: 651, + 0x9C75: 652, + 0x9C76: 653, + 0x9C77: 654, + 0x9C78: 655, + 0x9C7C: 656, + 0x9C7D: 657, + 0x9C81: 658, + 0x9C82: 659, + 0x9C85: 660, + 0x9C89: 661, + 0x9C91: 662, + 0x9C93: 663, + 0x9C95: 664, + 0x9C96: 665, + 0x9C97: 666, + 0x9CA1: 667, + 0x9CA2: 668, + 0x9CA5: 669, + 0x9CB5: 670, + 0x9CB7: 671, + 0x9CE1: 672, + 0x9CE2: 673, + 0x9CE5: 674, + 0x9CE9: 675, + 0x9CF1: 676, + 0x9CF3: 677, + 0x9CF5: 678, + 0x9CF6: 679, + 0x9CF7: 680, + 0x9CFD: 681, + 0x9D41: 682, + 0x9D42: 683, + 0x9D45: 684, + 0x9D49: 685, + 0x9D51: 686, + 0x9D53: 687, + 0x9D55: 688, + 0x9D57: 689, + 0x9D61: 690, + 0x9D62: 691, + 0x9D65: 692, + 0x9D69: 693, + 0x9D71: 694, + 0x9D73: 695, + 0x9D75: 696, + 0x9D76: 697, + 0x9D77: 698, + 0x9D81: 699, + 0x9D85: 700, + 0x9D93: 701, + 0x9D95: 702, + 0x9DA1: 703, + 0x9DA2: 704, + 0x9DA5: 705, + 0x9DA9: 706, + 0x9DB1: 707, + 0x9DB3: 708, + 0x9DB5: 709, + 0x9DB7: 710, + 0x9DC1: 711, + 0x9DC5: 712, + 0x9DD7: 713, + 0x9DF6: 714, + 0x9E41: 715, + 0x9E45: 716, + 0x9E49: 717, + 0x9E51: 718, + 0x9E53: 719, + 0x9E55: 720, + 0x9E57: 721, + 0x9E61: 722, + 0x9E65: 723, + 0x9E69: 724, + 0x9E73: 725, + 0x9E75: 726, + 0x9E77: 727, + 0x9E81: 728, + 0x9E82: 729, + 0x9E85: 730, + 0x9E89: 731, + 0x9E91: 732, + 0x9E93: 733, + 0x9E95: 734, + 0x9E97: 735, + 0x9EA1: 736, + 0x9EB6: 737, + 0x9EC1: 738, + 0x9EE1: 739, + 0x9EE2: 740, + 0x9EE5: 741, + 0x9EE9: 742, + 0x9EF1: 743, + 0x9EF5: 744, + 0x9EF7: 745, + 0x9F41: 746, + 0x9F42: 747, + 0x9F45: 748, + 0x9F49: 749, + 0x9F51: 750, + 0x9F53: 751, + 0x9F55: 752, + 0x9F57: 753, + 0x9F61: 754, + 0x9F62: 755, + 0x9F65: 756, + 0x9F69: 757, + 0x9F71: 758, + 0x9F73: 759, + 0x9F75: 760, + 0x9F77: 761, + 0x9F78: 762, + 0x9F7B: 763, + 0x9F7C: 764, + 0x9FA1: 765, + 0x9FA2: 766, + 0x9FA5: 767, + 0x9FA9: 768, + 0x9FB1: 769, + 0x9FB3: 770, + 0x9FB5: 771, + 0x9FB7: 772, + 0xA061: 773, + 0xA062: 774, + 0xA065: 775, + 0xA067: 776, + 0xA068: 777, + 0xA069: 778, + 0xA06A: 779, + 0xA06B: 780, + 0xA071: 781, + 0xA073: 782, + 0xA075: 783, + 0xA077: 784, + 0xA078: 785, + 0xA07B: 786, + 0xA07D: 787, + 0xA081: 788, + 0xA082: 789, + 0xA085: 790, + 0xA089: 791, + 0xA091: 792, + 0xA093: 793, + 0xA095: 794, + 0xA096: 795, + 0xA097: 796, + 0xA098: 797, + 0xA0A1: 798, + 0xA0A2: 799, + 0xA0A9: 800, + 0xA0B7: 801, + 0xA0E1: 802, + 0xA0E2: 803, + 0xA0E5: 804, + 0xA0E9: 805, + 0xA0EB: 806, + 0xA0F1: 807, + 0xA0F3: 808, + 0xA0F5: 809, + 0xA0F7: 810, + 0xA0F8: 811, + 0xA0FD: 812, + 0xA141: 813, + 0xA142: 814, + 0xA145: 815, + 0xA149: 816, + 0xA151: 817, + 0xA153: 818, + 0xA155: 819, + 0xA156: 820, + 0xA157: 821, + 0xA161: 822, + 0xA162: 823, + 0xA165: 824, + 0xA169: 825, + 0xA175: 826, + 0xA176: 827, + 0xA177: 828, + 0xA179: 829, + 0xA181: 830, + 0xA1A1: 831, + 0xA1A2: 832, + 0xA1A4: 833, + 0xA1A5: 834, + 0xA1A9: 835, + 0xA1AB: 836, + 0xA1B1: 837, + 0xA1B3: 838, + 0xA1B5: 839, + 0xA1B7: 840, + 0xA1C1: 841, + 0xA1C5: 842, + 0xA1D6: 843, + 0xA1D7: 844, + 0xA241: 845, + 0xA245: 846, + 0xA249: 847, + 0xA253: 848, + 0xA255: 849, + 0xA257: 850, + 0xA261: 851, + 0xA265: 852, + 0xA269: 853, + 0xA273: 854, + 0xA275: 855, + 0xA281: 856, + 0xA282: 857, + 0xA283: 858, + 0xA285: 859, + 0xA288: 860, + 0xA289: 861, + 0xA28A: 862, + 0xA28B: 863, + 0xA291: 864, + 0xA293: 865, + 0xA295: 866, + 0xA297: 867, + 0xA29B: 868, + 0xA29D: 869, + 0xA2A1: 870, + 0xA2A5: 871, + 0xA2A9: 872, + 0xA2B3: 873, + 0xA2B5: 874, + 0xA2C1: 875, + 0xA2E1: 876, + 0xA2E5: 877, + 0xA2E9: 878, + 0xA341: 879, + 0xA345: 880, + 0xA349: 881, + 0xA351: 882, + 0xA355: 883, + 0xA361: 884, + 0xA365: 885, + 0xA369: 886, + 0xA371: 887, + 0xA375: 888, + 0xA3A1: 889, + 0xA3A2: 890, + 0xA3A5: 891, + 0xA3A8: 892, + 0xA3A9: 893, + 0xA3AB: 894, + 0xA3B1: 895, + 0xA3B3: 896, + 0xA3B5: 897, + 0xA3B6: 898, + 0xA3B7: 899, + 0xA3B9: 900, + 0xA3BB: 901, + 0xA461: 902, + 0xA462: 903, + 0xA463: 904, + 0xA464: 905, + 0xA465: 906, + 0xA468: 907, + 0xA469: 908, + 0xA46A: 909, + 0xA46B: 910, + 0xA46C: 911, + 0xA471: 912, + 0xA473: 913, + 0xA475: 914, + 0xA477: 915, + 0xA47B: 916, + 0xA481: 917, + 0xA482: 918, + 0xA485: 919, + 0xA489: 920, + 0xA491: 921, + 0xA493: 922, + 0xA495: 923, + 0xA496: 924, + 0xA497: 925, + 0xA49B: 926, + 0xA4A1: 927, + 0xA4A2: 928, + 0xA4A5: 929, + 0xA4B3: 930, + 0xA4E1: 931, + 0xA4E2: 932, + 0xA4E5: 933, + 0xA4E8: 934, + 0xA4E9: 935, + 0xA4EB: 936, + 0xA4F1: 937, + 0xA4F3: 938, + 0xA4F5: 939, + 0xA4F7: 940, + 0xA4F8: 941, + 0xA541: 942, + 0xA542: 943, + 0xA545: 944, + 0xA548: 945, + 0xA549: 946, + 0xA551: 947, + 0xA553: 948, + 0xA555: 949, + 0xA556: 950, + 0xA557: 951, + 0xA561: 952, + 0xA562: 953, + 0xA565: 954, + 0xA569: 955, + 0xA573: 956, + 0xA575: 957, + 0xA576: 958, + 0xA577: 959, + 0xA57B: 960, + 0xA581: 961, + 0xA585: 962, + 0xA5A1: 963, + 0xA5A2: 964, + 0xA5A3: 965, + 0xA5A5: 966, + 0xA5A9: 967, + 0xA5B1: 968, + 0xA5B3: 969, + 0xA5B5: 970, + 0xA5B7: 971, + 0xA5C1: 972, + 0xA5C5: 973, + 0xA5D6: 974, + 0xA5E1: 975, + 0xA5F6: 976, + 0xA641: 977, + 0xA642: 978, + 0xA645: 979, + 0xA649: 980, + 0xA651: 981, + 0xA653: 982, + 0xA661: 983, + 0xA665: 984, + 0xA681: 985, + 0xA682: 986, + 0xA685: 987, + 0xA688: 988, + 0xA689: 989, + 0xA68A: 990, + 0xA68B: 991, + 0xA691: 992, + 0xA693: 993, + 0xA695: 994, + 0xA697: 995, + 0xA69B: 996, + 0xA69C: 997, + 0xA6A1: 998, + 0xA6A9: 999, + 0xA6B6: 1000, + 0xA6C1: 1001, + 0xA6E1: 1002, + 0xA6E2: 1003, + 0xA6E5: 1004, + 0xA6E9: 1005, + 0xA6F7: 1006, + 0xA741: 1007, + 0xA745: 1008, + 0xA749: 1009, + 0xA751: 1010, + 0xA755: 1011, + 0xA757: 1012, + 0xA761: 1013, + 0xA762: 1014, + 0xA765: 1015, + 0xA769: 1016, + 0xA771: 1017, + 0xA773: 1018, + 0xA775: 1019, + 0xA7A1: 1020, + 0xA7A2: 1021, + 0xA7A5: 1022, + 0xA7A9: 1023, + 0xA7AB: 1024, + 0xA7B1: 1025, + 0xA7B3: 1026, + 0xA7B5: 1027, + 0xA7B7: 1028, + 0xA7B8: 1029, + 0xA7B9: 1030, + 0xA861: 1031, + 0xA862: 1032, + 0xA865: 1033, + 0xA869: 1034, + 0xA86B: 1035, + 0xA871: 1036, + 0xA873: 1037, + 0xA875: 1038, + 0xA876: 1039, + 0xA877: 1040, + 0xA87D: 1041, + 0xA881: 1042, + 0xA882: 1043, + 0xA885: 1044, + 0xA889: 1045, + 0xA891: 1046, + 0xA893: 1047, + 0xA895: 1048, + 0xA896: 1049, + 0xA897: 1050, + 0xA8A1: 1051, + 0xA8A2: 1052, + 0xA8B1: 1053, + 0xA8E1: 1054, + 0xA8E2: 1055, + 0xA8E5: 1056, + 0xA8E8: 1057, + 0xA8E9: 1058, + 0xA8F1: 1059, + 0xA8F5: 1060, + 0xA8F6: 1061, + 0xA8F7: 1062, + 0xA941: 1063, + 0xA957: 1064, + 0xA961: 1065, + 0xA962: 1066, + 0xA971: 1067, + 0xA973: 1068, + 0xA975: 1069, + 0xA976: 1070, + 0xA977: 1071, + 0xA9A1: 1072, + 0xA9A2: 1073, + 0xA9A5: 1074, + 0xA9A9: 1075, + 0xA9B1: 1076, + 0xA9B3: 1077, + 0xA9B7: 1078, + 0xAA41: 1079, + 0xAA61: 1080, + 0xAA77: 1081, + 0xAA81: 1082, + 0xAA82: 1083, + 0xAA85: 1084, + 0xAA89: 1085, + 0xAA91: 1086, + 0xAA95: 1087, + 0xAA97: 1088, + 0xAB41: 1089, + 0xAB57: 1090, + 0xAB61: 1091, + 0xAB65: 1092, + 0xAB69: 1093, + 0xAB71: 1094, + 0xAB73: 1095, + 0xABA1: 1096, + 0xABA2: 1097, + 0xABA5: 1098, + 0xABA9: 1099, + 0xABB1: 1100, + 0xABB3: 1101, + 0xABB5: 1102, + 0xABB7: 1103, + 0xAC61: 1104, + 0xAC62: 1105, + 0xAC64: 1106, + 0xAC65: 1107, + 0xAC68: 1108, + 0xAC69: 1109, + 0xAC6A: 1110, + 0xAC6B: 1111, + 0xAC71: 1112, + 0xAC73: 1113, + 0xAC75: 1114, + 0xAC76: 1115, + 0xAC77: 1116, + 0xAC7B: 1117, + 0xAC81: 1118, + 0xAC82: 1119, + 0xAC85: 1120, + 0xAC89: 1121, + 0xAC91: 1122, + 0xAC93: 1123, + 0xAC95: 1124, + 0xAC96: 1125, + 0xAC97: 1126, + 0xACA1: 1127, + 0xACA2: 1128, + 0xACA5: 1129, + 0xACA9: 1130, + 0xACB1: 1131, + 0xACB3: 1132, + 0xACB5: 1133, + 0xACB7: 1134, + 0xACC1: 1135, + 0xACC5: 1136, + 0xACC9: 1137, + 0xACD1: 1138, + 0xACD7: 1139, + 0xACE1: 1140, + 0xACE2: 1141, + 0xACE3: 1142, + 0xACE4: 1143, + 0xACE5: 1144, + 0xACE8: 1145, + 0xACE9: 1146, + 0xACEB: 1147, + 0xACEC: 1148, + 0xACF1: 1149, + 0xACF3: 1150, + 0xACF5: 1151, + 0xACF6: 1152, + 0xACF7: 1153, + 0xACFC: 1154, + 0xAD41: 1155, + 0xAD42: 1156, + 0xAD45: 1157, + 0xAD49: 1158, + 0xAD51: 1159, + 0xAD53: 1160, + 0xAD55: 1161, + 0xAD56: 1162, + 0xAD57: 1163, + 0xAD61: 1164, + 0xAD62: 1165, + 0xAD65: 1166, + 0xAD69: 1167, + 0xAD71: 1168, + 0xAD73: 1169, + 0xAD75: 1170, + 0xAD76: 1171, + 0xAD77: 1172, + 0xAD81: 1173, + 0xAD85: 1174, + 0xAD89: 1175, + 0xAD97: 1176, + 0xADA1: 1177, + 0xADA2: 1178, + 0xADA3: 1179, + 0xADA5: 1180, + 0xADA9: 1181, + 0xADAB: 1182, + 0xADB1: 1183, + 0xADB3: 1184, + 0xADB5: 1185, + 0xADB7: 1186, + 0xADBB: 1187, + 0xADC1: 1188, + 0xADC2: 1189, + 0xADC5: 1190, + 0xADC9: 1191, + 0xADD7: 1192, + 0xADE1: 1193, + 0xADE5: 1194, + 0xADE9: 1195, + 0xADF1: 1196, + 0xADF5: 1197, + 0xADF6: 1198, + 0xAE41: 1199, + 0xAE45: 1200, + 0xAE49: 1201, + 0xAE51: 1202, + 0xAE53: 1203, + 0xAE55: 1204, + 0xAE61: 1205, + 0xAE62: 1206, + 0xAE65: 1207, + 0xAE69: 1208, + 0xAE71: 1209, + 0xAE73: 1210, + 0xAE75: 1211, + 0xAE77: 1212, + 0xAE81: 1213, + 0xAE82: 1214, + 0xAE85: 1215, + 0xAE88: 1216, + 0xAE89: 1217, + 0xAE91: 1218, + 0xAE93: 1219, + 0xAE95: 1220, + 0xAE97: 1221, + 0xAE99: 1222, + 0xAE9B: 1223, + 0xAE9C: 1224, + 0xAEA1: 1225, + 0xAEB6: 1226, + 0xAEC1: 1227, + 0xAEC2: 1228, + 0xAEC5: 1229, + 0xAEC9: 1230, + 0xAED1: 1231, + 0xAED7: 1232, + 0xAEE1: 1233, + 0xAEE2: 1234, + 0xAEE5: 1235, + 0xAEE9: 1236, + 0xAEF1: 1237, + 0xAEF3: 1238, + 0xAEF5: 1239, + 0xAEF7: 1240, + 0xAF41: 1241, + 0xAF42: 1242, + 0xAF49: 1243, + 0xAF51: 1244, + 0xAF55: 1245, + 0xAF57: 1246, + 0xAF61: 1247, + 0xAF62: 1248, + 0xAF65: 1249, + 0xAF69: 1250, + 0xAF6A: 1251, + 0xAF71: 1252, + 0xAF73: 1253, + 0xAF75: 1254, + 0xAF77: 1255, + 0xAFA1: 1256, + 0xAFA2: 1257, + 0xAFA5: 1258, + 0xAFA8: 1259, + 0xAFA9: 1260, + 0xAFB0: 1261, + 0xAFB1: 1262, + 0xAFB3: 1263, + 0xAFB5: 1264, + 0xAFB7: 1265, + 0xAFBC: 1266, + 0xB061: 1267, + 0xB062: 1268, + 0xB064: 1269, + 0xB065: 1270, + 0xB069: 1271, + 0xB071: 1272, + 0xB073: 1273, + 0xB076: 1274, + 0xB077: 1275, + 0xB07D: 1276, + 0xB081: 1277, + 0xB082: 1278, + 0xB085: 1279, + 0xB089: 1280, + 0xB091: 1281, + 0xB093: 1282, + 0xB096: 1283, + 0xB097: 1284, + 0xB0B7: 1285, + 0xB0E1: 1286, + 0xB0E2: 1287, + 0xB0E5: 1288, + 0xB0E9: 1289, + 0xB0EB: 1290, + 0xB0F1: 1291, + 0xB0F3: 1292, + 0xB0F6: 1293, + 0xB0F7: 1294, + 0xB141: 1295, + 0xB145: 1296, + 0xB149: 1297, + 0xB185: 1298, + 0xB1A1: 1299, + 0xB1A2: 1300, + 0xB1A5: 1301, + 0xB1A8: 1302, + 0xB1A9: 1303, + 0xB1AB: 1304, + 0xB1B1: 1305, + 0xB1B3: 1306, + 0xB1B7: 1307, + 0xB1C1: 1308, + 0xB1C2: 1309, + 0xB1C5: 1310, + 0xB1D6: 1311, + 0xB1E1: 1312, + 0xB1F6: 1313, + 0xB241: 1314, + 0xB245: 1315, + 0xB249: 1316, + 0xB251: 1317, + 0xB253: 1318, + 0xB261: 1319, + 0xB281: 1320, + 0xB282: 1321, + 0xB285: 1322, + 0xB289: 1323, + 0xB291: 1324, + 0xB293: 1325, + 0xB297: 1326, + 0xB2A1: 1327, + 0xB2B6: 1328, + 0xB2C1: 1329, + 0xB2E1: 1330, + 0xB2E5: 1331, + 0xB357: 1332, + 0xB361: 1333, + 0xB362: 1334, + 0xB365: 1335, + 0xB369: 1336, + 0xB36B: 1337, + 0xB370: 1338, + 0xB371: 1339, + 0xB373: 1340, + 0xB381: 1341, + 0xB385: 1342, + 0xB389: 1343, + 0xB391: 1344, + 0xB3A1: 1345, + 0xB3A2: 1346, + 0xB3A5: 1347, + 0xB3A9: 1348, + 0xB3B1: 1349, + 0xB3B3: 1350, + 0xB3B5: 1351, + 0xB3B7: 1352, + 0xB461: 1353, + 0xB462: 1354, + 0xB465: 1355, + 0xB466: 1356, + 0xB467: 1357, + 0xB469: 1358, + 0xB46A: 1359, + 0xB46B: 1360, + 0xB470: 1361, + 0xB471: 1362, + 0xB473: 1363, + 0xB475: 1364, + 0xB476: 1365, + 0xB477: 1366, + 0xB47B: 1367, + 0xB47C: 1368, + 0xB481: 1369, + 0xB482: 1370, + 0xB485: 1371, + 0xB489: 1372, + 0xB491: 1373, + 0xB493: 1374, + 0xB495: 1375, + 0xB496: 1376, + 0xB497: 1377, + 0xB4A1: 1378, + 0xB4A2: 1379, + 0xB4A5: 1380, + 0xB4A9: 1381, + 0xB4AC: 1382, + 0xB4B1: 1383, + 0xB4B3: 1384, + 0xB4B5: 1385, + 0xB4B7: 1386, + 0xB4BB: 1387, + 0xB4BD: 1388, + 0xB4C1: 1389, + 0xB4C5: 1390, + 0xB4C9: 1391, + 0xB4D3: 1392, + 0xB4E1: 1393, + 0xB4E2: 1394, + 0xB4E5: 1395, + 0xB4E6: 1396, + 0xB4E8: 1397, + 0xB4E9: 1398, + 0xB4EA: 1399, + 0xB4EB: 1400, + 0xB4F1: 1401, + 0xB4F3: 1402, + 0xB4F4: 1403, + 0xB4F5: 1404, + 0xB4F6: 1405, + 0xB4F7: 1406, + 0xB4F8: 1407, + 0xB4FA: 1408, + 0xB4FC: 1409, + 0xB541: 1410, + 0xB542: 1411, + 0xB545: 1412, + 0xB549: 1413, + 0xB551: 1414, + 0xB553: 1415, + 0xB555: 1416, + 0xB557: 1417, + 0xB561: 1418, + 0xB562: 1419, + 0xB563: 1420, + 0xB565: 1421, + 0xB569: 1422, + 0xB56B: 1423, + 0xB56C: 1424, + 0xB571: 1425, + 0xB573: 1426, + 0xB574: 1427, + 0xB575: 1428, + 0xB576: 1429, + 0xB577: 1430, + 0xB57B: 1431, + 0xB57C: 1432, + 0xB57D: 1433, + 0xB581: 1434, + 0xB585: 1435, + 0xB589: 1436, + 0xB591: 1437, + 0xB593: 1438, + 0xB595: 1439, + 0xB596: 1440, + 0xB5A1: 1441, + 0xB5A2: 1442, + 0xB5A5: 1443, + 0xB5A9: 1444, + 0xB5AA: 1445, + 0xB5AB: 1446, + 0xB5AD: 1447, + 0xB5B0: 1448, + 0xB5B1: 1449, + 0xB5B3: 1450, + 0xB5B5: 1451, + 0xB5B7: 1452, + 0xB5B9: 1453, + 0xB5C1: 1454, + 0xB5C2: 1455, + 0xB5C5: 1456, + 0xB5C9: 1457, + 0xB5D1: 1458, + 0xB5D3: 1459, + 0xB5D5: 1460, + 0xB5D6: 1461, + 0xB5D7: 1462, + 0xB5E1: 1463, + 0xB5E2: 1464, + 0xB5E5: 1465, + 0xB5F1: 1466, + 0xB5F5: 1467, + 0xB5F7: 1468, + 0xB641: 1469, + 0xB642: 1470, + 0xB645: 1471, + 0xB649: 1472, + 0xB651: 1473, + 0xB653: 1474, + 0xB655: 1475, + 0xB657: 1476, + 0xB661: 1477, + 0xB662: 1478, + 0xB665: 1479, + 0xB669: 1480, + 0xB671: 1481, + 0xB673: 1482, + 0xB675: 1483, + 0xB677: 1484, + 0xB681: 1485, + 0xB682: 1486, + 0xB685: 1487, + 0xB689: 1488, + 0xB68A: 1489, + 0xB68B: 1490, + 0xB691: 1491, + 0xB693: 1492, + 0xB695: 1493, + 0xB697: 1494, + 0xB6A1: 1495, + 0xB6A2: 1496, + 0xB6A5: 1497, + 0xB6A9: 1498, + 0xB6B1: 1499, + 0xB6B3: 1500, + 0xB6B6: 1501, + 0xB6B7: 1502, + 0xB6C1: 1503, + 0xB6C2: 1504, + 0xB6C5: 1505, + 0xB6C9: 1506, + 0xB6D1: 1507, + 0xB6D3: 1508, + 0xB6D7: 1509, + 0xB6E1: 1510, + 0xB6E2: 1511, + 0xB6E5: 1512, + 0xB6E9: 1513, + 0xB6F1: 1514, + 0xB6F3: 1515, + 0xB6F5: 1516, + 0xB6F7: 1517, + 0xB741: 1518, + 0xB742: 1519, + 0xB745: 1520, + 0xB749: 1521, + 0xB751: 1522, + 0xB753: 1523, + 0xB755: 1524, + 0xB757: 1525, + 0xB759: 1526, + 0xB761: 1527, + 0xB762: 1528, + 0xB765: 1529, + 0xB769: 1530, + 0xB76F: 1531, + 0xB771: 1532, + 0xB773: 1533, + 0xB775: 1534, + 0xB777: 1535, + 0xB778: 1536, + 0xB779: 1537, + 0xB77A: 1538, + 0xB77B: 1539, + 0xB77C: 1540, + 0xB77D: 1541, + 0xB781: 1542, + 0xB785: 1543, + 0xB789: 1544, + 0xB791: 1545, + 0xB795: 1546, + 0xB7A1: 1547, + 0xB7A2: 1548, + 0xB7A5: 1549, + 0xB7A9: 1550, + 0xB7AA: 1551, + 0xB7AB: 1552, + 0xB7B0: 1553, + 0xB7B1: 1554, + 0xB7B3: 1555, + 0xB7B5: 1556, + 0xB7B6: 1557, + 0xB7B7: 1558, + 0xB7B8: 1559, + 0xB7BC: 1560, + 0xB861: 1561, + 0xB862: 1562, + 0xB865: 1563, + 0xB867: 1564, + 0xB868: 1565, + 0xB869: 1566, + 0xB86B: 1567, + 0xB871: 1568, + 0xB873: 1569, + 0xB875: 1570, + 0xB876: 1571, + 0xB877: 1572, + 0xB878: 1573, + 0xB881: 1574, + 0xB882: 1575, + 0xB885: 1576, + 0xB889: 1577, + 0xB891: 1578, + 0xB893: 1579, + 0xB895: 1580, + 0xB896: 1581, + 0xB897: 1582, + 0xB8A1: 1583, + 0xB8A2: 1584, + 0xB8A5: 1585, + 0xB8A7: 1586, + 0xB8A9: 1587, + 0xB8B1: 1588, + 0xB8B7: 1589, + 0xB8C1: 1590, + 0xB8C5: 1591, + 0xB8C9: 1592, + 0xB8E1: 1593, + 0xB8E2: 1594, + 0xB8E5: 1595, + 0xB8E9: 1596, + 0xB8EB: 1597, + 0xB8F1: 1598, + 0xB8F3: 1599, + 0xB8F5: 1600, + 0xB8F7: 1601, + 0xB8F8: 1602, + 0xB941: 1603, + 0xB942: 1604, + 0xB945: 1605, + 0xB949: 1606, + 0xB951: 1607, + 0xB953: 1608, + 0xB955: 1609, + 0xB957: 1610, + 0xB961: 1611, + 0xB965: 1612, + 0xB969: 1613, + 0xB971: 1614, + 0xB973: 1615, + 0xB976: 1616, + 0xB977: 1617, + 0xB981: 1618, + 0xB9A1: 1619, + 0xB9A2: 1620, + 0xB9A5: 1621, + 0xB9A9: 1622, + 0xB9AB: 1623, + 0xB9B1: 1624, + 0xB9B3: 1625, + 0xB9B5: 1626, + 0xB9B7: 1627, + 0xB9B8: 1628, + 0xB9B9: 1629, + 0xB9BD: 1630, + 0xB9C1: 1631, + 0xB9C2: 1632, + 0xB9C9: 1633, + 0xB9D3: 1634, + 0xB9D5: 1635, + 0xB9D7: 1636, + 0xB9E1: 1637, + 0xB9F6: 1638, + 0xB9F7: 1639, + 0xBA41: 1640, + 0xBA45: 1641, + 0xBA49: 1642, + 0xBA51: 1643, + 0xBA53: 1644, + 0xBA55: 1645, + 0xBA57: 1646, + 0xBA61: 1647, + 0xBA62: 1648, + 0xBA65: 1649, + 0xBA77: 1650, + 0xBA81: 1651, + 0xBA82: 1652, + 0xBA85: 1653, + 0xBA89: 1654, + 0xBA8A: 1655, + 0xBA8B: 1656, + 0xBA91: 1657, + 0xBA93: 1658, + 0xBA95: 1659, + 0xBA97: 1660, + 0xBAA1: 1661, + 0xBAB6: 1662, + 0xBAC1: 1663, + 0xBAE1: 1664, + 0xBAE2: 1665, + 0xBAE5: 1666, + 0xBAE9: 1667, + 0xBAF1: 1668, + 0xBAF3: 1669, + 0xBAF5: 1670, + 0xBB41: 1671, + 0xBB45: 1672, + 0xBB49: 1673, + 0xBB51: 1674, + 0xBB61: 1675, + 0xBB62: 1676, + 0xBB65: 1677, + 0xBB69: 1678, + 0xBB71: 1679, + 0xBB73: 1680, + 0xBB75: 1681, + 0xBB77: 1682, + 0xBBA1: 1683, + 0xBBA2: 1684, + 0xBBA5: 1685, + 0xBBA8: 1686, + 0xBBA9: 1687, + 0xBBAB: 1688, + 0xBBB1: 1689, + 0xBBB3: 1690, + 0xBBB5: 1691, + 0xBBB7: 1692, + 0xBBB8: 1693, + 0xBBBB: 1694, + 0xBBBC: 1695, + 0xBC61: 1696, + 0xBC62: 1697, + 0xBC65: 1698, + 0xBC67: 1699, + 0xBC69: 1700, + 0xBC6C: 1701, + 0xBC71: 1702, + 0xBC73: 1703, + 0xBC75: 1704, + 0xBC76: 1705, + 0xBC77: 1706, + 0xBC81: 1707, + 0xBC82: 1708, + 0xBC85: 1709, + 0xBC89: 1710, + 0xBC91: 1711, + 0xBC93: 1712, + 0xBC95: 1713, + 0xBC96: 1714, + 0xBC97: 1715, + 0xBCA1: 1716, + 0xBCA5: 1717, + 0xBCB7: 1718, + 0xBCE1: 1719, + 0xBCE2: 1720, + 0xBCE5: 1721, + 0xBCE9: 1722, + 0xBCF1: 1723, + 0xBCF3: 1724, + 0xBCF5: 1725, + 0xBCF6: 1726, + 0xBCF7: 1727, + 0xBD41: 1728, + 0xBD57: 1729, + 0xBD61: 1730, + 0xBD76: 1731, + 0xBDA1: 1732, + 0xBDA2: 1733, + 0xBDA5: 1734, + 0xBDA9: 1735, + 0xBDB1: 1736, + 0xBDB3: 1737, + 0xBDB5: 1738, + 0xBDB7: 1739, + 0xBDB9: 1740, + 0xBDC1: 1741, + 0xBDC2: 1742, + 0xBDC9: 1743, + 0xBDD6: 1744, + 0xBDE1: 1745, + 0xBDF6: 1746, + 0xBE41: 1747, + 0xBE45: 1748, + 0xBE49: 1749, + 0xBE51: 1750, + 0xBE53: 1751, + 0xBE77: 1752, + 0xBE81: 1753, + 0xBE82: 1754, + 0xBE85: 1755, + 0xBE89: 1756, + 0xBE91: 1757, + 0xBE93: 1758, + 0xBE97: 1759, + 0xBEA1: 1760, + 0xBEB6: 1761, + 0xBEB7: 1762, + 0xBEE1: 1763, + 0xBF41: 1764, + 0xBF61: 1765, + 0xBF71: 1766, + 0xBF75: 1767, + 0xBF77: 1768, + 0xBFA1: 1769, + 0xBFA2: 1770, + 0xBFA5: 1771, + 0xBFA9: 1772, + 0xBFB1: 1773, + 0xBFB3: 1774, + 0xBFB7: 1775, + 0xBFB8: 1776, + 0xBFBD: 1777, + 0xC061: 1778, + 0xC062: 1779, + 0xC065: 1780, + 0xC067: 1781, + 0xC069: 1782, + 0xC071: 1783, + 0xC073: 1784, + 0xC075: 1785, + 0xC076: 1786, + 0xC077: 1787, + 0xC078: 1788, + 0xC081: 1789, + 0xC082: 1790, + 0xC085: 1791, + 0xC089: 1792, + 0xC091: 1793, + 0xC093: 1794, + 0xC095: 1795, + 0xC096: 1796, + 0xC097: 1797, + 0xC0A1: 1798, + 0xC0A5: 1799, + 0xC0A7: 1800, + 0xC0A9: 1801, + 0xC0B1: 1802, + 0xC0B7: 1803, + 0xC0E1: 1804, + 0xC0E2: 1805, + 0xC0E5: 1806, + 0xC0E9: 1807, + 0xC0F1: 1808, + 0xC0F3: 1809, + 0xC0F5: 1810, + 0xC0F6: 1811, + 0xC0F7: 1812, + 0xC141: 1813, + 0xC142: 1814, + 0xC145: 1815, + 0xC149: 1816, + 0xC151: 1817, + 0xC153: 1818, + 0xC155: 1819, + 0xC157: 1820, + 0xC161: 1821, + 0xC165: 1822, + 0xC176: 1823, + 0xC181: 1824, + 0xC185: 1825, + 0xC197: 1826, + 0xC1A1: 1827, + 0xC1A2: 1828, + 0xC1A5: 1829, + 0xC1A9: 1830, + 0xC1B1: 1831, + 0xC1B3: 1832, + 0xC1B5: 1833, + 0xC1B7: 1834, + 0xC1C1: 1835, + 0xC1C5: 1836, + 0xC1C9: 1837, + 0xC1D7: 1838, + 0xC241: 1839, + 0xC245: 1840, + 0xC249: 1841, + 0xC251: 1842, + 0xC253: 1843, + 0xC255: 1844, + 0xC257: 1845, + 0xC261: 1846, + 0xC271: 1847, + 0xC281: 1848, + 0xC282: 1849, + 0xC285: 1850, + 0xC289: 1851, + 0xC291: 1852, + 0xC293: 1853, + 0xC295: 1854, + 0xC297: 1855, + 0xC2A1: 1856, + 0xC2B6: 1857, + 0xC2C1: 1858, + 0xC2C5: 1859, + 0xC2E1: 1860, + 0xC2E5: 1861, + 0xC2E9: 1862, + 0xC2F1: 1863, + 0xC2F3: 1864, + 0xC2F5: 1865, + 0xC2F7: 1866, + 0xC341: 1867, + 0xC345: 1868, + 0xC349: 1869, + 0xC351: 1870, + 0xC357: 1871, + 0xC361: 1872, + 0xC362: 1873, + 0xC365: 1874, + 0xC369: 1875, + 0xC371: 1876, + 0xC373: 1877, + 0xC375: 1878, + 0xC377: 1879, + 0xC3A1: 1880, + 0xC3A2: 1881, + 0xC3A5: 1882, + 0xC3A8: 1883, + 0xC3A9: 1884, + 0xC3AA: 1885, + 0xC3B1: 1886, + 0xC3B3: 1887, + 0xC3B5: 1888, + 0xC3B7: 1889, + 0xC461: 1890, + 0xC462: 1891, + 0xC465: 1892, + 0xC469: 1893, + 0xC471: 1894, + 0xC473: 1895, + 0xC475: 1896, + 0xC477: 1897, + 0xC481: 1898, + 0xC482: 1899, + 0xC485: 1900, + 0xC489: 1901, + 0xC491: 1902, + 0xC493: 1903, + 0xC495: 1904, + 0xC496: 1905, + 0xC497: 1906, + 0xC4A1: 1907, + 0xC4A2: 1908, + 0xC4B7: 1909, + 0xC4E1: 1910, + 0xC4E2: 1911, + 0xC4E5: 1912, + 0xC4E8: 1913, + 0xC4E9: 1914, + 0xC4F1: 1915, + 0xC4F3: 1916, + 0xC4F5: 1917, + 0xC4F6: 1918, + 0xC4F7: 1919, + 0xC541: 1920, + 0xC542: 1921, + 0xC545: 1922, + 0xC549: 1923, + 0xC551: 1924, + 0xC553: 1925, + 0xC555: 1926, + 0xC557: 1927, + 0xC561: 1928, + 0xC565: 1929, + 0xC569: 1930, + 0xC571: 1931, + 0xC573: 1932, + 0xC575: 1933, + 0xC576: 1934, + 0xC577: 1935, + 0xC581: 1936, + 0xC5A1: 1937, + 0xC5A2: 1938, + 0xC5A5: 1939, + 0xC5A9: 1940, + 0xC5B1: 1941, + 0xC5B3: 1942, + 0xC5B5: 1943, + 0xC5B7: 1944, + 0xC5C1: 1945, + 0xC5C2: 1946, + 0xC5C5: 1947, + 0xC5C9: 1948, + 0xC5D1: 1949, + 0xC5D7: 1950, + 0xC5E1: 1951, + 0xC5F7: 1952, + 0xC641: 1953, + 0xC649: 1954, + 0xC661: 1955, + 0xC681: 1956, + 0xC682: 1957, + 0xC685: 1958, + 0xC689: 1959, + 0xC691: 1960, + 0xC693: 1961, + 0xC695: 1962, + 0xC697: 1963, + 0xC6A1: 1964, + 0xC6A5: 1965, + 0xC6A9: 1966, + 0xC6B7: 1967, + 0xC6C1: 1968, + 0xC6D7: 1969, + 0xC6E1: 1970, + 0xC6E2: 1971, + 0xC6E5: 1972, + 0xC6E9: 1973, + 0xC6F1: 1974, + 0xC6F3: 1975, + 0xC6F5: 1976, + 0xC6F7: 1977, + 0xC741: 1978, + 0xC745: 1979, + 0xC749: 1980, + 0xC751: 1981, + 0xC761: 1982, + 0xC762: 1983, + 0xC765: 1984, + 0xC769: 1985, + 0xC771: 1986, + 0xC773: 1987, + 0xC777: 1988, + 0xC7A1: 1989, + 0xC7A2: 1990, + 0xC7A5: 1991, + 0xC7A9: 1992, + 0xC7B1: 1993, + 0xC7B3: 1994, + 0xC7B5: 1995, + 0xC7B7: 1996, + 0xC861: 1997, + 0xC862: 1998, + 0xC865: 1999, + 0xC869: 2000, + 0xC86A: 2001, + 0xC871: 2002, + 0xC873: 2003, + 0xC875: 2004, + 0xC876: 2005, + 0xC877: 2006, + 0xC881: 2007, + 0xC882: 2008, + 0xC885: 2009, + 0xC889: 2010, + 0xC891: 2011, + 0xC893: 2012, + 0xC895: 2013, + 0xC896: 2014, + 0xC897: 2015, + 0xC8A1: 2016, + 0xC8B7: 2017, + 0xC8E1: 2018, + 0xC8E2: 2019, + 0xC8E5: 2020, + 0xC8E9: 2021, + 0xC8EB: 2022, + 0xC8F1: 2023, + 0xC8F3: 2024, + 0xC8F5: 2025, + 0xC8F6: 2026, + 0xC8F7: 2027, + 0xC941: 2028, + 0xC942: 2029, + 0xC945: 2030, + 0xC949: 2031, + 0xC951: 2032, + 0xC953: 2033, + 0xC955: 2034, + 0xC957: 2035, + 0xC961: 2036, + 0xC965: 2037, + 0xC976: 2038, + 0xC981: 2039, + 0xC985: 2040, + 0xC9A1: 2041, + 0xC9A2: 2042, + 0xC9A5: 2043, + 0xC9A9: 2044, + 0xC9B1: 2045, + 0xC9B3: 2046, + 0xC9B5: 2047, + 0xC9B7: 2048, + 0xC9BC: 2049, + 0xC9C1: 2050, + 0xC9C5: 2051, + 0xC9E1: 2052, + 0xCA41: 2053, + 0xCA45: 2054, + 0xCA55: 2055, + 0xCA57: 2056, + 0xCA61: 2057, + 0xCA81: 2058, + 0xCA82: 2059, + 0xCA85: 2060, + 0xCA89: 2061, + 0xCA91: 2062, + 0xCA93: 2063, + 0xCA95: 2064, + 0xCA97: 2065, + 0xCAA1: 2066, + 0xCAB6: 2067, + 0xCAC1: 2068, + 0xCAE1: 2069, + 0xCAE2: 2070, + 0xCAE5: 2071, + 0xCAE9: 2072, + 0xCAF1: 2073, + 0xCAF3: 2074, + 0xCAF7: 2075, + 0xCB41: 2076, + 0xCB45: 2077, + 0xCB49: 2078, + 0xCB51: 2079, + 0xCB57: 2080, + 0xCB61: 2081, + 0xCB62: 2082, + 0xCB65: 2083, + 0xCB68: 2084, + 0xCB69: 2085, + 0xCB6B: 2086, + 0xCB71: 2087, + 0xCB73: 2088, + 0xCB75: 2089, + 0xCB81: 2090, + 0xCB85: 2091, + 0xCB89: 2092, + 0xCB91: 2093, + 0xCB93: 2094, + 0xCBA1: 2095, + 0xCBA2: 2096, + 0xCBA5: 2097, + 0xCBA9: 2098, + 0xCBB1: 2099, + 0xCBB3: 2100, + 0xCBB5: 2101, + 0xCBB7: 2102, + 0xCC61: 2103, + 0xCC62: 2104, + 0xCC63: 2105, + 0xCC65: 2106, + 0xCC69: 2107, + 0xCC6B: 2108, + 0xCC71: 2109, + 0xCC73: 2110, + 0xCC75: 2111, + 0xCC76: 2112, + 0xCC77: 2113, + 0xCC7B: 2114, + 0xCC81: 2115, + 0xCC82: 2116, + 0xCC85: 2117, + 0xCC89: 2118, + 0xCC91: 2119, + 0xCC93: 2120, + 0xCC95: 2121, + 0xCC96: 2122, + 0xCC97: 2123, + 0xCCA1: 2124, + 0xCCA2: 2125, + 0xCCE1: 2126, + 0xCCE2: 2127, + 0xCCE5: 2128, + 0xCCE9: 2129, + 0xCCF1: 2130, + 0xCCF3: 2131, + 0xCCF5: 2132, + 0xCCF6: 2133, + 0xCCF7: 2134, + 0xCD41: 2135, + 0xCD42: 2136, + 0xCD45: 2137, + 0xCD49: 2138, + 0xCD51: 2139, + 0xCD53: 2140, + 0xCD55: 2141, + 0xCD57: 2142, + 0xCD61: 2143, + 0xCD65: 2144, + 0xCD69: 2145, + 0xCD71: 2146, + 0xCD73: 2147, + 0xCD76: 2148, + 0xCD77: 2149, + 0xCD81: 2150, + 0xCD89: 2151, + 0xCD93: 2152, + 0xCD95: 2153, + 0xCDA1: 2154, + 0xCDA2: 2155, + 0xCDA5: 2156, + 0xCDA9: 2157, + 0xCDB1: 2158, + 0xCDB3: 2159, + 0xCDB5: 2160, + 0xCDB7: 2161, + 0xCDC1: 2162, + 0xCDD7: 2163, + 0xCE41: 2164, + 0xCE45: 2165, + 0xCE61: 2166, + 0xCE65: 2167, + 0xCE69: 2168, + 0xCE73: 2169, + 0xCE75: 2170, + 0xCE81: 2171, + 0xCE82: 2172, + 0xCE85: 2173, + 0xCE88: 2174, + 0xCE89: 2175, + 0xCE8B: 2176, + 0xCE91: 2177, + 0xCE93: 2178, + 0xCE95: 2179, + 0xCE97: 2180, + 0xCEA1: 2181, + 0xCEB7: 2182, + 0xCEE1: 2183, + 0xCEE5: 2184, + 0xCEE9: 2185, + 0xCEF1: 2186, + 0xCEF5: 2187, + 0xCF41: 2188, + 0xCF45: 2189, + 0xCF49: 2190, + 0xCF51: 2191, + 0xCF55: 2192, + 0xCF57: 2193, + 0xCF61: 2194, + 0xCF65: 2195, + 0xCF69: 2196, + 0xCF71: 2197, + 0xCF73: 2198, + 0xCF75: 2199, + 0xCFA1: 2200, + 0xCFA2: 2201, + 0xCFA5: 2202, + 0xCFA9: 2203, + 0xCFB1: 2204, + 0xCFB3: 2205, + 0xCFB5: 2206, + 0xCFB7: 2207, + 0xD061: 2208, + 0xD062: 2209, + 0xD065: 2210, + 0xD069: 2211, + 0xD06E: 2212, + 0xD071: 2213, + 0xD073: 2214, + 0xD075: 2215, + 0xD077: 2216, + 0xD081: 2217, + 0xD082: 2218, + 0xD085: 2219, + 0xD089: 2220, + 0xD091: 2221, + 0xD093: 2222, + 0xD095: 2223, + 0xD096: 2224, + 0xD097: 2225, + 0xD0A1: 2226, + 0xD0B7: 2227, + 0xD0E1: 2228, + 0xD0E2: 2229, + 0xD0E5: 2230, + 0xD0E9: 2231, + 0xD0EB: 2232, + 0xD0F1: 2233, + 0xD0F3: 2234, + 0xD0F5: 2235, + 0xD0F7: 2236, + 0xD141: 2237, + 0xD142: 2238, + 0xD145: 2239, + 0xD149: 2240, + 0xD151: 2241, + 0xD153: 2242, + 0xD155: 2243, + 0xD157: 2244, + 0xD161: 2245, + 0xD162: 2246, + 0xD165: 2247, + 0xD169: 2248, + 0xD171: 2249, + 0xD173: 2250, + 0xD175: 2251, + 0xD176: 2252, + 0xD177: 2253, + 0xD181: 2254, + 0xD185: 2255, + 0xD189: 2256, + 0xD193: 2257, + 0xD1A1: 2258, + 0xD1A2: 2259, + 0xD1A5: 2260, + 0xD1A9: 2261, + 0xD1AE: 2262, + 0xD1B1: 2263, + 0xD1B3: 2264, + 0xD1B5: 2265, + 0xD1B7: 2266, + 0xD1BB: 2267, + 0xD1C1: 2268, + 0xD1C2: 2269, + 0xD1C5: 2270, + 0xD1C9: 2271, + 0xD1D5: 2272, + 0xD1D7: 2273, + 0xD1E1: 2274, + 0xD1E2: 2275, + 0xD1E5: 2276, + 0xD1F5: 2277, + 0xD1F7: 2278, + 0xD241: 2279, + 0xD242: 2280, + 0xD245: 2281, + 0xD249: 2282, + 0xD253: 2283, + 0xD255: 2284, + 0xD257: 2285, + 0xD261: 2286, + 0xD265: 2287, + 0xD269: 2288, + 0xD273: 2289, + 0xD275: 2290, + 0xD281: 2291, + 0xD282: 2292, + 0xD285: 2293, + 0xD289: 2294, + 0xD28E: 2295, + 0xD291: 2296, + 0xD295: 2297, + 0xD297: 2298, + 0xD2A1: 2299, + 0xD2A5: 2300, + 0xD2A9: 2301, + 0xD2B1: 2302, + 0xD2B7: 2303, + 0xD2C1: 2304, + 0xD2C2: 2305, + 0xD2C5: 2306, + 0xD2C9: 2307, + 0xD2D7: 2308, + 0xD2E1: 2309, + 0xD2E2: 2310, + 0xD2E5: 2311, + 0xD2E9: 2312, + 0xD2F1: 2313, + 0xD2F3: 2314, + 0xD2F5: 2315, + 0xD2F7: 2316, + 0xD341: 2317, + 0xD342: 2318, + 0xD345: 2319, + 0xD349: 2320, + 0xD351: 2321, + 0xD355: 2322, + 0xD357: 2323, + 0xD361: 2324, + 0xD362: 2325, + 0xD365: 2326, + 0xD367: 2327, + 0xD368: 2328, + 0xD369: 2329, + 0xD36A: 2330, + 0xD371: 2331, + 0xD373: 2332, + 0xD375: 2333, + 0xD377: 2334, + 0xD37B: 2335, + 0xD381: 2336, + 0xD385: 2337, + 0xD389: 2338, + 0xD391: 2339, + 0xD393: 2340, + 0xD397: 2341, + 0xD3A1: 2342, + 0xD3A2: 2343, + 0xD3A5: 2344, + 0xD3A9: 2345, + 0xD3B1: 2346, + 0xD3B3: 2347, + 0xD3B5: 2348, + 0xD3B7: 2349, +} diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabprober.py new file mode 100644 index 0000000..d7364ba --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/johabprober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import JOHABDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import JOHAB_SM_MODEL + + +class JOHABProber(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(JOHAB_SM_MODEL) + self.distribution_analyzer = JOHABDistributionAnalysis() + self.reset() + + @property + def charset_name(self) -> str: + return "Johab" + + @property + def language(self) -> str: + return "Korean" diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jpcntx.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jpcntx.py new file mode 100644 index 0000000..2f53bdd --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/jpcntx.py @@ -0,0 +1,238 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import List, Tuple, Union + +# This is hiragana 2-char sequence table, the number in each cell represents its frequency category +# fmt: off +jp2_char_context = ( + (0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), + (2, 4, 0, 4, 0, 3, 0, 4, 0, 3, 4, 4, 4, 2, 4, 3, 3, 4, 3, 2, 3, 3, 4, 2, 3, 3, 3, 2, 4, 1, 4, 3, 3, 1, 5, 4, 3, 4, 3, 4, 3, 5, 3, 0, 3, 5, 4, 2, 0, 3, 1, 0, 3, 3, 0, 3, 3, 0, 1, 1, 0, 4, 3, 0, 3, 3, 0, 4, 0, 2, 0, 3, 5, 5, 5, 5, 4, 0, 4, 1, 0, 3, 4), + (0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2), + (0, 4, 0, 5, 0, 5, 0, 4, 0, 4, 5, 4, 4, 3, 5, 3, 5, 1, 5, 3, 4, 3, 4, 4, 3, 4, 3, 3, 4, 3, 5, 4, 4, 3, 5, 5, 3, 5, 5, 5, 3, 5, 5, 3, 4, 5, 5, 3, 1, 3, 2, 0, 3, 4, 0, 4, 2, 0, 4, 2, 1, 5, 3, 2, 3, 5, 0, 4, 0, 2, 0, 5, 4, 4, 5, 4, 5, 0, 4, 0, 0, 4, 4), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 3, 0, 4, 0, 3, 0, 3, 0, 4, 5, 4, 3, 3, 3, 3, 4, 3, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 4, 4, 4, 4, 5, 3, 4, 4, 3, 4, 5, 5, 4, 5, 5, 1, 4, 5, 4, 3, 0, 3, 3, 1, 3, 3, 0, 4, 4, 0, 3, 3, 1, 5, 3, 3, 3, 5, 0, 4, 0, 3, 0, 4, 4, 3, 4, 3, 3, 0, 4, 1, 1, 3, 4), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 4, 0, 3, 0, 3, 0, 4, 0, 3, 4, 4, 3, 2, 2, 1, 2, 1, 3, 1, 3, 3, 3, 3, 3, 4, 3, 1, 3, 3, 5, 3, 3, 0, 4, 3, 0, 5, 4, 3, 3, 5, 4, 4, 3, 4, 4, 5, 0, 1, 2, 0, 1, 2, 0, 2, 2, 0, 1, 0, 0, 5, 2, 2, 1, 4, 0, 3, 0, 1, 0, 4, 4, 3, 5, 4, 3, 0, 2, 1, 0, 4, 3), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 3, 0, 5, 0, 4, 0, 2, 1, 4, 4, 2, 4, 1, 4, 2, 4, 2, 4, 3, 3, 3, 4, 3, 3, 3, 3, 1, 4, 2, 3, 3, 3, 1, 4, 4, 1, 1, 1, 4, 3, 3, 2, 0, 2, 4, 3, 2, 0, 3, 3, 0, 3, 1, 1, 0, 0, 0, 3, 3, 0, 4, 2, 2, 3, 4, 0, 4, 0, 3, 0, 4, 4, 5, 3, 4, 4, 0, 3, 0, 0, 1, 4), + (1, 4, 0, 4, 0, 4, 0, 4, 0, 3, 5, 4, 4, 3, 4, 3, 5, 4, 3, 3, 4, 3, 5, 4, 4, 4, 4, 3, 4, 2, 4, 3, 3, 1, 5, 4, 3, 2, 4, 5, 4, 5, 5, 4, 4, 5, 4, 4, 0, 3, 2, 2, 3, 3, 0, 4, 3, 1, 3, 2, 1, 4, 3, 3, 4, 5, 0, 3, 0, 2, 0, 4, 5, 5, 4, 5, 4, 0, 4, 0, 0, 5, 4), + (0, 5, 0, 5, 0, 4, 0, 3, 0, 4, 4, 3, 4, 3, 3, 3, 4, 0, 4, 4, 4, 3, 4, 3, 4, 3, 3, 1, 4, 2, 4, 3, 4, 0, 5, 4, 1, 4, 5, 4, 4, 5, 3, 2, 4, 3, 4, 3, 2, 4, 1, 3, 3, 3, 2, 3, 2, 0, 4, 3, 3, 4, 3, 3, 3, 4, 0, 4, 0, 3, 0, 4, 5, 4, 4, 4, 3, 0, 4, 1, 0, 1, 3), + (0, 3, 1, 4, 0, 3, 0, 2, 0, 3, 4, 4, 3, 1, 4, 2, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 2, 3, 1, 5, 4, 4, 1, 4, 4, 3, 5, 4, 4, 3, 5, 5, 4, 3, 4, 4, 3, 1, 2, 3, 1, 2, 2, 0, 3, 2, 0, 3, 1, 0, 5, 3, 3, 3, 4, 3, 3, 3, 3, 4, 4, 4, 4, 5, 4, 2, 0, 3, 3, 2, 4, 3), + (0, 2, 0, 3, 0, 1, 0, 1, 0, 0, 3, 2, 0, 0, 2, 0, 1, 0, 2, 1, 3, 3, 3, 1, 2, 3, 1, 0, 1, 0, 4, 2, 1, 1, 3, 3, 0, 4, 3, 3, 1, 4, 3, 3, 0, 3, 3, 2, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 4, 1, 0, 2, 3, 2, 2, 2, 1, 3, 3, 3, 4, 4, 3, 2, 0, 3, 1, 0, 3, 3), + (0, 4, 0, 4, 0, 3, 0, 3, 0, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 2, 4, 3, 4, 3, 3, 2, 4, 3, 4, 5, 4, 1, 4, 5, 3, 5, 4, 5, 3, 5, 4, 0, 3, 5, 5, 3, 1, 3, 3, 2, 2, 3, 0, 3, 4, 1, 3, 3, 2, 4, 3, 3, 3, 4, 0, 4, 0, 3, 0, 4, 5, 4, 4, 5, 3, 0, 4, 1, 0, 3, 4), + (0, 2, 0, 3, 0, 3, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 3, 0, 3, 0, 3, 0, 1, 3, 1, 0, 3, 1, 3, 3, 3, 1, 3, 3, 3, 0, 1, 3, 1, 3, 4, 0, 0, 3, 1, 1, 0, 3, 2, 0, 0, 0, 0, 1, 3, 0, 1, 0, 0, 3, 3, 2, 0, 3, 0, 0, 0, 0, 0, 3, 4, 3, 4, 3, 3, 0, 3, 0, 0, 2, 3), + (2, 3, 0, 3, 0, 2, 0, 1, 0, 3, 3, 4, 3, 1, 3, 1, 1, 1, 3, 1, 4, 3, 4, 3, 3, 3, 0, 0, 3, 1, 5, 4, 3, 1, 4, 3, 2, 5, 5, 4, 4, 4, 4, 3, 3, 4, 4, 4, 0, 2, 1, 1, 3, 2, 0, 1, 2, 0, 0, 1, 0, 4, 1, 3, 3, 3, 0, 3, 0, 1, 0, 4, 4, 4, 5, 5, 3, 0, 2, 0, 0, 4, 4), + (0, 2, 0, 1, 0, 3, 1, 3, 0, 2, 3, 3, 3, 0, 3, 1, 0, 0, 3, 0, 3, 2, 3, 1, 3, 2, 1, 1, 0, 0, 4, 2, 1, 0, 2, 3, 1, 4, 3, 2, 0, 4, 4, 3, 1, 3, 1, 3, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4, 1, 1, 1, 2, 0, 3, 0, 0, 0, 3, 4, 2, 4, 3, 2, 0, 1, 0, 0, 3, 3), + (0, 1, 0, 4, 0, 5, 0, 4, 0, 2, 4, 4, 2, 3, 3, 2, 3, 3, 5, 3, 3, 3, 4, 3, 4, 2, 3, 0, 4, 3, 3, 3, 4, 1, 4, 3, 2, 1, 5, 5, 3, 4, 5, 1, 3, 5, 4, 2, 0, 3, 3, 0, 1, 3, 0, 4, 2, 0, 1, 3, 1, 4, 3, 3, 3, 3, 0, 3, 0, 1, 0, 3, 4, 4, 4, 5, 5, 0, 3, 0, 1, 4, 5), + (0, 2, 0, 3, 0, 3, 0, 0, 0, 2, 3, 1, 3, 0, 4, 0, 1, 1, 3, 0, 3, 4, 3, 2, 3, 1, 0, 3, 3, 2, 3, 1, 3, 0, 2, 3, 0, 2, 1, 4, 1, 2, 2, 0, 0, 3, 3, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 2, 2, 0, 3, 2, 1, 3, 3, 0, 2, 0, 2, 0, 0, 3, 3, 1, 2, 4, 0, 3, 0, 2, 2, 3), + (2, 4, 0, 5, 0, 4, 0, 4, 0, 2, 4, 4, 4, 3, 4, 3, 3, 3, 1, 2, 4, 3, 4, 3, 4, 4, 5, 0, 3, 3, 3, 3, 2, 0, 4, 3, 1, 4, 3, 4, 1, 4, 4, 3, 3, 4, 4, 3, 1, 2, 3, 0, 4, 2, 0, 4, 1, 0, 3, 3, 0, 4, 3, 3, 3, 4, 0, 4, 0, 2, 0, 3, 5, 3, 4, 5, 2, 0, 3, 0, 0, 4, 5), + (0, 3, 0, 4, 0, 1, 0, 1, 0, 1, 3, 2, 2, 1, 3, 0, 3, 0, 2, 0, 2, 0, 3, 0, 2, 0, 0, 0, 1, 0, 1, 1, 0, 0, 3, 1, 0, 0, 0, 4, 0, 3, 1, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 3, 1, 0, 3, 0, 0, 0, 1, 4, 4, 4, 3, 0, 0, 4, 0, 0, 1, 4), + (1, 4, 1, 5, 0, 3, 0, 3, 0, 4, 5, 4, 4, 3, 5, 3, 3, 4, 4, 3, 4, 1, 3, 3, 3, 3, 2, 1, 4, 1, 5, 4, 3, 1, 4, 4, 3, 5, 4, 4, 3, 5, 4, 3, 3, 4, 4, 4, 0, 3, 3, 1, 2, 3, 0, 3, 1, 0, 3, 3, 0, 5, 4, 4, 4, 4, 4, 4, 3, 3, 5, 4, 4, 3, 3, 5, 4, 0, 3, 2, 0, 4, 4), + (0, 2, 0, 3, 0, 1, 0, 0, 0, 1, 3, 3, 3, 2, 4, 1, 3, 0, 3, 1, 3, 0, 2, 2, 1, 1, 0, 0, 2, 0, 4, 3, 1, 0, 4, 3, 0, 4, 4, 4, 1, 4, 3, 1, 1, 3, 3, 1, 0, 2, 0, 0, 1, 3, 0, 0, 0, 0, 2, 0, 0, 4, 3, 2, 4, 3, 5, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 0, 2, 1, 0, 3, 3), + (0, 2, 0, 4, 0, 3, 0, 2, 0, 2, 5, 5, 3, 4, 4, 4, 4, 1, 4, 3, 3, 0, 4, 3, 4, 3, 1, 3, 3, 2, 4, 3, 0, 3, 4, 3, 0, 3, 4, 4, 2, 4, 4, 0, 4, 5, 3, 3, 2, 2, 1, 1, 1, 2, 0, 1, 5, 0, 3, 3, 2, 4, 3, 3, 3, 4, 0, 3, 0, 2, 0, 4, 4, 3, 5, 5, 0, 0, 3, 0, 2, 3, 3), + (0, 3, 0, 4, 0, 3, 0, 1, 0, 3, 4, 3, 3, 1, 3, 3, 3, 0, 3, 1, 3, 0, 4, 3, 3, 1, 1, 0, 3, 0, 3, 3, 0, 0, 4, 4, 0, 1, 5, 4, 3, 3, 5, 0, 3, 3, 4, 3, 0, 2, 0, 1, 1, 1, 0, 1, 3, 0, 1, 2, 1, 3, 3, 2, 3, 3, 0, 3, 0, 1, 0, 1, 3, 3, 4, 4, 1, 0, 1, 2, 2, 1, 3), + (0, 1, 0, 4, 0, 4, 0, 3, 0, 1, 3, 3, 3, 2, 3, 1, 1, 0, 3, 0, 3, 3, 4, 3, 2, 4, 2, 0, 1, 0, 4, 3, 2, 0, 4, 3, 0, 5, 3, 3, 2, 4, 4, 4, 3, 3, 3, 4, 0, 1, 3, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 4, 2, 3, 3, 3, 0, 3, 0, 0, 0, 4, 4, 4, 5, 3, 2, 0, 3, 3, 0, 3, 5), + (0, 2, 0, 3, 0, 0, 0, 3, 0, 1, 3, 0, 2, 0, 0, 0, 1, 0, 3, 1, 1, 3, 3, 0, 0, 3, 0, 0, 3, 0, 2, 3, 1, 0, 3, 1, 0, 3, 3, 2, 0, 4, 2, 2, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 1, 0, 1, 0, 0, 0, 1, 3, 1, 2, 0, 0, 0, 1, 0, 0, 1, 4), + (0, 3, 0, 3, 0, 5, 0, 1, 0, 2, 4, 3, 1, 3, 3, 2, 1, 1, 5, 2, 1, 0, 5, 1, 2, 0, 0, 0, 3, 3, 2, 2, 3, 2, 4, 3, 0, 0, 3, 3, 1, 3, 3, 0, 2, 5, 3, 4, 0, 3, 3, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, 2, 2, 3, 3, 3, 0, 2, 0, 1, 0, 3, 4, 4, 2, 5, 4, 0, 3, 0, 0, 3, 5), + (0, 3, 0, 3, 0, 3, 0, 1, 0, 3, 3, 3, 3, 0, 3, 0, 2, 0, 2, 1, 1, 0, 2, 0, 1, 0, 0, 0, 2, 1, 0, 0, 1, 0, 3, 2, 0, 0, 3, 3, 1, 2, 3, 1, 0, 3, 3, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 3, 1, 2, 3, 0, 3, 0, 1, 0, 3, 2, 1, 0, 4, 3, 0, 1, 1, 0, 3, 3), + (0, 4, 0, 5, 0, 3, 0, 3, 0, 4, 5, 5, 4, 3, 5, 3, 4, 3, 5, 3, 3, 2, 5, 3, 4, 4, 4, 3, 4, 3, 4, 5, 5, 3, 4, 4, 3, 4, 4, 5, 4, 4, 4, 3, 4, 5, 5, 4, 2, 3, 4, 2, 3, 4, 0, 3, 3, 1, 4, 3, 2, 4, 3, 3, 5, 5, 0, 3, 0, 3, 0, 5, 5, 5, 5, 4, 4, 0, 4, 0, 1, 4, 4), + (0, 4, 0, 4, 0, 3, 0, 3, 0, 3, 5, 4, 4, 2, 3, 2, 5, 1, 3, 2, 5, 1, 4, 2, 3, 2, 3, 3, 4, 3, 3, 3, 3, 2, 5, 4, 1, 3, 3, 5, 3, 4, 4, 0, 4, 4, 3, 1, 1, 3, 1, 0, 2, 3, 0, 2, 3, 0, 3, 0, 0, 4, 3, 1, 3, 4, 0, 3, 0, 2, 0, 4, 4, 4, 3, 4, 5, 0, 4, 0, 0, 3, 4), + (0, 3, 0, 3, 0, 3, 1, 2, 0, 3, 4, 4, 3, 3, 3, 0, 2, 2, 4, 3, 3, 1, 3, 3, 3, 1, 1, 0, 3, 1, 4, 3, 2, 3, 4, 4, 2, 4, 4, 4, 3, 4, 4, 3, 2, 4, 4, 3, 1, 3, 3, 1, 3, 3, 0, 4, 1, 0, 2, 2, 1, 4, 3, 2, 3, 3, 5, 4, 3, 3, 5, 4, 4, 3, 3, 0, 4, 0, 3, 2, 2, 4, 4), + (0, 2, 0, 1, 0, 0, 0, 0, 0, 1, 2, 1, 3, 0, 0, 0, 0, 0, 2, 0, 1, 2, 1, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 1, 0, 1, 1, 3, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 0, 3, 4, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1), + (0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 4, 1, 4, 0, 3, 0, 4, 0, 3, 0, 4, 0, 3, 0, 3, 0, 4, 1, 5, 1, 4, 0, 0, 3, 0, 5, 0, 5, 2, 0, 1, 0, 0, 0, 2, 1, 4, 0, 1, 3, 0, 0, 3, 0, 0, 3, 1, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0), + (1, 4, 0, 5, 0, 3, 0, 2, 0, 3, 5, 4, 4, 3, 4, 3, 5, 3, 4, 3, 3, 0, 4, 3, 3, 3, 3, 3, 3, 2, 4, 4, 3, 1, 3, 4, 4, 5, 4, 4, 3, 4, 4, 1, 3, 5, 4, 3, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 3, 3, 5, 3, 3, 4, 5, 0, 3, 0, 3, 0, 3, 4, 3, 4, 4, 3, 0, 3, 0, 2, 4, 3), + (0, 1, 0, 4, 0, 0, 0, 0, 0, 1, 4, 0, 4, 1, 4, 2, 4, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 3, 1, 1, 1, 0, 3, 0, 0, 0, 1, 2, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 3, 2, 0, 2, 2, 0, 1, 0, 0, 0, 2, 3, 2, 3, 3, 0, 0, 0, 0, 2, 1, 0), + (0, 5, 1, 5, 0, 3, 0, 3, 0, 5, 4, 4, 5, 1, 5, 3, 3, 0, 4, 3, 4, 3, 5, 3, 4, 3, 3, 2, 4, 3, 4, 3, 3, 0, 3, 3, 1, 4, 4, 3, 4, 4, 4, 3, 4, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 3, 1, 1, 3, 3, 2, 4, 5, 3, 3, 5, 0, 4, 0, 3, 0, 4, 4, 3, 5, 3, 3, 0, 3, 4, 0, 4, 3), + (0, 5, 0, 5, 0, 3, 0, 2, 0, 4, 4, 3, 5, 2, 4, 3, 3, 3, 4, 4, 4, 3, 5, 3, 5, 3, 3, 1, 4, 0, 4, 3, 3, 0, 3, 3, 0, 4, 4, 4, 4, 5, 4, 3, 3, 5, 5, 3, 2, 3, 1, 2, 3, 2, 0, 1, 0, 0, 3, 2, 2, 4, 4, 3, 1, 5, 0, 4, 0, 3, 0, 4, 3, 1, 3, 2, 1, 0, 3, 3, 0, 3, 3), + (0, 4, 0, 5, 0, 5, 0, 4, 0, 4, 5, 5, 5, 3, 4, 3, 3, 2, 5, 4, 4, 3, 5, 3, 5, 3, 4, 0, 4, 3, 4, 4, 3, 2, 4, 4, 3, 4, 5, 4, 4, 5, 5, 0, 3, 5, 5, 4, 1, 3, 3, 2, 3, 3, 1, 3, 1, 0, 4, 3, 1, 4, 4, 3, 4, 5, 0, 4, 0, 2, 0, 4, 3, 4, 4, 3, 3, 0, 4, 0, 0, 5, 5), + (0, 4, 0, 4, 0, 5, 0, 1, 1, 3, 3, 4, 4, 3, 4, 1, 3, 0, 5, 1, 3, 0, 3, 1, 3, 1, 1, 0, 3, 0, 3, 3, 4, 0, 4, 3, 0, 4, 4, 4, 3, 4, 4, 0, 3, 5, 4, 1, 0, 3, 0, 0, 2, 3, 0, 3, 1, 0, 3, 1, 0, 3, 2, 1, 3, 5, 0, 3, 0, 1, 0, 3, 2, 3, 3, 4, 4, 0, 2, 2, 0, 4, 4), + (2, 4, 0, 5, 0, 4, 0, 3, 0, 4, 5, 5, 4, 3, 5, 3, 5, 3, 5, 3, 5, 2, 5, 3, 4, 3, 3, 4, 3, 4, 5, 3, 2, 1, 5, 4, 3, 2, 3, 4, 5, 3, 4, 1, 2, 5, 4, 3, 0, 3, 3, 0, 3, 2, 0, 2, 3, 0, 4, 1, 0, 3, 4, 3, 3, 5, 0, 3, 0, 1, 0, 4, 5, 5, 5, 4, 3, 0, 4, 2, 0, 3, 5), + (0, 5, 0, 4, 0, 4, 0, 2, 0, 5, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 2, 5, 3, 5, 3, 4, 1, 4, 3, 4, 4, 4, 0, 3, 5, 0, 4, 4, 4, 4, 5, 3, 1, 3, 4, 5, 3, 3, 3, 3, 3, 3, 3, 0, 2, 2, 0, 3, 3, 2, 4, 3, 3, 3, 5, 3, 4, 1, 3, 3, 5, 3, 2, 0, 0, 0, 0, 4, 3, 1, 3, 3), + (0, 1, 0, 3, 0, 3, 0, 1, 0, 1, 3, 3, 3, 2, 3, 3, 3, 0, 3, 0, 0, 0, 3, 1, 3, 0, 0, 0, 2, 2, 2, 3, 0, 0, 3, 2, 0, 1, 2, 4, 1, 3, 3, 0, 0, 3, 3, 3, 0, 1, 0, 0, 2, 1, 0, 0, 3, 0, 3, 1, 0, 3, 0, 0, 1, 3, 0, 2, 0, 1, 0, 3, 3, 1, 3, 3, 0, 0, 1, 1, 0, 3, 3), + (0, 2, 0, 3, 0, 2, 1, 4, 0, 2, 2, 3, 1, 1, 3, 1, 1, 0, 2, 0, 3, 1, 2, 3, 1, 3, 0, 0, 1, 0, 4, 3, 2, 3, 3, 3, 1, 4, 2, 3, 3, 3, 3, 1, 0, 3, 1, 4, 0, 1, 1, 0, 1, 2, 0, 1, 1, 0, 1, 1, 0, 3, 1, 3, 2, 2, 0, 1, 0, 0, 0, 2, 3, 3, 3, 1, 0, 0, 0, 0, 0, 2, 3), + (0, 5, 0, 4, 0, 5, 0, 2, 0, 4, 5, 5, 3, 3, 4, 3, 3, 1, 5, 4, 4, 2, 4, 4, 4, 3, 4, 2, 4, 3, 5, 5, 4, 3, 3, 4, 3, 3, 5, 5, 4, 5, 5, 1, 3, 4, 5, 3, 1, 4, 3, 1, 3, 3, 0, 3, 3, 1, 4, 3, 1, 4, 5, 3, 3, 5, 0, 4, 0, 3, 0, 5, 3, 3, 1, 4, 3, 0, 4, 0, 1, 5, 3), + (0, 5, 0, 5, 0, 4, 0, 2, 0, 4, 4, 3, 4, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 5, 3, 3, 5, 2, 4, 4, 4, 3, 4, 4, 3, 3, 4, 4, 5, 5, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 3, 3, 1, 2, 2, 1, 4, 3, 3, 5, 4, 4, 3, 4, 0, 4, 0, 3, 0, 4, 4, 4, 4, 4, 1, 0, 4, 2, 0, 2, 4), + (0, 4, 0, 4, 0, 3, 0, 1, 0, 3, 5, 2, 3, 0, 3, 0, 2, 1, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 1, 3, 3, 3, 0, 3, 3, 0, 0, 3, 3, 3, 5, 3, 3, 3, 3, 3, 2, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 1, 0, 0, 3, 1, 2, 2, 3, 0, 3, 0, 2, 0, 4, 4, 3, 3, 4, 1, 0, 3, 0, 0, 2, 4), + (0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 3, 1, 3, 0, 3, 2, 0, 0, 0, 1, 0, 3, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 0, 2, 0, 0, 0, 0, 0, 0, 2), + (0, 2, 1, 3, 0, 2, 0, 2, 0, 3, 3, 3, 3, 1, 3, 1, 3, 3, 3, 3, 3, 3, 4, 2, 2, 1, 2, 1, 4, 0, 4, 3, 1, 3, 3, 3, 2, 4, 3, 5, 4, 3, 3, 3, 3, 3, 3, 3, 0, 1, 3, 0, 2, 0, 0, 1, 0, 0, 1, 0, 0, 4, 2, 0, 2, 3, 0, 3, 3, 0, 3, 3, 4, 2, 3, 1, 4, 0, 1, 2, 0, 2, 3), + (0, 3, 0, 3, 0, 1, 0, 3, 0, 2, 3, 3, 3, 0, 3, 1, 2, 0, 3, 3, 2, 3, 3, 2, 3, 2, 3, 1, 3, 0, 4, 3, 2, 0, 3, 3, 1, 4, 3, 3, 2, 3, 4, 3, 1, 3, 3, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 4, 1, 1, 0, 3, 0, 3, 1, 0, 2, 3, 3, 3, 3, 3, 1, 0, 0, 2, 0, 3, 3), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 3, 0, 3, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3), + (0, 2, 0, 3, 1, 3, 0, 3, 0, 2, 3, 3, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 1, 3, 0, 2, 3, 1, 1, 4, 3, 3, 2, 3, 3, 1, 2, 2, 4, 1, 3, 3, 0, 1, 4, 2, 3, 0, 1, 3, 0, 3, 0, 0, 1, 3, 0, 2, 0, 0, 3, 3, 2, 1, 3, 0, 3, 0, 2, 0, 3, 4, 4, 4, 3, 1, 0, 3, 0, 0, 3, 3), + (0, 2, 0, 1, 0, 2, 0, 0, 0, 1, 3, 2, 2, 1, 3, 0, 1, 1, 3, 0, 3, 2, 3, 1, 2, 0, 2, 0, 1, 1, 3, 3, 3, 0, 3, 3, 1, 1, 2, 3, 2, 3, 3, 1, 2, 3, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 2, 1, 2, 1, 3, 0, 3, 0, 0, 0, 3, 4, 4, 4, 3, 2, 0, 2, 0, 0, 2, 4), + (0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 3), + (0, 3, 0, 3, 0, 2, 0, 3, 0, 3, 3, 3, 2, 3, 2, 2, 2, 0, 3, 1, 3, 3, 3, 2, 3, 3, 0, 0, 3, 0, 3, 2, 2, 0, 2, 3, 1, 4, 3, 4, 3, 3, 2, 3, 1, 5, 4, 4, 0, 3, 1, 2, 1, 3, 0, 3, 1, 1, 2, 0, 2, 3, 1, 3, 1, 3, 0, 3, 0, 1, 0, 3, 3, 4, 4, 2, 1, 0, 2, 1, 0, 2, 4), + (0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 4, 2, 5, 1, 4, 0, 2, 0, 2, 1, 3, 1, 4, 0, 2, 1, 0, 0, 2, 1, 4, 1, 1, 0, 3, 3, 0, 5, 1, 3, 2, 3, 3, 1, 0, 3, 2, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 1, 0, 3, 0, 2, 0, 1, 0, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 2, 3), + (0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, 3), + (0, 1, 0, 3, 0, 4, 0, 3, 0, 2, 4, 3, 1, 0, 3, 2, 2, 1, 3, 1, 2, 2, 3, 1, 1, 1, 2, 1, 3, 0, 1, 2, 0, 1, 3, 2, 1, 3, 0, 5, 5, 1, 0, 0, 1, 3, 2, 1, 0, 3, 0, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 1, 1, 1, 3, 2, 0, 2, 0, 1, 0, 2, 3, 3, 1, 2, 3, 0, 1, 0, 1, 0, 4), + (0, 0, 0, 1, 0, 3, 0, 3, 0, 2, 2, 1, 0, 0, 4, 0, 3, 0, 3, 1, 3, 0, 3, 0, 3, 0, 1, 0, 3, 0, 3, 1, 3, 0, 3, 3, 0, 0, 1, 2, 1, 1, 1, 0, 1, 2, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 0, 0, 2, 0, 0, 0, 0, 2, 3, 3, 3, 3, 0, 0, 0, 0, 1, 4), + (0, 0, 0, 3, 0, 3, 0, 0, 0, 0, 3, 1, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 0, 2, 0, 2, 3, 0, 0, 2, 2, 3, 1, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 0, 2, 3), + (2, 4, 0, 5, 0, 5, 0, 4, 0, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 4, 5, 4, 5, 5, 5, 2, 3, 0, 5, 5, 4, 1, 5, 4, 3, 1, 5, 4, 3, 4, 4, 3, 3, 4, 3, 3, 0, 3, 2, 0, 2, 3, 0, 3, 0, 0, 3, 3, 0, 5, 3, 2, 3, 3, 0, 3, 0, 3, 0, 3, 4, 5, 4, 5, 3, 0, 4, 3, 0, 3, 4), + (0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 3, 4, 3, 2, 3, 2, 3, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 2, 4, 3, 3, 1, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 2, 4, 4, 1, 0, 2, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0, 5, 3, 2, 1, 3, 0, 3, 0, 1, 2, 4, 3, 2, 4, 3, 3, 0, 3, 2, 0, 4, 4), + (0, 3, 0, 3, 0, 1, 0, 0, 0, 1, 4, 3, 3, 2, 3, 1, 3, 1, 4, 2, 3, 2, 4, 2, 3, 4, 3, 0, 2, 2, 3, 3, 3, 0, 3, 3, 3, 0, 3, 4, 1, 3, 3, 0, 3, 4, 3, 3, 0, 1, 1, 0, 1, 0, 0, 0, 4, 0, 3, 0, 0, 3, 1, 2, 1, 3, 0, 4, 0, 1, 0, 4, 3, 3, 4, 3, 3, 0, 2, 0, 0, 3, 3), + (0, 3, 0, 4, 0, 1, 0, 3, 0, 3, 4, 3, 3, 0, 3, 3, 3, 1, 3, 1, 3, 3, 4, 3, 3, 3, 0, 0, 3, 1, 5, 3, 3, 1, 3, 3, 2, 5, 4, 3, 3, 4, 5, 3, 2, 5, 3, 4, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 0, 4, 2, 2, 1, 3, 0, 3, 0, 2, 0, 4, 4, 3, 5, 3, 2, 0, 1, 1, 0, 3, 4), + (0, 5, 0, 4, 0, 5, 0, 2, 0, 4, 4, 3, 3, 2, 3, 3, 3, 1, 4, 3, 4, 1, 5, 3, 4, 3, 4, 0, 4, 2, 4, 3, 4, 1, 5, 4, 0, 4, 4, 4, 4, 5, 4, 1, 3, 5, 4, 2, 1, 4, 1, 1, 3, 2, 0, 3, 1, 0, 3, 2, 1, 4, 3, 3, 3, 4, 0, 4, 0, 3, 0, 4, 4, 4, 3, 3, 3, 0, 4, 2, 0, 3, 4), + (1, 4, 0, 4, 0, 3, 0, 1, 0, 3, 3, 3, 1, 1, 3, 3, 2, 2, 3, 3, 1, 0, 3, 2, 2, 1, 2, 0, 3, 1, 2, 1, 2, 0, 3, 2, 0, 2, 2, 3, 3, 4, 3, 0, 3, 3, 1, 2, 0, 1, 1, 3, 1, 2, 0, 0, 3, 0, 1, 1, 0, 3, 2, 2, 3, 3, 0, 3, 0, 0, 0, 2, 3, 3, 4, 3, 3, 0, 1, 0, 0, 1, 4), + (0, 4, 0, 4, 0, 4, 0, 0, 0, 3, 4, 4, 3, 1, 4, 2, 3, 2, 3, 3, 3, 1, 4, 3, 4, 0, 3, 0, 4, 2, 3, 3, 2, 2, 5, 4, 2, 1, 3, 4, 3, 4, 3, 1, 3, 3, 4, 2, 0, 2, 1, 0, 3, 3, 0, 0, 2, 0, 3, 1, 0, 4, 4, 3, 4, 3, 0, 4, 0, 1, 0, 2, 4, 4, 4, 4, 4, 0, 3, 2, 0, 3, 3), + (0, 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2), + (0, 2, 0, 3, 0, 4, 0, 4, 0, 1, 3, 3, 3, 0, 4, 0, 2, 1, 2, 1, 1, 1, 2, 0, 3, 1, 1, 0, 1, 0, 3, 1, 0, 0, 3, 3, 2, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 2, 2, 0, 3, 1, 0, 0, 1, 0, 1, 1, 0, 1, 2, 0, 3, 0, 0, 0, 0, 1, 0, 0, 3, 3, 4, 3, 1, 0, 1, 0, 3, 0, 2), + (0, 0, 0, 3, 0, 5, 0, 0, 0, 0, 1, 0, 2, 0, 3, 1, 0, 1, 3, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 4, 0, 0, 0, 2, 3, 0, 1, 4, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 3), + (0, 2, 0, 5, 0, 5, 0, 1, 0, 2, 4, 3, 3, 2, 5, 1, 3, 2, 3, 3, 3, 0, 4, 1, 2, 0, 3, 0, 4, 0, 2, 2, 1, 1, 5, 3, 0, 0, 1, 4, 2, 3, 2, 0, 3, 3, 3, 2, 0, 2, 4, 1, 1, 2, 0, 1, 1, 0, 3, 1, 0, 1, 3, 1, 2, 3, 0, 2, 0, 0, 0, 1, 3, 5, 4, 4, 4, 0, 3, 0, 0, 1, 3), + (0, 4, 0, 5, 0, 4, 0, 4, 0, 4, 5, 4, 3, 3, 4, 3, 3, 3, 4, 3, 4, 4, 5, 3, 4, 5, 4, 2, 4, 2, 3, 4, 3, 1, 4, 4, 1, 3, 5, 4, 4, 5, 5, 4, 4, 5, 5, 5, 2, 3, 3, 1, 4, 3, 1, 3, 3, 0, 3, 3, 1, 4, 3, 4, 4, 4, 0, 3, 0, 4, 0, 3, 3, 4, 4, 5, 0, 0, 4, 3, 0, 4, 5), + (0, 4, 0, 4, 0, 3, 0, 3, 0, 3, 4, 4, 4, 3, 3, 2, 4, 3, 4, 3, 4, 3, 5, 3, 4, 3, 2, 1, 4, 2, 4, 4, 3, 1, 3, 4, 2, 4, 5, 5, 3, 4, 5, 4, 1, 5, 4, 3, 0, 3, 2, 2, 3, 2, 1, 3, 1, 0, 3, 3, 3, 5, 3, 3, 3, 5, 4, 4, 2, 3, 3, 4, 3, 3, 3, 2, 1, 0, 3, 2, 1, 4, 3), + (0, 4, 0, 5, 0, 4, 0, 3, 0, 3, 5, 5, 3, 2, 4, 3, 4, 0, 5, 4, 4, 1, 4, 4, 4, 3, 3, 3, 4, 3, 5, 5, 2, 3, 3, 4, 1, 2, 5, 5, 3, 5, 5, 2, 3, 5, 5, 4, 0, 3, 2, 0, 3, 3, 1, 1, 5, 1, 4, 1, 0, 4, 3, 2, 3, 5, 0, 4, 0, 3, 0, 5, 4, 3, 4, 3, 0, 0, 4, 1, 0, 4, 4), + (1, 3, 0, 4, 0, 2, 0, 2, 0, 2, 5, 5, 3, 3, 3, 3, 3, 0, 4, 2, 3, 4, 4, 4, 3, 4, 0, 0, 3, 4, 5, 4, 3, 3, 3, 3, 2, 5, 5, 4, 5, 5, 5, 4, 3, 5, 5, 5, 1, 3, 1, 0, 1, 0, 0, 3, 2, 0, 4, 2, 0, 5, 2, 3, 2, 4, 1, 3, 0, 3, 0, 4, 5, 4, 5, 4, 3, 0, 4, 2, 0, 5, 4), + (0, 3, 0, 4, 0, 5, 0, 3, 0, 3, 4, 4, 3, 2, 3, 2, 3, 3, 3, 3, 3, 2, 4, 3, 3, 2, 2, 0, 3, 3, 3, 3, 3, 1, 3, 3, 3, 0, 4, 4, 3, 4, 4, 1, 1, 4, 4, 2, 0, 3, 1, 0, 1, 1, 0, 4, 1, 0, 2, 3, 1, 3, 3, 1, 3, 4, 0, 3, 0, 1, 0, 3, 1, 3, 0, 0, 1, 0, 2, 0, 0, 4, 4), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 3, 0, 3, 0, 2, 0, 3, 0, 1, 5, 4, 3, 3, 3, 1, 4, 2, 1, 2, 3, 4, 4, 2, 4, 4, 5, 0, 3, 1, 4, 3, 4, 0, 4, 3, 3, 3, 2, 3, 2, 5, 3, 4, 3, 2, 2, 3, 0, 0, 3, 0, 2, 1, 0, 1, 2, 0, 0, 0, 0, 2, 1, 1, 3, 1, 0, 2, 0, 4, 0, 3, 4, 4, 4, 5, 2, 0, 2, 0, 0, 1, 3), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 4, 2, 1, 1, 0, 1, 0, 3, 2, 0, 0, 3, 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 4, 0, 4, 2, 1, 0, 0, 0, 0, 0, 1), + (0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 2, 0, 2, 1, 0, 0, 1, 2, 1, 0, 1, 1, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2), + (0, 4, 0, 4, 0, 4, 0, 3, 0, 4, 4, 3, 4, 2, 4, 3, 2, 0, 4, 4, 4, 3, 5, 3, 5, 3, 3, 2, 4, 2, 4, 3, 4, 3, 1, 4, 0, 2, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 3, 4, 1, 3, 4, 3, 2, 1, 2, 1, 3, 3, 3, 4, 4, 3, 3, 5, 0, 4, 0, 3, 0, 4, 3, 3, 3, 2, 1, 0, 3, 0, 0, 3, 3), + (0, 4, 0, 3, 0, 3, 0, 3, 0, 3, 5, 5, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 4, 3, 5, 3, 3, 1, 3, 2, 4, 5, 5, 5, 5, 4, 3, 4, 5, 5, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 1, 2, 3, 2, 4, 3, 3, 3, 4, 0, 4, 0, 2, 0, 4, 3, 2, 2, 1, 2, 0, 3, 0, 0, 4, 1), +) +# fmt: on + + +class JapaneseContextAnalysis: + NUM_OF_CATEGORY = 6 + DONT_KNOW = -1 + ENOUGH_REL_THRESHOLD = 100 + MAX_REL_THRESHOLD = 1000 + MINIMUM_DATA_THRESHOLD = 4 + + def __init__(self) -> None: + self._total_rel = 0 + self._rel_sample: List[int] = [] + self._need_to_skip_char_num = 0 + self._last_char_order = -1 + self._done = False + self.reset() + + def reset(self) -> None: + self._total_rel = 0 # total sequence received + # category counters, each integer counts sequence in its category + self._rel_sample = [0] * self.NUM_OF_CATEGORY + # if last byte in current buffer is not the last byte of a character, + # we need to know how many bytes to skip in next buffer + self._need_to_skip_char_num = 0 + self._last_char_order = -1 # The order of previous char + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + + def feed(self, byte_str: Union[bytes, bytearray], num_bytes: int) -> None: + if self._done: + return + + # The buffer we got is byte oriented, and a character may span in more than one + # buffers. In case the last one or two byte in last buffer is not + # complete, we record how many byte needed to complete that character + # and skip these bytes here. We can choose to record those bytes as + # well and analyse the character once it is complete, but since a + # character will not make much difference, by simply skipping + # this character will simply our logic and improve performance. + i = self._need_to_skip_char_num + while i < num_bytes: + order, char_len = self.get_order(byte_str[i : i + 2]) + i += char_len + if i > num_bytes: + self._need_to_skip_char_num = i - num_bytes + self._last_char_order = -1 + else: + if (order != -1) and (self._last_char_order != -1): + self._total_rel += 1 + if self._total_rel > self.MAX_REL_THRESHOLD: + self._done = True + break + self._rel_sample[ + jp2_char_context[self._last_char_order][order] + ] += 1 + self._last_char_order = order + + def got_enough_data(self) -> bool: + return self._total_rel > self.ENOUGH_REL_THRESHOLD + + def get_confidence(self) -> float: + # This is just one way to calculate confidence. It works well for me. + if self._total_rel > self.MINIMUM_DATA_THRESHOLD: + return (self._total_rel - self._rel_sample[0]) / self._total_rel + return self.DONT_KNOW + + def get_order(self, _: Union[bytes, bytearray]) -> Tuple[int, int]: + return -1, 1 + + +class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self) -> None: + super().__init__() + self._charset_name = "SHIFT_JIS" + + @property + def charset_name(self) -> str: + return self._charset_name + + def get_order(self, byte_str: Union[bytes, bytearray]) -> Tuple[int, int]: + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): + char_len = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self._charset_name = "CP932" + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 202) and (0x9F <= second_char <= 0xF1): + return second_char - 0x9F, char_len + + return -1, char_len + + +class EUCJPContextAnalysis(JapaneseContextAnalysis): + def get_order(self, byte_str: Union[bytes, bytearray]) -> Tuple[int, int]: + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): + char_len = 2 + elif first_char == 0x8F: + char_len = 3 + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): + return second_char - 0xA1, char_len + + return -1, char_len diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langbulgarianmodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langbulgarianmodel.py new file mode 100644 index 0000000..2f771bb --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langbulgarianmodel.py @@ -0,0 +1,4649 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +BULGARIAN_LANG_MODEL = { + 63: { # 'e' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 1, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 45: { # '\xad' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 1, # 'М' + 36: 0, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 31: { # 'А' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 1, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 2, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 2, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 0, # 'и' + 26: 2, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 32: { # 'Б' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 1, # 'Щ' + 61: 2, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 35: { # 'В' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 2, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 2, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 43: { # 'Г' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 1, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 37: { # 'Д' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 2, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 44: { # 'Е' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 2, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 0, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 55: { # 'Ж' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 47: { # 'З' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 40: { # 'И' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 2, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 2, # 'Я' + 1: 1, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 3, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 0, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 59: { # 'Й' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 33: { # 'К' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 46: { # 'Л' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 2, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 38: { # 'М' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 36: { # 'Н' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 2, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 41: { # 'О' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 2, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 2, # 'ч' + 27: 0, # 'ш' + 24: 2, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 30: { # 'П' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 2, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 39: { # 'Р' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 2, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 28: { # 'С' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 3, # 'А' + 32: 2, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 34: { # 'Т' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 51: { # 'У' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 2, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 48: { # 'Ф' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 49: { # 'Х' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 53: { # 'Ц' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 50: { # 'Ч' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 54: { # 'Ш' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 57: { # 'Щ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 61: { # 'Ъ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 60: { # 'Ю' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 1, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 0, # 'е' + 23: 2, # 'ж' + 15: 1, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 56: { # 'Я' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 1, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 1: { # 'а' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 18: { # 'б' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 0, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 2, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 3, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 9: { # 'в' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 0, # 'в' + 20: 2, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 20: { # 'г' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 11: { # 'д' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 1, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 3: { # 'е' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 2, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 23: { # 'ж' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 15: { # 'з' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 2: { # 'и' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 1, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 26: { # 'й' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 12: { # 'к' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 3, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 10: { # 'л' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 1, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 3, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 14: { # 'м' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 6: { # 'н' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 2, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 3, # 'ф' + 25: 2, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 4: { # 'о' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 13: { # 'п' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 7: { # 'р' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 2, # 'ч' + 27: 3, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 8: { # 'с' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 5: { # 'т' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 19: { # 'у' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 2, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 2, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 29: { # 'ф' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 2, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 25: { # 'х' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 22: { # 'ц' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 21: { # 'ч' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 27: { # 'ш' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 24: { # 'щ' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 17: { # 'ъ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 1, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 3, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 2, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 52: { # 'ь' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 42: { # 'ю' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 1, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 1, # 'е' + 23: 2, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 1, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 16: { # 'я' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 1, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 3, # 'х' + 22: 2, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 2, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 58: { # 'є' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 62: { # '№' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +ISO_8859_5_BULGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 77, # 'A' + 66: 90, # 'B' + 67: 99, # 'C' + 68: 100, # 'D' + 69: 72, # 'E' + 70: 109, # 'F' + 71: 107, # 'G' + 72: 101, # 'H' + 73: 79, # 'I' + 74: 185, # 'J' + 75: 81, # 'K' + 76: 102, # 'L' + 77: 76, # 'M' + 78: 94, # 'N' + 79: 82, # 'O' + 80: 110, # 'P' + 81: 186, # 'Q' + 82: 108, # 'R' + 83: 91, # 'S' + 84: 74, # 'T' + 85: 119, # 'U' + 86: 84, # 'V' + 87: 96, # 'W' + 88: 111, # 'X' + 89: 187, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 65, # 'a' + 98: 69, # 'b' + 99: 70, # 'c' + 100: 66, # 'd' + 101: 63, # 'e' + 102: 68, # 'f' + 103: 112, # 'g' + 104: 103, # 'h' + 105: 92, # 'i' + 106: 194, # 'j' + 107: 104, # 'k' + 108: 95, # 'l' + 109: 86, # 'm' + 110: 87, # 'n' + 111: 71, # 'o' + 112: 116, # 'p' + 113: 195, # 'q' + 114: 85, # 'r' + 115: 93, # 's' + 116: 97, # 't' + 117: 113, # 'u' + 118: 196, # 'v' + 119: 197, # 'w' + 120: 198, # 'x' + 121: 199, # 'y' + 122: 200, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 194, # '\x80' + 129: 195, # '\x81' + 130: 196, # '\x82' + 131: 197, # '\x83' + 132: 198, # '\x84' + 133: 199, # '\x85' + 134: 200, # '\x86' + 135: 201, # '\x87' + 136: 202, # '\x88' + 137: 203, # '\x89' + 138: 204, # '\x8a' + 139: 205, # '\x8b' + 140: 206, # '\x8c' + 141: 207, # '\x8d' + 142: 208, # '\x8e' + 143: 209, # '\x8f' + 144: 210, # '\x90' + 145: 211, # '\x91' + 146: 212, # '\x92' + 147: 213, # '\x93' + 148: 214, # '\x94' + 149: 215, # '\x95' + 150: 216, # '\x96' + 151: 217, # '\x97' + 152: 218, # '\x98' + 153: 219, # '\x99' + 154: 220, # '\x9a' + 155: 221, # '\x9b' + 156: 222, # '\x9c' + 157: 223, # '\x9d' + 158: 224, # '\x9e' + 159: 225, # '\x9f' + 160: 81, # '\xa0' + 161: 226, # 'Ё' + 162: 227, # 'Ђ' + 163: 228, # 'Ѓ' + 164: 229, # 'Є' + 165: 230, # 'Ѕ' + 166: 105, # 'І' + 167: 231, # 'Ї' + 168: 232, # 'Ј' + 169: 233, # 'Љ' + 170: 234, # 'Њ' + 171: 235, # 'Ћ' + 172: 236, # 'Ќ' + 173: 45, # '\xad' + 174: 237, # 'Ў' + 175: 238, # 'Џ' + 176: 31, # 'А' + 177: 32, # 'Б' + 178: 35, # 'В' + 179: 43, # 'Г' + 180: 37, # 'Д' + 181: 44, # 'Е' + 182: 55, # 'Ж' + 183: 47, # 'З' + 184: 40, # 'И' + 185: 59, # 'Й' + 186: 33, # 'К' + 187: 46, # 'Л' + 188: 38, # 'М' + 189: 36, # 'Н' + 190: 41, # 'О' + 191: 30, # 'П' + 192: 39, # 'Р' + 193: 28, # 'С' + 194: 34, # 'Т' + 195: 51, # 'У' + 196: 48, # 'Ф' + 197: 49, # 'Х' + 198: 53, # 'Ц' + 199: 50, # 'Ч' + 200: 54, # 'Ш' + 201: 57, # 'Щ' + 202: 61, # 'Ъ' + 203: 239, # 'Ы' + 204: 67, # 'Ь' + 205: 240, # 'Э' + 206: 60, # 'Ю' + 207: 56, # 'Я' + 208: 1, # 'а' + 209: 18, # 'б' + 210: 9, # 'в' + 211: 20, # 'г' + 212: 11, # 'д' + 213: 3, # 'е' + 214: 23, # 'ж' + 215: 15, # 'з' + 216: 2, # 'и' + 217: 26, # 'й' + 218: 12, # 'к' + 219: 10, # 'л' + 220: 14, # 'м' + 221: 6, # 'н' + 222: 4, # 'о' + 223: 13, # 'п' + 224: 7, # 'р' + 225: 8, # 'с' + 226: 5, # 'т' + 227: 19, # 'у' + 228: 29, # 'ф' + 229: 25, # 'х' + 230: 22, # 'ц' + 231: 21, # 'ч' + 232: 27, # 'ш' + 233: 24, # 'щ' + 234: 17, # 'ъ' + 235: 75, # 'ы' + 236: 52, # 'ь' + 237: 241, # 'э' + 238: 42, # 'ю' + 239: 16, # 'я' + 240: 62, # '№' + 241: 242, # 'ё' + 242: 243, # 'ђ' + 243: 244, # 'ѓ' + 244: 58, # 'є' + 245: 245, # 'ѕ' + 246: 98, # 'і' + 247: 246, # 'ї' + 248: 247, # 'ј' + 249: 248, # 'љ' + 250: 249, # 'њ' + 251: 250, # 'ћ' + 252: 251, # 'ќ' + 253: 91, # '§' + 254: 252, # 'ў' + 255: 253, # 'џ' +} + +ISO_8859_5_BULGARIAN_MODEL = SingleByteCharSetModel( + charset_name="ISO-8859-5", + language="Bulgarian", + char_to_order_map=ISO_8859_5_BULGARIAN_CHAR_TO_ORDER, + language_model=BULGARIAN_LANG_MODEL, + typical_positive_ratio=0.969392, + keep_ascii_letters=False, + alphabet="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя", +) + +WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 77, # 'A' + 66: 90, # 'B' + 67: 99, # 'C' + 68: 100, # 'D' + 69: 72, # 'E' + 70: 109, # 'F' + 71: 107, # 'G' + 72: 101, # 'H' + 73: 79, # 'I' + 74: 185, # 'J' + 75: 81, # 'K' + 76: 102, # 'L' + 77: 76, # 'M' + 78: 94, # 'N' + 79: 82, # 'O' + 80: 110, # 'P' + 81: 186, # 'Q' + 82: 108, # 'R' + 83: 91, # 'S' + 84: 74, # 'T' + 85: 119, # 'U' + 86: 84, # 'V' + 87: 96, # 'W' + 88: 111, # 'X' + 89: 187, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 65, # 'a' + 98: 69, # 'b' + 99: 70, # 'c' + 100: 66, # 'd' + 101: 63, # 'e' + 102: 68, # 'f' + 103: 112, # 'g' + 104: 103, # 'h' + 105: 92, # 'i' + 106: 194, # 'j' + 107: 104, # 'k' + 108: 95, # 'l' + 109: 86, # 'm' + 110: 87, # 'n' + 111: 71, # 'o' + 112: 116, # 'p' + 113: 195, # 'q' + 114: 85, # 'r' + 115: 93, # 's' + 116: 97, # 't' + 117: 113, # 'u' + 118: 196, # 'v' + 119: 197, # 'w' + 120: 198, # 'x' + 121: 199, # 'y' + 122: 200, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 206, # 'Ђ' + 129: 207, # 'Ѓ' + 130: 208, # '‚' + 131: 209, # 'ѓ' + 132: 210, # '„' + 133: 211, # '…' + 134: 212, # '†' + 135: 213, # '‡' + 136: 120, # '€' + 137: 214, # '‰' + 138: 215, # 'Љ' + 139: 216, # '‹' + 140: 217, # 'Њ' + 141: 218, # 'Ќ' + 142: 219, # 'Ћ' + 143: 220, # 'Џ' + 144: 221, # 'ђ' + 145: 78, # '‘' + 146: 64, # '’' + 147: 83, # '“' + 148: 121, # '”' + 149: 98, # '•' + 150: 117, # '–' + 151: 105, # '—' + 152: 222, # None + 153: 223, # '™' + 154: 224, # 'љ' + 155: 225, # '›' + 156: 226, # 'њ' + 157: 227, # 'ќ' + 158: 228, # 'ћ' + 159: 229, # 'џ' + 160: 88, # '\xa0' + 161: 230, # 'Ў' + 162: 231, # 'ў' + 163: 232, # 'Ј' + 164: 233, # '¤' + 165: 122, # 'Ґ' + 166: 89, # '¦' + 167: 106, # '§' + 168: 234, # 'Ё' + 169: 235, # '©' + 170: 236, # 'Є' + 171: 237, # '«' + 172: 238, # '¬' + 173: 45, # '\xad' + 174: 239, # '®' + 175: 240, # 'Ї' + 176: 73, # '°' + 177: 80, # '±' + 178: 118, # 'І' + 179: 114, # 'і' + 180: 241, # 'ґ' + 181: 242, # 'µ' + 182: 243, # '¶' + 183: 244, # '·' + 184: 245, # 'ё' + 185: 62, # '№' + 186: 58, # 'є' + 187: 246, # '»' + 188: 247, # 'ј' + 189: 248, # 'Ѕ' + 190: 249, # 'ѕ' + 191: 250, # 'ї' + 192: 31, # 'А' + 193: 32, # 'Б' + 194: 35, # 'В' + 195: 43, # 'Г' + 196: 37, # 'Д' + 197: 44, # 'Е' + 198: 55, # 'Ж' + 199: 47, # 'З' + 200: 40, # 'И' + 201: 59, # 'Й' + 202: 33, # 'К' + 203: 46, # 'Л' + 204: 38, # 'М' + 205: 36, # 'Н' + 206: 41, # 'О' + 207: 30, # 'П' + 208: 39, # 'Р' + 209: 28, # 'С' + 210: 34, # 'Т' + 211: 51, # 'У' + 212: 48, # 'Ф' + 213: 49, # 'Х' + 214: 53, # 'Ц' + 215: 50, # 'Ч' + 216: 54, # 'Ш' + 217: 57, # 'Щ' + 218: 61, # 'Ъ' + 219: 251, # 'Ы' + 220: 67, # 'Ь' + 221: 252, # 'Э' + 222: 60, # 'Ю' + 223: 56, # 'Я' + 224: 1, # 'а' + 225: 18, # 'б' + 226: 9, # 'в' + 227: 20, # 'г' + 228: 11, # 'д' + 229: 3, # 'е' + 230: 23, # 'ж' + 231: 15, # 'з' + 232: 2, # 'и' + 233: 26, # 'й' + 234: 12, # 'к' + 235: 10, # 'л' + 236: 14, # 'м' + 237: 6, # 'н' + 238: 4, # 'о' + 239: 13, # 'п' + 240: 7, # 'р' + 241: 8, # 'с' + 242: 5, # 'т' + 243: 19, # 'у' + 244: 29, # 'ф' + 245: 25, # 'х' + 246: 22, # 'ц' + 247: 21, # 'ч' + 248: 27, # 'ш' + 249: 24, # 'щ' + 250: 17, # 'ъ' + 251: 75, # 'ы' + 252: 52, # 'ь' + 253: 253, # 'э' + 254: 42, # 'ю' + 255: 16, # 'я' +} + +WINDOWS_1251_BULGARIAN_MODEL = SingleByteCharSetModel( + charset_name="windows-1251", + language="Bulgarian", + char_to_order_map=WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER, + language_model=BULGARIAN_LANG_MODEL, + typical_positive_ratio=0.969392, + keep_ascii_letters=False, + alphabet="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langgreekmodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langgreekmodel.py new file mode 100644 index 0000000..0471d8b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langgreekmodel.py @@ -0,0 +1,4397 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +GREEK_LANG_MODEL = { + 60: { # 'e' + 60: 2, # 'e' + 55: 1, # 'o' + 58: 2, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 55: { # 'o' + 60: 0, # 'e' + 55: 2, # 'o' + 58: 2, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 58: { # 't' + 60: 2, # 'e' + 55: 1, # 'o' + 58: 1, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 1, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 36: { # '·' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 61: { # 'Ά' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 1, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 1, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 46: { # 'Έ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 2, # 'β' + 20: 2, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 1, # 'σ' + 2: 2, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 54: { # 'Ό' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 31: { # 'Α' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 2, # 'Β' + 43: 2, # 'Γ' + 41: 1, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 2, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 1, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 2, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 2, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 1, # 'θ' + 5: 0, # 'ι' + 11: 2, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 2, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 51: { # 'Β' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 1, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 43: { # 'Γ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 1, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 41: { # 'Δ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 1, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 34: { # 'Ε' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 2, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 1, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 2, # 'Χ' + 57: 2, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 1, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 1, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 1, # 'ύ' + 27: 0, # 'ώ' + }, + 40: { # 'Η' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 2, # 'Θ' + 47: 0, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 52: { # 'Θ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 1, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 47: { # 'Ι' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 1, # 'Β' + 43: 1, # 'Γ' + 41: 2, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 2, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 1, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 1, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 44: { # 'Κ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 1, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 1, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 53: { # 'Λ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 2, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 2, # 'Σ' + 33: 0, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 1, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 38: { # 'Μ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 2, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 2, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 49: { # 'Ν' + 60: 2, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 1, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 1, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 59: { # 'Ξ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 39: { # 'Ο' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 1, # 'Β' + 43: 2, # 'Γ' + 41: 2, # 'Δ' + 34: 2, # 'Ε' + 40: 1, # 'Η' + 52: 2, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 2, # 'Φ' + 50: 2, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 1, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 35: { # 'Π' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 2, # 'Λ' + 38: 1, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 1, # 'έ' + 22: 1, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 3, # 'ώ' + }, + 48: { # 'Ρ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 1, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 1, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 37: { # 'Σ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 2, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 2, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 2, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 2, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 33: { # 'Τ' + 60: 0, # 'e' + 55: 1, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 2, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 45: { # 'Υ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 2, # 'Η' + 52: 2, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 1, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 56: { # 'Φ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 1, # 'ύ' + 27: 1, # 'ώ' + }, + 50: { # 'Χ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 1, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 1, # 'Ω' + 17: 2, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 57: { # 'Ω' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 2, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 17: { # 'ά' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 3, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 3, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 18: { # 'έ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 3, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 22: { # 'ή' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 15: { # 'ί' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 3, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 1, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 1: { # 'α' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 2, # 'ε' + 32: 3, # 'ζ' + 13: 1, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 29: { # 'β' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 2, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 3, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 20: { # 'γ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 21: { # 'δ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 3: { # 'ε' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 2, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 2, # 'ε' + 32: 2, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 32: { # 'ζ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 2, # 'ώ' + }, + 13: { # 'η' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 25: { # 'θ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 1, # 'λ' + 10: 3, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 5: { # 'ι' + 60: 0, # 'e' + 55: 1, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 0, # 'ύ' + 27: 3, # 'ώ' + }, + 11: { # 'κ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 16: { # 'λ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 1, # 'β' + 20: 2, # 'γ' + 21: 1, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 10: { # 'μ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 3, # 'φ' + 23: 0, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 6: { # 'ν' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 1, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 30: { # 'ξ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 3, # 'ύ' + 27: 1, # 'ώ' + }, + 4: { # 'ο' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 2, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 1, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 9: { # 'π' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 3, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 2, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 8: { # 'ρ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 1, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 3, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 14: { # 'ς' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 7: { # 'σ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 2: { # 'τ' + 60: 0, # 'e' + 55: 2, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 12: { # 'υ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 2, # 'ε' + 32: 2, # 'ζ' + 13: 2, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 2, # 'ώ' + }, + 28: { # 'φ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 1, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 23: { # 'χ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 42: { # 'ψ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 1, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 24: { # 'ω' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 1, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 19: { # 'ό' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 1, # 'ε' + 32: 2, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 1, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 26: { # 'ύ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 2, # 'β' + 20: 2, # 'γ' + 21: 1, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 27: { # 'ώ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 1, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 1, # 'η' + 25: 2, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 1, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 1, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +WINDOWS_1253_GREEK_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 82, # 'A' + 66: 100, # 'B' + 67: 104, # 'C' + 68: 94, # 'D' + 69: 98, # 'E' + 70: 101, # 'F' + 71: 116, # 'G' + 72: 102, # 'H' + 73: 111, # 'I' + 74: 187, # 'J' + 75: 117, # 'K' + 76: 92, # 'L' + 77: 88, # 'M' + 78: 113, # 'N' + 79: 85, # 'O' + 80: 79, # 'P' + 81: 118, # 'Q' + 82: 105, # 'R' + 83: 83, # 'S' + 84: 67, # 'T' + 85: 114, # 'U' + 86: 119, # 'V' + 87: 95, # 'W' + 88: 99, # 'X' + 89: 109, # 'Y' + 90: 188, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 72, # 'a' + 98: 70, # 'b' + 99: 80, # 'c' + 100: 81, # 'd' + 101: 60, # 'e' + 102: 96, # 'f' + 103: 93, # 'g' + 104: 89, # 'h' + 105: 68, # 'i' + 106: 120, # 'j' + 107: 97, # 'k' + 108: 77, # 'l' + 109: 86, # 'm' + 110: 69, # 'n' + 111: 55, # 'o' + 112: 78, # 'p' + 113: 115, # 'q' + 114: 65, # 'r' + 115: 66, # 's' + 116: 58, # 't' + 117: 76, # 'u' + 118: 106, # 'v' + 119: 103, # 'w' + 120: 87, # 'x' + 121: 107, # 'y' + 122: 112, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 255, # '€' + 129: 255, # None + 130: 255, # '‚' + 131: 255, # 'ƒ' + 132: 255, # '„' + 133: 255, # '…' + 134: 255, # '†' + 135: 255, # '‡' + 136: 255, # None + 137: 255, # '‰' + 138: 255, # None + 139: 255, # '‹' + 140: 255, # None + 141: 255, # None + 142: 255, # None + 143: 255, # None + 144: 255, # None + 145: 255, # '‘' + 146: 255, # '’' + 147: 255, # '“' + 148: 255, # '”' + 149: 255, # '•' + 150: 255, # '–' + 151: 255, # '—' + 152: 255, # None + 153: 255, # '™' + 154: 255, # None + 155: 255, # '›' + 156: 255, # None + 157: 255, # None + 158: 255, # None + 159: 255, # None + 160: 253, # '\xa0' + 161: 233, # '΅' + 162: 61, # 'Ά' + 163: 253, # '£' + 164: 253, # '¤' + 165: 253, # '¥' + 166: 253, # '¦' + 167: 253, # '§' + 168: 253, # '¨' + 169: 253, # '©' + 170: 253, # None + 171: 253, # '«' + 172: 253, # '¬' + 173: 74, # '\xad' + 174: 253, # '®' + 175: 253, # '―' + 176: 253, # '°' + 177: 253, # '±' + 178: 253, # '²' + 179: 253, # '³' + 180: 247, # '΄' + 181: 253, # 'µ' + 182: 253, # '¶' + 183: 36, # '·' + 184: 46, # 'Έ' + 185: 71, # 'Ή' + 186: 73, # 'Ί' + 187: 253, # '»' + 188: 54, # 'Ό' + 189: 253, # '½' + 190: 108, # 'Ύ' + 191: 123, # 'Ώ' + 192: 110, # 'ΐ' + 193: 31, # 'Α' + 194: 51, # 'Β' + 195: 43, # 'Γ' + 196: 41, # 'Δ' + 197: 34, # 'Ε' + 198: 91, # 'Ζ' + 199: 40, # 'Η' + 200: 52, # 'Θ' + 201: 47, # 'Ι' + 202: 44, # 'Κ' + 203: 53, # 'Λ' + 204: 38, # 'Μ' + 205: 49, # 'Ν' + 206: 59, # 'Ξ' + 207: 39, # 'Ο' + 208: 35, # 'Π' + 209: 48, # 'Ρ' + 210: 250, # None + 211: 37, # 'Σ' + 212: 33, # 'Τ' + 213: 45, # 'Υ' + 214: 56, # 'Φ' + 215: 50, # 'Χ' + 216: 84, # 'Ψ' + 217: 57, # 'Ω' + 218: 120, # 'Ϊ' + 219: 121, # 'Ϋ' + 220: 17, # 'ά' + 221: 18, # 'έ' + 222: 22, # 'ή' + 223: 15, # 'ί' + 224: 124, # 'ΰ' + 225: 1, # 'α' + 226: 29, # 'β' + 227: 20, # 'γ' + 228: 21, # 'δ' + 229: 3, # 'ε' + 230: 32, # 'ζ' + 231: 13, # 'η' + 232: 25, # 'θ' + 233: 5, # 'ι' + 234: 11, # 'κ' + 235: 16, # 'λ' + 236: 10, # 'μ' + 237: 6, # 'ν' + 238: 30, # 'ξ' + 239: 4, # 'ο' + 240: 9, # 'π' + 241: 8, # 'ρ' + 242: 14, # 'ς' + 243: 7, # 'σ' + 244: 2, # 'τ' + 245: 12, # 'υ' + 246: 28, # 'φ' + 247: 23, # 'χ' + 248: 42, # 'ψ' + 249: 24, # 'ω' + 250: 64, # 'ϊ' + 251: 75, # 'ϋ' + 252: 19, # 'ό' + 253: 26, # 'ύ' + 254: 27, # 'ώ' + 255: 253, # None +} + +WINDOWS_1253_GREEK_MODEL = SingleByteCharSetModel( + charset_name="windows-1253", + language="Greek", + char_to_order_map=WINDOWS_1253_GREEK_CHAR_TO_ORDER, + language_model=GREEK_LANG_MODEL, + typical_positive_ratio=0.982851, + keep_ascii_letters=False, + alphabet="ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ", +) + +ISO_8859_7_GREEK_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 82, # 'A' + 66: 100, # 'B' + 67: 104, # 'C' + 68: 94, # 'D' + 69: 98, # 'E' + 70: 101, # 'F' + 71: 116, # 'G' + 72: 102, # 'H' + 73: 111, # 'I' + 74: 187, # 'J' + 75: 117, # 'K' + 76: 92, # 'L' + 77: 88, # 'M' + 78: 113, # 'N' + 79: 85, # 'O' + 80: 79, # 'P' + 81: 118, # 'Q' + 82: 105, # 'R' + 83: 83, # 'S' + 84: 67, # 'T' + 85: 114, # 'U' + 86: 119, # 'V' + 87: 95, # 'W' + 88: 99, # 'X' + 89: 109, # 'Y' + 90: 188, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 72, # 'a' + 98: 70, # 'b' + 99: 80, # 'c' + 100: 81, # 'd' + 101: 60, # 'e' + 102: 96, # 'f' + 103: 93, # 'g' + 104: 89, # 'h' + 105: 68, # 'i' + 106: 120, # 'j' + 107: 97, # 'k' + 108: 77, # 'l' + 109: 86, # 'm' + 110: 69, # 'n' + 111: 55, # 'o' + 112: 78, # 'p' + 113: 115, # 'q' + 114: 65, # 'r' + 115: 66, # 's' + 116: 58, # 't' + 117: 76, # 'u' + 118: 106, # 'v' + 119: 103, # 'w' + 120: 87, # 'x' + 121: 107, # 'y' + 122: 112, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 255, # '\x80' + 129: 255, # '\x81' + 130: 255, # '\x82' + 131: 255, # '\x83' + 132: 255, # '\x84' + 133: 255, # '\x85' + 134: 255, # '\x86' + 135: 255, # '\x87' + 136: 255, # '\x88' + 137: 255, # '\x89' + 138: 255, # '\x8a' + 139: 255, # '\x8b' + 140: 255, # '\x8c' + 141: 255, # '\x8d' + 142: 255, # '\x8e' + 143: 255, # '\x8f' + 144: 255, # '\x90' + 145: 255, # '\x91' + 146: 255, # '\x92' + 147: 255, # '\x93' + 148: 255, # '\x94' + 149: 255, # '\x95' + 150: 255, # '\x96' + 151: 255, # '\x97' + 152: 255, # '\x98' + 153: 255, # '\x99' + 154: 255, # '\x9a' + 155: 255, # '\x9b' + 156: 255, # '\x9c' + 157: 255, # '\x9d' + 158: 255, # '\x9e' + 159: 255, # '\x9f' + 160: 253, # '\xa0' + 161: 233, # '‘' + 162: 90, # '’' + 163: 253, # '£' + 164: 253, # '€' + 165: 253, # '₯' + 166: 253, # '¦' + 167: 253, # '§' + 168: 253, # '¨' + 169: 253, # '©' + 170: 253, # 'ͺ' + 171: 253, # '«' + 172: 253, # '¬' + 173: 74, # '\xad' + 174: 253, # None + 175: 253, # '―' + 176: 253, # '°' + 177: 253, # '±' + 178: 253, # '²' + 179: 253, # '³' + 180: 247, # '΄' + 181: 248, # '΅' + 182: 61, # 'Ά' + 183: 36, # '·' + 184: 46, # 'Έ' + 185: 71, # 'Ή' + 186: 73, # 'Ί' + 187: 253, # '»' + 188: 54, # 'Ό' + 189: 253, # '½' + 190: 108, # 'Ύ' + 191: 123, # 'Ώ' + 192: 110, # 'ΐ' + 193: 31, # 'Α' + 194: 51, # 'Β' + 195: 43, # 'Γ' + 196: 41, # 'Δ' + 197: 34, # 'Ε' + 198: 91, # 'Ζ' + 199: 40, # 'Η' + 200: 52, # 'Θ' + 201: 47, # 'Ι' + 202: 44, # 'Κ' + 203: 53, # 'Λ' + 204: 38, # 'Μ' + 205: 49, # 'Ν' + 206: 59, # 'Ξ' + 207: 39, # 'Ο' + 208: 35, # 'Π' + 209: 48, # 'Ρ' + 210: 250, # None + 211: 37, # 'Σ' + 212: 33, # 'Τ' + 213: 45, # 'Υ' + 214: 56, # 'Φ' + 215: 50, # 'Χ' + 216: 84, # 'Ψ' + 217: 57, # 'Ω' + 218: 120, # 'Ϊ' + 219: 121, # 'Ϋ' + 220: 17, # 'ά' + 221: 18, # 'έ' + 222: 22, # 'ή' + 223: 15, # 'ί' + 224: 124, # 'ΰ' + 225: 1, # 'α' + 226: 29, # 'β' + 227: 20, # 'γ' + 228: 21, # 'δ' + 229: 3, # 'ε' + 230: 32, # 'ζ' + 231: 13, # 'η' + 232: 25, # 'θ' + 233: 5, # 'ι' + 234: 11, # 'κ' + 235: 16, # 'λ' + 236: 10, # 'μ' + 237: 6, # 'ν' + 238: 30, # 'ξ' + 239: 4, # 'ο' + 240: 9, # 'π' + 241: 8, # 'ρ' + 242: 14, # 'ς' + 243: 7, # 'σ' + 244: 2, # 'τ' + 245: 12, # 'υ' + 246: 28, # 'φ' + 247: 23, # 'χ' + 248: 42, # 'ψ' + 249: 24, # 'ω' + 250: 64, # 'ϊ' + 251: 75, # 'ϋ' + 252: 19, # 'ό' + 253: 26, # 'ύ' + 254: 27, # 'ώ' + 255: 253, # None +} + +ISO_8859_7_GREEK_MODEL = SingleByteCharSetModel( + charset_name="ISO-8859-7", + language="Greek", + char_to_order_map=ISO_8859_7_GREEK_CHAR_TO_ORDER, + language_model=GREEK_LANG_MODEL, + typical_positive_ratio=0.982851, + keep_ascii_letters=False, + alphabet="ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhebrewmodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhebrewmodel.py new file mode 100644 index 0000000..86b3c5e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhebrewmodel.py @@ -0,0 +1,4380 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +HEBREW_LANG_MODEL = { + 50: { # 'a' + 50: 0, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 2, # 'l' + 54: 2, # 'n' + 49: 0, # 'o' + 51: 2, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 0, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 60: { # 'c' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 61: { # 'd' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 0, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 42: { # 'e' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 2, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 2, # 'l' + 54: 2, # 'n' + 49: 1, # 'o' + 51: 2, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 1, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 53: { # 'i' + 50: 1, # 'a' + 60: 2, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 0, # 'i' + 56: 1, # 'l' + 54: 2, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 56: { # 'l' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 2, # 'e' + 53: 2, # 'i' + 56: 2, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 54: { # 'n' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 49: { # 'o' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 2, # 'n' + 49: 1, # 'o' + 51: 2, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 51: { # 'r' + 50: 2, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 2, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 43: { # 's' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 2, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 2, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 44: { # 't' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 2, # 'e' + 53: 2, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 63: { # 'u' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 34: { # '\xa0' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 1, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 2, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 55: { # '´' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 2, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 1, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 48: { # '¼' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 39: { # '½' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 57: { # '¾' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 30: { # 'ְ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 2, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 0, # 'ף' + 18: 2, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 59: { # 'ֱ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 1, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 41: { # 'ֲ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 33: { # 'ִ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 2, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 37: { # 'ֵ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 1, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 36: { # 'ֶ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 1, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 2, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 31: { # 'ַ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 29: { # 'ָ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 2, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 35: { # 'ֹ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 62: { # 'ֻ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 1, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 28: { # 'ּ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 3, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 3, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 3, # 'ַ' + 29: 3, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 2, # 'ׁ' + 45: 1, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 2, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 2, # 'מ' + 23: 1, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 38: { # 'ׁ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 2, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 45: { # 'ׂ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 2, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 9: { # 'א' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 2, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 8: { # 'ב' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 1, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 20: { # 'ג' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 2, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 16: { # 'ד' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 3: { # 'ה' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 3, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 0, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 2: { # 'ו' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 3, # 'ֹ' + 62: 0, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 24: { # 'ז' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 1, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 1, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 14: { # 'ח' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 1, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 22: { # 'ט' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 1, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 2, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 1: { # 'י' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 25: { # 'ך' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 15: { # 'כ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 4: { # 'ל' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 3, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 11: { # 'ם' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 6: { # 'מ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 0, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 23: { # 'ן' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 12: { # 'נ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 19: { # 'ס' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 2, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 1, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 13: { # 'ע' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 1, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 26: { # 'ף' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 18: { # 'פ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 2, # 'ב' + 20: 3, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 27: { # 'ץ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 21: { # 'צ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 1, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 0, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 17: { # 'ק' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 7: { # 'ר' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 2, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 10: { # 'ש' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 3, # 'ׁ' + 45: 2, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 5: { # 'ת' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 1, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 32: { # '–' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 52: { # '’' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 47: { # '“' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 46: { # '”' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 58: { # '†' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 2, # '†' + 40: 0, # '…' + }, + 40: { # '…' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +WINDOWS_1255_HEBREW_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 69, # 'A' + 66: 91, # 'B' + 67: 79, # 'C' + 68: 80, # 'D' + 69: 92, # 'E' + 70: 89, # 'F' + 71: 97, # 'G' + 72: 90, # 'H' + 73: 68, # 'I' + 74: 111, # 'J' + 75: 112, # 'K' + 76: 82, # 'L' + 77: 73, # 'M' + 78: 95, # 'N' + 79: 85, # 'O' + 80: 78, # 'P' + 81: 121, # 'Q' + 82: 86, # 'R' + 83: 71, # 'S' + 84: 67, # 'T' + 85: 102, # 'U' + 86: 107, # 'V' + 87: 84, # 'W' + 88: 114, # 'X' + 89: 103, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 50, # 'a' + 98: 74, # 'b' + 99: 60, # 'c' + 100: 61, # 'd' + 101: 42, # 'e' + 102: 76, # 'f' + 103: 70, # 'g' + 104: 64, # 'h' + 105: 53, # 'i' + 106: 105, # 'j' + 107: 93, # 'k' + 108: 56, # 'l' + 109: 65, # 'm' + 110: 54, # 'n' + 111: 49, # 'o' + 112: 66, # 'p' + 113: 110, # 'q' + 114: 51, # 'r' + 115: 43, # 's' + 116: 44, # 't' + 117: 63, # 'u' + 118: 81, # 'v' + 119: 77, # 'w' + 120: 98, # 'x' + 121: 75, # 'y' + 122: 108, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 124, # '€' + 129: 202, # None + 130: 203, # '‚' + 131: 204, # 'ƒ' + 132: 205, # '„' + 133: 40, # '…' + 134: 58, # '†' + 135: 206, # '‡' + 136: 207, # 'ˆ' + 137: 208, # '‰' + 138: 209, # None + 139: 210, # '‹' + 140: 211, # None + 141: 212, # None + 142: 213, # None + 143: 214, # None + 144: 215, # None + 145: 83, # '‘' + 146: 52, # '’' + 147: 47, # '“' + 148: 46, # '”' + 149: 72, # '•' + 150: 32, # '–' + 151: 94, # '—' + 152: 216, # '˜' + 153: 113, # '™' + 154: 217, # None + 155: 109, # '›' + 156: 218, # None + 157: 219, # None + 158: 220, # None + 159: 221, # None + 160: 34, # '\xa0' + 161: 116, # '¡' + 162: 222, # '¢' + 163: 118, # '£' + 164: 100, # '₪' + 165: 223, # '¥' + 166: 224, # '¦' + 167: 117, # '§' + 168: 119, # '¨' + 169: 104, # '©' + 170: 125, # '×' + 171: 225, # '«' + 172: 226, # '¬' + 173: 87, # '\xad' + 174: 99, # '®' + 175: 227, # '¯' + 176: 106, # '°' + 177: 122, # '±' + 178: 123, # '²' + 179: 228, # '³' + 180: 55, # '´' + 181: 229, # 'µ' + 182: 230, # '¶' + 183: 101, # '·' + 184: 231, # '¸' + 185: 232, # '¹' + 186: 120, # '÷' + 187: 233, # '»' + 188: 48, # '¼' + 189: 39, # '½' + 190: 57, # '¾' + 191: 234, # '¿' + 192: 30, # 'ְ' + 193: 59, # 'ֱ' + 194: 41, # 'ֲ' + 195: 88, # 'ֳ' + 196: 33, # 'ִ' + 197: 37, # 'ֵ' + 198: 36, # 'ֶ' + 199: 31, # 'ַ' + 200: 29, # 'ָ' + 201: 35, # 'ֹ' + 202: 235, # None + 203: 62, # 'ֻ' + 204: 28, # 'ּ' + 205: 236, # 'ֽ' + 206: 126, # '־' + 207: 237, # 'ֿ' + 208: 238, # '׀' + 209: 38, # 'ׁ' + 210: 45, # 'ׂ' + 211: 239, # '׃' + 212: 240, # 'װ' + 213: 241, # 'ױ' + 214: 242, # 'ײ' + 215: 243, # '׳' + 216: 127, # '״' + 217: 244, # None + 218: 245, # None + 219: 246, # None + 220: 247, # None + 221: 248, # None + 222: 249, # None + 223: 250, # None + 224: 9, # 'א' + 225: 8, # 'ב' + 226: 20, # 'ג' + 227: 16, # 'ד' + 228: 3, # 'ה' + 229: 2, # 'ו' + 230: 24, # 'ז' + 231: 14, # 'ח' + 232: 22, # 'ט' + 233: 1, # 'י' + 234: 25, # 'ך' + 235: 15, # 'כ' + 236: 4, # 'ל' + 237: 11, # 'ם' + 238: 6, # 'מ' + 239: 23, # 'ן' + 240: 12, # 'נ' + 241: 19, # 'ס' + 242: 13, # 'ע' + 243: 26, # 'ף' + 244: 18, # 'פ' + 245: 27, # 'ץ' + 246: 21, # 'צ' + 247: 17, # 'ק' + 248: 7, # 'ר' + 249: 10, # 'ש' + 250: 5, # 'ת' + 251: 251, # None + 252: 252, # None + 253: 128, # '\u200e' + 254: 96, # '\u200f' + 255: 253, # None +} + +WINDOWS_1255_HEBREW_MODEL = SingleByteCharSetModel( + charset_name="windows-1255", + language="Hebrew", + char_to_order_map=WINDOWS_1255_HEBREW_CHAR_TO_ORDER, + language_model=HEBREW_LANG_MODEL, + typical_positive_ratio=0.984004, + keep_ascii_letters=False, + alphabet="אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhungarianmodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhungarianmodel.py new file mode 100644 index 0000000..bd6630a --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langhungarianmodel.py @@ -0,0 +1,4649 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +HUNGARIAN_LANG_MODEL = { + 28: { # 'A' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 2, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 2, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 2, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 1, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 40: { # 'B' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 3, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 54: { # 'C' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 3, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 45: { # 'D' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 32: { # 'E' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 2, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 50: { # 'F' + 28: 1, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 0, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 49: { # 'G' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 2, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 38: { # 'H' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 0, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 1, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 2, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 39: { # 'I' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 2, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 53: { # 'J' + 28: 2, # 'A' + 40: 0, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 0, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 36: { # 'K' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 2, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 41: { # 'L' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 34: { # 'M' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 3, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 1, # 'ű' + }, + 35: { # 'N' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 2, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 2, # 'Y' + 52: 1, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 47: { # 'O' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 2, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 46: { # 'P' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 3, # 'á' + 15: 2, # 'é' + 30: 0, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 43: { # 'R' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 2, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 2, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 33: { # 'S' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 3, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 1, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 37: { # 'T' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 57: { # 'U' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 48: { # 'V' + 28: 2, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 55: { # 'Y' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 2, # 'Z' + 2: 1, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 52: { # 'Z' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 1, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 2: { # 'a' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 18: { # 'b' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 26: { # 'c' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 1, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 2, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 2, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 17: { # 'd' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 2, # 'k' + 6: 1, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 1: { # 'e' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 2, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 27: { # 'f' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 3, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 3, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 12: { # 'g' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 20: { # 'h' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 9: { # 'i' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 3, # 'ó' + 24: 1, # 'ö' + 31: 2, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 1, # 'ű' + }, + 22: { # 'j' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 1, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 1, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 7: { # 'k' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 1, # 'ú' + 29: 3, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 6: { # 'l' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 3, # 'ő' + 56: 1, # 'ű' + }, + 13: { # 'm' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 1, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 3, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 2, # 'ű' + }, + 4: { # 'n' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 1, # 'x' + 16: 3, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 8: { # 'o' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 23: { # 'p' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 10: { # 'r' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 2, # 'ű' + }, + 5: { # 's' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 3: { # 't' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 1, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 3, # 'ő' + 56: 2, # 'ű' + }, + 21: { # 'u' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 2, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 1, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 1, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 19: { # 'v' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 2, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 62: { # 'x' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 16: { # 'y' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 2, # 'ű' + }, + 11: { # 'z' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 51: { # 'Á' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 44: { # 'É' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 61: { # 'Í' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 0, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 58: { # 'Ó' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 2, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 59: { # 'Ö' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 60: { # 'Ú' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 2, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 63: { # 'Ü' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 14: { # 'á' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 15: { # 'é' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 30: { # 'í' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 25: { # 'ó' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 1, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 24: { # 'ö' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 0, # 'a' + 18: 3, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 31: { # 'ú' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 3, # 'j' + 7: 1, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 29: { # 'ü' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 0, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 42: { # 'ő' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 56: { # 'ű' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 28, # 'A' + 66: 40, # 'B' + 67: 54, # 'C' + 68: 45, # 'D' + 69: 32, # 'E' + 70: 50, # 'F' + 71: 49, # 'G' + 72: 38, # 'H' + 73: 39, # 'I' + 74: 53, # 'J' + 75: 36, # 'K' + 76: 41, # 'L' + 77: 34, # 'M' + 78: 35, # 'N' + 79: 47, # 'O' + 80: 46, # 'P' + 81: 72, # 'Q' + 82: 43, # 'R' + 83: 33, # 'S' + 84: 37, # 'T' + 85: 57, # 'U' + 86: 48, # 'V' + 87: 64, # 'W' + 88: 68, # 'X' + 89: 55, # 'Y' + 90: 52, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 2, # 'a' + 98: 18, # 'b' + 99: 26, # 'c' + 100: 17, # 'd' + 101: 1, # 'e' + 102: 27, # 'f' + 103: 12, # 'g' + 104: 20, # 'h' + 105: 9, # 'i' + 106: 22, # 'j' + 107: 7, # 'k' + 108: 6, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 8, # 'o' + 112: 23, # 'p' + 113: 67, # 'q' + 114: 10, # 'r' + 115: 5, # 's' + 116: 3, # 't' + 117: 21, # 'u' + 118: 19, # 'v' + 119: 65, # 'w' + 120: 62, # 'x' + 121: 16, # 'y' + 122: 11, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 161, # '€' + 129: 162, # None + 130: 163, # '‚' + 131: 164, # None + 132: 165, # '„' + 133: 166, # '…' + 134: 167, # '†' + 135: 168, # '‡' + 136: 169, # None + 137: 170, # '‰' + 138: 171, # 'Š' + 139: 172, # '‹' + 140: 173, # 'Ś' + 141: 174, # 'Ť' + 142: 175, # 'Ž' + 143: 176, # 'Ź' + 144: 177, # None + 145: 178, # '‘' + 146: 179, # '’' + 147: 180, # '“' + 148: 78, # '”' + 149: 181, # '•' + 150: 69, # '–' + 151: 182, # '—' + 152: 183, # None + 153: 184, # '™' + 154: 185, # 'š' + 155: 186, # '›' + 156: 187, # 'ś' + 157: 188, # 'ť' + 158: 189, # 'ž' + 159: 190, # 'ź' + 160: 191, # '\xa0' + 161: 192, # 'ˇ' + 162: 193, # '˘' + 163: 194, # 'Ł' + 164: 195, # '¤' + 165: 196, # 'Ą' + 166: 197, # '¦' + 167: 76, # '§' + 168: 198, # '¨' + 169: 199, # '©' + 170: 200, # 'Ş' + 171: 201, # '«' + 172: 202, # '¬' + 173: 203, # '\xad' + 174: 204, # '®' + 175: 205, # 'Ż' + 176: 81, # '°' + 177: 206, # '±' + 178: 207, # '˛' + 179: 208, # 'ł' + 180: 209, # '´' + 181: 210, # 'µ' + 182: 211, # '¶' + 183: 212, # '·' + 184: 213, # '¸' + 185: 214, # 'ą' + 186: 215, # 'ş' + 187: 216, # '»' + 188: 217, # 'Ľ' + 189: 218, # '˝' + 190: 219, # 'ľ' + 191: 220, # 'ż' + 192: 221, # 'Ŕ' + 193: 51, # 'Á' + 194: 83, # 'Â' + 195: 222, # 'Ă' + 196: 80, # 'Ä' + 197: 223, # 'Ĺ' + 198: 224, # 'Ć' + 199: 225, # 'Ç' + 200: 226, # 'Č' + 201: 44, # 'É' + 202: 227, # 'Ę' + 203: 228, # 'Ë' + 204: 229, # 'Ě' + 205: 61, # 'Í' + 206: 230, # 'Î' + 207: 231, # 'Ď' + 208: 232, # 'Đ' + 209: 233, # 'Ń' + 210: 234, # 'Ň' + 211: 58, # 'Ó' + 212: 235, # 'Ô' + 213: 66, # 'Ő' + 214: 59, # 'Ö' + 215: 236, # '×' + 216: 237, # 'Ř' + 217: 238, # 'Ů' + 218: 60, # 'Ú' + 219: 70, # 'Ű' + 220: 63, # 'Ü' + 221: 239, # 'Ý' + 222: 240, # 'Ţ' + 223: 241, # 'ß' + 224: 84, # 'ŕ' + 225: 14, # 'á' + 226: 75, # 'â' + 227: 242, # 'ă' + 228: 71, # 'ä' + 229: 82, # 'ĺ' + 230: 243, # 'ć' + 231: 73, # 'ç' + 232: 244, # 'č' + 233: 15, # 'é' + 234: 85, # 'ę' + 235: 79, # 'ë' + 236: 86, # 'ě' + 237: 30, # 'í' + 238: 77, # 'î' + 239: 87, # 'ď' + 240: 245, # 'đ' + 241: 246, # 'ń' + 242: 247, # 'ň' + 243: 25, # 'ó' + 244: 74, # 'ô' + 245: 42, # 'ő' + 246: 24, # 'ö' + 247: 248, # '÷' + 248: 249, # 'ř' + 249: 250, # 'ů' + 250: 31, # 'ú' + 251: 56, # 'ű' + 252: 29, # 'ü' + 253: 251, # 'ý' + 254: 252, # 'ţ' + 255: 253, # '˙' +} + +WINDOWS_1250_HUNGARIAN_MODEL = SingleByteCharSetModel( + charset_name="windows-1250", + language="Hungarian", + char_to_order_map=WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER, + language_model=HUNGARIAN_LANG_MODEL, + typical_positive_ratio=0.947368, + keep_ascii_letters=True, + alphabet="ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű", +) + +ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 28, # 'A' + 66: 40, # 'B' + 67: 54, # 'C' + 68: 45, # 'D' + 69: 32, # 'E' + 70: 50, # 'F' + 71: 49, # 'G' + 72: 38, # 'H' + 73: 39, # 'I' + 74: 53, # 'J' + 75: 36, # 'K' + 76: 41, # 'L' + 77: 34, # 'M' + 78: 35, # 'N' + 79: 47, # 'O' + 80: 46, # 'P' + 81: 71, # 'Q' + 82: 43, # 'R' + 83: 33, # 'S' + 84: 37, # 'T' + 85: 57, # 'U' + 86: 48, # 'V' + 87: 64, # 'W' + 88: 68, # 'X' + 89: 55, # 'Y' + 90: 52, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 2, # 'a' + 98: 18, # 'b' + 99: 26, # 'c' + 100: 17, # 'd' + 101: 1, # 'e' + 102: 27, # 'f' + 103: 12, # 'g' + 104: 20, # 'h' + 105: 9, # 'i' + 106: 22, # 'j' + 107: 7, # 'k' + 108: 6, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 8, # 'o' + 112: 23, # 'p' + 113: 67, # 'q' + 114: 10, # 'r' + 115: 5, # 's' + 116: 3, # 't' + 117: 21, # 'u' + 118: 19, # 'v' + 119: 65, # 'w' + 120: 62, # 'x' + 121: 16, # 'y' + 122: 11, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 159, # '\x80' + 129: 160, # '\x81' + 130: 161, # '\x82' + 131: 162, # '\x83' + 132: 163, # '\x84' + 133: 164, # '\x85' + 134: 165, # '\x86' + 135: 166, # '\x87' + 136: 167, # '\x88' + 137: 168, # '\x89' + 138: 169, # '\x8a' + 139: 170, # '\x8b' + 140: 171, # '\x8c' + 141: 172, # '\x8d' + 142: 173, # '\x8e' + 143: 174, # '\x8f' + 144: 175, # '\x90' + 145: 176, # '\x91' + 146: 177, # '\x92' + 147: 178, # '\x93' + 148: 179, # '\x94' + 149: 180, # '\x95' + 150: 181, # '\x96' + 151: 182, # '\x97' + 152: 183, # '\x98' + 153: 184, # '\x99' + 154: 185, # '\x9a' + 155: 186, # '\x9b' + 156: 187, # '\x9c' + 157: 188, # '\x9d' + 158: 189, # '\x9e' + 159: 190, # '\x9f' + 160: 191, # '\xa0' + 161: 192, # 'Ą' + 162: 193, # '˘' + 163: 194, # 'Ł' + 164: 195, # '¤' + 165: 196, # 'Ľ' + 166: 197, # 'Ś' + 167: 75, # '§' + 168: 198, # '¨' + 169: 199, # 'Š' + 170: 200, # 'Ş' + 171: 201, # 'Ť' + 172: 202, # 'Ź' + 173: 203, # '\xad' + 174: 204, # 'Ž' + 175: 205, # 'Ż' + 176: 79, # '°' + 177: 206, # 'ą' + 178: 207, # '˛' + 179: 208, # 'ł' + 180: 209, # '´' + 181: 210, # 'ľ' + 182: 211, # 'ś' + 183: 212, # 'ˇ' + 184: 213, # '¸' + 185: 214, # 'š' + 186: 215, # 'ş' + 187: 216, # 'ť' + 188: 217, # 'ź' + 189: 218, # '˝' + 190: 219, # 'ž' + 191: 220, # 'ż' + 192: 221, # 'Ŕ' + 193: 51, # 'Á' + 194: 81, # 'Â' + 195: 222, # 'Ă' + 196: 78, # 'Ä' + 197: 223, # 'Ĺ' + 198: 224, # 'Ć' + 199: 225, # 'Ç' + 200: 226, # 'Č' + 201: 44, # 'É' + 202: 227, # 'Ę' + 203: 228, # 'Ë' + 204: 229, # 'Ě' + 205: 61, # 'Í' + 206: 230, # 'Î' + 207: 231, # 'Ď' + 208: 232, # 'Đ' + 209: 233, # 'Ń' + 210: 234, # 'Ň' + 211: 58, # 'Ó' + 212: 235, # 'Ô' + 213: 66, # 'Ő' + 214: 59, # 'Ö' + 215: 236, # '×' + 216: 237, # 'Ř' + 217: 238, # 'Ů' + 218: 60, # 'Ú' + 219: 69, # 'Ű' + 220: 63, # 'Ü' + 221: 239, # 'Ý' + 222: 240, # 'Ţ' + 223: 241, # 'ß' + 224: 82, # 'ŕ' + 225: 14, # 'á' + 226: 74, # 'â' + 227: 242, # 'ă' + 228: 70, # 'ä' + 229: 80, # 'ĺ' + 230: 243, # 'ć' + 231: 72, # 'ç' + 232: 244, # 'č' + 233: 15, # 'é' + 234: 83, # 'ę' + 235: 77, # 'ë' + 236: 84, # 'ě' + 237: 30, # 'í' + 238: 76, # 'î' + 239: 85, # 'ď' + 240: 245, # 'đ' + 241: 246, # 'ń' + 242: 247, # 'ň' + 243: 25, # 'ó' + 244: 73, # 'ô' + 245: 42, # 'ő' + 246: 24, # 'ö' + 247: 248, # '÷' + 248: 249, # 'ř' + 249: 250, # 'ů' + 250: 31, # 'ú' + 251: 56, # 'ű' + 252: 29, # 'ü' + 253: 251, # 'ý' + 254: 252, # 'ţ' + 255: 253, # '˙' +} + +ISO_8859_2_HUNGARIAN_MODEL = SingleByteCharSetModel( + charset_name="ISO-8859-2", + language="Hungarian", + char_to_order_map=ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER, + language_model=HUNGARIAN_LANG_MODEL, + typical_positive_ratio=0.947368, + keep_ascii_letters=True, + alphabet="ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langrussianmodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langrussianmodel.py new file mode 100644 index 0000000..0d5b178 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langrussianmodel.py @@ -0,0 +1,5725 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +RUSSIAN_LANG_MODEL = { + 37: { # 'А' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 44: { # 'Б' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 33: { # 'В' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 0, # 'ю' + 16: 1, # 'я' + }, + 46: { # 'Г' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 41: { # 'Д' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 3, # 'ж' + 20: 1, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 48: { # 'Е' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 2, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 1, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 56: { # 'Ж' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 1, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 2, # 'ю' + 16: 0, # 'я' + }, + 51: { # 'З' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 1, # 'я' + }, + 42: { # 'И' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 2, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 60: { # 'Й' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 36: { # 'К' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 49: { # 'Л' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 1, # 'я' + }, + 38: { # 'М' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 1, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 31: { # 'Н' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 2, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 34: { # 'О' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 2, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 2, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 35: { # 'П' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 1, # 'с' + 6: 1, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 2, # 'я' + }, + 45: { # 'Р' + 37: 2, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 2, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 32: { # 'С' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 2, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 40: { # 'Т' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 52: { # 'У' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 1, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 1, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 53: { # 'Ф' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 1, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 55: { # 'Х' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 58: { # 'Ц' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 50: { # 'Ч' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 57: { # 'Ш' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 63: { # 'Щ' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 62: { # 'Ы' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 61: { # 'Ь' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 47: { # 'Э' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 59: { # 'Ю' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 43: { # 'Я' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 1, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 3: { # 'а' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 21: { # 'б' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 10: { # 'в' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 19: { # 'г' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 13: { # 'д' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 3, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 2: { # 'е' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 24: { # 'ж' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 1, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 20: { # 'з' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 4: { # 'и' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 23: { # 'й' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 11: { # 'к' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 3, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 8: { # 'л' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 3, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 12: { # 'м' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 5: { # 'н' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 2, # 'щ' + 54: 1, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 1: { # 'о' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 15: { # 'п' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 9: { # 'р' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 7: { # 'с' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 6: { # 'т' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 2, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 14: { # 'у' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 2, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 2, # 'я' + }, + 39: { # 'ф' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 26: { # 'х' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 3, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 28: { # 'ц' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 1, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 22: { # 'ч' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 3, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 25: { # 'ш' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 29: { # 'щ' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 2, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 54: { # 'ъ' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 18: { # 'ы' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 1, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 2, # 'я' + }, + 17: { # 'ь' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 0, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 0, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 30: { # 'э' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 27: { # 'ю' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 1, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 1, # 'я' + }, + 16: { # 'я' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 2, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 2, # 'ю' + 16: 2, # 'я' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +IBM866_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 37, # 'А' + 129: 44, # 'Б' + 130: 33, # 'В' + 131: 46, # 'Г' + 132: 41, # 'Д' + 133: 48, # 'Е' + 134: 56, # 'Ж' + 135: 51, # 'З' + 136: 42, # 'И' + 137: 60, # 'Й' + 138: 36, # 'К' + 139: 49, # 'Л' + 140: 38, # 'М' + 141: 31, # 'Н' + 142: 34, # 'О' + 143: 35, # 'П' + 144: 45, # 'Р' + 145: 32, # 'С' + 146: 40, # 'Т' + 147: 52, # 'У' + 148: 53, # 'Ф' + 149: 55, # 'Х' + 150: 58, # 'Ц' + 151: 50, # 'Ч' + 152: 57, # 'Ш' + 153: 63, # 'Щ' + 154: 70, # 'Ъ' + 155: 62, # 'Ы' + 156: 61, # 'Ь' + 157: 47, # 'Э' + 158: 59, # 'Ю' + 159: 43, # 'Я' + 160: 3, # 'а' + 161: 21, # 'б' + 162: 10, # 'в' + 163: 19, # 'г' + 164: 13, # 'д' + 165: 2, # 'е' + 166: 24, # 'ж' + 167: 20, # 'з' + 168: 4, # 'и' + 169: 23, # 'й' + 170: 11, # 'к' + 171: 8, # 'л' + 172: 12, # 'м' + 173: 5, # 'н' + 174: 1, # 'о' + 175: 15, # 'п' + 176: 191, # '░' + 177: 192, # '▒' + 178: 193, # '▓' + 179: 194, # '│' + 180: 195, # '┤' + 181: 196, # '╡' + 182: 197, # '╢' + 183: 198, # '╖' + 184: 199, # '╕' + 185: 200, # '╣' + 186: 201, # '║' + 187: 202, # '╗' + 188: 203, # '╝' + 189: 204, # '╜' + 190: 205, # '╛' + 191: 206, # '┐' + 192: 207, # '└' + 193: 208, # '┴' + 194: 209, # '┬' + 195: 210, # '├' + 196: 211, # '─' + 197: 212, # '┼' + 198: 213, # '╞' + 199: 214, # '╟' + 200: 215, # '╚' + 201: 216, # '╔' + 202: 217, # '╩' + 203: 218, # '╦' + 204: 219, # '╠' + 205: 220, # '═' + 206: 221, # '╬' + 207: 222, # '╧' + 208: 223, # '╨' + 209: 224, # '╤' + 210: 225, # '╥' + 211: 226, # '╙' + 212: 227, # '╘' + 213: 228, # '╒' + 214: 229, # '╓' + 215: 230, # '╫' + 216: 231, # '╪' + 217: 232, # '┘' + 218: 233, # '┌' + 219: 234, # '█' + 220: 235, # '▄' + 221: 236, # '▌' + 222: 237, # '▐' + 223: 238, # '▀' + 224: 9, # 'р' + 225: 7, # 'с' + 226: 6, # 'т' + 227: 14, # 'у' + 228: 39, # 'ф' + 229: 26, # 'х' + 230: 28, # 'ц' + 231: 22, # 'ч' + 232: 25, # 'ш' + 233: 29, # 'щ' + 234: 54, # 'ъ' + 235: 18, # 'ы' + 236: 17, # 'ь' + 237: 30, # 'э' + 238: 27, # 'ю' + 239: 16, # 'я' + 240: 239, # 'Ё' + 241: 68, # 'ё' + 242: 240, # 'Є' + 243: 241, # 'є' + 244: 242, # 'Ї' + 245: 243, # 'ї' + 246: 244, # 'Ў' + 247: 245, # 'ў' + 248: 246, # '°' + 249: 247, # '∙' + 250: 248, # '·' + 251: 249, # '√' + 252: 250, # '№' + 253: 251, # '¤' + 254: 252, # '■' + 255: 255, # '\xa0' +} + +IBM866_RUSSIAN_MODEL = SingleByteCharSetModel( + charset_name="IBM866", + language="Russian", + char_to_order_map=IBM866_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё", +) + +WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # 'Ђ' + 129: 192, # 'Ѓ' + 130: 193, # '‚' + 131: 194, # 'ѓ' + 132: 195, # '„' + 133: 196, # '…' + 134: 197, # '†' + 135: 198, # '‡' + 136: 199, # '€' + 137: 200, # '‰' + 138: 201, # 'Љ' + 139: 202, # '‹' + 140: 203, # 'Њ' + 141: 204, # 'Ќ' + 142: 205, # 'Ћ' + 143: 206, # 'Џ' + 144: 207, # 'ђ' + 145: 208, # '‘' + 146: 209, # '’' + 147: 210, # '“' + 148: 211, # '”' + 149: 212, # '•' + 150: 213, # '–' + 151: 214, # '—' + 152: 215, # None + 153: 216, # '™' + 154: 217, # 'љ' + 155: 218, # '›' + 156: 219, # 'њ' + 157: 220, # 'ќ' + 158: 221, # 'ћ' + 159: 222, # 'џ' + 160: 223, # '\xa0' + 161: 224, # 'Ў' + 162: 225, # 'ў' + 163: 226, # 'Ј' + 164: 227, # '¤' + 165: 228, # 'Ґ' + 166: 229, # '¦' + 167: 230, # '§' + 168: 231, # 'Ё' + 169: 232, # '©' + 170: 233, # 'Є' + 171: 234, # '«' + 172: 235, # '¬' + 173: 236, # '\xad' + 174: 237, # '®' + 175: 238, # 'Ї' + 176: 239, # '°' + 177: 240, # '±' + 178: 241, # 'І' + 179: 242, # 'і' + 180: 243, # 'ґ' + 181: 244, # 'µ' + 182: 245, # '¶' + 183: 246, # '·' + 184: 68, # 'ё' + 185: 247, # '№' + 186: 248, # 'є' + 187: 249, # '»' + 188: 250, # 'ј' + 189: 251, # 'Ѕ' + 190: 252, # 'ѕ' + 191: 253, # 'ї' + 192: 37, # 'А' + 193: 44, # 'Б' + 194: 33, # 'В' + 195: 46, # 'Г' + 196: 41, # 'Д' + 197: 48, # 'Е' + 198: 56, # 'Ж' + 199: 51, # 'З' + 200: 42, # 'И' + 201: 60, # 'Й' + 202: 36, # 'К' + 203: 49, # 'Л' + 204: 38, # 'М' + 205: 31, # 'Н' + 206: 34, # 'О' + 207: 35, # 'П' + 208: 45, # 'Р' + 209: 32, # 'С' + 210: 40, # 'Т' + 211: 52, # 'У' + 212: 53, # 'Ф' + 213: 55, # 'Х' + 214: 58, # 'Ц' + 215: 50, # 'Ч' + 216: 57, # 'Ш' + 217: 63, # 'Щ' + 218: 70, # 'Ъ' + 219: 62, # 'Ы' + 220: 61, # 'Ь' + 221: 47, # 'Э' + 222: 59, # 'Ю' + 223: 43, # 'Я' + 224: 3, # 'а' + 225: 21, # 'б' + 226: 10, # 'в' + 227: 19, # 'г' + 228: 13, # 'д' + 229: 2, # 'е' + 230: 24, # 'ж' + 231: 20, # 'з' + 232: 4, # 'и' + 233: 23, # 'й' + 234: 11, # 'к' + 235: 8, # 'л' + 236: 12, # 'м' + 237: 5, # 'н' + 238: 1, # 'о' + 239: 15, # 'п' + 240: 9, # 'р' + 241: 7, # 'с' + 242: 6, # 'т' + 243: 14, # 'у' + 244: 39, # 'ф' + 245: 26, # 'х' + 246: 28, # 'ц' + 247: 22, # 'ч' + 248: 25, # 'ш' + 249: 29, # 'щ' + 250: 54, # 'ъ' + 251: 18, # 'ы' + 252: 17, # 'ь' + 253: 30, # 'э' + 254: 27, # 'ю' + 255: 16, # 'я' +} + +WINDOWS_1251_RUSSIAN_MODEL = SingleByteCharSetModel( + charset_name="windows-1251", + language="Russian", + char_to_order_map=WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё", +) + +IBM855_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # 'ђ' + 129: 192, # 'Ђ' + 130: 193, # 'ѓ' + 131: 194, # 'Ѓ' + 132: 68, # 'ё' + 133: 195, # 'Ё' + 134: 196, # 'є' + 135: 197, # 'Є' + 136: 198, # 'ѕ' + 137: 199, # 'Ѕ' + 138: 200, # 'і' + 139: 201, # 'І' + 140: 202, # 'ї' + 141: 203, # 'Ї' + 142: 204, # 'ј' + 143: 205, # 'Ј' + 144: 206, # 'љ' + 145: 207, # 'Љ' + 146: 208, # 'њ' + 147: 209, # 'Њ' + 148: 210, # 'ћ' + 149: 211, # 'Ћ' + 150: 212, # 'ќ' + 151: 213, # 'Ќ' + 152: 214, # 'ў' + 153: 215, # 'Ў' + 154: 216, # 'џ' + 155: 217, # 'Џ' + 156: 27, # 'ю' + 157: 59, # 'Ю' + 158: 54, # 'ъ' + 159: 70, # 'Ъ' + 160: 3, # 'а' + 161: 37, # 'А' + 162: 21, # 'б' + 163: 44, # 'Б' + 164: 28, # 'ц' + 165: 58, # 'Ц' + 166: 13, # 'д' + 167: 41, # 'Д' + 168: 2, # 'е' + 169: 48, # 'Е' + 170: 39, # 'ф' + 171: 53, # 'Ф' + 172: 19, # 'г' + 173: 46, # 'Г' + 174: 218, # '«' + 175: 219, # '»' + 176: 220, # '░' + 177: 221, # '▒' + 178: 222, # '▓' + 179: 223, # '│' + 180: 224, # '┤' + 181: 26, # 'х' + 182: 55, # 'Х' + 183: 4, # 'и' + 184: 42, # 'И' + 185: 225, # '╣' + 186: 226, # '║' + 187: 227, # '╗' + 188: 228, # '╝' + 189: 23, # 'й' + 190: 60, # 'Й' + 191: 229, # '┐' + 192: 230, # '└' + 193: 231, # '┴' + 194: 232, # '┬' + 195: 233, # '├' + 196: 234, # '─' + 197: 235, # '┼' + 198: 11, # 'к' + 199: 36, # 'К' + 200: 236, # '╚' + 201: 237, # '╔' + 202: 238, # '╩' + 203: 239, # '╦' + 204: 240, # '╠' + 205: 241, # '═' + 206: 242, # '╬' + 207: 243, # '¤' + 208: 8, # 'л' + 209: 49, # 'Л' + 210: 12, # 'м' + 211: 38, # 'М' + 212: 5, # 'н' + 213: 31, # 'Н' + 214: 1, # 'о' + 215: 34, # 'О' + 216: 15, # 'п' + 217: 244, # '┘' + 218: 245, # '┌' + 219: 246, # '█' + 220: 247, # '▄' + 221: 35, # 'П' + 222: 16, # 'я' + 223: 248, # '▀' + 224: 43, # 'Я' + 225: 9, # 'р' + 226: 45, # 'Р' + 227: 7, # 'с' + 228: 32, # 'С' + 229: 6, # 'т' + 230: 40, # 'Т' + 231: 14, # 'у' + 232: 52, # 'У' + 233: 24, # 'ж' + 234: 56, # 'Ж' + 235: 10, # 'в' + 236: 33, # 'В' + 237: 17, # 'ь' + 238: 61, # 'Ь' + 239: 249, # '№' + 240: 250, # '\xad' + 241: 18, # 'ы' + 242: 62, # 'Ы' + 243: 20, # 'з' + 244: 51, # 'З' + 245: 25, # 'ш' + 246: 57, # 'Ш' + 247: 30, # 'э' + 248: 47, # 'Э' + 249: 29, # 'щ' + 250: 63, # 'Щ' + 251: 22, # 'ч' + 252: 50, # 'Ч' + 253: 251, # '§' + 254: 252, # '■' + 255: 255, # '\xa0' +} + +IBM855_RUSSIAN_MODEL = SingleByteCharSetModel( + charset_name="IBM855", + language="Russian", + char_to_order_map=IBM855_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё", +) + +KOI8_R_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # '─' + 129: 192, # '│' + 130: 193, # '┌' + 131: 194, # '┐' + 132: 195, # '└' + 133: 196, # '┘' + 134: 197, # '├' + 135: 198, # '┤' + 136: 199, # '┬' + 137: 200, # '┴' + 138: 201, # '┼' + 139: 202, # '▀' + 140: 203, # '▄' + 141: 204, # '█' + 142: 205, # '▌' + 143: 206, # '▐' + 144: 207, # '░' + 145: 208, # '▒' + 146: 209, # '▓' + 147: 210, # '⌠' + 148: 211, # '■' + 149: 212, # '∙' + 150: 213, # '√' + 151: 214, # '≈' + 152: 215, # '≤' + 153: 216, # '≥' + 154: 217, # '\xa0' + 155: 218, # '⌡' + 156: 219, # '°' + 157: 220, # '²' + 158: 221, # '·' + 159: 222, # '÷' + 160: 223, # '═' + 161: 224, # '║' + 162: 225, # '╒' + 163: 68, # 'ё' + 164: 226, # '╓' + 165: 227, # '╔' + 166: 228, # '╕' + 167: 229, # '╖' + 168: 230, # '╗' + 169: 231, # '╘' + 170: 232, # '╙' + 171: 233, # '╚' + 172: 234, # '╛' + 173: 235, # '╜' + 174: 236, # '╝' + 175: 237, # '╞' + 176: 238, # '╟' + 177: 239, # '╠' + 178: 240, # '╡' + 179: 241, # 'Ё' + 180: 242, # '╢' + 181: 243, # '╣' + 182: 244, # '╤' + 183: 245, # '╥' + 184: 246, # '╦' + 185: 247, # '╧' + 186: 248, # '╨' + 187: 249, # '╩' + 188: 250, # '╪' + 189: 251, # '╫' + 190: 252, # '╬' + 191: 253, # '©' + 192: 27, # 'ю' + 193: 3, # 'а' + 194: 21, # 'б' + 195: 28, # 'ц' + 196: 13, # 'д' + 197: 2, # 'е' + 198: 39, # 'ф' + 199: 19, # 'г' + 200: 26, # 'х' + 201: 4, # 'и' + 202: 23, # 'й' + 203: 11, # 'к' + 204: 8, # 'л' + 205: 12, # 'м' + 206: 5, # 'н' + 207: 1, # 'о' + 208: 15, # 'п' + 209: 16, # 'я' + 210: 9, # 'р' + 211: 7, # 'с' + 212: 6, # 'т' + 213: 14, # 'у' + 214: 24, # 'ж' + 215: 10, # 'в' + 216: 17, # 'ь' + 217: 18, # 'ы' + 218: 20, # 'з' + 219: 25, # 'ш' + 220: 30, # 'э' + 221: 29, # 'щ' + 222: 22, # 'ч' + 223: 54, # 'ъ' + 224: 59, # 'Ю' + 225: 37, # 'А' + 226: 44, # 'Б' + 227: 58, # 'Ц' + 228: 41, # 'Д' + 229: 48, # 'Е' + 230: 53, # 'Ф' + 231: 46, # 'Г' + 232: 55, # 'Х' + 233: 42, # 'И' + 234: 60, # 'Й' + 235: 36, # 'К' + 236: 49, # 'Л' + 237: 38, # 'М' + 238: 31, # 'Н' + 239: 34, # 'О' + 240: 35, # 'П' + 241: 43, # 'Я' + 242: 45, # 'Р' + 243: 32, # 'С' + 244: 40, # 'Т' + 245: 52, # 'У' + 246: 56, # 'Ж' + 247: 33, # 'В' + 248: 61, # 'Ь' + 249: 62, # 'Ы' + 250: 51, # 'З' + 251: 57, # 'Ш' + 252: 47, # 'Э' + 253: 63, # 'Щ' + 254: 50, # 'Ч' + 255: 70, # 'Ъ' +} + +KOI8_R_RUSSIAN_MODEL = SingleByteCharSetModel( + charset_name="KOI8-R", + language="Russian", + char_to_order_map=KOI8_R_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё", +) + +MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 37, # 'А' + 129: 44, # 'Б' + 130: 33, # 'В' + 131: 46, # 'Г' + 132: 41, # 'Д' + 133: 48, # 'Е' + 134: 56, # 'Ж' + 135: 51, # 'З' + 136: 42, # 'И' + 137: 60, # 'Й' + 138: 36, # 'К' + 139: 49, # 'Л' + 140: 38, # 'М' + 141: 31, # 'Н' + 142: 34, # 'О' + 143: 35, # 'П' + 144: 45, # 'Р' + 145: 32, # 'С' + 146: 40, # 'Т' + 147: 52, # 'У' + 148: 53, # 'Ф' + 149: 55, # 'Х' + 150: 58, # 'Ц' + 151: 50, # 'Ч' + 152: 57, # 'Ш' + 153: 63, # 'Щ' + 154: 70, # 'Ъ' + 155: 62, # 'Ы' + 156: 61, # 'Ь' + 157: 47, # 'Э' + 158: 59, # 'Ю' + 159: 43, # 'Я' + 160: 191, # '†' + 161: 192, # '°' + 162: 193, # 'Ґ' + 163: 194, # '£' + 164: 195, # '§' + 165: 196, # '•' + 166: 197, # '¶' + 167: 198, # 'І' + 168: 199, # '®' + 169: 200, # '©' + 170: 201, # '™' + 171: 202, # 'Ђ' + 172: 203, # 'ђ' + 173: 204, # '≠' + 174: 205, # 'Ѓ' + 175: 206, # 'ѓ' + 176: 207, # '∞' + 177: 208, # '±' + 178: 209, # '≤' + 179: 210, # '≥' + 180: 211, # 'і' + 181: 212, # 'µ' + 182: 213, # 'ґ' + 183: 214, # 'Ј' + 184: 215, # 'Є' + 185: 216, # 'є' + 186: 217, # 'Ї' + 187: 218, # 'ї' + 188: 219, # 'Љ' + 189: 220, # 'љ' + 190: 221, # 'Њ' + 191: 222, # 'њ' + 192: 223, # 'ј' + 193: 224, # 'Ѕ' + 194: 225, # '¬' + 195: 226, # '√' + 196: 227, # 'ƒ' + 197: 228, # '≈' + 198: 229, # '∆' + 199: 230, # '«' + 200: 231, # '»' + 201: 232, # '…' + 202: 233, # '\xa0' + 203: 234, # 'Ћ' + 204: 235, # 'ћ' + 205: 236, # 'Ќ' + 206: 237, # 'ќ' + 207: 238, # 'ѕ' + 208: 239, # '–' + 209: 240, # '—' + 210: 241, # '“' + 211: 242, # '”' + 212: 243, # '‘' + 213: 244, # '’' + 214: 245, # '÷' + 215: 246, # '„' + 216: 247, # 'Ў' + 217: 248, # 'ў' + 218: 249, # 'Џ' + 219: 250, # 'џ' + 220: 251, # '№' + 221: 252, # 'Ё' + 222: 68, # 'ё' + 223: 16, # 'я' + 224: 3, # 'а' + 225: 21, # 'б' + 226: 10, # 'в' + 227: 19, # 'г' + 228: 13, # 'д' + 229: 2, # 'е' + 230: 24, # 'ж' + 231: 20, # 'з' + 232: 4, # 'и' + 233: 23, # 'й' + 234: 11, # 'к' + 235: 8, # 'л' + 236: 12, # 'м' + 237: 5, # 'н' + 238: 1, # 'о' + 239: 15, # 'п' + 240: 9, # 'р' + 241: 7, # 'с' + 242: 6, # 'т' + 243: 14, # 'у' + 244: 39, # 'ф' + 245: 26, # 'х' + 246: 28, # 'ц' + 247: 22, # 'ч' + 248: 25, # 'ш' + 249: 29, # 'щ' + 250: 54, # 'ъ' + 251: 18, # 'ы' + 252: 17, # 'ь' + 253: 30, # 'э' + 254: 27, # 'ю' + 255: 255, # '€' +} + +MACCYRILLIC_RUSSIAN_MODEL = SingleByteCharSetModel( + charset_name="MacCyrillic", + language="Russian", + char_to_order_map=MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё", +) + +ISO_8859_5_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # '\x80' + 129: 192, # '\x81' + 130: 193, # '\x82' + 131: 194, # '\x83' + 132: 195, # '\x84' + 133: 196, # '\x85' + 134: 197, # '\x86' + 135: 198, # '\x87' + 136: 199, # '\x88' + 137: 200, # '\x89' + 138: 201, # '\x8a' + 139: 202, # '\x8b' + 140: 203, # '\x8c' + 141: 204, # '\x8d' + 142: 205, # '\x8e' + 143: 206, # '\x8f' + 144: 207, # '\x90' + 145: 208, # '\x91' + 146: 209, # '\x92' + 147: 210, # '\x93' + 148: 211, # '\x94' + 149: 212, # '\x95' + 150: 213, # '\x96' + 151: 214, # '\x97' + 152: 215, # '\x98' + 153: 216, # '\x99' + 154: 217, # '\x9a' + 155: 218, # '\x9b' + 156: 219, # '\x9c' + 157: 220, # '\x9d' + 158: 221, # '\x9e' + 159: 222, # '\x9f' + 160: 223, # '\xa0' + 161: 224, # 'Ё' + 162: 225, # 'Ђ' + 163: 226, # 'Ѓ' + 164: 227, # 'Є' + 165: 228, # 'Ѕ' + 166: 229, # 'І' + 167: 230, # 'Ї' + 168: 231, # 'Ј' + 169: 232, # 'Љ' + 170: 233, # 'Њ' + 171: 234, # 'Ћ' + 172: 235, # 'Ќ' + 173: 236, # '\xad' + 174: 237, # 'Ў' + 175: 238, # 'Џ' + 176: 37, # 'А' + 177: 44, # 'Б' + 178: 33, # 'В' + 179: 46, # 'Г' + 180: 41, # 'Д' + 181: 48, # 'Е' + 182: 56, # 'Ж' + 183: 51, # 'З' + 184: 42, # 'И' + 185: 60, # 'Й' + 186: 36, # 'К' + 187: 49, # 'Л' + 188: 38, # 'М' + 189: 31, # 'Н' + 190: 34, # 'О' + 191: 35, # 'П' + 192: 45, # 'Р' + 193: 32, # 'С' + 194: 40, # 'Т' + 195: 52, # 'У' + 196: 53, # 'Ф' + 197: 55, # 'Х' + 198: 58, # 'Ц' + 199: 50, # 'Ч' + 200: 57, # 'Ш' + 201: 63, # 'Щ' + 202: 70, # 'Ъ' + 203: 62, # 'Ы' + 204: 61, # 'Ь' + 205: 47, # 'Э' + 206: 59, # 'Ю' + 207: 43, # 'Я' + 208: 3, # 'а' + 209: 21, # 'б' + 210: 10, # 'в' + 211: 19, # 'г' + 212: 13, # 'д' + 213: 2, # 'е' + 214: 24, # 'ж' + 215: 20, # 'з' + 216: 4, # 'и' + 217: 23, # 'й' + 218: 11, # 'к' + 219: 8, # 'л' + 220: 12, # 'м' + 221: 5, # 'н' + 222: 1, # 'о' + 223: 15, # 'п' + 224: 9, # 'р' + 225: 7, # 'с' + 226: 6, # 'т' + 227: 14, # 'у' + 228: 39, # 'ф' + 229: 26, # 'х' + 230: 28, # 'ц' + 231: 22, # 'ч' + 232: 25, # 'ш' + 233: 29, # 'щ' + 234: 54, # 'ъ' + 235: 18, # 'ы' + 236: 17, # 'ь' + 237: 30, # 'э' + 238: 27, # 'ю' + 239: 16, # 'я' + 240: 239, # '№' + 241: 68, # 'ё' + 242: 240, # 'ђ' + 243: 241, # 'ѓ' + 244: 242, # 'є' + 245: 243, # 'ѕ' + 246: 244, # 'і' + 247: 245, # 'ї' + 248: 246, # 'ј' + 249: 247, # 'љ' + 250: 248, # 'њ' + 251: 249, # 'ћ' + 252: 250, # 'ќ' + 253: 251, # '§' + 254: 252, # 'ў' + 255: 255, # 'џ' +} + +ISO_8859_5_RUSSIAN_MODEL = SingleByteCharSetModel( + charset_name="ISO-8859-5", + language="Russian", + char_to_order_map=ISO_8859_5_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langthaimodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langthaimodel.py new file mode 100644 index 0000000..883fdb1 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langthaimodel.py @@ -0,0 +1,4380 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +THAI_LANG_MODEL = { + 5: { # 'ก' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 3, # 'ฎ' + 57: 2, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 2, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 2, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 3, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 1, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 30: { # 'ข' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 0, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 2, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 2, # 'ี' + 40: 3, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 24: { # 'ค' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 2, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 3, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 8: { # 'ง' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 1, # 'ฉ' + 34: 2, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 2, # 'ศ' + 46: 1, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 3, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 26: { # 'จ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 3, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 52: { # 'ฉ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 1, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 34: { # 'ช' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 1, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 51: { # 'ซ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 3, # 'ึ' + 27: 2, # 'ื' + 32: 1, # 'ุ' + 35: 1, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 1, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 47: { # 'ญ' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 3, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 2, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 58: { # 'ฎ' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 1, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 57: { # 'ฏ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 49: { # 'ฐ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 53: { # 'ฑ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 55: { # 'ฒ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 43: { # 'ณ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 3, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 3, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 3, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 20: { # 'ด' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 2, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 1, # 'ึ' + 27: 2, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 2, # 'ๆ' + 37: 2, # '็' + 6: 1, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 19: { # 'ต' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 2, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 2, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 1, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 2, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 44: { # 'ถ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 3, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 14: { # 'ท' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 3, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 3, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 1, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 3, # 'ศ' + 46: 1, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 48: { # 'ธ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 2, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 2, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 3: { # 'น' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 2, # 'ถ' + 14: 3, # 'ท' + 48: 3, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 1, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 3, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 3, # 'โ' + 29: 3, # 'ใ' + 33: 3, # 'ไ' + 50: 2, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 17: { # 'บ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 1, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 2, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 2, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 25: { # 'ป' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 1, # 'ฎ' + 57: 3, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 1, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 2, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 1, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 39: { # 'ผ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 1, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 0, # 'ุ' + 35: 3, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 1, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 62: { # 'ฝ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 1, # 'ี' + 40: 2, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 1, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 31: { # 'พ' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 2, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 1, # 'ึ' + 27: 3, # 'ื' + 32: 1, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 0, # '่' + 7: 1, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 54: { # 'ฟ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 45: { # 'ภ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 2, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 9: { # 'ม' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 2, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 1, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 2, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 16: { # 'ย' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 2, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 1, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 2, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 2: { # 'ร' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 2, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 3, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 3, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 3, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 2, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 2, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 3, # 'เ' + 28: 3, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 61: { # 'ฤ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 2, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 15: { # 'ล' + 5: 2, # 'ก' + 30: 3, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 3, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 2, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 2, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 12: { # 'ว' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 2, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 42: { # 'ศ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 2, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 3, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 2, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 46: { # 'ษ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 2, # 'ฎ' + 57: 1, # 'ฏ' + 49: 2, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 18: { # 'ส' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 3, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 2, # 'ภ' + 9: 3, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 0, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 1, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 21: { # 'ห' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 4: { # 'อ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 63: { # 'ฯ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 22: { # 'ะ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 1, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 10: { # 'ั' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 3, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 2, # 'ฐ' + 53: 0, # 'ฑ' + 55: 3, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 1: { # 'า' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 1, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 2, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 3, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 3, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 36: { # 'ำ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 23: { # 'ิ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 3, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 2, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 3, # 'ศ' + 46: 2, # 'ษ' + 18: 2, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 13: { # 'ี' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 40: { # 'ึ' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 3, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 27: { # 'ื' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 32: { # 'ุ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 3, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 1, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 1, # 'ศ' + 46: 2, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 35: { # 'ู' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 11: { # 'เ' + 5: 3, # 'ก' + 30: 3, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 3, # 'ฉ' + 34: 3, # 'ช' + 51: 2, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 3, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 3, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 28: { # 'แ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 3, # 'ต' + 44: 2, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 3, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 41: { # 'โ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 1, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 29: { # 'ใ' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 33: { # 'ไ' + 5: 1, # 'ก' + 30: 2, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 1, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 2, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 50: { # 'ๆ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 37: { # '็' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 6: { # '่' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 7: { # '้' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 38: { # '์' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 1, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 56: { # '๑' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 2, # '๑' + 59: 1, # '๒' + 60: 1, # '๕' + }, + 59: { # '๒' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 1, # '๑' + 59: 1, # '๒' + 60: 3, # '๕' + }, + 60: { # '๕' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 2, # '๑' + 59: 1, # '๒' + 60: 0, # '๕' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +TIS_620_THAI_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 182, # 'A' + 66: 106, # 'B' + 67: 107, # 'C' + 68: 100, # 'D' + 69: 183, # 'E' + 70: 184, # 'F' + 71: 185, # 'G' + 72: 101, # 'H' + 73: 94, # 'I' + 74: 186, # 'J' + 75: 187, # 'K' + 76: 108, # 'L' + 77: 109, # 'M' + 78: 110, # 'N' + 79: 111, # 'O' + 80: 188, # 'P' + 81: 189, # 'Q' + 82: 190, # 'R' + 83: 89, # 'S' + 84: 95, # 'T' + 85: 112, # 'U' + 86: 113, # 'V' + 87: 191, # 'W' + 88: 192, # 'X' + 89: 193, # 'Y' + 90: 194, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 64, # 'a' + 98: 72, # 'b' + 99: 73, # 'c' + 100: 114, # 'd' + 101: 74, # 'e' + 102: 115, # 'f' + 103: 116, # 'g' + 104: 102, # 'h' + 105: 81, # 'i' + 106: 201, # 'j' + 107: 117, # 'k' + 108: 90, # 'l' + 109: 103, # 'm' + 110: 78, # 'n' + 111: 82, # 'o' + 112: 96, # 'p' + 113: 202, # 'q' + 114: 91, # 'r' + 115: 79, # 's' + 116: 84, # 't' + 117: 104, # 'u' + 118: 105, # 'v' + 119: 97, # 'w' + 120: 98, # 'x' + 121: 92, # 'y' + 122: 203, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 209, # '\x80' + 129: 210, # '\x81' + 130: 211, # '\x82' + 131: 212, # '\x83' + 132: 213, # '\x84' + 133: 88, # '\x85' + 134: 214, # '\x86' + 135: 215, # '\x87' + 136: 216, # '\x88' + 137: 217, # '\x89' + 138: 218, # '\x8a' + 139: 219, # '\x8b' + 140: 220, # '\x8c' + 141: 118, # '\x8d' + 142: 221, # '\x8e' + 143: 222, # '\x8f' + 144: 223, # '\x90' + 145: 224, # '\x91' + 146: 99, # '\x92' + 147: 85, # '\x93' + 148: 83, # '\x94' + 149: 225, # '\x95' + 150: 226, # '\x96' + 151: 227, # '\x97' + 152: 228, # '\x98' + 153: 229, # '\x99' + 154: 230, # '\x9a' + 155: 231, # '\x9b' + 156: 232, # '\x9c' + 157: 233, # '\x9d' + 158: 234, # '\x9e' + 159: 235, # '\x9f' + 160: 236, # None + 161: 5, # 'ก' + 162: 30, # 'ข' + 163: 237, # 'ฃ' + 164: 24, # 'ค' + 165: 238, # 'ฅ' + 166: 75, # 'ฆ' + 167: 8, # 'ง' + 168: 26, # 'จ' + 169: 52, # 'ฉ' + 170: 34, # 'ช' + 171: 51, # 'ซ' + 172: 119, # 'ฌ' + 173: 47, # 'ญ' + 174: 58, # 'ฎ' + 175: 57, # 'ฏ' + 176: 49, # 'ฐ' + 177: 53, # 'ฑ' + 178: 55, # 'ฒ' + 179: 43, # 'ณ' + 180: 20, # 'ด' + 181: 19, # 'ต' + 182: 44, # 'ถ' + 183: 14, # 'ท' + 184: 48, # 'ธ' + 185: 3, # 'น' + 186: 17, # 'บ' + 187: 25, # 'ป' + 188: 39, # 'ผ' + 189: 62, # 'ฝ' + 190: 31, # 'พ' + 191: 54, # 'ฟ' + 192: 45, # 'ภ' + 193: 9, # 'ม' + 194: 16, # 'ย' + 195: 2, # 'ร' + 196: 61, # 'ฤ' + 197: 15, # 'ล' + 198: 239, # 'ฦ' + 199: 12, # 'ว' + 200: 42, # 'ศ' + 201: 46, # 'ษ' + 202: 18, # 'ส' + 203: 21, # 'ห' + 204: 76, # 'ฬ' + 205: 4, # 'อ' + 206: 66, # 'ฮ' + 207: 63, # 'ฯ' + 208: 22, # 'ะ' + 209: 10, # 'ั' + 210: 1, # 'า' + 211: 36, # 'ำ' + 212: 23, # 'ิ' + 213: 13, # 'ี' + 214: 40, # 'ึ' + 215: 27, # 'ื' + 216: 32, # 'ุ' + 217: 35, # 'ู' + 218: 86, # 'ฺ' + 219: 240, # None + 220: 241, # None + 221: 242, # None + 222: 243, # None + 223: 244, # '฿' + 224: 11, # 'เ' + 225: 28, # 'แ' + 226: 41, # 'โ' + 227: 29, # 'ใ' + 228: 33, # 'ไ' + 229: 245, # 'ๅ' + 230: 50, # 'ๆ' + 231: 37, # '็' + 232: 6, # '่' + 233: 7, # '้' + 234: 67, # '๊' + 235: 77, # '๋' + 236: 38, # '์' + 237: 93, # 'ํ' + 238: 246, # '๎' + 239: 247, # '๏' + 240: 68, # '๐' + 241: 56, # '๑' + 242: 59, # '๒' + 243: 65, # '๓' + 244: 69, # '๔' + 245: 60, # '๕' + 246: 70, # '๖' + 247: 80, # '๗' + 248: 71, # '๘' + 249: 87, # '๙' + 250: 248, # '๚' + 251: 249, # '๛' + 252: 250, # None + 253: 251, # None + 254: 252, # None + 255: 253, # None +} + +TIS_620_THAI_MODEL = SingleByteCharSetModel( + charset_name="TIS-620", + language="Thai", + char_to_order_map=TIS_620_THAI_CHAR_TO_ORDER, + language_model=THAI_LANG_MODEL, + typical_positive_ratio=0.926386, + keep_ascii_letters=False, + alphabet="กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langturkishmodel.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langturkishmodel.py new file mode 100644 index 0000000..64c9433 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/langturkishmodel.py @@ -0,0 +1,4380 @@ +from chardet.sbcharsetprober import SingleByteCharSetModel + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +TURKISH_LANG_MODEL = { + 23: { # 'A' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 37: { # 'B' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 47: { # 'C' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 39: { # 'D' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 0, # 'ş' + }, + 29: { # 'E' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 52: { # 'F' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 1, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 2, # 'ş' + }, + 36: { # 'G' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 2, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 1, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 45: { # 'H' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 2, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 2, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 53: { # 'I' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 60: { # 'J' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 16: { # 'K' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 1, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 49: { # 'L' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 2, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 20: { # 'M' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 0, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 46: { # 'N' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 42: { # 'O' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 2, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 48: { # 'P' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 44: { # 'R' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 35: { # 'S' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 1, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 2, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 31: { # 'T' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 2, # 't' + 14: 2, # 'u' + 32: 1, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 51: { # 'U' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 38: { # 'V' + 23: 1, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 1, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 62: { # 'W' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 43: { # 'Y' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 0, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 1, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 56: { # 'Z' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 1: { # 'a' + 23: 3, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 1, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 21: { # 'b' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 3, # 'g' + 25: 1, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 2, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 28: { # 'c' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 2, # 'T' + 51: 2, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 3, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 1, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 1, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 2, # 'ş' + }, + 12: { # 'd' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 2: { # 'e' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 18: { # 'f' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 1, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 1, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 27: { # 'g' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 25: { # 'h' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 3: { # 'i' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 1, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 3, # 'g' + 25: 1, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 24: { # 'j' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 10: { # 'k' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 2, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 5: { # 'l' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 1, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 2, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 13: { # 'm' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 2, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 4: { # 'n' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 15: { # 'o' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 2, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 2, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 2, # 'ş' + }, + 26: { # 'p' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 7: { # 'r' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 1, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 8: { # 's' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 9: { # 't' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 14: { # 'u' + 23: 3, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 2, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 2, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 32: { # 'v' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 57: { # 'w' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 1, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 58: { # 'x' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 11: { # 'y' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 22: { # 'z' + 23: 2, # 'A' + 37: 2, # 'B' + 47: 1, # 'C' + 39: 2, # 'D' + 29: 3, # 'E' + 52: 1, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 2, # 'N' + 42: 2, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 3, # 'T' + 51: 2, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 1, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 2, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 1, # 'Ş' + 19: 2, # 'ş' + }, + 63: { # '·' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 54: { # 'Ç' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 3, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 50: { # 'Ö' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 2, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 1, # 'N' + 42: 2, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 1, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 1, # 's' + 9: 2, # 't' + 14: 0, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 55: { # 'Ü' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 59: { # 'â' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 0, # 'ş' + }, + 33: { # 'ç' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 0, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 61: { # 'î' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 1, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 34: { # 'ö' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 1, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 3, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 1, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 17: { # 'ü' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 30: { # 'ğ' + 23: 0, # 'A' + 37: 2, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 2, # 'N' + 42: 2, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 3, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 2, # 'İ' + 6: 2, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 41: { # 'İ' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 2, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 6: { # 'ı' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 40: { # 'Ş' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 2, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 0, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 3, # 'f' + 27: 0, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 1, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 1, # 'ü' + 30: 2, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 2, # 'ş' + }, + 19: { # 'ş' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 2, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 1, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +ISO_8859_9_TURKISH_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 255, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 255, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 255, # ' ' + 33: 255, # '!' + 34: 255, # '"' + 35: 255, # '#' + 36: 255, # '$' + 37: 255, # '%' + 38: 255, # '&' + 39: 255, # "'" + 40: 255, # '(' + 41: 255, # ')' + 42: 255, # '*' + 43: 255, # '+' + 44: 255, # ',' + 45: 255, # '-' + 46: 255, # '.' + 47: 255, # '/' + 48: 255, # '0' + 49: 255, # '1' + 50: 255, # '2' + 51: 255, # '3' + 52: 255, # '4' + 53: 255, # '5' + 54: 255, # '6' + 55: 255, # '7' + 56: 255, # '8' + 57: 255, # '9' + 58: 255, # ':' + 59: 255, # ';' + 60: 255, # '<' + 61: 255, # '=' + 62: 255, # '>' + 63: 255, # '?' + 64: 255, # '@' + 65: 23, # 'A' + 66: 37, # 'B' + 67: 47, # 'C' + 68: 39, # 'D' + 69: 29, # 'E' + 70: 52, # 'F' + 71: 36, # 'G' + 72: 45, # 'H' + 73: 53, # 'I' + 74: 60, # 'J' + 75: 16, # 'K' + 76: 49, # 'L' + 77: 20, # 'M' + 78: 46, # 'N' + 79: 42, # 'O' + 80: 48, # 'P' + 81: 69, # 'Q' + 82: 44, # 'R' + 83: 35, # 'S' + 84: 31, # 'T' + 85: 51, # 'U' + 86: 38, # 'V' + 87: 62, # 'W' + 88: 65, # 'X' + 89: 43, # 'Y' + 90: 56, # 'Z' + 91: 255, # '[' + 92: 255, # '\\' + 93: 255, # ']' + 94: 255, # '^' + 95: 255, # '_' + 96: 255, # '`' + 97: 1, # 'a' + 98: 21, # 'b' + 99: 28, # 'c' + 100: 12, # 'd' + 101: 2, # 'e' + 102: 18, # 'f' + 103: 27, # 'g' + 104: 25, # 'h' + 105: 3, # 'i' + 106: 24, # 'j' + 107: 10, # 'k' + 108: 5, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 15, # 'o' + 112: 26, # 'p' + 113: 64, # 'q' + 114: 7, # 'r' + 115: 8, # 's' + 116: 9, # 't' + 117: 14, # 'u' + 118: 32, # 'v' + 119: 57, # 'w' + 120: 58, # 'x' + 121: 11, # 'y' + 122: 22, # 'z' + 123: 255, # '{' + 124: 255, # '|' + 125: 255, # '}' + 126: 255, # '~' + 127: 255, # '\x7f' + 128: 180, # '\x80' + 129: 179, # '\x81' + 130: 178, # '\x82' + 131: 177, # '\x83' + 132: 176, # '\x84' + 133: 175, # '\x85' + 134: 174, # '\x86' + 135: 173, # '\x87' + 136: 172, # '\x88' + 137: 171, # '\x89' + 138: 170, # '\x8a' + 139: 169, # '\x8b' + 140: 168, # '\x8c' + 141: 167, # '\x8d' + 142: 166, # '\x8e' + 143: 165, # '\x8f' + 144: 164, # '\x90' + 145: 163, # '\x91' + 146: 162, # '\x92' + 147: 161, # '\x93' + 148: 160, # '\x94' + 149: 159, # '\x95' + 150: 101, # '\x96' + 151: 158, # '\x97' + 152: 157, # '\x98' + 153: 156, # '\x99' + 154: 155, # '\x9a' + 155: 154, # '\x9b' + 156: 153, # '\x9c' + 157: 152, # '\x9d' + 158: 151, # '\x9e' + 159: 106, # '\x9f' + 160: 150, # '\xa0' + 161: 149, # '¡' + 162: 148, # '¢' + 163: 147, # '£' + 164: 146, # '¤' + 165: 145, # '¥' + 166: 144, # '¦' + 167: 100, # '§' + 168: 143, # '¨' + 169: 142, # '©' + 170: 141, # 'ª' + 171: 140, # '«' + 172: 139, # '¬' + 173: 138, # '\xad' + 174: 137, # '®' + 175: 136, # '¯' + 176: 94, # '°' + 177: 80, # '±' + 178: 93, # '²' + 179: 135, # '³' + 180: 105, # '´' + 181: 134, # 'µ' + 182: 133, # '¶' + 183: 63, # '·' + 184: 132, # '¸' + 185: 131, # '¹' + 186: 130, # 'º' + 187: 129, # '»' + 188: 128, # '¼' + 189: 127, # '½' + 190: 126, # '¾' + 191: 125, # '¿' + 192: 124, # 'À' + 193: 104, # 'Á' + 194: 73, # 'Â' + 195: 99, # 'Ã' + 196: 79, # 'Ä' + 197: 85, # 'Å' + 198: 123, # 'Æ' + 199: 54, # 'Ç' + 200: 122, # 'È' + 201: 98, # 'É' + 202: 92, # 'Ê' + 203: 121, # 'Ë' + 204: 120, # 'Ì' + 205: 91, # 'Í' + 206: 103, # 'Î' + 207: 119, # 'Ï' + 208: 68, # 'Ğ' + 209: 118, # 'Ñ' + 210: 117, # 'Ò' + 211: 97, # 'Ó' + 212: 116, # 'Ô' + 213: 115, # 'Õ' + 214: 50, # 'Ö' + 215: 90, # '×' + 216: 114, # 'Ø' + 217: 113, # 'Ù' + 218: 112, # 'Ú' + 219: 111, # 'Û' + 220: 55, # 'Ü' + 221: 41, # 'İ' + 222: 40, # 'Ş' + 223: 86, # 'ß' + 224: 89, # 'à' + 225: 70, # 'á' + 226: 59, # 'â' + 227: 78, # 'ã' + 228: 71, # 'ä' + 229: 82, # 'å' + 230: 88, # 'æ' + 231: 33, # 'ç' + 232: 77, # 'è' + 233: 66, # 'é' + 234: 84, # 'ê' + 235: 83, # 'ë' + 236: 110, # 'ì' + 237: 75, # 'í' + 238: 61, # 'î' + 239: 96, # 'ï' + 240: 30, # 'ğ' + 241: 67, # 'ñ' + 242: 109, # 'ò' + 243: 74, # 'ó' + 244: 87, # 'ô' + 245: 102, # 'õ' + 246: 34, # 'ö' + 247: 95, # '÷' + 248: 81, # 'ø' + 249: 108, # 'ù' + 250: 76, # 'ú' + 251: 72, # 'û' + 252: 17, # 'ü' + 253: 6, # 'ı' + 254: 19, # 'ş' + 255: 107, # 'ÿ' +} + +ISO_8859_9_TURKISH_MODEL = SingleByteCharSetModel( + charset_name="ISO-8859-9", + language="Turkish", + char_to_order_map=ISO_8859_9_TURKISH_CHAR_TO_ORDER, + language_model=TURKISH_LANG_MODEL, + typical_positive_ratio=0.97029, + keep_ascii_letters=True, + alphabet="ABCDEFGHIJKLMNOPRSTUVYZabcdefghijklmnoprstuvyzÂÇÎÖÛÜâçîöûüĞğİıŞş", +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/latin1prober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/latin1prober.py new file mode 100644 index 0000000..59a01d9 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/latin1prober.py @@ -0,0 +1,147 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import List, Union + +from .charsetprober import CharSetProber +from .enums import ProbingState + +FREQ_CAT_NUM = 4 + +UDF = 0 # undefined +OTH = 1 # other +ASC = 2 # ascii capital letter +ASS = 3 # ascii small letter +ACV = 4 # accent capital vowel +ACO = 5 # accent capital other +ASV = 6 # accent small vowel +ASO = 7 # accent small other +CLASS_NUM = 8 # total classes + +# fmt: off +Latin1_CharToClass = ( + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F + OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 + ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F + OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 + ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F + OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 + OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F + UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 + OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF + ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF + ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 + ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF + ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 + ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +Latin1ClassModel = ( +# UDF OTH ASC ASS ACV ACO ASV ASO + 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, # ASO +) +# fmt: on + + +class Latin1Prober(CharSetProber): + def __init__(self) -> None: + super().__init__() + self._last_char_class = OTH + self._freq_counter: List[int] = [] + self.reset() + + def reset(self) -> None: + self._last_char_class = OTH + self._freq_counter = [0] * FREQ_CAT_NUM + super().reset() + + @property + def charset_name(self) -> str: + return "ISO-8859-1" + + @property + def language(self) -> str: + return "" + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + byte_str = self.remove_xml_tags(byte_str) + for c in byte_str: + char_class = Latin1_CharToClass[c] + freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM) + char_class] + if freq == 0: + self._state = ProbingState.NOT_ME + break + self._freq_counter[freq] += 1 + self._last_char_class = char_class + + return self.state + + def get_confidence(self) -> float: + if self.state == ProbingState.NOT_ME: + return 0.01 + + total = sum(self._freq_counter) + confidence = ( + 0.0 + if total < 0.01 + else (self._freq_counter[3] - self._freq_counter[1] * 20.0) / total + ) + confidence = max(confidence, 0.0) + # lower the confidence of latin1 so that other more accurate + # detector can take priority. + confidence *= 0.73 + return confidence diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/macromanprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/macromanprober.py new file mode 100644 index 0000000..1425d10 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/macromanprober.py @@ -0,0 +1,162 @@ +######################## BEGIN LICENSE BLOCK ######################## +# This code was modified from latin1prober.py by Rob Speer . +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Rob Speer - adapt to MacRoman encoding +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import List, Union + +from .charsetprober import CharSetProber +from .enums import ProbingState + +FREQ_CAT_NUM = 4 + +UDF = 0 # undefined +OTH = 1 # other +ASC = 2 # ascii capital letter +ASS = 3 # ascii small letter +ACV = 4 # accent capital vowel +ACO = 5 # accent capital other +ASV = 6 # accent small vowel +ASO = 7 # accent small other +ODD = 8 # character that is unlikely to appear +CLASS_NUM = 9 # total classes + +# The change from Latin1 is that we explicitly look for extended characters +# that are infrequently-occurring symbols, and consider them to always be +# improbable. This should let MacRoman get out of the way of more likely +# encodings in most situations. + +# fmt: off +MacRoman_CharToClass = ( + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F + OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 + ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F + OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 + ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F + ACV, ACV, ACO, ACV, ACO, ACV, ACV, ASV, # 80 - 87 + ASV, ASV, ASV, ASV, ASV, ASO, ASV, ASV, # 88 - 8F + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASV, # 90 - 97 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, ASO, # A0 - A7 + OTH, OTH, ODD, ODD, OTH, OTH, ACV, ACV, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, ASV, ASV, # B8 - BF + OTH, OTH, ODD, OTH, ODD, OTH, OTH, OTH, # C0 - C7 + OTH, OTH, OTH, ACV, ACV, ACV, ACV, ASV, # C8 - CF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, ODD, # D0 - D7 + ASV, ACV, ODD, OTH, OTH, OTH, OTH, OTH, # D8 - DF + OTH, OTH, OTH, OTH, OTH, ACV, ACV, ACV, # E0 - E7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # E8 - EF + ODD, ACV, ACV, ACV, ACV, ASV, ODD, ODD, # F0 - F7 + ODD, ODD, ODD, ODD, ODD, ODD, ODD, ODD, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +MacRomanClassModel = ( +# UDF OTH ASC ASS ACV ACO ASV ASO ODD + 0, 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, 1, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, 1, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, 1, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, 1, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, 1, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, 1, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, 1, # ASO + 0, 1, 1, 1, 1, 1, 1, 1, 1, # ODD +) +# fmt: on + + +class MacRomanProber(CharSetProber): + def __init__(self) -> None: + super().__init__() + self._last_char_class = OTH + self._freq_counter: List[int] = [] + self.reset() + + def reset(self) -> None: + self._last_char_class = OTH + self._freq_counter = [0] * FREQ_CAT_NUM + + # express the prior that MacRoman is a somewhat rare encoding; + # this can be done by starting out in a slightly improbable state + # that must be overcome + self._freq_counter[2] = 10 + + super().reset() + + @property + def charset_name(self) -> str: + return "MacRoman" + + @property + def language(self) -> str: + return "" + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + byte_str = self.remove_xml_tags(byte_str) + for c in byte_str: + char_class = MacRoman_CharToClass[c] + freq = MacRomanClassModel[(self._last_char_class * CLASS_NUM) + char_class] + if freq == 0: + self._state = ProbingState.NOT_ME + break + self._freq_counter[freq] += 1 + self._last_char_class = char_class + + return self.state + + def get_confidence(self) -> float: + if self.state == ProbingState.NOT_ME: + return 0.01 + + total = sum(self._freq_counter) + confidence = ( + 0.0 + if total < 0.01 + else (self._freq_counter[3] - self._freq_counter[1] * 20.0) / total + ) + confidence = max(confidence, 0.0) + # lower the confidence of MacRoman so that other more accurate + # detector can take priority. + confidence *= 0.73 + return confidence diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcharsetprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcharsetprober.py new file mode 100644 index 0000000..666307e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcharsetprober.py @@ -0,0 +1,95 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Optional, Union + +from .chardistribution import CharDistributionAnalysis +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .enums import LanguageFilter, MachineState, ProbingState + + +class MultiByteCharSetProber(CharSetProber): + """ + MultiByteCharSetProber + """ + + def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None: + super().__init__(lang_filter=lang_filter) + self.distribution_analyzer: Optional[CharDistributionAnalysis] = None + self.coding_sm: Optional[CodingStateMachine] = None + self._last_char = bytearray(b"\0\0") + + def reset(self) -> None: + super().reset() + if self.coding_sm: + self.coding_sm.reset() + if self.distribution_analyzer: + self.distribution_analyzer.reset() + self._last_char = bytearray(b"\0\0") + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + assert self.coding_sm is not None + assert self.distribution_analyzer is not None + + for i, byte in enumerate(byte_str): + coding_state = self.coding_sm.next_state(byte) + if coding_state == MachineState.ERROR: + self.logger.debug( + "%s %s prober hit error at byte %s", + self.charset_name, + self.language, + i, + ) + self._state = ProbingState.NOT_ME + break + if coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + if coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.distribution_analyzer.feed(byte_str[i - 1 : i + 1], char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if self.distribution_analyzer.got_enough_data() and ( + self.get_confidence() > self.SHORTCUT_THRESHOLD + ): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self) -> float: + assert self.distribution_analyzer is not None + return self.distribution_analyzer.get_confidence() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcsgroupprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcsgroupprober.py new file mode 100644 index 0000000..6cb9cc7 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcsgroupprober.py @@ -0,0 +1,57 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .big5prober import Big5Prober +from .charsetgroupprober import CharSetGroupProber +from .cp949prober import CP949Prober +from .enums import LanguageFilter +from .eucjpprober import EUCJPProber +from .euckrprober import EUCKRProber +from .euctwprober import EUCTWProber +from .gb2312prober import GB2312Prober +from .johabprober import JOHABProber +from .sjisprober import SJISProber +from .utf8prober import UTF8Prober + + +class MBCSGroupProber(CharSetGroupProber): + def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None: + super().__init__(lang_filter=lang_filter) + self.probers = [ + UTF8Prober(), + SJISProber(), + EUCJPProber(), + GB2312Prober(), + EUCKRProber(), + CP949Prober(), + Big5Prober(), + EUCTWProber(), + JOHABProber(), + ] + self.reset() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcssm.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcssm.py new file mode 100644 index 0000000..7bbe97e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/mbcssm.py @@ -0,0 +1,661 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .codingstatemachinedict import CodingStateMachineDict +from .enums import MachineState + +# BIG5 + +# fmt: off +BIG5_CLS = ( + 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 #allow 0x00 as legal value + 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f + 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17 + 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f + 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27 + 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f + 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37 + 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f + 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47 + 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f + 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57 + 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f + 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67 + 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f + 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77 + 2, 2, 2, 2, 2, 2, 2, 1, # 78 - 7f + 4, 4, 4, 4, 4, 4, 4, 4, # 80 - 87 + 4, 4, 4, 4, 4, 4, 4, 4, # 88 - 8f + 4, 4, 4, 4, 4, 4, 4, 4, # 90 - 97 + 4, 4, 4, 4, 4, 4, 4, 4, # 98 - 9f + 4, 3, 3, 3, 3, 3, 3, 3, # a0 - a7 + 3, 3, 3, 3, 3, 3, 3, 3, # a8 - af + 3, 3, 3, 3, 3, 3, 3, 3, # b0 - b7 + 3, 3, 3, 3, 3, 3, 3, 3, # b8 - bf + 3, 3, 3, 3, 3, 3, 3, 3, # c0 - c7 + 3, 3, 3, 3, 3, 3, 3, 3, # c8 - cf + 3, 3, 3, 3, 3, 3, 3, 3, # d0 - d7 + 3, 3, 3, 3, 3, 3, 3, 3, # d8 - df + 3, 3, 3, 3, 3, 3, 3, 3, # e0 - e7 + 3, 3, 3, 3, 3, 3, 3, 3, # e8 - ef + 3, 3, 3, 3, 3, 3, 3, 3, # f0 - f7 + 3, 3, 3, 3, 3, 3, 3, 0 # f8 - ff +) + +BIG5_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,#08-0f + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START#10-17 +) +# fmt: on + +BIG5_CHAR_LEN_TABLE = (0, 1, 1, 2, 0) + +BIG5_SM_MODEL: CodingStateMachineDict = { + "class_table": BIG5_CLS, + "class_factor": 5, + "state_table": BIG5_ST, + "char_len_table": BIG5_CHAR_LEN_TABLE, + "name": "Big5", +} + +# CP949 +# fmt: off +CP949_CLS = ( + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, # 00 - 0f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, # 10 - 1f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 2f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 3f + 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, # 40 - 4f + 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, # 50 - 5f + 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, # 60 - 6f + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, # 70 - 7f + 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, # 80 - 8f + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, # 90 - 9f + 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, # a0 - af + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, # b0 - bf + 7, 7, 7, 7, 7, 7, 9, 2, 2, 3, 2, 2, 2, 2, 2, 2, # c0 - cf + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, # d0 - df + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, # e0 - ef + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, # f0 - ff +) + +CP949_ST = ( +#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 5 + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 6 +) +# fmt: on + +CP949_CHAR_LEN_TABLE = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2) + +CP949_SM_MODEL: CodingStateMachineDict = { + "class_table": CP949_CLS, + "class_factor": 10, + "state_table": CP949_ST, + "char_len_table": CP949_CHAR_LEN_TABLE, + "name": "CP949", +} + +# EUC-JP +# fmt: off +EUCJP_CLS = ( + 4, 4, 4, 4, 4, 4, 4, 4, # 00 - 07 + 4, 4, 4, 4, 4, 4, 5, 5, # 08 - 0f + 4, 4, 4, 4, 4, 4, 4, 4, # 10 - 17 + 4, 4, 4, 5, 4, 4, 4, 4, # 18 - 1f + 4, 4, 4, 4, 4, 4, 4, 4, # 20 - 27 + 4, 4, 4, 4, 4, 4, 4, 4, # 28 - 2f + 4, 4, 4, 4, 4, 4, 4, 4, # 30 - 37 + 4, 4, 4, 4, 4, 4, 4, 4, # 38 - 3f + 4, 4, 4, 4, 4, 4, 4, 4, # 40 - 47 + 4, 4, 4, 4, 4, 4, 4, 4, # 48 - 4f + 4, 4, 4, 4, 4, 4, 4, 4, # 50 - 57 + 4, 4, 4, 4, 4, 4, 4, 4, # 58 - 5f + 4, 4, 4, 4, 4, 4, 4, 4, # 60 - 67 + 4, 4, 4, 4, 4, 4, 4, 4, # 68 - 6f + 4, 4, 4, 4, 4, 4, 4, 4, # 70 - 77 + 4, 4, 4, 4, 4, 4, 4, 4, # 78 - 7f + 5, 5, 5, 5, 5, 5, 5, 5, # 80 - 87 + 5, 5, 5, 5, 5, 5, 1, 3, # 88 - 8f + 5, 5, 5, 5, 5, 5, 5, 5, # 90 - 97 + 5, 5, 5, 5, 5, 5, 5, 5, # 98 - 9f + 5, 2, 2, 2, 2, 2, 2, 2, # a0 - a7 + 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af + 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7 + 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf + 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7 + 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf + 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7 + 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df + 0, 0, 0, 0, 0, 0, 0, 0, # e0 - e7 + 0, 0, 0, 0, 0, 0, 0, 0, # e8 - ef + 0, 0, 0, 0, 0, 0, 0, 0, # f0 - f7 + 0, 0, 0, 0, 0, 0, 0, 5 # f8 - ff +) + +EUCJP_ST = ( + 3, 4, 3, 5,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 3,MachineState.ERROR,#18-1f + 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START#20-27 +) +# fmt: on + +EUCJP_CHAR_LEN_TABLE = (2, 2, 2, 3, 1, 0) + +EUCJP_SM_MODEL: CodingStateMachineDict = { + "class_table": EUCJP_CLS, + "class_factor": 6, + "state_table": EUCJP_ST, + "char_len_table": EUCJP_CHAR_LEN_TABLE, + "name": "EUC-JP", +} + +# EUC-KR +# fmt: off +EUCKR_CLS = ( + 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 + 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f + 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17 + 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f + 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27 + 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f + 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37 + 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f + 1, 1, 1, 1, 1, 1, 1, 1, # 40 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, # 48 - 4f + 1, 1, 1, 1, 1, 1, 1, 1, # 50 - 57 + 1, 1, 1, 1, 1, 1, 1, 1, # 58 - 5f + 1, 1, 1, 1, 1, 1, 1, 1, # 60 - 67 + 1, 1, 1, 1, 1, 1, 1, 1, # 68 - 6f + 1, 1, 1, 1, 1, 1, 1, 1, # 70 - 77 + 1, 1, 1, 1, 1, 1, 1, 1, # 78 - 7f + 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87 + 0, 0, 0, 0, 0, 0, 0, 0, # 88 - 8f + 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97 + 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f + 0, 2, 2, 2, 2, 2, 2, 2, # a0 - a7 + 2, 2, 2, 2, 2, 3, 3, 3, # a8 - af + 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7 + 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf + 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7 + 2, 3, 2, 2, 2, 2, 2, 2, # c8 - cf + 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7 + 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df + 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7 + 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef + 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7 + 2, 2, 2, 2, 2, 2, 2, 0 # f8 - ff +) + +EUCKR_ST = ( + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f +) +# fmt: on + +EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) + +EUCKR_SM_MODEL: CodingStateMachineDict = { + "class_table": EUCKR_CLS, + "class_factor": 4, + "state_table": EUCKR_ST, + "char_len_table": EUCKR_CHAR_LEN_TABLE, + "name": "EUC-KR", +} + +# JOHAB +# fmt: off +JOHAB_CLS = ( + 4,4,4,4,4,4,4,4, # 00 - 07 + 4,4,4,4,4,4,0,0, # 08 - 0f + 4,4,4,4,4,4,4,4, # 10 - 17 + 4,4,4,0,4,4,4,4, # 18 - 1f + 4,4,4,4,4,4,4,4, # 20 - 27 + 4,4,4,4,4,4,4,4, # 28 - 2f + 4,3,3,3,3,3,3,3, # 30 - 37 + 3,3,3,3,3,3,3,3, # 38 - 3f + 3,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,2, # 78 - 7f + 6,6,6,6,8,8,8,8, # 80 - 87 + 8,8,8,8,8,8,8,8, # 88 - 8f + 8,7,7,7,7,7,7,7, # 90 - 97 + 7,7,7,7,7,7,7,7, # 98 - 9f + 7,7,7,7,7,7,7,7, # a0 - a7 + 7,7,7,7,7,7,7,7, # a8 - af + 7,7,7,7,7,7,7,7, # b0 - b7 + 7,7,7,7,7,7,7,7, # b8 - bf + 7,7,7,7,7,7,7,7, # c0 - c7 + 7,7,7,7,7,7,7,7, # c8 - cf + 7,7,7,7,5,5,5,5, # d0 - d7 + 5,9,9,9,9,9,9,5, # d8 - df + 9,9,9,9,9,9,9,9, # e0 - e7 + 9,9,9,9,9,9,9,9, # e8 - ef + 9,9,9,9,9,9,9,9, # f0 - f7 + 9,9,5,5,5,5,5,0 # f8 - ff +) + +JOHAB_ST = ( +# cls = 0 1 2 3 4 5 6 7 8 9 + MachineState.ERROR ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.ERROR ,MachineState.ERROR ,3 ,3 ,4 , # MachineState.START + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME + MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR , # MachineState.ERROR + MachineState.ERROR ,MachineState.START ,MachineState.START ,MachineState.ERROR ,MachineState.ERROR ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.START , # 3 + MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START , # 4 +) +# fmt: on + +JOHAB_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 0, 0, 2, 2, 2) + +JOHAB_SM_MODEL: CodingStateMachineDict = { + "class_table": JOHAB_CLS, + "class_factor": 10, + "state_table": JOHAB_ST, + "char_len_table": JOHAB_CHAR_LEN_TABLE, + "name": "Johab", +} + +# EUC-TW +# fmt: off +EUCTW_CLS = ( + 2, 2, 2, 2, 2, 2, 2, 2, # 00 - 07 + 2, 2, 2, 2, 2, 2, 0, 0, # 08 - 0f + 2, 2, 2, 2, 2, 2, 2, 2, # 10 - 17 + 2, 2, 2, 0, 2, 2, 2, 2, # 18 - 1f + 2, 2, 2, 2, 2, 2, 2, 2, # 20 - 27 + 2, 2, 2, 2, 2, 2, 2, 2, # 28 - 2f + 2, 2, 2, 2, 2, 2, 2, 2, # 30 - 37 + 2, 2, 2, 2, 2, 2, 2, 2, # 38 - 3f + 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47 + 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f + 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57 + 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f + 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67 + 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f + 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77 + 2, 2, 2, 2, 2, 2, 2, 2, # 78 - 7f + 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87 + 0, 0, 0, 0, 0, 0, 6, 0, # 88 - 8f + 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97 + 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f + 0, 3, 4, 4, 4, 4, 4, 4, # a0 - a7 + 5, 5, 1, 1, 1, 1, 1, 1, # a8 - af + 1, 1, 1, 1, 1, 1, 1, 1, # b0 - b7 + 1, 1, 1, 1, 1, 1, 1, 1, # b8 - bf + 1, 1, 3, 1, 3, 3, 3, 3, # c0 - c7 + 3, 3, 3, 3, 3, 3, 3, 3, # c8 - cf + 3, 3, 3, 3, 3, 3, 3, 3, # d0 - d7 + 3, 3, 3, 3, 3, 3, 3, 3, # d8 - df + 3, 3, 3, 3, 3, 3, 3, 3, # e0 - e7 + 3, 3, 3, 3, 3, 3, 3, 3, # e8 - ef + 3, 3, 3, 3, 3, 3, 3, 3, # f0 - f7 + 3, 3, 3, 3, 3, 3, 3, 0 # f8 - ff +) + +EUCTW_ST = ( + MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 + MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,#20-27 + MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f +) +# fmt: on + +EUCTW_CHAR_LEN_TABLE = (0, 0, 1, 2, 2, 2, 3) + +EUCTW_SM_MODEL: CodingStateMachineDict = { + "class_table": EUCTW_CLS, + "class_factor": 7, + "state_table": EUCTW_ST, + "char_len_table": EUCTW_CHAR_LEN_TABLE, + "name": "x-euc-tw", +} + +# GB2312 +# fmt: off +GB2312_CLS = ( + 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 + 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f + 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17 + 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f + 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27 + 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f + 3, 3, 3, 3, 3, 3, 3, 3, # 30 - 37 + 3, 3, 1, 1, 1, 1, 1, 1, # 38 - 3f + 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47 + 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f + 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57 + 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f + 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67 + 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f + 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77 + 2, 2, 2, 2, 2, 2, 2, 4, # 78 - 7f + 5, 6, 6, 6, 6, 6, 6, 6, # 80 - 87 + 6, 6, 6, 6, 6, 6, 6, 6, # 88 - 8f + 6, 6, 6, 6, 6, 6, 6, 6, # 90 - 97 + 6, 6, 6, 6, 6, 6, 6, 6, # 98 - 9f + 6, 6, 6, 6, 6, 6, 6, 6, # a0 - a7 + 6, 6, 6, 6, 6, 6, 6, 6, # a8 - af + 6, 6, 6, 6, 6, 6, 6, 6, # b0 - b7 + 6, 6, 6, 6, 6, 6, 6, 6, # b8 - bf + 6, 6, 6, 6, 6, 6, 6, 6, # c0 - c7 + 6, 6, 6, 6, 6, 6, 6, 6, # c8 - cf + 6, 6, 6, 6, 6, 6, 6, 6, # d0 - d7 + 6, 6, 6, 6, 6, 6, 6, 6, # d8 - df + 6, 6, 6, 6, 6, 6, 6, 6, # e0 - e7 + 6, 6, 6, 6, 6, 6, 6, 6, # e8 - ef + 6, 6, 6, 6, 6, 6, 6, 6, # f0 - f7 + 6, 6, 6, 6, 6, 6, 6, 0 # f8 - ff +) + +GB2312_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 + 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#20-27 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f +) +# fmt: on + +# To be accurate, the length of class 6 can be either 2 or 4. +# But it is not necessary to discriminate between the two since +# it is used for frequency analysis only, and we are validating +# each code range there as well. So it is safe to set it to be +# 2 here. +GB2312_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 1, 2) + +GB2312_SM_MODEL: CodingStateMachineDict = { + "class_table": GB2312_CLS, + "class_factor": 7, + "state_table": GB2312_ST, + "char_len_table": GB2312_CHAR_LEN_TABLE, + "name": "GB2312", +} + +# Shift_JIS +# fmt: off +SJIS_CLS = ( + 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 + 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f + 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17 + 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f + 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27 + 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f + 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37 + 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f + 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47 + 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f + 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57 + 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f + 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67 + 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f + 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77 + 2, 2, 2, 2, 2, 2, 2, 1, # 78 - 7f + 3, 3, 3, 3, 3, 2, 2, 3, # 80 - 87 + 3, 3, 3, 3, 3, 3, 3, 3, # 88 - 8f + 3, 3, 3, 3, 3, 3, 3, 3, # 90 - 97 + 3, 3, 3, 3, 3, 3, 3, 3, # 98 - 9f + #0xa0 is illegal in sjis encoding, but some pages does + #contain such byte. We need to be more error forgiven. + 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7 + 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af + 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7 + 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf + 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7 + 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf + 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7 + 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df + 3, 3, 3, 3, 3, 3, 3, 3, # e0 - e7 + 3, 3, 3, 3, 3, 4, 4, 4, # e8 - ef + 3, 3, 3, 3, 3, 3, 3, 3, # f0 - f7 + 3, 3, 3, 3, 3, 0, 0, 0, # f8 - ff +) + +SJIS_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START #10-17 +) +# fmt: on + +SJIS_CHAR_LEN_TABLE = (0, 1, 1, 2, 0, 0) + +SJIS_SM_MODEL: CodingStateMachineDict = { + "class_table": SJIS_CLS, + "class_factor": 6, + "state_table": SJIS_ST, + "char_len_table": SJIS_CHAR_LEN_TABLE, + "name": "Shift_JIS", +} + +# UCS2-BE +# fmt: off +UCS2BE_CLS = ( + 0, 0, 0, 0, 0, 0, 0, 0, # 00 - 07 + 0, 0, 1, 0, 0, 2, 0, 0, # 08 - 0f + 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17 + 0, 0, 0, 3, 0, 0, 0, 0, # 18 - 1f + 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27 + 0, 3, 3, 3, 3, 3, 0, 0, # 28 - 2f + 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37 + 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f + 0, 0, 0, 0, 0, 0, 0, 0, # 40 - 47 + 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f + 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57 + 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f + 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67 + 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f + 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77 + 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f + 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87 + 0, 0, 0, 0, 0, 0, 0, 0, # 88 - 8f + 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97 + 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f + 0, 0, 0, 0, 0, 0, 0, 0, # a0 - a7 + 0, 0, 0, 0, 0, 0, 0, 0, # a8 - af + 0, 0, 0, 0, 0, 0, 0, 0, # b0 - b7 + 0, 0, 0, 0, 0, 0, 0, 0, # b8 - bf + 0, 0, 0, 0, 0, 0, 0, 0, # c0 - c7 + 0, 0, 0, 0, 0, 0, 0, 0, # c8 - cf + 0, 0, 0, 0, 0, 0, 0, 0, # d0 - d7 + 0, 0, 0, 0, 0, 0, 0, 0, # d8 - df + 0, 0, 0, 0, 0, 0, 0, 0, # e0 - e7 + 0, 0, 0, 0, 0, 0, 0, 0, # e8 - ef + 0, 0, 0, 0, 0, 0, 0, 0, # f0 - f7 + 0, 0, 0, 0, 0, 0, 4, 5 # f8 - ff +) + +UCS2BE_ST = ( + 5, 7, 7,MachineState.ERROR, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 + 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f + 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 + 5, 8, 6, 6,MachineState.ERROR, 6, 6, 6,#28-2f + 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #30-37 +) +# fmt: on + +UCS2BE_CHAR_LEN_TABLE = (2, 2, 2, 0, 2, 2) + +UCS2BE_SM_MODEL: CodingStateMachineDict = { + "class_table": UCS2BE_CLS, + "class_factor": 6, + "state_table": UCS2BE_ST, + "char_len_table": UCS2BE_CHAR_LEN_TABLE, + "name": "UTF-16BE", +} + +# UCS2-LE +# fmt: off +UCS2LE_CLS = ( + 0, 0, 0, 0, 0, 0, 0, 0, # 00 - 07 + 0, 0, 1, 0, 0, 2, 0, 0, # 08 - 0f + 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17 + 0, 0, 0, 3, 0, 0, 0, 0, # 18 - 1f + 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27 + 0, 3, 3, 3, 3, 3, 0, 0, # 28 - 2f + 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37 + 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f + 0, 0, 0, 0, 0, 0, 0, 0, # 40 - 47 + 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f + 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57 + 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f + 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67 + 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f + 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77 + 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f + 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87 + 0, 0, 0, 0, 0, 0, 0, 0, # 88 - 8f + 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97 + 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f + 0, 0, 0, 0, 0, 0, 0, 0, # a0 - a7 + 0, 0, 0, 0, 0, 0, 0, 0, # a8 - af + 0, 0, 0, 0, 0, 0, 0, 0, # b0 - b7 + 0, 0, 0, 0, 0, 0, 0, 0, # b8 - bf + 0, 0, 0, 0, 0, 0, 0, 0, # c0 - c7 + 0, 0, 0, 0, 0, 0, 0, 0, # c8 - cf + 0, 0, 0, 0, 0, 0, 0, 0, # d0 - d7 + 0, 0, 0, 0, 0, 0, 0, 0, # d8 - df + 0, 0, 0, 0, 0, 0, 0, 0, # e0 - e7 + 0, 0, 0, 0, 0, 0, 0, 0, # e8 - ef + 0, 0, 0, 0, 0, 0, 0, 0, # f0 - f7 + 0, 0, 0, 0, 0, 0, 4, 5 # f8 - ff +) + +UCS2LE_ST = ( + 6, 6, 7, 6, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f + 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 + 5, 5, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5,#28-2f + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR,MachineState.START,MachineState.START #30-37 +) +# fmt: on + +UCS2LE_CHAR_LEN_TABLE = (2, 2, 2, 2, 2, 2) + +UCS2LE_SM_MODEL: CodingStateMachineDict = { + "class_table": UCS2LE_CLS, + "class_factor": 6, + "state_table": UCS2LE_ST, + "char_len_table": UCS2LE_CHAR_LEN_TABLE, + "name": "UTF-16LE", +} + +# UTF-8 +# fmt: off +UTF8_CLS = ( + 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 #allow 0x00 as a legal value + 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f + 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17 + 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f + 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27 + 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f + 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37 + 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f + 1, 1, 1, 1, 1, 1, 1, 1, # 40 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, # 48 - 4f + 1, 1, 1, 1, 1, 1, 1, 1, # 50 - 57 + 1, 1, 1, 1, 1, 1, 1, 1, # 58 - 5f + 1, 1, 1, 1, 1, 1, 1, 1, # 60 - 67 + 1, 1, 1, 1, 1, 1, 1, 1, # 68 - 6f + 1, 1, 1, 1, 1, 1, 1, 1, # 70 - 77 + 1, 1, 1, 1, 1, 1, 1, 1, # 78 - 7f + 2, 2, 2, 2, 3, 3, 3, 3, # 80 - 87 + 4, 4, 4, 4, 4, 4, 4, 4, # 88 - 8f + 4, 4, 4, 4, 4, 4, 4, 4, # 90 - 97 + 4, 4, 4, 4, 4, 4, 4, 4, # 98 - 9f + 5, 5, 5, 5, 5, 5, 5, 5, # a0 - a7 + 5, 5, 5, 5, 5, 5, 5, 5, # a8 - af + 5, 5, 5, 5, 5, 5, 5, 5, # b0 - b7 + 5, 5, 5, 5, 5, 5, 5, 5, # b8 - bf + 0, 0, 6, 6, 6, 6, 6, 6, # c0 - c7 + 6, 6, 6, 6, 6, 6, 6, 6, # c8 - cf + 6, 6, 6, 6, 6, 6, 6, 6, # d0 - d7 + 6, 6, 6, 6, 6, 6, 6, 6, # d8 - df + 7, 8, 8, 8, 8, 8, 8, 8, # e0 - e7 + 8, 8, 8, 8, 8, 9, 8, 8, # e8 - ef + 10, 11, 11, 11, 11, 11, 11, 11, # f0 - f7 + 12, 13, 13, 13, 14, 15, 0, 0 # f8 - ff +) + +UTF8_ST = ( + MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 + 9, 11, 8, 7, 6, 5, 4, 3,#08-0f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f + MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f + MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f + MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f + MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af + MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,#c0-c7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR #c8-cf +) +# fmt: on + +UTF8_CHAR_LEN_TABLE = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6) + +UTF8_SM_MODEL: CodingStateMachineDict = { + "class_table": UTF8_CLS, + "class_factor": 16, + "state_table": UTF8_ST, + "char_len_table": UTF8_CHAR_LEN_TABLE, + "name": "UTF-8", +} diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/languages.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/languages.py new file mode 100644 index 0000000..eb40c5f --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/metadata/languages.py @@ -0,0 +1,352 @@ +""" +Metadata about languages used by our model training code for our +SingleByteCharSetProbers. Could be used for other things in the future. + +This code is based on the language metadata from the uchardet project. +""" + +from string import ascii_letters +from typing import List, Optional + +# TODO: Add Ukrainian (KOI8-U) + + +class Language: + """Metadata about a language useful for training models + + :ivar name: The human name for the language, in English. + :type name: str + :ivar iso_code: 2-letter ISO 639-1 if possible, 3-letter ISO code otherwise, + or use another catalog as a last resort. + :type iso_code: str + :ivar use_ascii: Whether or not ASCII letters should be included in trained + models. + :type use_ascii: bool + :ivar charsets: The charsets we want to support and create data for. + :type charsets: list of str + :ivar alphabet: The characters in the language's alphabet. If `use_ascii` is + `True`, you only need to add those not in the ASCII set. + :type alphabet: str + :ivar wiki_start_pages: The Wikipedia pages to start from if we're crawling + Wikipedia for training data. + :type wiki_start_pages: list of str + """ + + def __init__( + self, + name: Optional[str] = None, + iso_code: Optional[str] = None, + use_ascii: bool = True, + charsets: Optional[List[str]] = None, + alphabet: Optional[str] = None, + wiki_start_pages: Optional[List[str]] = None, + ) -> None: + super().__init__() + self.name = name + self.iso_code = iso_code + self.use_ascii = use_ascii + self.charsets = charsets + if self.use_ascii: + if alphabet: + alphabet += ascii_letters + else: + alphabet = ascii_letters + elif not alphabet: + raise ValueError("Must supply alphabet if use_ascii is False") + self.alphabet = "".join(sorted(set(alphabet))) if alphabet else None + self.wiki_start_pages = wiki_start_pages + + def __repr__(self) -> str: + param_str = ", ".join( + f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_") + ) + return f"{self.__class__.__name__}({param_str})" + + +LANGUAGES = { + "Arabic": Language( + name="Arabic", + iso_code="ar", + use_ascii=False, + # We only support encodings that use isolated + # forms, because the current recommendation is + # that the rendering system handles presentation + # forms. This means we purposefully skip IBM864. + charsets=["ISO-8859-6", "WINDOWS-1256", "CP720", "CP864"], + alphabet="ءآأؤإئابةتثجحخدذرزسشصضطظعغػؼؽؾؿـفقكلمنهوىيًٌٍَُِّ", + wiki_start_pages=["الصفحة_الرئيسية"], + ), + "Belarusian": Language( + name="Belarusian", + iso_code="be", + use_ascii=False, + charsets=["ISO-8859-5", "WINDOWS-1251", "IBM866", "MacCyrillic"], + alphabet="АБВГДЕЁЖЗІЙКЛМНОПРСТУЎФХЦЧШЫЬЭЮЯабвгдеёжзійклмнопрстуўфхцчшыьэюяʼ", + wiki_start_pages=["Галоўная_старонка"], + ), + "Bulgarian": Language( + name="Bulgarian", + iso_code="bg", + use_ascii=False, + charsets=["ISO-8859-5", "WINDOWS-1251", "IBM855"], + alphabet="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя", + wiki_start_pages=["Начална_страница"], + ), + "Czech": Language( + name="Czech", + iso_code="cz", + use_ascii=True, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ", + wiki_start_pages=["Hlavní_strana"], + ), + "Danish": Language( + name="Danish", + iso_code="da", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="æøåÆØÅ", + wiki_start_pages=["Forside"], + ), + "German": Language( + name="German", + iso_code="de", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="äöüßẞÄÖÜ", + wiki_start_pages=["Wikipedia:Hauptseite"], + ), + "Greek": Language( + name="Greek", + iso_code="el", + use_ascii=False, + charsets=["ISO-8859-7", "WINDOWS-1253"], + alphabet="αβγδεζηθικλμνξοπρσςτυφχψωάέήίόύώΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΣΤΥΦΧΨΩΆΈΉΊΌΎΏ", + wiki_start_pages=["Πύλη:Κύρια"], + ), + "English": Language( + name="English", + iso_code="en", + use_ascii=True, + charsets=["ISO-8859-1", "WINDOWS-1252", "MacRoman"], + wiki_start_pages=["Main_Page"], + ), + "Esperanto": Language( + name="Esperanto", + iso_code="eo", + # Q, W, X, and Y not used at all + use_ascii=False, + charsets=["ISO-8859-3"], + alphabet="abcĉdefgĝhĥijĵklmnoprsŝtuŭvzABCĈDEFGĜHĤIJĴKLMNOPRSŜTUŬVZ", + wiki_start_pages=["Vikipedio:Ĉefpaĝo"], + ), + "Spanish": Language( + name="Spanish", + iso_code="es", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="ñáéíóúüÑÁÉÍÓÚÜ", + wiki_start_pages=["Wikipedia:Portada"], + ), + "Estonian": Language( + name="Estonian", + iso_code="et", + use_ascii=False, + charsets=["ISO-8859-4", "ISO-8859-13", "WINDOWS-1257"], + # C, F, Š, Q, W, X, Y, Z, Ž are only for + # loanwords + alphabet="ABDEGHIJKLMNOPRSTUVÕÄÖÜabdeghijklmnoprstuvõäöü", + wiki_start_pages=["Esileht"], + ), + "Finnish": Language( + name="Finnish", + iso_code="fi", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="ÅÄÖŠŽåäöšž", + wiki_start_pages=["Wikipedia:Etusivu"], + ), + "French": Language( + name="French", + iso_code="fr", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="œàâçèéîïùûêŒÀÂÇÈÉÎÏÙÛÊ", + wiki_start_pages=["Wikipédia:Accueil_principal", "Bœuf (animal)"], + ), + "Hebrew": Language( + name="Hebrew", + iso_code="he", + use_ascii=False, + charsets=["ISO-8859-8", "WINDOWS-1255"], + alphabet="אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ", + wiki_start_pages=["עמוד_ראשי"], + ), + "Croatian": Language( + name="Croatian", + iso_code="hr", + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="abcčćdđefghijklmnoprsštuvzžABCČĆDĐEFGHIJKLMNOPRSŠTUVZŽ", + wiki_start_pages=["Glavna_stranica"], + ), + "Hungarian": Language( + name="Hungarian", + iso_code="hu", + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="abcdefghijklmnoprstuvzáéíóöőúüűABCDEFGHIJKLMNOPRSTUVZÁÉÍÓÖŐÚÜŰ", + wiki_start_pages=["Kezdőlap"], + ), + "Italian": Language( + name="Italian", + iso_code="it", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="ÀÈÉÌÒÓÙàèéìòóù", + wiki_start_pages=["Pagina_principale"], + ), + "Lithuanian": Language( + name="Lithuanian", + iso_code="lt", + use_ascii=False, + charsets=["ISO-8859-13", "WINDOWS-1257", "ISO-8859-4"], + # Q, W, and X not used at all + alphabet="AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽaąbcčdeęėfghiįyjklmnoprsštuųūvzž", + wiki_start_pages=["Pagrindinis_puslapis"], + ), + "Latvian": Language( + name="Latvian", + iso_code="lv", + use_ascii=False, + charsets=["ISO-8859-13", "WINDOWS-1257", "ISO-8859-4"], + # Q, W, X, Y are only for loanwords + alphabet="AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽaābcčdeēfgģhiījkķlļmnņoprsštuūvzž", + wiki_start_pages=["Sākumlapa"], + ), + "Macedonian": Language( + name="Macedonian", + iso_code="mk", + use_ascii=False, + charsets=["ISO-8859-5", "WINDOWS-1251", "MacCyrillic", "IBM855"], + alphabet="АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЦЧЏШабвгдѓежзѕијклљмнњопрстќуфхцчџш", + wiki_start_pages=["Главна_страница"], + ), + "Dutch": Language( + name="Dutch", + iso_code="nl", + use_ascii=True, + charsets=["ISO-8859-1", "WINDOWS-1252", "MacRoman"], + wiki_start_pages=["Hoofdpagina"], + ), + "Polish": Language( + name="Polish", + iso_code="pl", + # Q and X are only used for foreign words. + use_ascii=False, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻaąbcćdeęfghijklłmnńoóprsśtuwyzźż", + wiki_start_pages=["Wikipedia:Strona_główna"], + ), + "Portuguese": Language( + name="Portuguese", + iso_code="pt", + use_ascii=True, + charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"], + alphabet="ÁÂÃÀÇÉÊÍÓÔÕÚáâãàçéêíóôõú", + wiki_start_pages=["Wikipédia:Página_principal"], + ), + "Romanian": Language( + name="Romanian", + iso_code="ro", + use_ascii=True, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="ăâîșțĂÂÎȘȚ", + wiki_start_pages=["Pagina_principală"], + ), + "Russian": Language( + name="Russian", + iso_code="ru", + use_ascii=False, + charsets=[ + "ISO-8859-5", + "WINDOWS-1251", + "KOI8-R", + "MacCyrillic", + "IBM866", + "IBM855", + ], + alphabet="абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", + wiki_start_pages=["Заглавная_страница"], + ), + "Slovak": Language( + name="Slovak", + iso_code="sk", + use_ascii=True, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="áäčďéíĺľňóôŕšťúýžÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽ", + wiki_start_pages=["Hlavná_stránka"], + ), + "Slovene": Language( + name="Slovene", + iso_code="sl", + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=["ISO-8859-2", "WINDOWS-1250"], + alphabet="abcčdefghijklmnoprsštuvzžABCČDEFGHIJKLMNOPRSŠTUVZŽ", + wiki_start_pages=["Glavna_stran"], + ), + # Serbian can be written in both Latin and Cyrillic, but there's no + # simple way to get the Latin alphabet pages from Wikipedia through + # the API, so for now we just support Cyrillic. + "Serbian": Language( + name="Serbian", + iso_code="sr", + alphabet="АБВГДЂЕЖЗИЈКЛЉМНЊОПРСТЋУФХЦЧЏШабвгдђежзијклљмнњопрстћуфхцчџш", + charsets=["ISO-8859-5", "WINDOWS-1251", "MacCyrillic", "IBM855"], + wiki_start_pages=["Главна_страна"], + ), + "Thai": Language( + name="Thai", + iso_code="th", + use_ascii=False, + charsets=["ISO-8859-11", "TIS-620", "CP874"], + alphabet="กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛", + wiki_start_pages=["หน้าหลัก"], + ), + "Turkish": Language( + name="Turkish", + iso_code="tr", + # Q, W, and X are not used by Turkish + use_ascii=False, + charsets=["ISO-8859-3", "ISO-8859-9", "WINDOWS-1254"], + alphabet="abcçdefgğhıijklmnoöprsştuüvyzâîûABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZÂÎÛ", + wiki_start_pages=["Ana_Sayfa"], + ), + "Vietnamese": Language( + name="Vietnamese", + iso_code="vi", + use_ascii=False, + # Windows-1258 is the only common 8-bit + # Vietnamese encoding supported by Python. + # From Wikipedia: + # For systems that lack support for Unicode, + # dozens of 8-bit Vietnamese code pages are + # available.[1] The most common are VISCII + # (TCVN 5712:1993), VPS, and Windows-1258.[3] + # Where ASCII is required, such as when + # ensuring readability in plain text e-mail, + # Vietnamese letters are often encoded + # according to Vietnamese Quoted-Readable + # (VIQR) or VSCII Mnemonic (VSCII-MNEM),[4] + # though usage of either variable-width + # scheme has declined dramatically following + # the adoption of Unicode on the World Wide + # Web. + charsets=["WINDOWS-1258"], + alphabet="aăâbcdđeêghiklmnoôơpqrstuưvxyAĂÂBCDĐEÊGHIKLMNOÔƠPQRSTUƯVXY", + wiki_start_pages=["Chữ_Quốc_ngữ"], + ), +} diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/py.typed b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/resultdict.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/resultdict.py new file mode 100644 index 0000000..7d36e64 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/resultdict.py @@ -0,0 +1,16 @@ +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + # TypedDict was introduced in Python 3.8. + # + # TODO: Remove the else block and TYPE_CHECKING check when dropping support + # for Python 3.7. + from typing import TypedDict + + class ResultDict(TypedDict): + encoding: Optional[str] + confidence: float + language: Optional[str] + +else: + ResultDict = dict diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcharsetprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcharsetprober.py new file mode 100644 index 0000000..0ffbcdd --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcharsetprober.py @@ -0,0 +1,162 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Dict, List, NamedTuple, Optional, Union + +from .charsetprober import CharSetProber +from .enums import CharacterCategory, ProbingState, SequenceLikelihood + + +class SingleByteCharSetModel(NamedTuple): + charset_name: str + language: str + char_to_order_map: Dict[int, int] + language_model: Dict[int, Dict[int, int]] + typical_positive_ratio: float + keep_ascii_letters: bool + alphabet: str + + +class SingleByteCharSetProber(CharSetProber): + SAMPLE_SIZE = 64 + SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2 + POSITIVE_SHORTCUT_THRESHOLD = 0.95 + NEGATIVE_SHORTCUT_THRESHOLD = 0.05 + + def __init__( + self, + model: SingleByteCharSetModel, + is_reversed: bool = False, + name_prober: Optional[CharSetProber] = None, + ) -> None: + super().__init__() + self._model = model + # TRUE if we need to reverse every pair in the model lookup + self._reversed = is_reversed + # Optional auxiliary prober for name decision + self._name_prober = name_prober + self._last_order = 255 + self._seq_counters: List[int] = [] + self._total_seqs = 0 + self._total_char = 0 + self._control_char = 0 + self._freq_char = 0 + self.reset() + + def reset(self) -> None: + super().reset() + # char order of last character + self._last_order = 255 + self._seq_counters = [0] * SequenceLikelihood.get_num_categories() + self._total_seqs = 0 + self._total_char = 0 + self._control_char = 0 + # characters that fall in our sampling range + self._freq_char = 0 + + @property + def charset_name(self) -> Optional[str]: + if self._name_prober: + return self._name_prober.charset_name + return self._model.charset_name + + @property + def language(self) -> Optional[str]: + if self._name_prober: + return self._name_prober.language + return self._model.language + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + # TODO: Make filter_international_words keep things in self.alphabet + if not self._model.keep_ascii_letters: + byte_str = self.filter_international_words(byte_str) + else: + byte_str = self.remove_xml_tags(byte_str) + if not byte_str: + return self.state + char_to_order_map = self._model.char_to_order_map + language_model = self._model.language_model + for char in byte_str: + order = char_to_order_map.get(char, CharacterCategory.UNDEFINED) + # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but + # CharacterCategory.SYMBOL is actually 253, so we use CONTROL + # to make it closer to the original intent. The only difference + # is whether or not we count digits and control characters for + # _total_char purposes. + if order < CharacterCategory.CONTROL: + self._total_char += 1 + if order < self.SAMPLE_SIZE: + self._freq_char += 1 + if self._last_order < self.SAMPLE_SIZE: + self._total_seqs += 1 + if not self._reversed: + lm_cat = language_model[self._last_order][order] + else: + lm_cat = language_model[order][self._last_order] + self._seq_counters[lm_cat] += 1 + self._last_order = order + + charset_name = self._model.charset_name + if self.state == ProbingState.DETECTING: + if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD: + confidence = self.get_confidence() + if confidence > self.POSITIVE_SHORTCUT_THRESHOLD: + self.logger.debug( + "%s confidence = %s, we have a winner", charset_name, confidence + ) + self._state = ProbingState.FOUND_IT + elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD: + self.logger.debug( + "%s confidence = %s, below negative shortcut threshold %s", + charset_name, + confidence, + self.NEGATIVE_SHORTCUT_THRESHOLD, + ) + self._state = ProbingState.NOT_ME + + return self.state + + def get_confidence(self) -> float: + r = 0.01 + if self._total_seqs > 0: + r = ( + ( + self._seq_counters[SequenceLikelihood.POSITIVE] + + 0.25 * self._seq_counters[SequenceLikelihood.LIKELY] + ) + / self._total_seqs + / self._model.typical_positive_ratio + ) + # The more control characters (proportionnaly to the size + # of the text), the less confident we become in the current + # charset. + r = r * (self._total_char - self._control_char) / self._total_char + r = r * self._freq_char / self._total_char + if r >= 1.0: + r = 0.99 + return r diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcsgroupprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcsgroupprober.py new file mode 100644 index 0000000..890ae84 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sbcsgroupprober.py @@ -0,0 +1,88 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .hebrewprober import HebrewProber +from .langbulgarianmodel import ISO_8859_5_BULGARIAN_MODEL, WINDOWS_1251_BULGARIAN_MODEL +from .langgreekmodel import ISO_8859_7_GREEK_MODEL, WINDOWS_1253_GREEK_MODEL +from .langhebrewmodel import WINDOWS_1255_HEBREW_MODEL + +# from .langhungarianmodel import (ISO_8859_2_HUNGARIAN_MODEL, +# WINDOWS_1250_HUNGARIAN_MODEL) +from .langrussianmodel import ( + IBM855_RUSSIAN_MODEL, + IBM866_RUSSIAN_MODEL, + ISO_8859_5_RUSSIAN_MODEL, + KOI8_R_RUSSIAN_MODEL, + MACCYRILLIC_RUSSIAN_MODEL, + WINDOWS_1251_RUSSIAN_MODEL, +) +from .langthaimodel import TIS_620_THAI_MODEL +from .langturkishmodel import ISO_8859_9_TURKISH_MODEL +from .sbcharsetprober import SingleByteCharSetProber + + +class SBCSGroupProber(CharSetGroupProber): + def __init__(self) -> None: + super().__init__() + hebrew_prober = HebrewProber() + logical_hebrew_prober = SingleByteCharSetProber( + WINDOWS_1255_HEBREW_MODEL, is_reversed=False, name_prober=hebrew_prober + ) + # TODO: See if using ISO-8859-8 Hebrew model works better here, since + # it's actually the visual one + visual_hebrew_prober = SingleByteCharSetProber( + WINDOWS_1255_HEBREW_MODEL, is_reversed=True, name_prober=hebrew_prober + ) + hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) + # TODO: ORDER MATTERS HERE. I changed the order vs what was in master + # and several tests failed that did not before. Some thought + # should be put into the ordering, and we should consider making + # order not matter here, because that is very counter-intuitive. + self.probers = [ + SingleByteCharSetProber(WINDOWS_1251_RUSSIAN_MODEL), + SingleByteCharSetProber(KOI8_R_RUSSIAN_MODEL), + SingleByteCharSetProber(ISO_8859_5_RUSSIAN_MODEL), + SingleByteCharSetProber(MACCYRILLIC_RUSSIAN_MODEL), + SingleByteCharSetProber(IBM866_RUSSIAN_MODEL), + SingleByteCharSetProber(IBM855_RUSSIAN_MODEL), + SingleByteCharSetProber(ISO_8859_7_GREEK_MODEL), + SingleByteCharSetProber(WINDOWS_1253_GREEK_MODEL), + SingleByteCharSetProber(ISO_8859_5_BULGARIAN_MODEL), + SingleByteCharSetProber(WINDOWS_1251_BULGARIAN_MODEL), + # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) + # after we retrain model. + # SingleByteCharSetProber(ISO_8859_2_HUNGARIAN_MODEL), + # SingleByteCharSetProber(WINDOWS_1250_HUNGARIAN_MODEL), + SingleByteCharSetProber(TIS_620_THAI_MODEL), + SingleByteCharSetProber(ISO_8859_9_TURKISH_MODEL), + hebrew_prober, + logical_hebrew_prober, + visual_hebrew_prober, + ] + self.reset() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sjisprober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sjisprober.py new file mode 100644 index 0000000..91df077 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/sjisprober.py @@ -0,0 +1,105 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Union + +from .chardistribution import SJISDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .enums import MachineState, ProbingState +from .jpcntx import SJISContextAnalysis +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import SJIS_SM_MODEL + + +class SJISProber(MultiByteCharSetProber): + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(SJIS_SM_MODEL) + self.distribution_analyzer = SJISDistributionAnalysis() + self.context_analyzer = SJISContextAnalysis() + self.reset() + + def reset(self) -> None: + super().reset() + self.context_analyzer.reset() + + @property + def charset_name(self) -> str: + return self.context_analyzer.charset_name + + @property + def language(self) -> str: + return "Japanese" + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + assert self.coding_sm is not None + assert self.distribution_analyzer is not None + + for i, byte in enumerate(byte_str): + coding_state = self.coding_sm.next_state(byte) + if coding_state == MachineState.ERROR: + self.logger.debug( + "%s %s prober hit error at byte %s", + self.charset_name, + self.language, + i, + ) + self._state = ProbingState.NOT_ME + break + if coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + if coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte + self.context_analyzer.feed( + self._last_char[2 - char_len :], char_len + ) + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.context_analyzer.feed( + byte_str[i + 1 - char_len : i + 3 - char_len], char_len + ) + self.distribution_analyzer.feed(byte_str[i - 1 : i + 1], char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if self.context_analyzer.got_enough_data() and ( + self.get_confidence() > self.SHORTCUT_THRESHOLD + ): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self) -> float: + assert self.distribution_analyzer is not None + + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/universaldetector.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/universaldetector.py new file mode 100644 index 0000000..30c441d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/universaldetector.py @@ -0,0 +1,362 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### +""" +Module containing the UniversalDetector detector class, which is the primary +class a user of ``chardet`` should use. + +:author: Mark Pilgrim (initial port to Python) +:author: Shy Shalom (original C code) +:author: Dan Blanchard (major refactoring for 3.0) +:author: Ian Cordasco +""" + + +import codecs +import logging +import re +from typing import List, Optional, Union + +from .charsetgroupprober import CharSetGroupProber +from .charsetprober import CharSetProber +from .enums import InputState, LanguageFilter, ProbingState +from .escprober import EscCharSetProber +from .latin1prober import Latin1Prober +from .macromanprober import MacRomanProber +from .mbcsgroupprober import MBCSGroupProber +from .resultdict import ResultDict +from .sbcsgroupprober import SBCSGroupProber +from .utf1632prober import UTF1632Prober + + +class UniversalDetector: + """ + The ``UniversalDetector`` class underlies the ``chardet.detect`` function + and coordinates all of the different charset probers. + + To get a ``dict`` containing an encoding and its confidence, you can simply + run: + + .. code:: + + u = UniversalDetector() + u.feed(some_bytes) + u.close() + detected = u.result + + """ + + MINIMUM_THRESHOLD = 0.20 + HIGH_BYTE_DETECTOR = re.compile(b"[\x80-\xFF]") + ESC_DETECTOR = re.compile(b"(\033|~{)") + WIN_BYTE_DETECTOR = re.compile(b"[\x80-\x9F]") + ISO_WIN_MAP = { + "iso-8859-1": "Windows-1252", + "iso-8859-2": "Windows-1250", + "iso-8859-5": "Windows-1251", + "iso-8859-6": "Windows-1256", + "iso-8859-7": "Windows-1253", + "iso-8859-8": "Windows-1255", + "iso-8859-9": "Windows-1254", + "iso-8859-13": "Windows-1257", + } + # Based on https://encoding.spec.whatwg.org/#names-and-labels + # but altered to match Python names for encodings and remove mappings + # that break tests. + LEGACY_MAP = { + "ascii": "Windows-1252", + "iso-8859-1": "Windows-1252", + "tis-620": "ISO-8859-11", + "iso-8859-9": "Windows-1254", + "gb2312": "GB18030", + "euc-kr": "CP949", + "utf-16le": "UTF-16", + } + + def __init__( + self, + lang_filter: LanguageFilter = LanguageFilter.ALL, + should_rename_legacy: bool = False, + ) -> None: + self._esc_charset_prober: Optional[EscCharSetProber] = None + self._utf1632_prober: Optional[UTF1632Prober] = None + self._charset_probers: List[CharSetProber] = [] + self.result: ResultDict = { + "encoding": None, + "confidence": 0.0, + "language": None, + } + self.done = False + self._got_data = False + self._input_state = InputState.PURE_ASCII + self._last_char = b"" + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + self._has_win_bytes = False + self.should_rename_legacy = should_rename_legacy + self.reset() + + @property + def input_state(self) -> int: + return self._input_state + + @property + def has_win_bytes(self) -> bool: + return self._has_win_bytes + + @property + def charset_probers(self) -> List[CharSetProber]: + return self._charset_probers + + def reset(self) -> None: + """ + Reset the UniversalDetector and all of its probers back to their + initial states. This is called by ``__init__``, so you only need to + call this directly in between analyses of different documents. + """ + self.result = {"encoding": None, "confidence": 0.0, "language": None} + self.done = False + self._got_data = False + self._has_win_bytes = False + self._input_state = InputState.PURE_ASCII + self._last_char = b"" + if self._esc_charset_prober: + self._esc_charset_prober.reset() + if self._utf1632_prober: + self._utf1632_prober.reset() + for prober in self._charset_probers: + prober.reset() + + def feed(self, byte_str: Union[bytes, bytearray]) -> None: + """ + Takes a chunk of a document and feeds it through all of the relevant + charset probers. + + After calling ``feed``, you can check the value of the ``done`` + attribute to see if you need to continue feeding the + ``UniversalDetector`` more data, or if it has made a prediction + (in the ``result`` attribute). + + .. note:: + You should always call ``close`` when you're done feeding in your + document if ``done`` is not already ``True``. + """ + if self.done: + return + + if not byte_str: + return + + if not isinstance(byte_str, bytearray): + byte_str = bytearray(byte_str) + + # First check for known BOMs, since these are guaranteed to be correct + if not self._got_data: + # If the data starts with BOM, we know it is UTF + if byte_str.startswith(codecs.BOM_UTF8): + # EF BB BF UTF-8 with BOM + self.result = { + "encoding": "UTF-8-SIG", + "confidence": 1.0, + "language": "", + } + elif byte_str.startswith((codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)): + # FF FE 00 00 UTF-32, little-endian BOM + # 00 00 FE FF UTF-32, big-endian BOM + self.result = {"encoding": "UTF-32", "confidence": 1.0, "language": ""} + elif byte_str.startswith(b"\xFE\xFF\x00\x00"): + # FE FF 00 00 UCS-4, unusual octet order BOM (3412) + self.result = { + # TODO: This encoding is not supported by Python. Should remove? + "encoding": "X-ISO-10646-UCS-4-3412", + "confidence": 1.0, + "language": "", + } + elif byte_str.startswith(b"\x00\x00\xFF\xFE"): + # 00 00 FF FE UCS-4, unusual octet order BOM (2143) + self.result = { + # TODO: This encoding is not supported by Python. Should remove? + "encoding": "X-ISO-10646-UCS-4-2143", + "confidence": 1.0, + "language": "", + } + elif byte_str.startswith((codecs.BOM_LE, codecs.BOM_BE)): + # FF FE UTF-16, little endian BOM + # FE FF UTF-16, big endian BOM + self.result = {"encoding": "UTF-16", "confidence": 1.0, "language": ""} + + self._got_data = True + if self.result["encoding"] is not None: + self.done = True + return + + # If none of those matched and we've only see ASCII so far, check + # for high bytes and escape sequences + if self._input_state == InputState.PURE_ASCII: + if self.HIGH_BYTE_DETECTOR.search(byte_str): + self._input_state = InputState.HIGH_BYTE + elif ( + self._input_state == InputState.PURE_ASCII + and self.ESC_DETECTOR.search(self._last_char + byte_str) + ): + self._input_state = InputState.ESC_ASCII + + self._last_char = byte_str[-1:] + + # next we will look to see if it is appears to be either a UTF-16 or + # UTF-32 encoding + if not self._utf1632_prober: + self._utf1632_prober = UTF1632Prober() + + if self._utf1632_prober.state == ProbingState.DETECTING: + if self._utf1632_prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = { + "encoding": self._utf1632_prober.charset_name, + "confidence": self._utf1632_prober.get_confidence(), + "language": "", + } + self.done = True + return + + # If we've seen escape sequences, use the EscCharSetProber, which + # uses a simple state machine to check for known escape sequences in + # HZ and ISO-2022 encodings, since those are the only encodings that + # use such sequences. + if self._input_state == InputState.ESC_ASCII: + if not self._esc_charset_prober: + self._esc_charset_prober = EscCharSetProber(self.lang_filter) + if self._esc_charset_prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = { + "encoding": self._esc_charset_prober.charset_name, + "confidence": self._esc_charset_prober.get_confidence(), + "language": self._esc_charset_prober.language, + } + self.done = True + # If we've seen high bytes (i.e., those with values greater than 127), + # we need to do more complicated checks using all our multi-byte and + # single-byte probers that are left. The single-byte probers + # use character bigram distributions to determine the encoding, whereas + # the multi-byte probers use a combination of character unigram and + # bigram distributions. + elif self._input_state == InputState.HIGH_BYTE: + if not self._charset_probers: + self._charset_probers = [MBCSGroupProber(self.lang_filter)] + # If we're checking non-CJK encodings, use single-byte prober + if self.lang_filter & LanguageFilter.NON_CJK: + self._charset_probers.append(SBCSGroupProber()) + self._charset_probers.append(Latin1Prober()) + self._charset_probers.append(MacRomanProber()) + for prober in self._charset_probers: + if prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = { + "encoding": prober.charset_name, + "confidence": prober.get_confidence(), + "language": prober.language, + } + self.done = True + break + if self.WIN_BYTE_DETECTOR.search(byte_str): + self._has_win_bytes = True + + def close(self) -> ResultDict: + """ + Stop analyzing the current document and come up with a final + prediction. + + :returns: The ``result`` attribute, a ``dict`` with the keys + `encoding`, `confidence`, and `language`. + """ + # Don't bother with checks if we're already done + if self.done: + return self.result + self.done = True + + if not self._got_data: + self.logger.debug("no data received!") + + # Default to ASCII if it is all we've seen so far + elif self._input_state == InputState.PURE_ASCII: + self.result = {"encoding": "ascii", "confidence": 1.0, "language": ""} + + # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD + elif self._input_state == InputState.HIGH_BYTE: + prober_confidence = None + max_prober_confidence = 0.0 + max_prober = None + for prober in self._charset_probers: + if not prober: + continue + prober_confidence = prober.get_confidence() + if prober_confidence > max_prober_confidence: + max_prober_confidence = prober_confidence + max_prober = prober + if max_prober and (max_prober_confidence > self.MINIMUM_THRESHOLD): + charset_name = max_prober.charset_name + assert charset_name is not None + lower_charset_name = charset_name.lower() + confidence = max_prober.get_confidence() + # Use Windows encoding name instead of ISO-8859 if we saw any + # extra Windows-specific bytes + if lower_charset_name.startswith("iso-8859"): + if self._has_win_bytes: + charset_name = self.ISO_WIN_MAP.get( + lower_charset_name, charset_name + ) + # Rename legacy encodings with superset encodings if asked + if self.should_rename_legacy: + charset_name = self.LEGACY_MAP.get( + (charset_name or "").lower(), charset_name + ) + self.result = { + "encoding": charset_name, + "confidence": confidence, + "language": max_prober.language, + } + + # Log all prober confidences if none met MINIMUM_THRESHOLD + if self.logger.getEffectiveLevel() <= logging.DEBUG: + if self.result["encoding"] is None: + self.logger.debug("no probers hit minimum threshold") + for group_prober in self._charset_probers: + if not group_prober: + continue + if isinstance(group_prober, CharSetGroupProber): + for prober in group_prober.probers: + self.logger.debug( + "%s %s confidence = %s", + prober.charset_name, + prober.language, + prober.get_confidence(), + ) + else: + self.logger.debug( + "%s %s confidence = %s", + group_prober.charset_name, + group_prober.language, + group_prober.get_confidence(), + ) + return self.result diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf1632prober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf1632prober.py new file mode 100644 index 0000000..6bdec63 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf1632prober.py @@ -0,0 +1,225 @@ +######################## BEGIN LICENSE BLOCK ######################## +# +# Contributor(s): +# Jason Zavaglia +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### +from typing import List, Union + +from .charsetprober import CharSetProber +from .enums import ProbingState + + +class UTF1632Prober(CharSetProber): + """ + This class simply looks for occurrences of zero bytes, and infers + whether the file is UTF16 or UTF32 (low-endian or big-endian) + For instance, files looking like ( \0 \0 \0 [nonzero] )+ + have a good probability to be UTF32BE. Files looking like ( \0 [nonzero] )+ + may be guessed to be UTF16BE, and inversely for little-endian varieties. + """ + + # how many logical characters to scan before feeling confident of prediction + MIN_CHARS_FOR_DETECTION = 20 + # a fixed constant ratio of expected zeros or non-zeros in modulo-position. + EXPECTED_RATIO = 0.94 + + def __init__(self) -> None: + super().__init__() + self.position = 0 + self.zeros_at_mod = [0] * 4 + self.nonzeros_at_mod = [0] * 4 + self._state = ProbingState.DETECTING + self.quad = [0, 0, 0, 0] + self.invalid_utf16be = False + self.invalid_utf16le = False + self.invalid_utf32be = False + self.invalid_utf32le = False + self.first_half_surrogate_pair_detected_16be = False + self.first_half_surrogate_pair_detected_16le = False + self.reset() + + def reset(self) -> None: + super().reset() + self.position = 0 + self.zeros_at_mod = [0] * 4 + self.nonzeros_at_mod = [0] * 4 + self._state = ProbingState.DETECTING + self.invalid_utf16be = False + self.invalid_utf16le = False + self.invalid_utf32be = False + self.invalid_utf32le = False + self.first_half_surrogate_pair_detected_16be = False + self.first_half_surrogate_pair_detected_16le = False + self.quad = [0, 0, 0, 0] + + @property + def charset_name(self) -> str: + if self.is_likely_utf32be(): + return "utf-32be" + if self.is_likely_utf32le(): + return "utf-32le" + if self.is_likely_utf16be(): + return "utf-16be" + if self.is_likely_utf16le(): + return "utf-16le" + # default to something valid + return "utf-16" + + @property + def language(self) -> str: + return "" + + def approx_32bit_chars(self) -> float: + return max(1.0, self.position / 4.0) + + def approx_16bit_chars(self) -> float: + return max(1.0, self.position / 2.0) + + def is_likely_utf32be(self) -> bool: + approx_chars = self.approx_32bit_chars() + return approx_chars >= self.MIN_CHARS_FOR_DETECTION and ( + self.zeros_at_mod[0] / approx_chars > self.EXPECTED_RATIO + and self.zeros_at_mod[1] / approx_chars > self.EXPECTED_RATIO + and self.zeros_at_mod[2] / approx_chars > self.EXPECTED_RATIO + and self.nonzeros_at_mod[3] / approx_chars > self.EXPECTED_RATIO + and not self.invalid_utf32be + ) + + def is_likely_utf32le(self) -> bool: + approx_chars = self.approx_32bit_chars() + return approx_chars >= self.MIN_CHARS_FOR_DETECTION and ( + self.nonzeros_at_mod[0] / approx_chars > self.EXPECTED_RATIO + and self.zeros_at_mod[1] / approx_chars > self.EXPECTED_RATIO + and self.zeros_at_mod[2] / approx_chars > self.EXPECTED_RATIO + and self.zeros_at_mod[3] / approx_chars > self.EXPECTED_RATIO + and not self.invalid_utf32le + ) + + def is_likely_utf16be(self) -> bool: + approx_chars = self.approx_16bit_chars() + return approx_chars >= self.MIN_CHARS_FOR_DETECTION and ( + (self.nonzeros_at_mod[1] + self.nonzeros_at_mod[3]) / approx_chars + > self.EXPECTED_RATIO + and (self.zeros_at_mod[0] + self.zeros_at_mod[2]) / approx_chars + > self.EXPECTED_RATIO + and not self.invalid_utf16be + ) + + def is_likely_utf16le(self) -> bool: + approx_chars = self.approx_16bit_chars() + return approx_chars >= self.MIN_CHARS_FOR_DETECTION and ( + (self.nonzeros_at_mod[0] + self.nonzeros_at_mod[2]) / approx_chars + > self.EXPECTED_RATIO + and (self.zeros_at_mod[1] + self.zeros_at_mod[3]) / approx_chars + > self.EXPECTED_RATIO + and not self.invalid_utf16le + ) + + def validate_utf32_characters(self, quad: List[int]) -> None: + """ + Validate if the quad of bytes is valid UTF-32. + + UTF-32 is valid in the range 0x00000000 - 0x0010FFFF + excluding 0x0000D800 - 0x0000DFFF + + https://en.wikipedia.org/wiki/UTF-32 + """ + if ( + quad[0] != 0 + or quad[1] > 0x10 + or (quad[0] == 0 and quad[1] == 0 and 0xD8 <= quad[2] <= 0xDF) + ): + self.invalid_utf32be = True + if ( + quad[3] != 0 + or quad[2] > 0x10 + or (quad[3] == 0 and quad[2] == 0 and 0xD8 <= quad[1] <= 0xDF) + ): + self.invalid_utf32le = True + + def validate_utf16_characters(self, pair: List[int]) -> None: + """ + Validate if the pair of bytes is valid UTF-16. + + UTF-16 is valid in the range 0x0000 - 0xFFFF excluding 0xD800 - 0xFFFF + with an exception for surrogate pairs, which must be in the range + 0xD800-0xDBFF followed by 0xDC00-0xDFFF + + https://en.wikipedia.org/wiki/UTF-16 + """ + if not self.first_half_surrogate_pair_detected_16be: + if 0xD8 <= pair[0] <= 0xDB: + self.first_half_surrogate_pair_detected_16be = True + elif 0xDC <= pair[0] <= 0xDF: + self.invalid_utf16be = True + else: + if 0xDC <= pair[0] <= 0xDF: + self.first_half_surrogate_pair_detected_16be = False + else: + self.invalid_utf16be = True + + if not self.first_half_surrogate_pair_detected_16le: + if 0xD8 <= pair[1] <= 0xDB: + self.first_half_surrogate_pair_detected_16le = True + elif 0xDC <= pair[1] <= 0xDF: + self.invalid_utf16le = True + else: + if 0xDC <= pair[1] <= 0xDF: + self.first_half_surrogate_pair_detected_16le = False + else: + self.invalid_utf16le = True + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + for c in byte_str: + mod4 = self.position % 4 + self.quad[mod4] = c + if mod4 == 3: + self.validate_utf32_characters(self.quad) + self.validate_utf16_characters(self.quad[0:2]) + self.validate_utf16_characters(self.quad[2:4]) + if c == 0: + self.zeros_at_mod[mod4] += 1 + else: + self.nonzeros_at_mod[mod4] += 1 + self.position += 1 + return self.state + + @property + def state(self) -> ProbingState: + if self._state in {ProbingState.NOT_ME, ProbingState.FOUND_IT}: + # terminal, decided states + return self._state + if self.get_confidence() > 0.80: + self._state = ProbingState.FOUND_IT + elif self.position > 4 * 1024: + # if we get to 4kb into the file, and we can't conclude it's UTF, + # let's give up + self._state = ProbingState.NOT_ME + return self._state + + def get_confidence(self) -> float: + return ( + 0.85 + if ( + self.is_likely_utf16le() + or self.is_likely_utf16be() + or self.is_likely_utf32le() + or self.is_likely_utf32be() + ) + else 0.00 + ) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf8prober.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf8prober.py new file mode 100644 index 0000000..d96354d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/utf8prober.py @@ -0,0 +1,82 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from typing import Union + +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .enums import MachineState, ProbingState +from .mbcssm import UTF8_SM_MODEL + + +class UTF8Prober(CharSetProber): + ONE_CHAR_PROB = 0.5 + + def __init__(self) -> None: + super().__init__() + self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) + self._num_mb_chars = 0 + self.reset() + + def reset(self) -> None: + super().reset() + self.coding_sm.reset() + self._num_mb_chars = 0 + + @property + def charset_name(self) -> str: + return "utf-8" + + @property + def language(self) -> str: + return "" + + def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState: + for c in byte_str: + coding_state = self.coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + self._state = ProbingState.NOT_ME + break + if coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + if coding_state == MachineState.START: + if self.coding_sm.get_current_charlen() >= 2: + self._num_mb_chars += 1 + + if self.state == ProbingState.DETECTING: + if self.get_confidence() > self.SHORTCUT_THRESHOLD: + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self) -> float: + unlike = 0.99 + if self._num_mb_chars < 6: + unlike *= self.ONE_CHAR_PROB**self._num_mb_chars + return 1.0 - unlike + return unlike diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/version.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/version.py new file mode 100644 index 0000000..19dd01e --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/chardet/version.py @@ -0,0 +1,9 @@ +""" +This module exists only to simplify retrieving the version number of chardet +from within setuptools and from chardet subpackages. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + +__version__ = "5.2.0" +VERSION = __version__.split(".") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/__init__.py new file mode 100644 index 0000000..77f028b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/__init__.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" + CSS Selectors based on XPath + ============================ + + This module supports selecting XML/HTML elements based on CSS selectors. + See the `CSSSelector` class for details. + + + :copyright: (c) 2007-2012 Ian Bicking and contributors. + See AUTHORS for more details. + :license: BSD, see LICENSE for more details. + +""" + +from cssselect.parser import ( + parse, + Selector, + FunctionalPseudoElement, + SelectorError, + SelectorSyntaxError, +) +from cssselect.xpath import GenericTranslator, HTMLTranslator, ExpressionError + +__all__ = ( + "ExpressionError", + "FunctionalPseudoElement", + "GenericTranslator", + "HTMLTranslator", + "parse", + "Selector", + "SelectorError", + "SelectorSyntaxError", +) + +VERSION = "1.2.0" +__version__ = VERSION diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/parser.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/parser.py new file mode 100644 index 0000000..25a650c --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/parser.py @@ -0,0 +1,1062 @@ +# -*- coding: utf-8 -*- +""" + cssselect.parser + ================ + + Tokenizer, parser and parsed objects for CSS selectors. + + + :copyright: (c) 2007-2012 Ian Bicking and contributors. + See AUTHORS for more details. + :license: BSD, see LICENSE for more details. + +""" + +import sys +import re +import operator +import typing +from typing import Iterable, Iterator, List, Optional, Sequence, Tuple, Union + + +def ascii_lower(string: str) -> str: + """Lower-case, but only in the ASCII range.""" + return string.encode("utf8").lower().decode("utf8") + + +class SelectorError(Exception): + """Common parent for :class:`SelectorSyntaxError` and + :class:`ExpressionError`. + + You can just use ``except SelectorError:`` when calling + :meth:`~GenericTranslator.css_to_xpath` and handle both exceptions types. + + """ + + +class SelectorSyntaxError(SelectorError, SyntaxError): + """Parsing a selector that does not match the grammar.""" + + +#### Parsed objects + +Tree = Union[ + "Element", + "Hash", + "Class", + "Function", + "Pseudo", + "Attrib", + "Negation", + "Relation", + "Matching", + "SpecificityAdjustment", + "CombinedSelector", +] +PseudoElement = Union["FunctionalPseudoElement", str] + + +class Selector: + """ + Represents a parsed selector. + + :meth:`~GenericTranslator.selector_to_xpath` accepts this object, + but ignores :attr:`pseudo_element`. It is the user’s responsibility + to account for pseudo-elements and reject selectors with unknown + or unsupported pseudo-elements. + + """ + + def __init__(self, tree: Tree, pseudo_element: Optional[PseudoElement] = None) -> None: + self.parsed_tree = tree + if pseudo_element is not None and not isinstance(pseudo_element, FunctionalPseudoElement): + pseudo_element = ascii_lower(pseudo_element) + #: A :class:`FunctionalPseudoElement`, + #: or the identifier for the pseudo-element as a string, + # or ``None``. + #: + #: +-------------------------+----------------+--------------------------------+ + #: | | Selector | Pseudo-element | + #: +=========================+================+================================+ + #: | CSS3 syntax | ``a::before`` | ``'before'`` | + #: +-------------------------+----------------+--------------------------------+ + #: | Older syntax | ``a:before`` | ``'before'`` | + #: +-------------------------+----------------+--------------------------------+ + #: | From the Lists3_ draft, | ``li::marker`` | ``'marker'`` | + #: | not in Selectors3 | | | + #: +-------------------------+----------------+--------------------------------+ + #: | Invalid pseudo-class | ``li:marker`` | ``None`` | + #: +-------------------------+----------------+--------------------------------+ + #: | Functional | ``a::foo(2)`` | ``FunctionalPseudoElement(…)`` | + #: +-------------------------+----------------+--------------------------------+ + #: + #: .. _Lists3: http://www.w3.org/TR/2011/WD-css3-lists-20110524/#marker-pseudoelement + self.pseudo_element = pseudo_element + + def __repr__(self) -> str: + if isinstance(self.pseudo_element, FunctionalPseudoElement): + pseudo_element = repr(self.pseudo_element) + elif self.pseudo_element: + pseudo_element = "::%s" % self.pseudo_element + else: + pseudo_element = "" + return "%s[%r%s]" % (self.__class__.__name__, self.parsed_tree, pseudo_element) + + def canonical(self) -> str: + """Return a CSS representation for this selector (a string)""" + if isinstance(self.pseudo_element, FunctionalPseudoElement): + pseudo_element = "::%s" % self.pseudo_element.canonical() + elif self.pseudo_element: + pseudo_element = "::%s" % self.pseudo_element + else: + pseudo_element = "" + res = "%s%s" % (self.parsed_tree.canonical(), pseudo_element) + if len(res) > 1: + res = res.lstrip("*") + return res + + def specificity(self) -> Tuple[int, int, int]: + """Return the specificity_ of this selector as a tuple of 3 integers. + + .. _specificity: http://www.w3.org/TR/selectors/#specificity + + """ + a, b, c = self.parsed_tree.specificity() + if self.pseudo_element: + c += 1 + return a, b, c + + +class Class: + """ + Represents selector.class_name + """ + + def __init__(self, selector: Tree, class_name: str) -> None: + self.selector = selector + self.class_name = class_name + + def __repr__(self) -> str: + return "%s[%r.%s]" % (self.__class__.__name__, self.selector, self.class_name) + + def canonical(self) -> str: + return "%s.%s" % (self.selector.canonical(), self.class_name) + + def specificity(self) -> Tuple[int, int, int]: + a, b, c = self.selector.specificity() + b += 1 + return a, b, c + + +class FunctionalPseudoElement: + """ + Represents selector::name(arguments) + + .. attribute:: name + + The name (identifier) of the pseudo-element, as a string. + + .. attribute:: arguments + + The arguments of the pseudo-element, as a list of tokens. + + **Note:** tokens are not part of the public API, + and may change between cssselect versions. + Use at your own risks. + + """ + + def __init__(self, name: str, arguments: Sequence["Token"]): + self.name = ascii_lower(name) + self.arguments = arguments + + def __repr__(self) -> str: + return "%s[::%s(%r)]" % ( + self.__class__.__name__, + self.name, + [token.value for token in self.arguments], + ) + + def argument_types(self) -> List[str]: + return [token.type for token in self.arguments] + + def canonical(self) -> str: + args = "".join(token.css() for token in self.arguments) + return "%s(%s)" % (self.name, args) + + +class Function: + """ + Represents selector:name(expr) + """ + + def __init__(self, selector: Tree, name: str, arguments: Sequence["Token"]) -> None: + self.selector = selector + self.name = ascii_lower(name) + self.arguments = arguments + + def __repr__(self) -> str: + return "%s[%r:%s(%r)]" % ( + self.__class__.__name__, + self.selector, + self.name, + [token.value for token in self.arguments], + ) + + def argument_types(self) -> List[str]: + return [token.type for token in self.arguments] + + def canonical(self) -> str: + args = "".join(token.css() for token in self.arguments) + return "%s:%s(%s)" % (self.selector.canonical(), self.name, args) + + def specificity(self) -> Tuple[int, int, int]: + a, b, c = self.selector.specificity() + b += 1 + return a, b, c + + +class Pseudo: + """ + Represents selector:ident + """ + + def __init__(self, selector: Tree, ident: str) -> None: + self.selector = selector + self.ident = ascii_lower(ident) + + def __repr__(self) -> str: + return "%s[%r:%s]" % (self.__class__.__name__, self.selector, self.ident) + + def canonical(self) -> str: + return "%s:%s" % (self.selector.canonical(), self.ident) + + def specificity(self) -> Tuple[int, int, int]: + a, b, c = self.selector.specificity() + b += 1 + return a, b, c + + +class Negation: + """ + Represents selector:not(subselector) + """ + + def __init__(self, selector: Tree, subselector: Tree) -> None: + self.selector = selector + self.subselector = subselector + + def __repr__(self) -> str: + return "%s[%r:not(%r)]" % (self.__class__.__name__, self.selector, self.subselector) + + def canonical(self) -> str: + subsel = self.subselector.canonical() + if len(subsel) > 1: + subsel = subsel.lstrip("*") + return "%s:not(%s)" % (self.selector.canonical(), subsel) + + def specificity(self) -> Tuple[int, int, int]: + a1, b1, c1 = self.selector.specificity() + a2, b2, c2 = self.subselector.specificity() + return a1 + a2, b1 + b2, c1 + c2 + + +class Relation: + """ + Represents selector:has(subselector) + """ + + def __init__(self, selector: Tree, combinator: "Token", subselector: Selector): + self.selector = selector + self.combinator = combinator + self.subselector = subselector + + def __repr__(self) -> str: + return "%s[%r:has(%r)]" % ( + self.__class__.__name__, + self.selector, + self.subselector, + ) + + def canonical(self) -> str: + try: + subsel = self.subselector[0].canonical() # type: ignore + except TypeError: + subsel = self.subselector.canonical() + if len(subsel) > 1: + subsel = subsel.lstrip("*") + return "%s:has(%s)" % (self.selector.canonical(), subsel) + + def specificity(self) -> Tuple[int, int, int]: + a1, b1, c1 = self.selector.specificity() + try: + a2, b2, c2 = self.subselector[-1].specificity() # type: ignore + except TypeError: + a2, b2, c2 = self.subselector.specificity() + return a1 + a2, b1 + b2, c1 + c2 + + +class Matching: + """ + Represents selector:is(selector_list) + """ + + def __init__(self, selector: Tree, selector_list: Iterable[Tree]): + self.selector = selector + self.selector_list = selector_list + + def __repr__(self) -> str: + return "%s[%r:is(%s)]" % ( + self.__class__.__name__, + self.selector, + ", ".join(map(repr, self.selector_list)), + ) + + def canonical(self) -> str: + selector_arguments = [] + for s in self.selector_list: + selarg = s.canonical() + selector_arguments.append(selarg.lstrip("*")) + return "%s:is(%s)" % (self.selector.canonical(), ", ".join(map(str, selector_arguments))) + + def specificity(self) -> Tuple[int, int, int]: + return max(x.specificity() for x in self.selector_list) + + +class SpecificityAdjustment: + """ + Represents selector:where(selector_list) + Same as selector:is(selector_list), but its specificity is always 0 + """ + + def __init__(self, selector: Tree, selector_list: List[Tree]): + self.selector = selector + self.selector_list = selector_list + + def __repr__(self) -> str: + return "%s[%r:where(%s)]" % ( + self.__class__.__name__, + self.selector, + ", ".join(map(repr, self.selector_list)), + ) + + def canonical(self) -> str: + selector_arguments = [] + for s in self.selector_list: + selarg = s.canonical() + selector_arguments.append(selarg.lstrip("*")) + return "%s:where(%s)" % ( + self.selector.canonical(), + ", ".join(map(str, selector_arguments)), + ) + + def specificity(self) -> Tuple[int, int, int]: + return 0, 0, 0 + + +class Attrib: + """ + Represents selector[namespace|attrib operator value] + """ + + @typing.overload + def __init__( + self, + selector: Tree, + namespace: Optional[str], + attrib: str, + operator: 'typing.Literal["exists"]', + value: None, + ) -> None: + ... + + @typing.overload + def __init__( + self, selector: Tree, namespace: Optional[str], attrib: str, operator: str, value: "Token" + ) -> None: + ... + + def __init__( + self, + selector: Tree, + namespace: Optional[str], + attrib: str, + operator: str, + value: Optional["Token"], + ) -> None: + self.selector = selector + self.namespace = namespace + self.attrib = attrib + self.operator = operator + self.value = value + + def __repr__(self) -> str: + if self.namespace: + attrib = "%s|%s" % (self.namespace, self.attrib) + else: + attrib = self.attrib + if self.operator == "exists": + return "%s[%r[%s]]" % (self.__class__.__name__, self.selector, attrib) + else: + return "%s[%r[%s %s %r]]" % ( + self.__class__.__name__, + self.selector, + attrib, + self.operator, + typing.cast("Token", self.value).value, + ) + + def canonical(self) -> str: + if self.namespace: + attrib = "%s|%s" % (self.namespace, self.attrib) + else: + attrib = self.attrib + + if self.operator == "exists": + op = attrib + else: + op = "%s%s%s" % (attrib, self.operator, typing.cast("Token", self.value).css()) + + return "%s[%s]" % (self.selector.canonical(), op) + + def specificity(self) -> Tuple[int, int, int]: + a, b, c = self.selector.specificity() + b += 1 + return a, b, c + + +class Element: + """ + Represents namespace|element + + `None` is for the universal selector '*' + + """ + + def __init__(self, namespace: Optional[str] = None, element: Optional[str] = None) -> None: + self.namespace = namespace + self.element = element + + def __repr__(self) -> str: + return "%s[%s]" % (self.__class__.__name__, self.canonical()) + + def canonical(self) -> str: + element = self.element or "*" + if self.namespace: + element = "%s|%s" % (self.namespace, element) + return element + + def specificity(self) -> Tuple[int, int, int]: + if self.element: + return 0, 0, 1 + else: + return 0, 0, 0 + + +class Hash: + """ + Represents selector#id + """ + + def __init__(self, selector: Tree, id: str) -> None: + self.selector = selector + self.id = id + + def __repr__(self) -> str: + return "%s[%r#%s]" % (self.__class__.__name__, self.selector, self.id) + + def canonical(self) -> str: + return "%s#%s" % (self.selector.canonical(), self.id) + + def specificity(self) -> Tuple[int, int, int]: + a, b, c = self.selector.specificity() + a += 1 + return a, b, c + + +class CombinedSelector: + def __init__(self, selector: Tree, combinator: str, subselector: Tree) -> None: + assert selector is not None + self.selector = selector + self.combinator = combinator + self.subselector = subselector + + def __repr__(self) -> str: + if self.combinator == " ": + comb = "" + else: + comb = self.combinator + return "%s[%r %s %r]" % (self.__class__.__name__, self.selector, comb, self.subselector) + + def canonical(self) -> str: + subsel = self.subselector.canonical() + if len(subsel) > 1: + subsel = subsel.lstrip("*") + return "%s %s %s" % (self.selector.canonical(), self.combinator, subsel) + + def specificity(self) -> Tuple[int, int, int]: + a1, b1, c1 = self.selector.specificity() + a2, b2, c2 = self.subselector.specificity() + return a1 + a2, b1 + b2, c1 + c2 + + +#### Parser + +# foo +_el_re = re.compile(r"^[ \t\r\n\f]*([a-zA-Z]+)[ \t\r\n\f]*$") + +# foo#bar or #bar +_id_re = re.compile(r"^[ \t\r\n\f]*([a-zA-Z]*)#([a-zA-Z0-9_-]+)[ \t\r\n\f]*$") + +# foo.bar or .bar +_class_re = re.compile(r"^[ \t\r\n\f]*([a-zA-Z]*)\.([a-zA-Z][a-zA-Z0-9_-]*)[ \t\r\n\f]*$") + + +def parse(css: str) -> List[Selector]: + """Parse a CSS *group of selectors*. + + If you don't care about pseudo-elements or selector specificity, + you can skip this and use :meth:`~GenericTranslator.css_to_xpath`. + + :param css: + A *group of selectors* as a string. + :raises: + :class:`SelectorSyntaxError` on invalid selectors. + :returns: + A list of parsed :class:`Selector` objects, one for each + selector in the comma-separated group. + + """ + # Fast path for simple cases + match = _el_re.match(css) + if match: + return [Selector(Element(element=match.group(1)))] + match = _id_re.match(css) + if match is not None: + return [Selector(Hash(Element(element=match.group(1) or None), match.group(2)))] + match = _class_re.match(css) + if match is not None: + return [Selector(Class(Element(element=match.group(1) or None), match.group(2)))] + + stream = TokenStream(tokenize(css)) + stream.source = css + return list(parse_selector_group(stream)) + + +# except SelectorSyntaxError: +# e = sys.exc_info()[1] +# message = "%s at %s -> %r" % ( +# e, stream.used, stream.peek()) +# e.msg = message +# e.args = tuple([message]) +# raise + + +def parse_selector_group(stream: "TokenStream") -> Iterator[Selector]: + stream.skip_whitespace() + while 1: + yield Selector(*parse_selector(stream)) + if stream.peek() == ("DELIM", ","): + stream.next() + stream.skip_whitespace() + else: + break + + +def parse_selector(stream: "TokenStream") -> Tuple[Tree, Optional[PseudoElement]]: + result, pseudo_element = parse_simple_selector(stream) + while 1: + stream.skip_whitespace() + peek = stream.peek() + if peek in (("EOF", None), ("DELIM", ",")): + break + if pseudo_element: + raise SelectorSyntaxError( + "Got pseudo-element ::%s not at the end of a selector" % pseudo_element + ) + if peek.is_delim("+", ">", "~"): + # A combinator + combinator = typing.cast(str, stream.next().value) + stream.skip_whitespace() + else: + # By exclusion, the last parse_simple_selector() ended + # at peek == ' ' + combinator = " " + next_selector, pseudo_element = parse_simple_selector(stream) + result = CombinedSelector(result, combinator, next_selector) + return result, pseudo_element + + +def parse_simple_selector( + stream: "TokenStream", inside_negation: bool = False +) -> Tuple[Tree, Optional[PseudoElement]]: + stream.skip_whitespace() + selector_start = len(stream.used) + peek = stream.peek() + if peek.type == "IDENT" or peek == ("DELIM", "*"): + if peek.type == "IDENT": + namespace = stream.next().value + else: + stream.next() + namespace = None + if stream.peek() == ("DELIM", "|"): + stream.next() + element = stream.next_ident_or_star() + else: + element = namespace + namespace = None + else: + element = namespace = None + result: Tree = Element(namespace, element) + pseudo_element: Optional[PseudoElement] = None + while 1: + peek = stream.peek() + if ( + peek.type in ("S", "EOF") + or peek.is_delim(",", "+", ">", "~") + or (inside_negation and peek == ("DELIM", ")")) + ): + break + if pseudo_element: + raise SelectorSyntaxError( + "Got pseudo-element ::%s not at the end of a selector" % pseudo_element + ) + if peek.type == "HASH": + result = Hash(result, typing.cast(str, stream.next().value)) + elif peek == ("DELIM", "."): + stream.next() + result = Class(result, stream.next_ident()) + elif peek == ("DELIM", "|"): + stream.next() + result = Element(None, stream.next_ident()) + elif peek == ("DELIM", "["): + stream.next() + result = parse_attrib(result, stream) + elif peek == ("DELIM", ":"): + stream.next() + if stream.peek() == ("DELIM", ":"): + stream.next() + pseudo_element = stream.next_ident() + if stream.peek() == ("DELIM", "("): + stream.next() + pseudo_element = FunctionalPseudoElement( + pseudo_element, parse_arguments(stream) + ) + continue + ident = stream.next_ident() + if ident.lower() in ("first-line", "first-letter", "before", "after"): + # Special case: CSS 2.1 pseudo-elements can have a single ':' + # Any new pseudo-element must have two. + pseudo_element = str(ident) + continue + if stream.peek() != ("DELIM", "("): + result = Pseudo(result, ident) + if repr(result) == "Pseudo[Element[*]:scope]": + if not ( + len(stream.used) == 2 + or (len(stream.used) == 3 and stream.used[0].type == "S") + or (len(stream.used) >= 3 and stream.used[-3].is_delim(",")) + or ( + len(stream.used) >= 4 + and stream.used[-3].type == "S" + and stream.used[-4].is_delim(",") + ) + ): + raise SelectorSyntaxError( + 'Got immediate child pseudo-element ":scope" ' + "not at the start of a selector" + ) + continue + stream.next() + stream.skip_whitespace() + if ident.lower() == "not": + if inside_negation: + raise SelectorSyntaxError("Got nested :not()") + argument, argument_pseudo_element = parse_simple_selector( + stream, inside_negation=True + ) + next = stream.next() + if argument_pseudo_element: + raise SelectorSyntaxError( + "Got pseudo-element ::%s inside :not() at %s" + % (argument_pseudo_element, next.pos) + ) + if next != ("DELIM", ")"): + raise SelectorSyntaxError("Expected ')', got %s" % (next,)) + result = Negation(result, argument) + elif ident.lower() == "has": + combinator, arguments = parse_relative_selector(stream) + result = Relation(result, combinator, arguments) + + elif ident.lower() in ("matches", "is"): + selectors = parse_simple_selector_arguments(stream) + result = Matching(result, selectors) + elif ident.lower() == "where": + selectors = parse_simple_selector_arguments(stream) + result = SpecificityAdjustment(result, selectors) + else: + result = Function(result, ident, parse_arguments(stream)) + else: + raise SelectorSyntaxError("Expected selector, got %s" % (peek,)) + if len(stream.used) == selector_start: + raise SelectorSyntaxError("Expected selector, got %s" % (stream.peek(),)) + return result, pseudo_element + + +def parse_arguments(stream: "TokenStream") -> List["Token"]: + arguments: List["Token"] = [] + while 1: + stream.skip_whitespace() + next = stream.next() + if next.type in ("IDENT", "STRING", "NUMBER") or next in [("DELIM", "+"), ("DELIM", "-")]: + arguments.append(next) + elif next == ("DELIM", ")"): + return arguments + else: + raise SelectorSyntaxError("Expected an argument, got %s" % (next,)) + + +def parse_relative_selector(stream: "TokenStream") -> Tuple["Token", Selector]: + stream.skip_whitespace() + subselector = "" + next = stream.next() + + if next in [("DELIM", "+"), ("DELIM", "-"), ("DELIM", ">"), ("DELIM", "~")]: + combinator = next + stream.skip_whitespace() + next = stream.next() + else: + combinator = Token("DELIM", " ", pos=0) + + while 1: + if next.type in ("IDENT", "STRING", "NUMBER") or next in [("DELIM", "."), ("DELIM", "*")]: + subselector += typing.cast(str, next.value) + elif next == ("DELIM", ")"): + result = parse(subselector) + return combinator, result[0] + else: + raise SelectorSyntaxError("Expected an argument, got %s" % (next,)) + next = stream.next() + + +def parse_simple_selector_arguments(stream: "TokenStream") -> List[Tree]: + arguments = [] + while 1: + result, pseudo_element = parse_simple_selector(stream, True) + if pseudo_element: + raise SelectorSyntaxError( + "Got pseudo-element ::%s inside function" % (pseudo_element,) + ) + stream.skip_whitespace() + next = stream.next() + if next in (("EOF", None), ("DELIM", ",")): + stream.next() + stream.skip_whitespace() + arguments.append(result) + elif next == ("DELIM", ")"): + arguments.append(result) + break + else: + raise SelectorSyntaxError("Expected an argument, got %s" % (next,)) + return arguments + + +def parse_attrib(selector: Tree, stream: "TokenStream") -> Attrib: + stream.skip_whitespace() + attrib = stream.next_ident_or_star() + if attrib is None and stream.peek() != ("DELIM", "|"): + raise SelectorSyntaxError("Expected '|', got %s" % (stream.peek(),)) + namespace: Optional[str] + op: Optional[str] + if stream.peek() == ("DELIM", "|"): + stream.next() + if stream.peek() == ("DELIM", "="): + namespace = None + stream.next() + op = "|=" + else: + namespace = attrib + attrib = stream.next_ident() + op = None + else: + namespace = op = None + if op is None: + stream.skip_whitespace() + next = stream.next() + if next == ("DELIM", "]"): + return Attrib(selector, namespace, typing.cast(str, attrib), "exists", None) + elif next == ("DELIM", "="): + op = "=" + elif next.is_delim("^", "$", "*", "~", "|", "!") and (stream.peek() == ("DELIM", "=")): + op = typing.cast(str, next.value) + "=" + stream.next() + else: + raise SelectorSyntaxError("Operator expected, got %s" % (next,)) + stream.skip_whitespace() + value = stream.next() + if value.type not in ("IDENT", "STRING"): + raise SelectorSyntaxError("Expected string or ident, got %s" % (value,)) + stream.skip_whitespace() + next = stream.next() + if next != ("DELIM", "]"): + raise SelectorSyntaxError("Expected ']', got %s" % (next,)) + return Attrib(selector, namespace, typing.cast(str, attrib), op, value) + + +def parse_series(tokens: Iterable["Token"]) -> Tuple[int, int]: + """ + Parses the arguments for :nth-child() and friends. + + :raises: A list of tokens + :returns: :``(a, b)`` + + """ + for token in tokens: + if token.type == "STRING": + raise ValueError("String tokens not allowed in series.") + s = "".join(typing.cast(str, token.value) for token in tokens).strip() + if s == "odd": + return 2, 1 + elif s == "even": + return 2, 0 + elif s == "n": + return 1, 0 + if "n" not in s: + # Just b + return 0, int(s) + a, b = s.split("n", 1) + a_as_int: int + if not a: + a_as_int = 1 + elif a == "-" or a == "+": + a_as_int = int(a + "1") + else: + a_as_int = int(a) + b_as_int: int + if not b: + b_as_int = 0 + else: + b_as_int = int(b) + return a_as_int, b_as_int + + +#### Token objects + + +class Token(Tuple[str, Optional[str]]): + @typing.overload + def __new__( + cls, + type_: 'typing.Literal["IDENT", "HASH", "STRING", "S", "DELIM", "NUMBER"]', + value: str, + pos: int, + ) -> "Token": + ... + + @typing.overload + def __new__(cls, type_: 'typing.Literal["EOF"]', value: None, pos: int) -> "Token": + ... + + def __new__(cls, type_: str, value: Optional[str], pos: int) -> "Token": + obj = tuple.__new__(cls, (type_, value)) + obj.pos = pos + return obj + + def __repr__(self) -> str: + return "<%s '%s' at %i>" % (self.type, self.value, self.pos) + + def is_delim(self, *values: str) -> bool: + return self.type == "DELIM" and self.value in values + + pos: int + + @property + def type(self) -> str: + return self[0] + + @property + def value(self) -> Optional[str]: + return self[1] + + def css(self) -> str: + if self.type == "STRING": + return repr(self.value) + else: + return typing.cast(str, self.value) + + +class EOFToken(Token): + def __new__(cls, pos: int) -> "EOFToken": + return typing.cast("EOFToken", Token.__new__(cls, "EOF", None, pos)) + + def __repr__(self) -> str: + return "<%s at %i>" % (self.type, self.pos) + + +#### Tokenizer + + +class TokenMacros: + unicode_escape = r"\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?" + escape = unicode_escape + r"|\\[^\n\r\f0-9a-f]" + string_escape = r"\\(?:\n|\r\n|\r|\f)|" + escape + nonascii = r"[^\0-\177]" + nmchar = "[_a-z0-9-]|%s|%s" % (escape, nonascii) + nmstart = "[_a-z]|%s|%s" % (escape, nonascii) + + +if typing.TYPE_CHECKING: + + class MatchFunc(typing.Protocol): + def __call__( + self, string: str, pos: int = ..., endpos: int = ... + ) -> Optional["re.Match[str]"]: + ... + + +def _compile(pattern: str) -> "MatchFunc": + return re.compile(pattern % vars(TokenMacros), re.IGNORECASE).match + + +_match_whitespace = _compile(r"[ \t\r\n\f]+") +_match_number = _compile(r"[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)") +_match_hash = _compile("#(?:%(nmchar)s)+") +_match_ident = _compile("-?(?:%(nmstart)s)(?:%(nmchar)s)*") +_match_string_by_quote = { + "'": _compile(r"([^\n\r\f\\']|%(string_escape)s)*"), + '"': _compile(r'([^\n\r\f\\"]|%(string_escape)s)*'), +} + +_sub_simple_escape = re.compile(r"\\(.)").sub +_sub_unicode_escape = re.compile(TokenMacros.unicode_escape, re.I).sub +_sub_newline_escape = re.compile(r"\\(?:\n|\r\n|\r|\f)").sub + +# Same as r'\1', but faster on CPython +_replace_simple = operator.methodcaller("group", 1) + + +def _replace_unicode(match: "re.Match[str]") -> str: + codepoint = int(match.group(1), 16) + if codepoint > sys.maxunicode: + codepoint = 0xFFFD + return chr(codepoint) + + +def unescape_ident(value: str) -> str: + value = _sub_unicode_escape(_replace_unicode, value) + value = _sub_simple_escape(_replace_simple, value) + return value + + +def tokenize(s: str) -> Iterator[Token]: + pos = 0 + len_s = len(s) + while pos < len_s: + match = _match_whitespace(s, pos=pos) + if match: + yield Token("S", " ", pos) + pos = match.end() + continue + + match = _match_ident(s, pos=pos) + if match: + value = _sub_simple_escape( + _replace_simple, _sub_unicode_escape(_replace_unicode, match.group()) + ) + yield Token("IDENT", value, pos) + pos = match.end() + continue + + match = _match_hash(s, pos=pos) + if match: + value = _sub_simple_escape( + _replace_simple, _sub_unicode_escape(_replace_unicode, match.group()[1:]) + ) + yield Token("HASH", value, pos) + pos = match.end() + continue + + quote = s[pos] + if quote in _match_string_by_quote: + match = _match_string_by_quote[quote](s, pos=pos + 1) + assert match, "Should have found at least an empty match" + end_pos = match.end() + if end_pos == len_s: + raise SelectorSyntaxError("Unclosed string at %s" % pos) + if s[end_pos] != quote: + raise SelectorSyntaxError("Invalid string at %s" % pos) + value = _sub_simple_escape( + _replace_simple, + _sub_unicode_escape(_replace_unicode, _sub_newline_escape("", match.group())), + ) + yield Token("STRING", value, pos) + pos = end_pos + 1 + continue + + match = _match_number(s, pos=pos) + if match: + value = match.group() + yield Token("NUMBER", value, pos) + pos = match.end() + continue + + pos2 = pos + 2 + if s[pos:pos2] == "/*": + pos = s.find("*/", pos2) + if pos == -1: + pos = len_s + else: + pos += 2 + continue + + yield Token("DELIM", s[pos], pos) + pos += 1 + + assert pos == len_s + yield EOFToken(pos) + + +class TokenStream: + def __init__(self, tokens: Iterable[Token], source: Optional[str] = None) -> None: + self.used: List[Token] = [] + self.tokens = iter(tokens) + self.source = source + self.peeked: Optional[Token] = None + self._peeking = False + self.next_token = self.tokens.__next__ + + def next(self) -> Token: + if self._peeking: + self._peeking = False + self.used.append(typing.cast(Token, self.peeked)) + return typing.cast(Token, self.peeked) + else: + next = self.next_token() + self.used.append(next) + return next + + def peek(self) -> Token: + if not self._peeking: + self.peeked = self.next_token() + self._peeking = True + return typing.cast(Token, self.peeked) + + def next_ident(self) -> str: + next = self.next() + if next.type != "IDENT": + raise SelectorSyntaxError("Expected ident, got %s" % (next,)) + return typing.cast(str, next.value) + + def next_ident_or_star(self) -> Optional[str]: + next = self.next() + if next.type == "IDENT": + return next.value + elif next == ("DELIM", "*"): + return None + else: + raise SelectorSyntaxError("Expected ident or '*', got %s" % (next,)) + + def skip_whitespace(self) -> None: + peek = self.peek() + if peek.type == "S": + self.next() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/py.typed b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/xpath.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/xpath.py new file mode 100644 index 0000000..fd28c47 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/cssselect/xpath.py @@ -0,0 +1,889 @@ +# -*- coding: utf-8 -*- +""" + cssselect.xpath + =============== + + Translation of parsed CSS selectors to XPath expressions. + + + :copyright: (c) 2007-2012 Ian Bicking and contributors. + See AUTHORS for more details. + :license: BSD, see LICENSE for more details. + +""" + +import re +import typing +import warnings +from typing import Optional + +from cssselect.parser import ( + parse, + parse_series, + PseudoElement, + Selector, + SelectorError, + Tree, + Element, + Hash, + Class, + Function, + Pseudo, + Attrib, + Negation, + Relation, + Matching, + SpecificityAdjustment, + CombinedSelector, +) + + +@typing.no_type_check +def _unicode_safe_getattr(obj, name, default=None): + warnings.warn( + "_unicode_safe_getattr is deprecated and will be removed in the" + " next release, use getattr() instead", + DeprecationWarning, + stacklevel=2, + ) + return getattr(obj, name, default) + + +class ExpressionError(SelectorError, RuntimeError): + """Unknown or unsupported selector (eg. pseudo-class).""" + + +#### XPath Helpers + + +class XPathExpr: + def __init__( + self, path: str = "", element: str = "*", condition: str = "", star_prefix: bool = False + ) -> None: + self.path = path + self.element = element + self.condition = condition + + def __str__(self) -> str: + path = str(self.path) + str(self.element) + if self.condition: + path += "[%s]" % self.condition + return path + + def __repr__(self) -> str: + return "%s[%s]" % (self.__class__.__name__, self) + + def add_condition(self, condition: str, conjuction: str = "and") -> "XPathExpr": + if self.condition: + self.condition = "(%s) %s (%s)" % (self.condition, conjuction, condition) + else: + self.condition = condition + return self + + def add_name_test(self) -> None: + if self.element == "*": + # We weren't doing a test anyway + return + self.add_condition("name() = %s" % GenericTranslator.xpath_literal(self.element)) + self.element = "*" + + def add_star_prefix(self) -> None: + """ + Append '*/' to the path to keep the context constrained + to a single parent. + """ + self.path += "*/" + + def join( + self, + combiner: str, + other: "XPathExpr", + closing_combiner: Optional[str] = None, + has_inner_condition: bool = False, + ) -> "XPathExpr": + path = str(self) + combiner + # Any "star prefix" is redundant when joining. + if other.path != "*/": + path += other.path + self.path = path + if not has_inner_condition: + self.element = other.element + closing_combiner if closing_combiner else other.element + self.condition = other.condition + else: + self.element = other.element + if other.condition: + self.element += "[" + other.condition + "]" + if closing_combiner: + self.element += closing_combiner + return self + + +split_at_single_quotes = re.compile("('+)").split + +# The spec is actually more permissive than that, but don’t bother. +# This is just for the fast path. +# http://www.w3.org/TR/REC-xml/#NT-NameStartChar +is_safe_name = re.compile("^[a-zA-Z_][a-zA-Z0-9_.-]*$").match + +# Test that the string is not empty and does not contain whitespace +is_non_whitespace = re.compile(r"^[^ \t\r\n\f]+$").match + + +#### Translation + + +class GenericTranslator: + """ + Translator for "generic" XML documents. + + Everything is case-sensitive, no assumption is made on the meaning + of element names and attribute names. + + """ + + #### + #### HERE BE DRAGONS + #### + #### You are welcome to hook into this to change some behavior, + #### but do so at your own risks. + #### Until it has received a lot more work and review, + #### I reserve the right to change this API in backward-incompatible ways + #### with any minor version of cssselect. + #### See https://github.com/scrapy/cssselect/pull/22 + #### -- Simon Sapin. + #### + + combinator_mapping = { + " ": "descendant", + ">": "child", + "+": "direct_adjacent", + "~": "indirect_adjacent", + } + + attribute_operator_mapping = { + "exists": "exists", + "=": "equals", + "~=": "includes", + "|=": "dashmatch", + "^=": "prefixmatch", + "$=": "suffixmatch", + "*=": "substringmatch", + "!=": "different", # XXX Not in Level 3 but meh + } + + #: The attribute used for ID selectors depends on the document language: + #: http://www.w3.org/TR/selectors/#id-selectors + id_attribute = "id" + + #: The attribute used for ``:lang()`` depends on the document language: + #: http://www.w3.org/TR/selectors/#lang-pseudo + lang_attribute = "xml:lang" + + #: The case sensitivity of document language element names, + #: attribute names, and attribute values in selectors depends + #: on the document language. + #: http://www.w3.org/TR/selectors/#casesens + #: + #: When a document language defines one of these as case-insensitive, + #: cssselect assumes that the document parser makes the parsed values + #: lower-case. Making the selector lower-case too makes the comparaison + #: case-insensitive. + #: + #: In HTML, element names and attributes names (but not attribute values) + #: are case-insensitive. All of lxml.html, html5lib, BeautifulSoup4 + #: and HTMLParser make them lower-case in their parse result, so + #: the assumption holds. + lower_case_element_names = False + lower_case_attribute_names = False + lower_case_attribute_values = False + + # class used to represent and xpath expression + xpathexpr_cls = XPathExpr + + def css_to_xpath(self, css: str, prefix: str = "descendant-or-self::") -> str: + """Translate a *group of selectors* to XPath. + + Pseudo-elements are not supported here since XPath only knows + about "real" elements. + + :param css: + A *group of selectors* as a string. + :param prefix: + This string is prepended to the XPath expression for each selector. + The default makes selectors scoped to the context node’s subtree. + :raises: + :class:`~cssselect.SelectorSyntaxError` on invalid selectors, + :class:`ExpressionError` on unknown/unsupported selectors, + including pseudo-elements. + :returns: + The equivalent XPath 1.0 expression as a string. + + """ + return " | ".join( + self.selector_to_xpath(selector, prefix, translate_pseudo_elements=True) + for selector in parse(css) + ) + + def selector_to_xpath( + self, + selector: Selector, + prefix: str = "descendant-or-self::", + translate_pseudo_elements: bool = False, + ) -> str: + """Translate a parsed selector to XPath. + + + :param selector: + A parsed :class:`Selector` object. + :param prefix: + This string is prepended to the resulting XPath expression. + The default makes selectors scoped to the context node’s subtree. + :param translate_pseudo_elements: + Unless this is set to ``True`` (as :meth:`css_to_xpath` does), + the :attr:`~Selector.pseudo_element` attribute of the selector + is ignored. + It is the caller's responsibility to reject selectors + with pseudo-elements, or to account for them somehow. + :raises: + :class:`ExpressionError` on unknown/unsupported selectors. + :returns: + The equivalent XPath 1.0 expression as a string. + + """ + tree = getattr(selector, "parsed_tree", None) + if not tree: + raise TypeError("Expected a parsed selector, got %r" % (selector,)) + xpath = self.xpath(tree) + assert isinstance(xpath, self.xpathexpr_cls) # help debug a missing 'return' + if translate_pseudo_elements and selector.pseudo_element: + xpath = self.xpath_pseudo_element(xpath, selector.pseudo_element) + return (prefix or "") + str(xpath) + + def xpath_pseudo_element(self, xpath: XPathExpr, pseudo_element: PseudoElement) -> XPathExpr: + """Translate a pseudo-element. + + Defaults to not supporting pseudo-elements at all, + but can be overridden by sub-classes. + + """ + raise ExpressionError("Pseudo-elements are not supported.") + + @staticmethod + def xpath_literal(s: str) -> str: + s = str(s) + if "'" not in s: + s = "'%s'" % s + elif '"' not in s: + s = '"%s"' % s + else: + s = "concat(%s)" % ",".join( + [ + (("'" in part) and '"%s"' or "'%s'") % part + for part in split_at_single_quotes(s) + if part + ] + ) + return s + + def xpath(self, parsed_selector: Tree) -> XPathExpr: + """Translate any parsed selector object.""" + type_name = type(parsed_selector).__name__ + method = getattr(self, "xpath_%s" % type_name.lower(), None) + if method is None: + raise ExpressionError("%s is not supported." % type_name) + return typing.cast(XPathExpr, method(parsed_selector)) + + # Dispatched by parsed object type + + def xpath_combinedselector(self, combined: CombinedSelector) -> XPathExpr: + """Translate a combined selector.""" + combinator = self.combinator_mapping[combined.combinator] + method = getattr(self, "xpath_%s_combinator" % combinator) + return typing.cast( + XPathExpr, method(self.xpath(combined.selector), self.xpath(combined.subselector)) + ) + + def xpath_negation(self, negation: Negation) -> XPathExpr: + xpath = self.xpath(negation.selector) + sub_xpath = self.xpath(negation.subselector) + sub_xpath.add_name_test() + if sub_xpath.condition: + return xpath.add_condition("not(%s)" % sub_xpath.condition) + else: + return xpath.add_condition("0") + + def xpath_relation(self, relation: Relation) -> XPathExpr: + xpath = self.xpath(relation.selector) + combinator = relation.combinator + subselector = relation.subselector + right = self.xpath(subselector.parsed_tree) + method = getattr( + self, + "xpath_relation_%s_combinator" + % self.combinator_mapping[typing.cast(str, combinator.value)], + ) + return typing.cast(XPathExpr, method(xpath, right)) + + def xpath_matching(self, matching: Matching) -> XPathExpr: + xpath = self.xpath(matching.selector) + exprs = [self.xpath(selector) for selector in matching.selector_list] + for e in exprs: + e.add_name_test() + if e.condition: + xpath.add_condition(e.condition, "or") + return xpath + + def xpath_specificityadjustment(self, matching: SpecificityAdjustment) -> XPathExpr: + xpath = self.xpath(matching.selector) + exprs = [self.xpath(selector) for selector in matching.selector_list] + for e in exprs: + e.add_name_test() + if e.condition: + xpath.add_condition(e.condition, "or") + return xpath + + def xpath_function(self, function: Function) -> XPathExpr: + """Translate a functional pseudo-class.""" + method_name = "xpath_%s_function" % function.name.replace("-", "_") + method = getattr(self, method_name, None) + if not method: + raise ExpressionError("The pseudo-class :%s() is unknown" % function.name) + return typing.cast(XPathExpr, method(self.xpath(function.selector), function)) + + def xpath_pseudo(self, pseudo: Pseudo) -> XPathExpr: + """Translate a pseudo-class.""" + method_name = "xpath_%s_pseudo" % pseudo.ident.replace("-", "_") + method = getattr(self, method_name, None) + if not method: + # TODO: better error message for pseudo-elements? + raise ExpressionError("The pseudo-class :%s is unknown" % pseudo.ident) + return typing.cast(XPathExpr, method(self.xpath(pseudo.selector))) + + def xpath_attrib(self, selector: Attrib) -> XPathExpr: + """Translate an attribute selector.""" + operator = self.attribute_operator_mapping[selector.operator] + method = getattr(self, "xpath_attrib_%s" % operator) + if self.lower_case_attribute_names: + name = selector.attrib.lower() + else: + name = selector.attrib + safe = is_safe_name(name) + if selector.namespace: + name = "%s:%s" % (selector.namespace, name) + safe = safe and is_safe_name(selector.namespace) + if safe: + attrib = "@" + name + else: + attrib = "attribute::*[name() = %s]" % self.xpath_literal(name) + if selector.value is None: + value = None + elif self.lower_case_attribute_values: + value = typing.cast(str, selector.value.value).lower() + else: + value = selector.value.value + return typing.cast(XPathExpr, method(self.xpath(selector.selector), attrib, value)) + + def xpath_class(self, class_selector: Class) -> XPathExpr: + """Translate a class selector.""" + # .foo is defined as [class~=foo] in the spec. + xpath = self.xpath(class_selector.selector) + return self.xpath_attrib_includes(xpath, "@class", class_selector.class_name) + + def xpath_hash(self, id_selector: Hash) -> XPathExpr: + """Translate an ID selector.""" + xpath = self.xpath(id_selector.selector) + return self.xpath_attrib_equals(xpath, "@id", id_selector.id) + + def xpath_element(self, selector: Element) -> XPathExpr: + """Translate a type or universal selector.""" + element = selector.element + if not element: + element = "*" + safe = True + else: + safe = bool(is_safe_name(element)) + if self.lower_case_element_names: + element = element.lower() + if selector.namespace: + # Namespace prefixes are case-sensitive. + # http://www.w3.org/TR/css3-namespace/#prefixes + element = "%s:%s" % (selector.namespace, element) + safe = safe and bool(is_safe_name(selector.namespace)) + xpath = self.xpathexpr_cls(element=element) + if not safe: + xpath.add_name_test() + return xpath + + # CombinedSelector: dispatch by combinator + + def xpath_descendant_combinator(self, left: XPathExpr, right: XPathExpr) -> XPathExpr: + """right is a child, grand-child or further descendant of left""" + return left.join("/descendant-or-self::*/", right) + + def xpath_child_combinator(self, left: XPathExpr, right: XPathExpr) -> XPathExpr: + """right is an immediate child of left""" + return left.join("/", right) + + def xpath_direct_adjacent_combinator(self, left: XPathExpr, right: XPathExpr) -> XPathExpr: + """right is a sibling immediately after left""" + xpath = left.join("/following-sibling::", right) + xpath.add_name_test() + return xpath.add_condition("position() = 1") + + def xpath_indirect_adjacent_combinator(self, left: XPathExpr, right: XPathExpr) -> XPathExpr: + """right is a sibling after left, immediately or not""" + return left.join("/following-sibling::", right) + + def xpath_relation_descendant_combinator(self, left: XPathExpr, right: XPathExpr) -> XPathExpr: + """right is a child, grand-child or further descendant of left; select left""" + return left.join("[descendant::", right, closing_combiner="]", has_inner_condition=True) + + def xpath_relation_child_combinator(self, left: XPathExpr, right: XPathExpr) -> XPathExpr: + """right is an immediate child of left; select left""" + return left.join("[./", right, closing_combiner="]") + + def xpath_relation_direct_adjacent_combinator( + self, left: XPathExpr, right: XPathExpr + ) -> XPathExpr: + """right is a sibling immediately after left; select left""" + xpath = left.add_condition( + "following-sibling::*[(name() = '{}') and (position() = 1)]".format(right.element) + ) + return xpath + + def xpath_relation_indirect_adjacent_combinator( + self, left: XPathExpr, right: XPathExpr + ) -> XPathExpr: + """right is a sibling after left, immediately or not; select left""" + return left.join("[following-sibling::", right, closing_combiner="]") + + # Function: dispatch by function/pseudo-class name + + def xpath_nth_child_function( + self, xpath: XPathExpr, function: Function, last: bool = False, add_name_test: bool = True + ) -> XPathExpr: + try: + a, b = parse_series(function.arguments) + except ValueError: + raise ExpressionError("Invalid series: '%r'" % function.arguments) + + # From https://www.w3.org/TR/css3-selectors/#structural-pseudos: + # + # :nth-child(an+b) + # an+b-1 siblings before + # + # :nth-last-child(an+b) + # an+b-1 siblings after + # + # :nth-of-type(an+b) + # an+b-1 siblings with the same expanded element name before + # + # :nth-last-of-type(an+b) + # an+b-1 siblings with the same expanded element name after + # + # So, + # for :nth-child and :nth-of-type + # + # count(preceding-sibling::) = an+b-1 + # + # for :nth-last-child and :nth-last-of-type + # + # count(following-sibling::) = an+b-1 + # + # therefore, + # count(...) - (b-1) ≡ 0 (mod a) + # + # if a == 0: + # ~~~~~~~~~~ + # count(...) = b-1 + # + # if a < 0: + # ~~~~~~~~~ + # count(...) - b +1 <= 0 + # -> count(...) <= b-1 + # + # if a > 0: + # ~~~~~~~~~ + # count(...) - b +1 >= 0 + # -> count(...) >= b-1 + + # work with b-1 instead + b_min_1 = b - 1 + + # early-exit condition 1: + # ~~~~~~~~~~~~~~~~~~~~~~~ + # for a == 1, nth-*(an+b) means n+b-1 siblings before/after, + # and since n ∈ {0, 1, 2, ...}, if b-1<=0, + # there is always an "n" matching any number of siblings (maybe none) + if a == 1 and b_min_1 <= 0: + return xpath + + # early-exit condition 2: + # ~~~~~~~~~~~~~~~~~~~~~~~ + # an+b-1 siblings with a<0 and (b-1)<0 is not possible + if a < 0 and b_min_1 < 0: + return xpath.add_condition("0") + + # `add_name_test` boolean is inverted and somewhat counter-intuitive: + # + # nth_of_type() calls nth_child(add_name_test=False) + if add_name_test: + nodetest = "*" + else: + nodetest = "%s" % xpath.element + + # count siblings before or after the element + if not last: + siblings_count = "count(preceding-sibling::%s)" % nodetest + else: + siblings_count = "count(following-sibling::%s)" % nodetest + + # special case of fixed position: nth-*(0n+b) + # if a == 0: + # ~~~~~~~~~~ + # count(***-sibling::***) = b-1 + if a == 0: + return xpath.add_condition("%s = %s" % (siblings_count, b_min_1)) + + expressions = [] + + if a > 0: + # siblings count, an+b-1, is always >= 0, + # so if a>0, and (b-1)<=0, an "n" exists to satisfy this, + # therefore, the predicate is only interesting if (b-1)>0 + if b_min_1 > 0: + expressions.append("%s >= %s" % (siblings_count, b_min_1)) + else: + # if a<0, and (b-1)<0, no "n" satisfies this, + # this is tested above as an early exist condition + # otherwise, + expressions.append("%s <= %s" % (siblings_count, b_min_1)) + + # operations modulo 1 or -1 are simpler, one only needs to verify: + # + # - either: + # count(***-sibling::***) - (b-1) = n = 0, 1, 2, 3, etc., + # i.e. count(***-sibling::***) >= (b-1) + # + # - or: + # count(***-sibling::***) - (b-1) = -n = 0, -1, -2, -3, etc., + # i.e. count(***-sibling::***) <= (b-1) + # we we just did above. + # + if abs(a) != 1: + # count(***-sibling::***) - (b-1) ≡ 0 (mod a) + left = siblings_count + + # apply "modulo a" on 2nd term, -(b-1), + # to simplify things like "(... +6) % -3", + # and also make it positive with |a| + b_neg = (-b_min_1) % abs(a) + + if b_neg != 0: + b_neg_as_str = "+%s" % b_neg + left = "(%s %s)" % (left, b_neg_as_str) + + expressions.append("%s mod %s = 0" % (left, a)) + + if len(expressions) > 1: + template = "(%s)" + else: + template = "%s" + xpath.add_condition(" and ".join(template % expression for expression in expressions)) + return xpath + + def xpath_nth_last_child_function(self, xpath: XPathExpr, function: Function) -> XPathExpr: + return self.xpath_nth_child_function(xpath, function, last=True) + + def xpath_nth_of_type_function(self, xpath: XPathExpr, function: Function) -> XPathExpr: + if xpath.element == "*": + raise ExpressionError("*:nth-of-type() is not implemented") + return self.xpath_nth_child_function(xpath, function, add_name_test=False) + + def xpath_nth_last_of_type_function(self, xpath: XPathExpr, function: Function) -> XPathExpr: + if xpath.element == "*": + raise ExpressionError("*:nth-of-type() is not implemented") + return self.xpath_nth_child_function(xpath, function, last=True, add_name_test=False) + + def xpath_contains_function(self, xpath: XPathExpr, function: Function) -> XPathExpr: + # Defined there, removed in later drafts: + # http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors + if function.argument_types() not in (["STRING"], ["IDENT"]): + raise ExpressionError( + "Expected a single string or ident for :contains(), got %r" % function.arguments + ) + value = typing.cast(str, function.arguments[0].value) + return xpath.add_condition("contains(., %s)" % self.xpath_literal(value)) + + def xpath_lang_function(self, xpath: XPathExpr, function: Function) -> XPathExpr: + if function.argument_types() not in (["STRING"], ["IDENT"]): + raise ExpressionError( + "Expected a single string or ident for :lang(), got %r" % function.arguments + ) + value = typing.cast(str, function.arguments[0].value) + return xpath.add_condition("lang(%s)" % (self.xpath_literal(value))) + + # Pseudo: dispatch by pseudo-class name + + def xpath_root_pseudo(self, xpath: XPathExpr) -> XPathExpr: + return xpath.add_condition("not(parent::*)") + + # CSS immediate children (CSS ":scope > div" to XPath "child::div" or "./div") + # Works only at the start of a selector + # Needed to get immediate children of a processed selector in Scrapy + # for product in response.css('.product'): + # description = product.css(':scope > div::text').get() + def xpath_scope_pseudo(self, xpath: XPathExpr) -> XPathExpr: + return xpath.add_condition("1") + + def xpath_first_child_pseudo(self, xpath: XPathExpr) -> XPathExpr: + return xpath.add_condition("count(preceding-sibling::*) = 0") + + def xpath_last_child_pseudo(self, xpath: XPathExpr) -> XPathExpr: + return xpath.add_condition("count(following-sibling::*) = 0") + + def xpath_first_of_type_pseudo(self, xpath: XPathExpr) -> XPathExpr: + if xpath.element == "*": + raise ExpressionError("*:first-of-type is not implemented") + return xpath.add_condition("count(preceding-sibling::%s) = 0" % xpath.element) + + def xpath_last_of_type_pseudo(self, xpath: XPathExpr) -> XPathExpr: + if xpath.element == "*": + raise ExpressionError("*:last-of-type is not implemented") + return xpath.add_condition("count(following-sibling::%s) = 0" % xpath.element) + + def xpath_only_child_pseudo(self, xpath: XPathExpr) -> XPathExpr: + return xpath.add_condition("count(parent::*/child::*) = 1") + + def xpath_only_of_type_pseudo(self, xpath: XPathExpr) -> XPathExpr: + if xpath.element == "*": + raise ExpressionError("*:only-of-type is not implemented") + return xpath.add_condition("count(parent::*/child::%s) = 1" % xpath.element) + + def xpath_empty_pseudo(self, xpath: XPathExpr) -> XPathExpr: + return xpath.add_condition("not(*) and not(string-length())") + + def pseudo_never_matches(self, xpath: XPathExpr) -> XPathExpr: + """Common implementation for pseudo-classes that never match.""" + return xpath.add_condition("0") + + xpath_link_pseudo = pseudo_never_matches + xpath_visited_pseudo = pseudo_never_matches + xpath_hover_pseudo = pseudo_never_matches + xpath_active_pseudo = pseudo_never_matches + xpath_focus_pseudo = pseudo_never_matches + xpath_target_pseudo = pseudo_never_matches + xpath_enabled_pseudo = pseudo_never_matches + xpath_disabled_pseudo = pseudo_never_matches + xpath_checked_pseudo = pseudo_never_matches + + # Attrib: dispatch by attribute operator + + def xpath_attrib_exists(self, xpath: XPathExpr, name: str, value: Optional[str]) -> XPathExpr: + assert not value + xpath.add_condition(name) + return xpath + + def xpath_attrib_equals(self, xpath: XPathExpr, name: str, value: Optional[str]) -> XPathExpr: + assert value is not None + xpath.add_condition("%s = %s" % (name, self.xpath_literal(value))) + return xpath + + def xpath_attrib_different( + self, xpath: XPathExpr, name: str, value: Optional[str] + ) -> XPathExpr: + assert value is not None + # FIXME: this seems like a weird hack... + if value: + xpath.add_condition("not(%s) or %s != %s" % (name, name, self.xpath_literal(value))) + else: + xpath.add_condition("%s != %s" % (name, self.xpath_literal(value))) + return xpath + + def xpath_attrib_includes( + self, xpath: XPathExpr, name: str, value: Optional[str] + ) -> XPathExpr: + if value and is_non_whitespace(value): + xpath.add_condition( + "%s and contains(concat(' ', normalize-space(%s), ' '), %s)" + % (name, name, self.xpath_literal(" " + value + " ")) + ) + else: + xpath.add_condition("0") + return xpath + + def xpath_attrib_dashmatch( + self, xpath: XPathExpr, name: str, value: Optional[str] + ) -> XPathExpr: + assert value is not None + # Weird, but true... + xpath.add_condition( + "%s and (%s = %s or starts-with(%s, %s))" + % (name, name, self.xpath_literal(value), name, self.xpath_literal(value + "-")) + ) + return xpath + + def xpath_attrib_prefixmatch( + self, xpath: XPathExpr, name: str, value: Optional[str] + ) -> XPathExpr: + if value: + xpath.add_condition( + "%s and starts-with(%s, %s)" % (name, name, self.xpath_literal(value)) + ) + else: + xpath.add_condition("0") + return xpath + + def xpath_attrib_suffixmatch( + self, xpath: XPathExpr, name: str, value: Optional[str] + ) -> XPathExpr: + if value: + # Oddly there is a starts-with in XPath 1.0, but not ends-with + xpath.add_condition( + "%s and substring(%s, string-length(%s)-%s) = %s" + % (name, name, name, len(value) - 1, self.xpath_literal(value)) + ) + else: + xpath.add_condition("0") + return xpath + + def xpath_attrib_substringmatch( + self, xpath: XPathExpr, name: str, value: Optional[str] + ) -> XPathExpr: + if value: + # Attribute selectors are case sensitive + xpath.add_condition( + "%s and contains(%s, %s)" % (name, name, self.xpath_literal(value)) + ) + else: + xpath.add_condition("0") + return xpath + + +class HTMLTranslator(GenericTranslator): + """ + Translator for (X)HTML documents. + + Has a more useful implementation of some pseudo-classes based on + HTML-specific element names and attribute names, as described in + the `HTML5 specification`_. It assumes no-quirks mode. + The API is the same as :class:`GenericTranslator`. + + .. _HTML5 specification: http://www.w3.org/TR/html5/links.html#selectors + + :param xhtml: + If false (the default), element names and attribute names + are case-insensitive. + + """ + + lang_attribute = "lang" + + def __init__(self, xhtml: bool = False) -> None: + self.xhtml = xhtml # Might be useful for sub-classes? + if not xhtml: + # See their definition in GenericTranslator. + self.lower_case_element_names = True + self.lower_case_attribute_names = True + + def xpath_checked_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore + # FIXME: is this really all the elements? + return xpath.add_condition( + "(@selected and name(.) = 'option') or " + "(@checked " + "and (name(.) = 'input' or name(.) = 'command')" + "and (@type = 'checkbox' or @type = 'radio'))" + ) + + def xpath_lang_function(self, xpath: XPathExpr, function: Function) -> XPathExpr: + if function.argument_types() not in (["STRING"], ["IDENT"]): + raise ExpressionError( + "Expected a single string or ident for :lang(), got %r" % function.arguments + ) + value = function.arguments[0].value + assert value + return xpath.add_condition( + "ancestor-or-self::*[@lang][1][starts-with(concat(" + # XPath 1.0 has no lower-case function... + "translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', " + "'abcdefghijklmnopqrstuvwxyz'), " + "'-'), %s)]" % (self.lang_attribute, self.xpath_literal(value.lower() + "-")) + ) + + def xpath_link_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore + return xpath.add_condition( + "@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')" + ) + + # Links are never visited, the implementation for :visited is the same + # as in GenericTranslator + + def xpath_disabled_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore + # http://www.w3.org/TR/html5/section-index.html#attributes-1 + return xpath.add_condition( + """ + ( + @disabled and + ( + (name(.) = 'input' and @type != 'hidden') or + name(.) = 'button' or + name(.) = 'select' or + name(.) = 'textarea' or + name(.) = 'command' or + name(.) = 'fieldset' or + name(.) = 'optgroup' or + name(.) = 'option' + ) + ) or ( + ( + (name(.) = 'input' and @type != 'hidden') or + name(.) = 'button' or + name(.) = 'select' or + name(.) = 'textarea' + ) + and ancestor::fieldset[@disabled] + ) + """ + ) + # FIXME: in the second half, add "and is not a descendant of that + # fieldset element's first legend element child, if any." + + def xpath_enabled_pseudo(self, xpath: XPathExpr) -> XPathExpr: # type: ignore + # http://www.w3.org/TR/html5/section-index.html#attributes-1 + return xpath.add_condition( + """ + ( + @href and ( + name(.) = 'a' or + name(.) = 'link' or + name(.) = 'area' + ) + ) or ( + ( + name(.) = 'command' or + name(.) = 'fieldset' or + name(.) = 'optgroup' + ) + and not(@disabled) + ) or ( + ( + (name(.) = 'input' and @type != 'hidden') or + name(.) = 'button' or + name(.) = 'select' or + name(.) = 'textarea' or + name(.) = 'keygen' + ) + and not (@disabled or ancestor::fieldset[@disabled]) + ) or ( + name(.) = 'option' and not( + @disabled or ancestor::optgroup[@disabled] + ) + ) + """ + ) + # FIXME: ... or "li elements that are children of menu elements, + # and that have a child element that defines a command, if the first + # such element's Disabled State facet is false (not disabled)". + # FIXME: after ancestor::fieldset[@disabled], add "and is not a + # descendant of that fieldset element's first legend element child, + # if any." diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__init__.py new file mode 100644 index 0000000..499eb9c --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__init__.py @@ -0,0 +1,8 @@ +import logging +from fontTools.misc.loggingTools import configLogger + +log = logging.getLogger(__name__) + +version = __version__ = "4.40.1.dev0" + +__all__ = ["version", "log", "configLogger"] diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__main__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__main__.py new file mode 100644 index 0000000..7c74ad3 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/__main__.py @@ -0,0 +1,35 @@ +import sys + + +def main(args=None): + if args is None: + args = sys.argv[1:] + + # TODO Handle library-wide options. Eg.: + # --unicodedata + # --verbose / other logging stuff + + # TODO Allow a way to run arbitrary modules? Useful for setting + # library-wide options and calling another library. Eg.: + # + # $ fonttools --unicodedata=... fontmake ... + # + # This allows for a git-like command where thirdparty commands + # can be added. Should we just try importing the fonttools + # module first and try without if it fails? + + if len(sys.argv) < 2: + sys.argv.append("help") + if sys.argv[1] == "-h" or sys.argv[1] == "--help": + sys.argv[1] = "help" + mod = "fontTools." + sys.argv[1] + sys.argv[1] = sys.argv[0] + " " + sys.argv[1] + del sys.argv[0] + + import runpy + + runpy.run_module(mod, run_name="__main__") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/afmLib.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/afmLib.py new file mode 100644 index 0000000..198559d --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/afmLib.py @@ -0,0 +1,440 @@ +"""Module for reading and writing AFM (Adobe Font Metrics) files. + +Note that this has been designed to read in AFM files generated by Fontographer +and has not been tested on many other files. In particular, it does not +implement the whole Adobe AFM specification [#f1]_ but, it should read most +"common" AFM files. + +Here is an example of using `afmLib` to read, modify and write an AFM file: + + >>> from fontTools.afmLib import AFM + >>> f = AFM("Tests/afmLib/data/TestAFM.afm") + >>> + >>> # Accessing a pair gets you the kern value + >>> f[("V","A")] + -60 + >>> + >>> # Accessing a glyph name gets you metrics + >>> f["A"] + (65, 668, (8, -25, 660, 666)) + >>> # (charnum, width, bounding box) + >>> + >>> # Accessing an attribute gets you metadata + >>> f.FontName + 'TestFont-Regular' + >>> f.FamilyName + 'TestFont' + >>> f.Weight + 'Regular' + >>> f.XHeight + 500 + >>> f.Ascender + 750 + >>> + >>> # Attributes and items can also be set + >>> f[("A","V")] = -150 # Tighten kerning + >>> f.FontName = "TestFont Squished" + >>> + >>> # And the font written out again (remove the # in front) + >>> #f.write("testfont-squished.afm") + +.. rubric:: Footnotes + +.. [#f1] `Adobe Technote 5004 `_, + Adobe Font Metrics File Format Specification. + +""" + + +import re + +# every single line starts with a "word" +identifierRE = re.compile(r"^([A-Za-z]+).*") + +# regular expression to parse char lines +charRE = re.compile( + r"(-?\d+)" # charnum + r"\s*;\s*WX\s+" # ; WX + r"(-?\d+)" # width + r"\s*;\s*N\s+" # ; N + r"([.A-Za-z0-9_]+)" # charname + r"\s*;\s*B\s+" # ; B + r"(-?\d+)" # left + r"\s+" + r"(-?\d+)" # bottom + r"\s+" + r"(-?\d+)" # right + r"\s+" + r"(-?\d+)" # top + r"\s*;\s*" # ; +) + +# regular expression to parse kerning lines +kernRE = re.compile( + r"([.A-Za-z0-9_]+)" # leftchar + r"\s+" + r"([.A-Za-z0-9_]+)" # rightchar + r"\s+" + r"(-?\d+)" # value + r"\s*" +) + +# regular expressions to parse composite info lines of the form: +# Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ; +compositeRE = re.compile( + r"([.A-Za-z0-9_]+)" + r"\s+" + r"(\d+)" + r"\s*;\s*" # char name # number of parts +) +componentRE = re.compile( + r"PCC\s+" # PPC + r"([.A-Za-z0-9_]+)" # base char name + r"\s+" + r"(-?\d+)" # x offset + r"\s+" + r"(-?\d+)" # y offset + r"\s*;\s*" +) + +preferredAttributeOrder = [ + "FontName", + "FullName", + "FamilyName", + "Weight", + "ItalicAngle", + "IsFixedPitch", + "FontBBox", + "UnderlinePosition", + "UnderlineThickness", + "Version", + "Notice", + "EncodingScheme", + "CapHeight", + "XHeight", + "Ascender", + "Descender", +] + + +class error(Exception): + pass + + +class AFM(object): + _attrs = None + + _keywords = [ + "StartFontMetrics", + "EndFontMetrics", + "StartCharMetrics", + "EndCharMetrics", + "StartKernData", + "StartKernPairs", + "EndKernPairs", + "EndKernData", + "StartComposites", + "EndComposites", + ] + + def __init__(self, path=None): + """AFM file reader. + + Instantiating an object with a path name will cause the file to be opened, + read, and parsed. Alternatively the path can be left unspecified, and a + file can be parsed later with the :meth:`read` method.""" + self._attrs = {} + self._chars = {} + self._kerning = {} + self._index = {} + self._comments = [] + self._composites = {} + if path is not None: + self.read(path) + + def read(self, path): + """Opens, reads and parses a file.""" + lines = readlines(path) + for line in lines: + if not line.strip(): + continue + m = identifierRE.match(line) + if m is None: + raise error("syntax error in AFM file: " + repr(line)) + + pos = m.regs[1][1] + word = line[:pos] + rest = line[pos:].strip() + if word in self._keywords: + continue + if word == "C": + self.parsechar(rest) + elif word == "KPX": + self.parsekernpair(rest) + elif word == "CC": + self.parsecomposite(rest) + else: + self.parseattr(word, rest) + + def parsechar(self, rest): + m = charRE.match(rest) + if m is None: + raise error("syntax error in AFM file: " + repr(rest)) + things = [] + for fr, to in m.regs[1:]: + things.append(rest[fr:to]) + charname = things[2] + del things[2] + charnum, width, l, b, r, t = (int(thing) for thing in things) + self._chars[charname] = charnum, width, (l, b, r, t) + + def parsekernpair(self, rest): + m = kernRE.match(rest) + if m is None: + raise error("syntax error in AFM file: " + repr(rest)) + things = [] + for fr, to in m.regs[1:]: + things.append(rest[fr:to]) + leftchar, rightchar, value = things + value = int(value) + self._kerning[(leftchar, rightchar)] = value + + def parseattr(self, word, rest): + if word == "FontBBox": + l, b, r, t = [int(thing) for thing in rest.split()] + self._attrs[word] = l, b, r, t + elif word == "Comment": + self._comments.append(rest) + else: + try: + value = int(rest) + except (ValueError, OverflowError): + self._attrs[word] = rest + else: + self._attrs[word] = value + + def parsecomposite(self, rest): + m = compositeRE.match(rest) + if m is None: + raise error("syntax error in AFM file: " + repr(rest)) + charname = m.group(1) + ncomponents = int(m.group(2)) + rest = rest[m.regs[0][1] :] + components = [] + while True: + m = componentRE.match(rest) + if m is None: + raise error("syntax error in AFM file: " + repr(rest)) + basechar = m.group(1) + xoffset = int(m.group(2)) + yoffset = int(m.group(3)) + components.append((basechar, xoffset, yoffset)) + rest = rest[m.regs[0][1] :] + if not rest: + break + assert len(components) == ncomponents + self._composites[charname] = components + + def write(self, path, sep="\r"): + """Writes out an AFM font to the given path.""" + import time + + lines = [ + "StartFontMetrics 2.0", + "Comment Generated by afmLib; at %s" + % (time.strftime("%m/%d/%Y %H:%M:%S", time.localtime(time.time()))), + ] + + # write comments, assuming (possibly wrongly!) they should + # all appear at the top + for comment in self._comments: + lines.append("Comment " + comment) + + # write attributes, first the ones we know about, in + # a preferred order + attrs = self._attrs + for attr in preferredAttributeOrder: + if attr in attrs: + value = attrs[attr] + if attr == "FontBBox": + value = "%s %s %s %s" % value + lines.append(attr + " " + str(value)) + # then write the attributes we don't know about, + # in alphabetical order + items = sorted(attrs.items()) + for attr, value in items: + if attr in preferredAttributeOrder: + continue + lines.append(attr + " " + str(value)) + + # write char metrics + lines.append("StartCharMetrics " + repr(len(self._chars))) + items = [ + (charnum, (charname, width, box)) + for charname, (charnum, width, box) in self._chars.items() + ] + + def myKey(a): + """Custom key function to make sure unencoded chars (-1) + end up at the end of the list after sorting.""" + if a[0] == -1: + a = (0xFFFF,) + a[1:] # 0xffff is an arbitrary large number + return a + + items.sort(key=myKey) + + for charnum, (charname, width, (l, b, r, t)) in items: + lines.append( + "C %d ; WX %d ; N %s ; B %d %d %d %d ;" + % (charnum, width, charname, l, b, r, t) + ) + lines.append("EndCharMetrics") + + # write kerning info + lines.append("StartKernData") + lines.append("StartKernPairs " + repr(len(self._kerning))) + items = sorted(self._kerning.items()) + for (leftchar, rightchar), value in items: + lines.append("KPX %s %s %d" % (leftchar, rightchar, value)) + lines.append("EndKernPairs") + lines.append("EndKernData") + + if self._composites: + composites = sorted(self._composites.items()) + lines.append("StartComposites %s" % len(self._composites)) + for charname, components in composites: + line = "CC %s %s ;" % (charname, len(components)) + for basechar, xoffset, yoffset in components: + line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset) + lines.append(line) + lines.append("EndComposites") + + lines.append("EndFontMetrics") + + writelines(path, lines, sep) + + def has_kernpair(self, pair): + """Returns `True` if the given glyph pair (specified as a tuple) exists + in the kerning dictionary.""" + return pair in self._kerning + + def kernpairs(self): + """Returns a list of all kern pairs in the kerning dictionary.""" + return list(self._kerning.keys()) + + def has_char(self, char): + """Returns `True` if the given glyph exists in the font.""" + return char in self._chars + + def chars(self): + """Returns a list of all glyph names in the font.""" + return list(self._chars.keys()) + + def comments(self): + """Returns all comments from the file.""" + return self._comments + + def addComment(self, comment): + """Adds a new comment to the file.""" + self._comments.append(comment) + + def addComposite(self, glyphName, components): + """Specifies that the glyph `glyphName` is made up of the given components. + The components list should be of the following form:: + + [ + (glyphname, xOffset, yOffset), + ... + ] + + """ + self._composites[glyphName] = components + + def __getattr__(self, attr): + if attr in self._attrs: + return self._attrs[attr] + else: + raise AttributeError(attr) + + def __setattr__(self, attr, value): + # all attrs *not* starting with "_" are consider to be AFM keywords + if attr[:1] == "_": + self.__dict__[attr] = value + else: + self._attrs[attr] = value + + def __delattr__(self, attr): + # all attrs *not* starting with "_" are consider to be AFM keywords + if attr[:1] == "_": + try: + del self.__dict__[attr] + except KeyError: + raise AttributeError(attr) + else: + try: + del self._attrs[attr] + except KeyError: + raise AttributeError(attr) + + def __getitem__(self, key): + if isinstance(key, tuple): + # key is a tuple, return the kernpair + return self._kerning[key] + else: + # return the metrics instead + return self._chars[key] + + def __setitem__(self, key, value): + if isinstance(key, tuple): + # key is a tuple, set kernpair + self._kerning[key] = value + else: + # set char metrics + self._chars[key] = value + + def __delitem__(self, key): + if isinstance(key, tuple): + # key is a tuple, del kernpair + del self._kerning[key] + else: + # del char metrics + del self._chars[key] + + def __repr__(self): + if hasattr(self, "FullName"): + return "" % self.FullName + else: + return "" % id(self) + + +def readlines(path): + with open(path, "r", encoding="ascii") as f: + data = f.read() + return data.splitlines() + + +def writelines(path, lines, sep="\r"): + with open(path, "w", encoding="ascii", newline=sep) as f: + f.write("\n".join(lines) + "\n") + + +if __name__ == "__main__": + import EasyDialogs + + path = EasyDialogs.AskFileForOpen() + if path: + afm = AFM(path) + char = "A" + if afm.has_char(char): + print(afm[char]) # print charnum, width and boundingbox + pair = ("A", "V") + if afm.has_kernpair(pair): + print(afm[pair]) # print kerning value for pair + print(afm.Version) # various other afm entries have become attributes + print(afm.Weight) + # afm.comments() returns a list of all Comment lines found in the AFM + print(afm.comments()) + # print afm.chars() + # print afm.kernpairs() + print(afm) + afm.write(path + ".muck") diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/agl.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/agl.py new file mode 100644 index 0000000..d699462 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/agl.py @@ -0,0 +1,5233 @@ +# -*- coding: utf-8 -*- +# The tables below are taken from +# https://github.com/adobe-type-tools/agl-aglfn/raw/4036a9ca80a62f64f9de4f7321a9a045ad0ecfd6/glyphlist.txt +# and +# https://github.com/adobe-type-tools/agl-aglfn/raw/4036a9ca80a62f64f9de4f7321a9a045ad0ecfd6/aglfn.txt +""" +Interface to the Adobe Glyph List + +This module exists to convert glyph names from the Adobe Glyph List +to their Unicode equivalents. Example usage: + + >>> from fontTools.agl import toUnicode + >>> toUnicode("nahiragana") + 'な' + +It also contains two dictionaries, ``UV2AGL`` and ``AGL2UV``, which map from +Unicode codepoints to AGL names and vice versa: + + >>> import fontTools + >>> fontTools.agl.UV2AGL[ord("?")] + 'question' + >>> fontTools.agl.AGL2UV["wcircumflex"] + 373 + +This is used by fontTools when it has to construct glyph names for a font which +doesn't include any (e.g. format 3.0 post tables). +""" + +from fontTools.misc.textTools import tostr +import re + + +_aglText = """\ +# ----------------------------------------------------------- +# Copyright 2002-2019 Adobe (http://www.adobe.com/). +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the +# following conditions are met: +# +# Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# Neither the name of Adobe nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------- +# Name: Adobe Glyph List +# Table version: 2.0 +# Date: September 20, 2002 +# URL: https://github.com/adobe-type-tools/agl-aglfn +# +# Format: two semicolon-delimited fields: +# (1) glyph name--upper/lowercase letters and digits +# (2) Unicode scalar value--four uppercase hexadecimal digits +# +A;0041 +AE;00C6 +AEacute;01FC +AEmacron;01E2 +AEsmall;F7E6 +Aacute;00C1 +Aacutesmall;F7E1 +Abreve;0102 +Abreveacute;1EAE +Abrevecyrillic;04D0 +Abrevedotbelow;1EB6 +Abrevegrave;1EB0 +Abrevehookabove;1EB2 +Abrevetilde;1EB4 +Acaron;01CD +Acircle;24B6 +Acircumflex;00C2 +Acircumflexacute;1EA4 +Acircumflexdotbelow;1EAC +Acircumflexgrave;1EA6 +Acircumflexhookabove;1EA8 +Acircumflexsmall;F7E2 +Acircumflextilde;1EAA +Acute;F6C9 +Acutesmall;F7B4 +Acyrillic;0410 +Adblgrave;0200 +Adieresis;00C4 +Adieresiscyrillic;04D2 +Adieresismacron;01DE +Adieresissmall;F7E4 +Adotbelow;1EA0 +Adotmacron;01E0 +Agrave;00C0 +Agravesmall;F7E0 +Ahookabove;1EA2 +Aiecyrillic;04D4 +Ainvertedbreve;0202 +Alpha;0391 +Alphatonos;0386 +Amacron;0100 +Amonospace;FF21 +Aogonek;0104 +Aring;00C5 +Aringacute;01FA +Aringbelow;1E00 +Aringsmall;F7E5 +Asmall;F761 +Atilde;00C3 +Atildesmall;F7E3 +Aybarmenian;0531 +B;0042 +Bcircle;24B7 +Bdotaccent;1E02 +Bdotbelow;1E04 +Becyrillic;0411 +Benarmenian;0532 +Beta;0392 +Bhook;0181 +Blinebelow;1E06 +Bmonospace;FF22 +Brevesmall;F6F4 +Bsmall;F762 +Btopbar;0182 +C;0043 +Caarmenian;053E +Cacute;0106 +Caron;F6CA +Caronsmall;F6F5 +Ccaron;010C +Ccedilla;00C7 +Ccedillaacute;1E08 +Ccedillasmall;F7E7 +Ccircle;24B8 +Ccircumflex;0108 +Cdot;010A +Cdotaccent;010A +Cedillasmall;F7B8 +Chaarmenian;0549 +Cheabkhasiancyrillic;04BC +Checyrillic;0427 +Chedescenderabkhasiancyrillic;04BE +Chedescendercyrillic;04B6 +Chedieresiscyrillic;04F4 +Cheharmenian;0543 +Chekhakassiancyrillic;04CB +Cheverticalstrokecyrillic;04B8 +Chi;03A7 +Chook;0187 +Circumflexsmall;F6F6 +Cmonospace;FF23 +Coarmenian;0551 +Csmall;F763 +D;0044 +DZ;01F1 +DZcaron;01C4 +Daarmenian;0534 +Dafrican;0189 +Dcaron;010E +Dcedilla;1E10 +Dcircle;24B9 +Dcircumflexbelow;1E12 +Dcroat;0110 +Ddotaccent;1E0A +Ddotbelow;1E0C +Decyrillic;0414 +Deicoptic;03EE +Delta;2206 +Deltagreek;0394 +Dhook;018A +Dieresis;F6CB +DieresisAcute;F6CC +DieresisGrave;F6CD +Dieresissmall;F7A8 +Digammagreek;03DC +Djecyrillic;0402 +Dlinebelow;1E0E +Dmonospace;FF24 +Dotaccentsmall;F6F7 +Dslash;0110 +Dsmall;F764 +Dtopbar;018B +Dz;01F2 +Dzcaron;01C5 +Dzeabkhasiancyrillic;04E0 +Dzecyrillic;0405 +Dzhecyrillic;040F +E;0045 +Eacute;00C9 +Eacutesmall;F7E9 +Ebreve;0114 +Ecaron;011A +Ecedillabreve;1E1C +Echarmenian;0535 +Ecircle;24BA +Ecircumflex;00CA +Ecircumflexacute;1EBE +Ecircumflexbelow;1E18 +Ecircumflexdotbelow;1EC6 +Ecircumflexgrave;1EC0 +Ecircumflexhookabove;1EC2 +Ecircumflexsmall;F7EA +Ecircumflextilde;1EC4 +Ecyrillic;0404 +Edblgrave;0204 +Edieresis;00CB +Edieresissmall;F7EB +Edot;0116 +Edotaccent;0116 +Edotbelow;1EB8 +Efcyrillic;0424 +Egrave;00C8 +Egravesmall;F7E8 +Eharmenian;0537 +Ehookabove;1EBA +Eightroman;2167 +Einvertedbreve;0206 +Eiotifiedcyrillic;0464 +Elcyrillic;041B +Elevenroman;216A +Emacron;0112 +Emacronacute;1E16 +Emacrongrave;1E14 +Emcyrillic;041C +Emonospace;FF25 +Encyrillic;041D +Endescendercyrillic;04A2 +Eng;014A +Enghecyrillic;04A4 +Enhookcyrillic;04C7 +Eogonek;0118 +Eopen;0190 +Epsilon;0395 +Epsilontonos;0388 +Ercyrillic;0420 +Ereversed;018E +Ereversedcyrillic;042D +Escyrillic;0421 +Esdescendercyrillic;04AA +Esh;01A9 +Esmall;F765 +Eta;0397 +Etarmenian;0538 +Etatonos;0389 +Eth;00D0 +Ethsmall;F7F0 +Etilde;1EBC +Etildebelow;1E1A +Euro;20AC +Ezh;01B7 +Ezhcaron;01EE +Ezhreversed;01B8 +F;0046 +Fcircle;24BB +Fdotaccent;1E1E +Feharmenian;0556 +Feicoptic;03E4 +Fhook;0191 +Fitacyrillic;0472 +Fiveroman;2164 +Fmonospace;FF26 +Fourroman;2163 +Fsmall;F766 +G;0047 +GBsquare;3387 +Gacute;01F4 +Gamma;0393 +Gammaafrican;0194 +Gangiacoptic;03EA +Gbreve;011E +Gcaron;01E6 +Gcedilla;0122 +Gcircle;24BC +Gcircumflex;011C +Gcommaaccent;0122 +Gdot;0120 +Gdotaccent;0120 +Gecyrillic;0413 +Ghadarmenian;0542 +Ghemiddlehookcyrillic;0494 +Ghestrokecyrillic;0492 +Gheupturncyrillic;0490 +Ghook;0193 +Gimarmenian;0533 +Gjecyrillic;0403 +Gmacron;1E20 +Gmonospace;FF27 +Grave;F6CE +Gravesmall;F760 +Gsmall;F767 +Gsmallhook;029B +Gstroke;01E4 +H;0048 +H18533;25CF +H18543;25AA +H18551;25AB +H22073;25A1 +HPsquare;33CB +Haabkhasiancyrillic;04A8 +Hadescendercyrillic;04B2 +Hardsigncyrillic;042A +Hbar;0126 +Hbrevebelow;1E2A +Hcedilla;1E28 +Hcircle;24BD +Hcircumflex;0124 +Hdieresis;1E26 +Hdotaccent;1E22 +Hdotbelow;1E24 +Hmonospace;FF28 +Hoarmenian;0540 +Horicoptic;03E8 +Hsmall;F768 +Hungarumlaut;F6CF +Hungarumlautsmall;F6F8 +Hzsquare;3390 +I;0049 +IAcyrillic;042F +IJ;0132 +IUcyrillic;042E +Iacute;00CD +Iacutesmall;F7ED +Ibreve;012C +Icaron;01CF +Icircle;24BE +Icircumflex;00CE +Icircumflexsmall;F7EE +Icyrillic;0406 +Idblgrave;0208 +Idieresis;00CF +Idieresisacute;1E2E +Idieresiscyrillic;04E4 +Idieresissmall;F7EF +Idot;0130 +Idotaccent;0130 +Idotbelow;1ECA +Iebrevecyrillic;04D6 +Iecyrillic;0415 +Ifraktur;2111 +Igrave;00CC +Igravesmall;F7EC +Ihookabove;1EC8 +Iicyrillic;0418 +Iinvertedbreve;020A +Iishortcyrillic;0419 +Imacron;012A +Imacroncyrillic;04E2 +Imonospace;FF29 +Iniarmenian;053B +Iocyrillic;0401 +Iogonek;012E +Iota;0399 +Iotaafrican;0196 +Iotadieresis;03AA +Iotatonos;038A +Ismall;F769 +Istroke;0197 +Itilde;0128 +Itildebelow;1E2C +Izhitsacyrillic;0474 +Izhitsadblgravecyrillic;0476 +J;004A +Jaarmenian;0541 +Jcircle;24BF +Jcircumflex;0134 +Jecyrillic;0408 +Jheharmenian;054B +Jmonospace;FF2A +Jsmall;F76A +K;004B +KBsquare;3385 +KKsquare;33CD +Kabashkircyrillic;04A0 +Kacute;1E30 +Kacyrillic;041A +Kadescendercyrillic;049A +Kahookcyrillic;04C3 +Kappa;039A +Kastrokecyrillic;049E +Kaverticalstrokecyrillic;049C +Kcaron;01E8 +Kcedilla;0136 +Kcircle;24C0 +Kcommaaccent;0136 +Kdotbelow;1E32 +Keharmenian;0554 +Kenarmenian;053F +Khacyrillic;0425 +Kheicoptic;03E6 +Khook;0198 +Kjecyrillic;040C +Klinebelow;1E34 +Kmonospace;FF2B +Koppacyrillic;0480 +Koppagreek;03DE +Ksicyrillic;046E +Ksmall;F76B +L;004C +LJ;01C7 +LL;F6BF +Lacute;0139 +Lambda;039B +Lcaron;013D +Lcedilla;013B +Lcircle;24C1 +Lcircumflexbelow;1E3C +Lcommaaccent;013B +Ldot;013F +Ldotaccent;013F +Ldotbelow;1E36 +Ldotbelowmacron;1E38 +Liwnarmenian;053C +Lj;01C8 +Ljecyrillic;0409 +Llinebelow;1E3A +Lmonospace;FF2C +Lslash;0141 +Lslashsmall;F6F9 +Lsmall;F76C +M;004D +MBsquare;3386 +Macron;F6D0 +Macronsmall;F7AF +Macute;1E3E +Mcircle;24C2 +Mdotaccent;1E40 +Mdotbelow;1E42 +Menarmenian;0544 +Mmonospace;FF2D +Msmall;F76D +Mturned;019C +Mu;039C +N;004E +NJ;01CA +Nacute;0143 +Ncaron;0147 +Ncedilla;0145 +Ncircle;24C3 +Ncircumflexbelow;1E4A +Ncommaaccent;0145 +Ndotaccent;1E44 +Ndotbelow;1E46 +Nhookleft;019D +Nineroman;2168 +Nj;01CB +Njecyrillic;040A +Nlinebelow;1E48 +Nmonospace;FF2E +Nowarmenian;0546 +Nsmall;F76E +Ntilde;00D1 +Ntildesmall;F7F1 +Nu;039D +O;004F +OE;0152 +OEsmall;F6FA +Oacute;00D3 +Oacutesmall;F7F3 +Obarredcyrillic;04E8 +Obarreddieresiscyrillic;04EA +Obreve;014E +Ocaron;01D1 +Ocenteredtilde;019F +Ocircle;24C4 +Ocircumflex;00D4 +Ocircumflexacute;1ED0 +Ocircumflexdotbelow;1ED8 +Ocircumflexgrave;1ED2 +Ocircumflexhookabove;1ED4 +Ocircumflexsmall;F7F4 +Ocircumflextilde;1ED6 +Ocyrillic;041E +Odblacute;0150 +Odblgrave;020C +Odieresis;00D6 +Odieresiscyrillic;04E6 +Odieresissmall;F7F6 +Odotbelow;1ECC +Ogoneksmall;F6FB +Ograve;00D2 +Ogravesmall;F7F2 +Oharmenian;0555 +Ohm;2126 +Ohookabove;1ECE +Ohorn;01A0 +Ohornacute;1EDA +Ohorndotbelow;1EE2 +Ohorngrave;1EDC +Ohornhookabove;1EDE +Ohorntilde;1EE0 +Ohungarumlaut;0150 +Oi;01A2 +Oinvertedbreve;020E +Omacron;014C +Omacronacute;1E52 +Omacrongrave;1E50 +Omega;2126 +Omegacyrillic;0460 +Omegagreek;03A9 +Omegaroundcyrillic;047A +Omegatitlocyrillic;047C +Omegatonos;038F +Omicron;039F +Omicrontonos;038C +Omonospace;FF2F +Oneroman;2160 +Oogonek;01EA +Oogonekmacron;01EC +Oopen;0186 +Oslash;00D8 +Oslashacute;01FE +Oslashsmall;F7F8 +Osmall;F76F +Ostrokeacute;01FE +Otcyrillic;047E +Otilde;00D5 +Otildeacute;1E4C +Otildedieresis;1E4E +Otildesmall;F7F5 +P;0050 +Pacute;1E54 +Pcircle;24C5 +Pdotaccent;1E56 +Pecyrillic;041F +Peharmenian;054A +Pemiddlehookcyrillic;04A6 +Phi;03A6 +Phook;01A4 +Pi;03A0 +Piwrarmenian;0553 +Pmonospace;FF30 +Psi;03A8 +Psicyrillic;0470 +Psmall;F770 +Q;0051 +Qcircle;24C6 +Qmonospace;FF31 +Qsmall;F771 +R;0052 +Raarmenian;054C +Racute;0154 +Rcaron;0158 +Rcedilla;0156 +Rcircle;24C7 +Rcommaaccent;0156 +Rdblgrave;0210 +Rdotaccent;1E58 +Rdotbelow;1E5A +Rdotbelowmacron;1E5C +Reharmenian;0550 +Rfraktur;211C +Rho;03A1 +Ringsmall;F6FC +Rinvertedbreve;0212 +Rlinebelow;1E5E +Rmonospace;FF32 +Rsmall;F772 +Rsmallinverted;0281 +Rsmallinvertedsuperior;02B6 +S;0053 +SF010000;250C +SF020000;2514 +SF030000;2510 +SF040000;2518 +SF050000;253C +SF060000;252C +SF070000;2534 +SF080000;251C +SF090000;2524 +SF100000;2500 +SF110000;2502 +SF190000;2561 +SF200000;2562 +SF210000;2556 +SF220000;2555 +SF230000;2563 +SF240000;2551 +SF250000;2557 +SF260000;255D +SF270000;255C +SF280000;255B +SF360000;255E +SF370000;255F +SF380000;255A +SF390000;2554 +SF400000;2569 +SF410000;2566 +SF420000;2560 +SF430000;2550 +SF440000;256C +SF450000;2567 +SF460000;2568 +SF470000;2564 +SF480000;2565 +SF490000;2559 +SF500000;2558 +SF510000;2552 +SF520000;2553 +SF530000;256B +SF540000;256A +Sacute;015A +Sacutedotaccent;1E64 +Sampigreek;03E0 +Scaron;0160 +Scarondotaccent;1E66 +Scaronsmall;F6FD +Scedilla;015E +Schwa;018F +Schwacyrillic;04D8 +Schwadieresiscyrillic;04DA +Scircle;24C8 +Scircumflex;015C +Scommaaccent;0218 +Sdotaccent;1E60 +Sdotbelow;1E62 +Sdotbelowdotaccent;1E68 +Seharmenian;054D +Sevenroman;2166 +Shaarmenian;0547 +Shacyrillic;0428 +Shchacyrillic;0429 +Sheicoptic;03E2 +Shhacyrillic;04BA +Shimacoptic;03EC +Sigma;03A3 +Sixroman;2165 +Smonospace;FF33 +Softsigncyrillic;042C +Ssmall;F773 +Stigmagreek;03DA +T;0054 +Tau;03A4 +Tbar;0166 +Tcaron;0164 +Tcedilla;0162 +Tcircle;24C9 +Tcircumflexbelow;1E70 +Tcommaaccent;0162 +Tdotaccent;1E6A +Tdotbelow;1E6C +Tecyrillic;0422 +Tedescendercyrillic;04AC +Tenroman;2169 +Tetsecyrillic;04B4 +Theta;0398 +Thook;01AC +Thorn;00DE +Thornsmall;F7FE +Threeroman;2162 +Tildesmall;F6FE +Tiwnarmenian;054F +Tlinebelow;1E6E +Tmonospace;FF34 +Toarmenian;0539 +Tonefive;01BC +Tonesix;0184 +Tonetwo;01A7 +Tretroflexhook;01AE +Tsecyrillic;0426 +Tshecyrillic;040B +Tsmall;F774 +Twelveroman;216B +Tworoman;2161 +U;0055 +Uacute;00DA +Uacutesmall;F7FA +Ubreve;016C +Ucaron;01D3 +Ucircle;24CA +Ucircumflex;00DB +Ucircumflexbelow;1E76 +Ucircumflexsmall;F7FB +Ucyrillic;0423 +Udblacute;0170 +Udblgrave;0214 +Udieresis;00DC +Udieresisacute;01D7 +Udieresisbelow;1E72 +Udieresiscaron;01D9 +Udieresiscyrillic;04F0 +Udieresisgrave;01DB +Udieresismacron;01D5 +Udieresissmall;F7FC +Udotbelow;1EE4 +Ugrave;00D9 +Ugravesmall;F7F9 +Uhookabove;1EE6 +Uhorn;01AF +Uhornacute;1EE8 +Uhorndotbelow;1EF0 +Uhorngrave;1EEA +Uhornhookabove;1EEC +Uhorntilde;1EEE +Uhungarumlaut;0170 +Uhungarumlautcyrillic;04F2 +Uinvertedbreve;0216 +Ukcyrillic;0478 +Umacron;016A +Umacroncyrillic;04EE +Umacrondieresis;1E7A +Umonospace;FF35 +Uogonek;0172 +Upsilon;03A5 +Upsilon1;03D2 +Upsilonacutehooksymbolgreek;03D3 +Upsilonafrican;01B1 +Upsilondieresis;03AB +Upsilondieresishooksymbolgreek;03D4 +Upsilonhooksymbol;03D2 +Upsilontonos;038E +Uring;016E +Ushortcyrillic;040E +Usmall;F775 +Ustraightcyrillic;04AE +Ustraightstrokecyrillic;04B0 +Utilde;0168 +Utildeacute;1E78 +Utildebelow;1E74 +V;0056 +Vcircle;24CB +Vdotbelow;1E7E +Vecyrillic;0412 +Vewarmenian;054E +Vhook;01B2 +Vmonospace;FF36 +Voarmenian;0548 +Vsmall;F776 +Vtilde;1E7C +W;0057 +Wacute;1E82 +Wcircle;24CC +Wcircumflex;0174 +Wdieresis;1E84 +Wdotaccent;1E86 +Wdotbelow;1E88 +Wgrave;1E80 +Wmonospace;FF37 +Wsmall;F777 +X;0058 +Xcircle;24CD +Xdieresis;1E8C +Xdotaccent;1E8A +Xeharmenian;053D +Xi;039E +Xmonospace;FF38 +Xsmall;F778 +Y;0059 +Yacute;00DD +Yacutesmall;F7FD +Yatcyrillic;0462 +Ycircle;24CE +Ycircumflex;0176 +Ydieresis;0178 +Ydieresissmall;F7FF +Ydotaccent;1E8E +Ydotbelow;1EF4 +Yericyrillic;042B +Yerudieresiscyrillic;04F8 +Ygrave;1EF2 +Yhook;01B3 +Yhookabove;1EF6 +Yiarmenian;0545 +Yicyrillic;0407 +Yiwnarmenian;0552 +Ymonospace;FF39 +Ysmall;F779 +Ytilde;1EF8 +Yusbigcyrillic;046A +Yusbigiotifiedcyrillic;046C +Yuslittlecyrillic;0466 +Yuslittleiotifiedcyrillic;0468 +Z;005A +Zaarmenian;0536 +Zacute;0179 +Zcaron;017D +Zcaronsmall;F6FF +Zcircle;24CF +Zcircumflex;1E90 +Zdot;017B +Zdotaccent;017B +Zdotbelow;1E92 +Zecyrillic;0417 +Zedescendercyrillic;0498 +Zedieresiscyrillic;04DE +Zeta;0396 +Zhearmenian;053A +Zhebrevecyrillic;04C1 +Zhecyrillic;0416 +Zhedescendercyrillic;0496 +Zhedieresiscyrillic;04DC +Zlinebelow;1E94 +Zmonospace;FF3A +Zsmall;F77A +Zstroke;01B5 +a;0061 +aabengali;0986 +aacute;00E1 +aadeva;0906 +aagujarati;0A86 +aagurmukhi;0A06 +aamatragurmukhi;0A3E +aarusquare;3303 +aavowelsignbengali;09BE +aavowelsigndeva;093E +aavowelsigngujarati;0ABE +abbreviationmarkarmenian;055F +abbreviationsigndeva;0970 +abengali;0985 +abopomofo;311A +abreve;0103 +abreveacute;1EAF +abrevecyrillic;04D1 +abrevedotbelow;1EB7 +abrevegrave;1EB1 +abrevehookabove;1EB3 +abrevetilde;1EB5 +acaron;01CE +acircle;24D0 +acircumflex;00E2 +acircumflexacute;1EA5 +acircumflexdotbelow;1EAD +acircumflexgrave;1EA7 +acircumflexhookabove;1EA9 +acircumflextilde;1EAB +acute;00B4 +acutebelowcmb;0317 +acutecmb;0301 +acutecomb;0301 +acutedeva;0954 +acutelowmod;02CF +acutetonecmb;0341 +acyrillic;0430 +adblgrave;0201 +addakgurmukhi;0A71 +adeva;0905 +adieresis;00E4 +adieresiscyrillic;04D3 +adieresismacron;01DF +adotbelow;1EA1 +adotmacron;01E1 +ae;00E6 +aeacute;01FD +aekorean;3150 +aemacron;01E3 +afii00208;2015 +afii08941;20A4 +afii10017;0410 +afii10018;0411 +afii10019;0412 +afii10020;0413 +afii10021;0414 +afii10022;0415 +afii10023;0401 +afii10024;0416 +afii10025;0417 +afii10026;0418 +afii10027;0419 +afii10028;041A +afii10029;041B +afii10030;041C +afii10031;041D +afii10032;041E +afii10033;041F +afii10034;0420 +afii10035;0421 +afii10036;0422 +afii10037;0423 +afii10038;0424 +afii10039;0425 +afii10040;0426 +afii10041;0427 +afii10042;0428 +afii10043;0429 +afii10044;042A +afii10045;042B +afii10046;042C +afii10047;042D +afii10048;042E +afii10049;042F +afii10050;0490 +afii10051;0402 +afii10052;0403 +afii10053;0404 +afii10054;0405 +afii10055;0406 +afii10056;0407 +afii10057;0408 +afii10058;0409 +afii10059;040A +afii10060;040B +afii10061;040C +afii10062;040E +afii10063;F6C4 +afii10064;F6C5 +afii10065;0430 +afii10066;0431 +afii10067;0432 +afii10068;0433 +afii10069;0434 +afii10070;0435 +afii10071;0451 +afii10072;0436 +afii10073;0437 +afii10074;0438 +afii10075;0439 +afii10076;043A +afii10077;043B +afii10078;043C +afii10079;043D +afii10080;043E +afii10081;043F +afii10082;0440 +afii10083;0441 +afii10084;0442 +afii10085;0443 +afii10086;0444 +afii10087;0445 +afii10088;0446 +afii10089;0447 +afii10090;0448 +afii10091;0449 +afii10092;044A +afii10093;044B +afii10094;044C +afii10095;044D +afii10096;044E +afii10097;044F +afii10098;0491 +afii10099;0452 +afii10100;0453 +afii10101;0454 +afii10102;0455 +afii10103;0456 +afii10104;0457 +afii10105;0458 +afii10106;0459 +afii10107;045A +afii10108;045B +afii10109;045C +afii10110;045E +afii10145;040F +afii10146;0462 +afii10147;0472 +afii10148;0474 +afii10192;F6C6 +afii10193;045F +afii10194;0463 +afii10195;0473 +afii10196;0475 +afii10831;F6C7 +afii10832;F6C8 +afii10846;04D9 +afii299;200E +afii300;200F +afii301;200D +afii57381;066A +afii57388;060C +afii57392;0660 +afii57393;0661 +afii57394;0662 +afii57395;0663 +afii57396;0664 +afii57397;0665 +afii57398;0666 +afii57399;0667 +afii57400;0668 +afii57401;0669 +afii57403;061B +afii57407;061F +afii57409;0621 +afii57410;0622 +afii57411;0623 +afii57412;0624 +afii57413;0625 +afii57414;0626 +afii57415;0627 +afii57416;0628 +afii57417;0629 +afii57418;062A +afii57419;062B +afii57420;062C +afii57421;062D +afii57422;062E +afii57423;062F +afii57424;0630 +afii57425;0631 +afii57426;0632 +afii57427;0633 +afii57428;0634 +afii57429;0635 +afii57430;0636 +afii57431;0637 +afii57432;0638 +afii57433;0639 +afii57434;063A +afii57440;0640 +afii57441;0641 +afii57442;0642 +afii57443;0643 +afii57444;0644 +afii57445;0645 +afii57446;0646 +afii57448;0648 +afii57449;0649 +afii57450;064A +afii57451;064B +afii57452;064C +afii57453;064D +afii57454;064E +afii57455;064F +afii57456;0650 +afii57457;0651 +afii57458;0652 +afii57470;0647 +afii57505;06A4 +afii57506;067E +afii57507;0686 +afii57508;0698 +afii57509;06AF +afii57511;0679 +afii57512;0688 +afii57513;0691 +afii57514;06BA +afii57519;06D2 +afii57534;06D5 +afii57636;20AA +afii57645;05BE +afii57658;05C3 +afii57664;05D0 +afii57665;05D1 +afii57666;05D2 +afii57667;05D3 +afii57668;05D4 +afii57669;05D5 +afii57670;05D6 +afii57671;05D7 +afii57672;05D8 +afii57673;05D9 +afii57674;05DA +afii57675;05DB +afii57676;05DC +afii57677;05DD +afii57678;05DE +afii57679;05DF +afii57680;05E0 +afii57681;05E1 +afii57682;05E2 +afii57683;05E3 +afii57684;05E4 +afii57685;05E5 +afii57686;05E6 +afii57687;05E7 +afii57688;05E8 +afii57689;05E9 +afii57690;05EA +afii57694;FB2A +afii57695;FB2B +afii57700;FB4B +afii57705;FB1F +afii57716;05F0 +afii57717;05F1 +afii57718;05F2 +afii57723;FB35 +afii57793;05B4 +afii57794;05B5 +afii57795;05B6 +afii57796;05BB +afii57797;05B8 +afii57798;05B7 +afii57799;05B0 +afii57800;05B2 +afii57801;05B1 +afii57802;05B3 +afii57803;05C2 +afii57804;05C1 +afii57806;05B9 +afii57807;05BC +afii57839;05BD +afii57841;05BF +afii57842;05C0 +afii57929;02BC +afii61248;2105 +afii61289;2113 +afii61352;2116 +afii61573;202C +afii61574;202D +afii61575;202E +afii61664;200C +afii63167;066D +afii64937;02BD +agrave;00E0 +agujarati;0A85 +agurmukhi;0A05 +ahiragana;3042 +ahookabove;1EA3 +aibengali;0990 +aibopomofo;311E +aideva;0910 +aiecyrillic;04D5 +aigujarati;0A90 +aigurmukhi;0A10 +aimatragurmukhi;0A48 +ainarabic;0639 +ainfinalarabic;FECA +aininitialarabic;FECB +ainmedialarabic;FECC +ainvertedbreve;0203 +aivowelsignbengali;09C8 +aivowelsigndeva;0948 +aivowelsigngujarati;0AC8 +akatakana;30A2 +akatakanahalfwidth;FF71 +akorean;314F +alef;05D0 +alefarabic;0627 +alefdageshhebrew;FB30 +aleffinalarabic;FE8E +alefhamzaabovearabic;0623 +alefhamzaabovefinalarabic;FE84 +alefhamzabelowarabic;0625 +alefhamzabelowfinalarabic;FE88 +alefhebrew;05D0 +aleflamedhebrew;FB4F +alefmaddaabovearabic;0622 +alefmaddaabovefinalarabic;FE82 +alefmaksuraarabic;0649 +alefmaksurafinalarabic;FEF0 +alefmaksurainitialarabic;FEF3 +alefmaksuramedialarabic;FEF4 +alefpatahhebrew;FB2E +alefqamatshebrew;FB2F +aleph;2135 +allequal;224C +alpha;03B1 +alphatonos;03AC +amacron;0101 +amonospace;FF41 +ampersand;0026 +ampersandmonospace;FF06 +ampersandsmall;F726 +amsquare;33C2 +anbopomofo;3122 +angbopomofo;3124 +angkhankhuthai;0E5A +angle;2220 +anglebracketleft;3008 +anglebracketleftvertical;FE3F +anglebracketright;3009 +anglebracketrightvertical;FE40 +angleleft;2329 +angleright;232A +angstrom;212B +anoteleia;0387 +anudattadeva;0952 +anusvarabengali;0982 +anusvaradeva;0902 +anusvaragujarati;0A82 +aogonek;0105 +apaatosquare;3300 +aparen;249C +apostrophearmenian;055A +apostrophemod;02BC +apple;F8FF +approaches;2250 +approxequal;2248 +approxequalorimage;2252 +approximatelyequal;2245 +araeaekorean;318E +araeakorean;318D +arc;2312 +arighthalfring;1E9A +aring;00E5 +aringacute;01FB +aringbelow;1E01 +arrowboth;2194 +arrowdashdown;21E3 +arrowdashleft;21E0 +arrowdashright;21E2 +arrowdashup;21E1 +arrowdblboth;21D4 +arrowdbldown;21D3 +arrowdblleft;21D0 +arrowdblright;21D2 +arrowdblup;21D1 +arrowdown;2193 +arrowdownleft;2199 +arrowdownright;2198 +arrowdownwhite;21E9 +arrowheaddownmod;02C5 +arrowheadleftmod;02C2 +arrowheadrightmod;02C3 +arrowheadupmod;02C4 +arrowhorizex;F8E7 +arrowleft;2190 +arrowleftdbl;21D0 +arrowleftdblstroke;21CD +arrowleftoverright;21C6 +arrowleftwhite;21E6 +arrowright;2192 +arrowrightdblstroke;21CF +arrowrightheavy;279E +arrowrightoverleft;21C4 +arrowrightwhite;21E8 +arrowtableft;21E4 +arrowtabright;21E5 +arrowup;2191 +arrowupdn;2195 +arrowupdnbse;21A8 +arrowupdownbase;21A8 +arrowupleft;2196 +arrowupleftofdown;21C5 +arrowupright;2197 +arrowupwhite;21E7 +arrowvertex;F8E6 +asciicircum;005E +asciicircummonospace;FF3E +asciitilde;007E +asciitildemonospace;FF5E +ascript;0251 +ascriptturned;0252 +asmallhiragana;3041 +asmallkatakana;30A1 +asmallkatakanahalfwidth;FF67 +asterisk;002A +asteriskaltonearabic;066D +asteriskarabic;066D +asteriskmath;2217 +asteriskmonospace;FF0A +asterisksmall;FE61 +asterism;2042 +asuperior;F6E9 +asymptoticallyequal;2243 +at;0040 +atilde;00E3 +atmonospace;FF20 +atsmall;FE6B +aturned;0250 +aubengali;0994 +aubopomofo;3120 +audeva;0914 +augujarati;0A94 +augurmukhi;0A14 +aulengthmarkbengali;09D7 +aumatragurmukhi;0A4C +auvowelsignbengali;09CC +auvowelsigndeva;094C +auvowelsigngujarati;0ACC +avagrahadeva;093D +aybarmenian;0561 +ayin;05E2 +ayinaltonehebrew;FB20 +ayinhebrew;05E2 +b;0062 +babengali;09AC +backslash;005C +backslashmonospace;FF3C +badeva;092C +bagujarati;0AAC +bagurmukhi;0A2C +bahiragana;3070 +bahtthai;0E3F +bakatakana;30D0 +bar;007C +barmonospace;FF5C +bbopomofo;3105 +bcircle;24D1 +bdotaccent;1E03 +bdotbelow;1E05 +beamedsixteenthnotes;266C +because;2235 +becyrillic;0431 +beharabic;0628 +behfinalarabic;FE90 +behinitialarabic;FE91 +behiragana;3079 +behmedialarabic;FE92 +behmeeminitialarabic;FC9F +behmeemisolatedarabic;FC08 +behnoonfinalarabic;FC6D +bekatakana;30D9 +benarmenian;0562 +bet;05D1 +beta;03B2 +betasymbolgreek;03D0 +betdagesh;FB31 +betdageshhebrew;FB31 +bethebrew;05D1 +betrafehebrew;FB4C +bhabengali;09AD +bhadeva;092D +bhagujarati;0AAD +bhagurmukhi;0A2D +bhook;0253 +bihiragana;3073 +bikatakana;30D3 +bilabialclick;0298 +bindigurmukhi;0A02 +birusquare;3331 +blackcircle;25CF +blackdiamond;25C6 +blackdownpointingtriangle;25BC +blackleftpointingpointer;25C4 +blackleftpointingtriangle;25C0 +blacklenticularbracketleft;3010 +blacklenticularbracketleftvertical;FE3B +blacklenticularbracketright;3011 +blacklenticularbracketrightvertical;FE3C +blacklowerlefttriangle;25E3 +blacklowerrighttriangle;25E2 +blackrectangle;25AC +blackrightpointingpointer;25BA +blackrightpointingtriangle;25B6 +blacksmallsquare;25AA +blacksmilingface;263B +blacksquare;25A0 +blackstar;2605 +blackupperlefttriangle;25E4 +blackupperrighttriangle;25E5 +blackuppointingsmalltriangle;25B4 +blackuppointingtriangle;25B2 +blank;2423 +blinebelow;1E07 +block;2588 +bmonospace;FF42 +bobaimaithai;0E1A +bohiragana;307C +bokatakana;30DC +bparen;249D +bqsquare;33C3 +braceex;F8F4 +braceleft;007B +braceleftbt;F8F3 +braceleftmid;F8F2 +braceleftmonospace;FF5B +braceleftsmall;FE5B +bracelefttp;F8F1 +braceleftvertical;FE37 +braceright;007D +bracerightbt;F8FE +bracerightmid;F8FD +bracerightmonospace;FF5D +bracerightsmall;FE5C +bracerighttp;F8FC +bracerightvertical;FE38 +bracketleft;005B +bracketleftbt;F8F0 +bracketleftex;F8EF +bracketleftmonospace;FF3B +bracketlefttp;F8EE +bracketright;005D +bracketrightbt;F8FB +bracketrightex;F8FA +bracketrightmonospace;FF3D +bracketrighttp;F8F9 +breve;02D8 +brevebelowcmb;032E +brevecmb;0306 +breveinvertedbelowcmb;032F +breveinvertedcmb;0311 +breveinverteddoublecmb;0361 +bridgebelowcmb;032A +bridgeinvertedbelowcmb;033A +brokenbar;00A6 +bstroke;0180 +bsuperior;F6EA +btopbar;0183 +buhiragana;3076 +bukatakana;30D6 +bullet;2022 +bulletinverse;25D8 +bulletoperator;2219 +bullseye;25CE +c;0063 +caarmenian;056E +cabengali;099A +cacute;0107 +cadeva;091A +cagujarati;0A9A +cagurmukhi;0A1A +calsquare;3388 +candrabindubengali;0981 +candrabinducmb;0310 +candrabindudeva;0901 +candrabindugujarati;0A81 +capslock;21EA +careof;2105 +caron;02C7 +caronbelowcmb;032C +caroncmb;030C +carriagereturn;21B5 +cbopomofo;3118 +ccaron;010D +ccedilla;00E7 +ccedillaacute;1E09 +ccircle;24D2 +ccircumflex;0109 +ccurl;0255 +cdot;010B +cdotaccent;010B +cdsquare;33C5 +cedilla;00B8 +cedillacmb;0327 +cent;00A2 +centigrade;2103 +centinferior;F6DF +centmonospace;FFE0 +centoldstyle;F7A2 +centsuperior;F6E0 +chaarmenian;0579 +chabengali;099B +chadeva;091B +chagujarati;0A9B +chagurmukhi;0A1B +chbopomofo;3114 +cheabkhasiancyrillic;04BD +checkmark;2713 +checyrillic;0447 +chedescenderabkhasiancyrillic;04BF +chedescendercyrillic;04B7 +chedieresiscyrillic;04F5 +cheharmenian;0573 +chekhakassiancyrillic;04CC +cheverticalstrokecyrillic;04B9 +chi;03C7 +chieuchacirclekorean;3277 +chieuchaparenkorean;3217 +chieuchcirclekorean;3269 +chieuchkorean;314A +chieuchparenkorean;3209 +chochangthai;0E0A +chochanthai;0E08 +chochingthai;0E09 +chochoethai;0E0C +chook;0188 +cieucacirclekorean;3276 +cieucaparenkorean;3216 +cieuccirclekorean;3268 +cieuckorean;3148 +cieucparenkorean;3208 +cieucuparenkorean;321C +circle;25CB +circlemultiply;2297 +circleot;2299 +circleplus;2295 +circlepostalmark;3036 +circlewithlefthalfblack;25D0 +circlewithrighthalfblack;25D1 +circumflex;02C6 +circumflexbelowcmb;032D +circumflexcmb;0302 +clear;2327 +clickalveolar;01C2 +clickdental;01C0 +clicklateral;01C1 +clickretroflex;01C3 +club;2663 +clubsuitblack;2663 +clubsuitwhite;2667 +cmcubedsquare;33A4 +cmonospace;FF43 +cmsquaredsquare;33A0 +coarmenian;0581 +colon;003A +colonmonetary;20A1 +colonmonospace;FF1A +colonsign;20A1 +colonsmall;FE55 +colontriangularhalfmod;02D1 +colontriangularmod;02D0 +comma;002C +commaabovecmb;0313 +commaaboverightcmb;0315 +commaaccent;F6C3 +commaarabic;060C +commaarmenian;055D +commainferior;F6E1 +commamonospace;FF0C +commareversedabovecmb;0314 +commareversedmod;02BD +commasmall;FE50 +commasuperior;F6E2 +commaturnedabovecmb;0312 +commaturnedmod;02BB +compass;263C +congruent;2245 +contourintegral;222E +control;2303 +controlACK;0006 +controlBEL;0007 +controlBS;0008 +controlCAN;0018 +controlCR;000D +controlDC1;0011 +controlDC2;0012 +controlDC3;0013 +controlDC4;0014 +controlDEL;007F +controlDLE;0010 +controlEM;0019 +controlENQ;0005 +controlEOT;0004 +controlESC;001B +controlETB;0017 +controlETX;0003 +controlFF;000C +controlFS;001C +controlGS;001D +controlHT;0009 +controlLF;000A +controlNAK;0015 +controlRS;001E +controlSI;000F +controlSO;000E +controlSOT;0002 +controlSTX;0001 +controlSUB;001A +controlSYN;0016 +controlUS;001F +controlVT;000B +copyright;00A9 +copyrightsans;F8E9 +copyrightserif;F6D9 +cornerbracketleft;300C +cornerbracketlefthalfwidth;FF62 +cornerbracketleftvertical;FE41 +cornerbracketright;300D +cornerbracketrighthalfwidth;FF63 +cornerbracketrightvertical;FE42 +corporationsquare;337F +cosquare;33C7 +coverkgsquare;33C6 +cparen;249E +cruzeiro;20A2 +cstretched;0297 +curlyand;22CF +curlyor;22CE +currency;00A4 +cyrBreve;F6D1 +cyrFlex;F6D2 +cyrbreve;F6D4 +cyrflex;F6D5 +d;0064 +daarmenian;0564 +dabengali;09A6 +dadarabic;0636 +dadeva;0926 +dadfinalarabic;FEBE +dadinitialarabic;FEBF +dadmedialarabic;FEC0 +dagesh;05BC +dageshhebrew;05BC +dagger;2020 +daggerdbl;2021 +dagujarati;0AA6 +dagurmukhi;0A26 +dahiragana;3060 +dakatakana;30C0 +dalarabic;062F +dalet;05D3 +daletdagesh;FB33 +daletdageshhebrew;FB33 +dalethatafpatah;05D3 05B2 +dalethatafpatahhebrew;05D3 05B2 +dalethatafsegol;05D3 05B1 +dalethatafsegolhebrew;05D3 05B1 +dalethebrew;05D3 +dalethiriq;05D3 05B4 +dalethiriqhebrew;05D3 05B4 +daletholam;05D3 05B9 +daletholamhebrew;05D3 05B9 +daletpatah;05D3 05B7 +daletpatahhebrew;05D3 05B7 +daletqamats;05D3 05B8 +daletqamatshebrew;05D3 05B8 +daletqubuts;05D3 05BB +daletqubutshebrew;05D3 05BB +daletsegol;05D3 05B6 +daletsegolhebrew;05D3 05B6 +daletsheva;05D3 05B0 +daletshevahebrew;05D3 05B0 +dalettsere;05D3 05B5 +dalettserehebrew;05D3 05B5 +dalfinalarabic;FEAA +dammaarabic;064F +dammalowarabic;064F +dammatanaltonearabic;064C +dammatanarabic;064C +danda;0964 +dargahebrew;05A7 +dargalefthebrew;05A7 +dasiapneumatacyrilliccmb;0485 +dblGrave;F6D3 +dblanglebracketleft;300A +dblanglebracketleftvertical;FE3D +dblanglebracketright;300B +dblanglebracketrightvertical;FE3E +dblarchinvertedbelowcmb;032B +dblarrowleft;21D4 +dblarrowright;21D2 +dbldanda;0965 +dblgrave;F6D6 +dblgravecmb;030F +dblintegral;222C +dbllowline;2017 +dbllowlinecmb;0333 +dbloverlinecmb;033F +dblprimemod;02BA +dblverticalbar;2016 +dblverticallineabovecmb;030E +dbopomofo;3109 +dbsquare;33C8 +dcaron;010F +dcedilla;1E11 +dcircle;24D3 +dcircumflexbelow;1E13 +dcroat;0111 +ddabengali;09A1 +ddadeva;0921 +ddagujarati;0AA1 +ddagurmukhi;0A21 +ddalarabic;0688 +ddalfinalarabic;FB89 +dddhadeva;095C +ddhabengali;09A2 +ddhadeva;0922 +ddhagujarati;0AA2 +ddhagurmukhi;0A22 +ddotaccent;1E0B +ddotbelow;1E0D +decimalseparatorarabic;066B +decimalseparatorpersian;066B +decyrillic;0434 +degree;00B0 +dehihebrew;05AD +dehiragana;3067 +deicoptic;03EF +dekatakana;30C7 +deleteleft;232B +deleteright;2326 +delta;03B4 +deltaturned;018D +denominatorminusonenumeratorbengali;09F8 +dezh;02A4 +dhabengali;09A7 +dhadeva;0927 +dhagujarati;0AA7 +dhagurmukhi;0A27 +dhook;0257 +dialytikatonos;0385 +dialytikatonoscmb;0344 +diamond;2666 +diamondsuitwhite;2662 +dieresis;00A8 +dieresisacute;F6D7 +dieresisbelowcmb;0324 +dieresiscmb;0308 +dieresisgrave;F6D8 +dieresistonos;0385 +dihiragana;3062 +dikatakana;30C2 +dittomark;3003 +divide;00F7 +divides;2223 +divisionslash;2215 +djecyrillic;0452 +dkshade;2593 +dlinebelow;1E0F +dlsquare;3397 +dmacron;0111 +dmonospace;FF44 +dnblock;2584 +dochadathai;0E0E +dodekthai;0E14 +dohiragana;3069 +dokatakana;30C9 +dollar;0024 +dollarinferior;F6E3 +dollarmonospace;FF04 +dollaroldstyle;F724 +dollarsmall;FE69 +dollarsuperior;F6E4 +dong;20AB +dorusquare;3326 +dotaccent;02D9 +dotaccentcmb;0307 +dotbelowcmb;0323 +dotbelowcomb;0323 +dotkatakana;30FB +dotlessi;0131 +dotlessj;F6BE +dotlessjstrokehook;0284 +dotmath;22C5 +dottedcircle;25CC +doubleyodpatah;FB1F +doubleyodpatahhebrew;FB1F +downtackbelowcmb;031E +downtackmod;02D5 +dparen;249F +dsuperior;F6EB +dtail;0256 +dtopbar;018C +duhiragana;3065 +dukatakana;30C5 +dz;01F3 +dzaltone;02A3 +dzcaron;01C6 +dzcurl;02A5 +dzeabkhasiancyrillic;04E1 +dzecyrillic;0455 +dzhecyrillic;045F +e;0065 +eacute;00E9 +earth;2641 +ebengali;098F +ebopomofo;311C +ebreve;0115 +ecandradeva;090D +ecandragujarati;0A8D +ecandravowelsigndeva;0945 +ecandravowelsigngujarati;0AC5 +ecaron;011B +ecedillabreve;1E1D +echarmenian;0565 +echyiwnarmenian;0587 +ecircle;24D4 +ecircumflex;00EA +ecircumflexacute;1EBF +ecircumflexbelow;1E19 +ecircumflexdotbelow;1EC7 +ecircumflexgrave;1EC1 +ecircumflexhookabove;1EC3 +ecircumflextilde;1EC5 +ecyrillic;0454 +edblgrave;0205 +edeva;090F +edieresis;00EB +edot;0117 +edotaccent;0117 +edotbelow;1EB9 +eegurmukhi;0A0F +eematragurmukhi;0A47 +efcyrillic;0444 +egrave;00E8 +egujarati;0A8F +eharmenian;0567 +ehbopomofo;311D +ehiragana;3048 +ehookabove;1EBB +eibopomofo;311F +eight;0038 +eightarabic;0668 +eightbengali;09EE +eightcircle;2467 +eightcircleinversesansserif;2791 +eightdeva;096E +eighteencircle;2471 +eighteenparen;2485 +eighteenperiod;2499 +eightgujarati;0AEE +eightgurmukhi;0A6E +eighthackarabic;0668 +eighthangzhou;3028 +eighthnotebeamed;266B +eightideographicparen;3227 +eightinferior;2088 +eightmonospace;FF18 +eightoldstyle;F738 +eightparen;247B +eightperiod;248F +eightpersian;06F8 +eightroman;2177 +eightsuperior;2078 +eightthai;0E58 +einvertedbreve;0207 +eiotifiedcyrillic;0465 +ekatakana;30A8 +ekatakanahalfwidth;FF74 +ekonkargurmukhi;0A74 +ekorean;3154 +elcyrillic;043B +element;2208 +elevencircle;246A +elevenparen;247E +elevenperiod;2492 +elevenroman;217A +ellipsis;2026 +ellipsisvertical;22EE +emacron;0113 +emacronacute;1E17 +emacrongrave;1E15 +emcyrillic;043C +emdash;2014 +emdashvertical;FE31 +emonospace;FF45 +emphasismarkarmenian;055B +emptyset;2205 +enbopomofo;3123 +encyrillic;043D +endash;2013 +endashvertical;FE32 +endescendercyrillic;04A3 +eng;014B +engbopomofo;3125 +enghecyrillic;04A5 +enhookcyrillic;04C8 +enspace;2002 +eogonek;0119 +eokorean;3153 +eopen;025B +eopenclosed;029A +eopenreversed;025C +eopenreversedclosed;025E +eopenreversedhook;025D +eparen;24A0 +epsilon;03B5 +epsilontonos;03AD +equal;003D +equalmonospace;FF1D +equalsmall;FE66 +equalsuperior;207C +equivalence;2261 +erbopomofo;3126 +ercyrillic;0440 +ereversed;0258 +ereversedcyrillic;044D +escyrillic;0441 +esdescendercyrillic;04AB +esh;0283 +eshcurl;0286 +eshortdeva;090E +eshortvowelsigndeva;0946 +eshreversedloop;01AA +eshsquatreversed;0285 +esmallhiragana;3047 +esmallkatakana;30A7 +esmallkatakanahalfwidth;FF6A +estimated;212E +esuperior;F6EC +eta;03B7 +etarmenian;0568 +etatonos;03AE +eth;00F0 +etilde;1EBD +etildebelow;1E1B +etnahtafoukhhebrew;0591 +etnahtafoukhlefthebrew;0591 +etnahtahebrew;0591 +etnahtalefthebrew;0591 +eturned;01DD +eukorean;3161 +euro;20AC +evowelsignbengali;09C7 +evowelsigndeva;0947 +evowelsigngujarati;0AC7 +exclam;0021 +exclamarmenian;055C +exclamdbl;203C +exclamdown;00A1 +exclamdownsmall;F7A1 +exclammonospace;FF01 +exclamsmall;F721 +existential;2203 +ezh;0292 +ezhcaron;01EF +ezhcurl;0293 +ezhreversed;01B9 +ezhtail;01BA +f;0066 +fadeva;095E +fagurmukhi;0A5E +fahrenheit;2109 +fathaarabic;064E +fathalowarabic;064E +fathatanarabic;064B +fbopomofo;3108 +fcircle;24D5 +fdotaccent;1E1F +feharabic;0641 +feharmenian;0586 +fehfinalarabic;FED2 +fehinitialarabic;FED3 +fehmedialarabic;FED4 +feicoptic;03E5 +female;2640 +ff;FB00 +ffi;FB03 +ffl;FB04 +fi;FB01 +fifteencircle;246E +fifteenparen;2482 +fifteenperiod;2496 +figuredash;2012 +filledbox;25A0 +filledrect;25AC +finalkaf;05DA +finalkafdagesh;FB3A +finalkafdageshhebrew;FB3A +finalkafhebrew;05DA +finalkafqamats;05DA 05B8 +finalkafqamatshebrew;05DA 05B8 +finalkafsheva;05DA 05B0 +finalkafshevahebrew;05DA 05B0 +finalmem;05DD +finalmemhebrew;05DD +finalnun;05DF +finalnunhebrew;05DF +finalpe;05E3 +finalpehebrew;05E3 +finaltsadi;05E5 +finaltsadihebrew;05E5 +firsttonechinese;02C9 +fisheye;25C9 +fitacyrillic;0473 +five;0035 +fivearabic;0665 +fivebengali;09EB +fivecircle;2464 +fivecircleinversesansserif;278E +fivedeva;096B +fiveeighths;215D +fivegujarati;0AEB +fivegurmukhi;0A6B +fivehackarabic;0665 +fivehangzhou;3025 +fiveideographicparen;3224 +fiveinferior;2085 +fivemonospace;FF15 +fiveoldstyle;F735 +fiveparen;2478 +fiveperiod;248C +fivepersian;06F5 +fiveroman;2174 +fivesuperior;2075 +fivethai;0E55 +fl;FB02 +florin;0192 +fmonospace;FF46 +fmsquare;3399 +fofanthai;0E1F +fofathai;0E1D +fongmanthai;0E4F +forall;2200 +four;0034 +fourarabic;0664 +fourbengali;09EA +fourcircle;2463 +fourcircleinversesansserif;278D +fourdeva;096A +fourgujarati;0AEA +fourgurmukhi;0A6A +fourhackarabic;0664 +fourhangzhou;3024 +fourideographicparen;3223 +fourinferior;2084 +fourmonospace;FF14 +fournumeratorbengali;09F7 +fouroldstyle;F734 +fourparen;2477 +fourperiod;248B +fourpersian;06F4 +fourroman;2173 +foursuperior;2074 +fourteencircle;246D +fourteenparen;2481 +fourteenperiod;2495 +fourthai;0E54 +fourthtonechinese;02CB +fparen;24A1 +fraction;2044 +franc;20A3 +g;0067 +gabengali;0997 +gacute;01F5 +gadeva;0917 +gafarabic;06AF +gaffinalarabic;FB93 +gafinitialarabic;FB94 +gafmedialarabic;FB95 +gagujarati;0A97 +gagurmukhi;0A17 +gahiragana;304C +gakatakana;30AC +gamma;03B3 +gammalatinsmall;0263 +gammasuperior;02E0 +gangiacoptic;03EB +gbopomofo;310D +gbreve;011F +gcaron;01E7 +gcedilla;0123 +gcircle;24D6 +gcircumflex;011D +gcommaaccent;0123 +gdot;0121 +gdotaccent;0121 +gecyrillic;0433 +gehiragana;3052 +gekatakana;30B2 +geometricallyequal;2251 +gereshaccenthebrew;059C +gereshhebrew;05F3 +gereshmuqdamhebrew;059D +germandbls;00DF +gershayimaccenthebrew;059E +gershayimhebrew;05F4 +getamark;3013 +ghabengali;0998 +ghadarmenian;0572 +ghadeva;0918 +ghagujarati;0A98 +ghagurmukhi;0A18 +ghainarabic;063A +ghainfinalarabic;FECE +ghaininitialarabic;FECF +ghainmedialarabic;FED0 +ghemiddlehookcyrillic;0495 +ghestrokecyrillic;0493 +gheupturncyrillic;0491 +ghhadeva;095A +ghhagurmukhi;0A5A +ghook;0260 +ghzsquare;3393 +gihiragana;304E +gikatakana;30AE +gimarmenian;0563 +gimel;05D2 +gimeldagesh;FB32 +gimeldageshhebrew;FB32 +gimelhebrew;05D2 +gjecyrillic;0453 +glottalinvertedstroke;01BE +glottalstop;0294 +glottalstopinverted;0296 +glottalstopmod;02C0 +glottalstopreversed;0295 +glottalstopreversedmod;02C1 +glottalstopreversedsuperior;02E4 +glottalstopstroke;02A1 +glottalstopstrokereversed;02A2 +gmacron;1E21 +gmonospace;FF47 +gohiragana;3054 +gokatakana;30B4 +gparen;24A2 +gpasquare;33AC +gradient;2207 +grave;0060 +gravebelowcmb;0316 +gravecmb;0300 +gravecomb;0300 +gravedeva;0953 +gravelowmod;02CE +gravemonospace;FF40 +gravetonecmb;0340 +greater;003E +greaterequal;2265 +greaterequalorless;22DB +greatermonospace;FF1E +greaterorequivalent;2273 +greaterorless;2277 +greateroverequal;2267 +greatersmall;FE65 +gscript;0261 +gstroke;01E5 +guhiragana;3050 +guillemotleft;00AB +guillemotright;00BB +guilsinglleft;2039 +guilsinglright;203A +gukatakana;30B0 +guramusquare;3318 +gysquare;33C9 +h;0068 +haabkhasiancyrillic;04A9 +haaltonearabic;06C1 +habengali;09B9 +hadescendercyrillic;04B3 +hadeva;0939 +hagujarati;0AB9 +hagurmukhi;0A39 +haharabic;062D +hahfinalarabic;FEA2 +hahinitialarabic;FEA3 +hahiragana;306F +hahmedialarabic;FEA4 +haitusquare;332A +hakatakana;30CF +hakatakanahalfwidth;FF8A +halantgurmukhi;0A4D +hamzaarabic;0621 +hamzadammaarabic;0621 064F +hamzadammatanarabic;0621 064C +hamzafathaarabic;0621 064E +hamzafathatanarabic;0621 064B +hamzalowarabic;0621 +hamzalowkasraarabic;0621 0650 +hamzalowkasratanarabic;0621 064D +hamzasukunarabic;0621 0652 +hangulfiller;3164 +hardsigncyrillic;044A +harpoonleftbarbup;21BC +harpoonrightbarbup;21C0 +hasquare;33CA +hatafpatah;05B2 +hatafpatah16;05B2 +hatafpatah23;05B2 +hatafpatah2f;05B2 +hatafpatahhebrew;05B2 +hatafpatahnarrowhebrew;05B2 +hatafpatahquarterhebrew;05B2 +hatafpatahwidehebrew;05B2 +hatafqamats;05B3 +hatafqamats1b;05B3 +hatafqamats28;05B3 +hatafqamats34;05B3 +hatafqamatshebrew;05B3 +hatafqamatsnarrowhebrew;05B3 +hatafqamatsquarterhebrew;05B3 +hatafqamatswidehebrew;05B3 +hatafsegol;05B1 +hatafsegol17;05B1 +hatafsegol24;05B1 +hatafsegol30;05B1 +hatafsegolhebrew;05B1 +hatafsegolnarrowhebrew;05B1 +hatafsegolquarterhebrew;05B1 +hatafsegolwidehebrew;05B1 +hbar;0127 +hbopomofo;310F +hbrevebelow;1E2B +hcedilla;1E29 +hcircle;24D7 +hcircumflex;0125 +hdieresis;1E27 +hdotaccent;1E23 +hdotbelow;1E25 +he;05D4 +heart;2665 +heartsuitblack;2665 +heartsuitwhite;2661 +hedagesh;FB34 +hedageshhebrew;FB34 +hehaltonearabic;06C1 +heharabic;0647 +hehebrew;05D4 +hehfinalaltonearabic;FBA7 +hehfinalalttwoarabic;FEEA +hehfinalarabic;FEEA +hehhamzaabovefinalarabic;FBA5 +hehhamzaaboveisolatedarabic;FBA4 +hehinitialaltonearabic;FBA8 +hehinitialarabic;FEEB +hehiragana;3078 +hehmedialaltonearabic;FBA9 +hehmedialarabic;FEEC +heiseierasquare;337B +hekatakana;30D8 +hekatakanahalfwidth;FF8D +hekutaarusquare;3336 +henghook;0267 +herutusquare;3339 +het;05D7 +hethebrew;05D7 +hhook;0266 +hhooksuperior;02B1 +hieuhacirclekorean;327B +hieuhaparenkorean;321B +hieuhcirclekorean;326D +hieuhkorean;314E +hieuhparenkorean;320D +hihiragana;3072 +hikatakana;30D2 +hikatakanahalfwidth;FF8B +hiriq;05B4 +hiriq14;05B4 +hiriq21;05B4 +hiriq2d;05B4 +hiriqhebrew;05B4 +hiriqnarrowhebrew;05B4 +hiriqquarterhebrew;05B4 +hiriqwidehebrew;05B4 +hlinebelow;1E96 +hmonospace;FF48 +hoarmenian;0570 +hohipthai;0E2B +hohiragana;307B +hokatakana;30DB +hokatakanahalfwidth;FF8E +holam;05B9 +holam19;05B9 +holam26;05B9 +holam32;05B9 +holamhebrew;05B9 +holamnarrowhebrew;05B9 +holamquarterhebrew;05B9 +holamwidehebrew;05B9 +honokhukthai;0E2E +hookabovecomb;0309 +hookcmb;0309 +hookpalatalizedbelowcmb;0321 +hookretroflexbelowcmb;0322 +hoonsquare;3342 +horicoptic;03E9 +horizontalbar;2015 +horncmb;031B +hotsprings;2668 +house;2302 +hparen;24A3 +hsuperior;02B0 +hturned;0265 +huhiragana;3075 +huiitosquare;3333 +hukatakana;30D5 +hukatakanahalfwidth;FF8C +hungarumlaut;02DD +hungarumlautcmb;030B +hv;0195 +hyphen;002D +hypheninferior;F6E5 +hyphenmonospace;FF0D +hyphensmall;FE63 +hyphensuperior;F6E6 +hyphentwo;2010 +i;0069 +iacute;00ED +iacyrillic;044F +ibengali;0987 +ibopomofo;3127 +ibreve;012D +icaron;01D0 +icircle;24D8 +icircumflex;00EE +icyrillic;0456 +idblgrave;0209 +ideographearthcircle;328F +ideographfirecircle;328B +ideographicallianceparen;323F +ideographiccallparen;323A +ideographiccentrecircle;32A5 +ideographicclose;3006 +ideographiccomma;3001 +ideographiccommaleft;FF64 +ideographiccongratulationparen;3237 +ideographiccorrectcircle;32A3 +ideographicearthparen;322F +ideographicenterpriseparen;323D +ideographicexcellentcircle;329D +ideographicfestivalparen;3240 +ideographicfinancialcircle;3296 +ideographicfinancialparen;3236 +ideographicfireparen;322B +ideographichaveparen;3232 +ideographichighcircle;32A4 +ideographiciterationmark;3005 +ideographiclaborcircle;3298 +ideographiclaborparen;3238 +ideographicleftcircle;32A7 +ideographiclowcircle;32A6 +ideographicmedicinecircle;32A9 +ideographicmetalparen;322E +ideographicmoonparen;322A +ideographicnameparen;3234 +ideographicperiod;3002 +ideographicprintcircle;329E +ideographicreachparen;3243 +ideographicrepresentparen;3239 +ideographicresourceparen;323E +ideographicrightcircle;32A8 +ideographicsecretcircle;3299 +ideographicselfparen;3242 +ideographicsocietyparen;3233 +ideographicspace;3000 +ideographicspecialparen;3235 +ideographicstockparen;3231 +ideographicstudyparen;323B +ideographicsunparen;3230 +ideographicsuperviseparen;323C +ideographicwaterparen;322C +ideographicwoodparen;322D +ideographiczero;3007 +ideographmetalcircle;328E +ideographmooncircle;328A +ideographnamecircle;3294 +ideographsuncircle;3290 +ideographwatercircle;328C +ideographwoodcircle;328D +ideva;0907 +idieresis;00EF +idieresisacute;1E2F +idieresiscyrillic;04E5 +idotbelow;1ECB +iebrevecyrillic;04D7 +iecyrillic;0435 +ieungacirclekorean;3275 +ieungaparenkorean;3215 +ieungcirclekorean;3267 +ieungkorean;3147 +ieungparenkorean;3207 +igrave;00EC +igujarati;0A87 +igurmukhi;0A07 +ihiragana;3044 +ihookabove;1EC9 +iibengali;0988 +iicyrillic;0438 +iideva;0908 +iigujarati;0A88 +iigurmukhi;0A08 +iimatragurmukhi;0A40 +iinvertedbreve;020B +iishortcyrillic;0439 +iivowelsignbengali;09C0 +iivowelsigndeva;0940 +iivowelsigngujarati;0AC0 +ij;0133 +ikatakana;30A4 +ikatakanahalfwidth;FF72 +ikorean;3163 +ilde;02DC +iluyhebrew;05AC +imacron;012B +imacroncyrillic;04E3 +imageorapproximatelyequal;2253 +imatragurmukhi;0A3F +imonospace;FF49 +increment;2206 +infinity;221E +iniarmenian;056B +integral;222B +integralbottom;2321 +integralbt;2321 +integralex;F8F5 +integraltop;2320 +integraltp;2320 +intersection;2229 +intisquare;3305 +invbullet;25D8 +invcircle;25D9 +invsmileface;263B +iocyrillic;0451 +iogonek;012F +iota;03B9 +iotadieresis;03CA +iotadieresistonos;0390 +iotalatin;0269 +iotatonos;03AF +iparen;24A4 +irigurmukhi;0A72 +ismallhiragana;3043 +ismallkatakana;30A3 +ismallkatakanahalfwidth;FF68 +issharbengali;09FA +istroke;0268 +isuperior;F6ED +iterationhiragana;309D +iterationkatakana;30FD +itilde;0129 +itildebelow;1E2D +iubopomofo;3129 +iucyrillic;044E +ivowelsignbengali;09BF +ivowelsigndeva;093F +ivowelsigngujarati;0ABF +izhitsacyrillic;0475 +izhitsadblgravecyrillic;0477 +j;006A +jaarmenian;0571 +jabengali;099C +jadeva;091C +jagujarati;0A9C +jagurmukhi;0A1C +jbopomofo;3110 +jcaron;01F0 +jcircle;24D9 +jcircumflex;0135 +jcrossedtail;029D +jdotlessstroke;025F +jecyrillic;0458 +jeemarabic;062C +jeemfinalarabic;FE9E +jeeminitialarabic;FE9F +jeemmedialarabic;FEA0 +jeharabic;0698 +jehfinalarabic;FB8B +jhabengali;099D +jhadeva;091D +jhagujarati;0A9D +jhagurmukhi;0A1D +jheharmenian;057B +jis;3004 +jmonospace;FF4A +jparen;24A5 +jsuperior;02B2 +k;006B +kabashkircyrillic;04A1 +kabengali;0995 +kacute;1E31 +kacyrillic;043A +kadescendercyrillic;049B +kadeva;0915 +kaf;05DB +kafarabic;0643 +kafdagesh;FB3B +kafdageshhebrew;FB3B +kaffinalarabic;FEDA +kafhebrew;05DB +kafinitialarabic;FEDB +kafmedialarabic;FEDC +kafrafehebrew;FB4D +kagujarati;0A95 +kagurmukhi;0A15 +kahiragana;304B +kahookcyrillic;04C4 +kakatakana;30AB +kakatakanahalfwidth;FF76 +kappa;03BA +kappasymbolgreek;03F0 +kapyeounmieumkorean;3171 +kapyeounphieuphkorean;3184 +kapyeounpieupkorean;3178 +kapyeounssangpieupkorean;3179 +karoriisquare;330D +kashidaautoarabic;0640 +kashidaautonosidebearingarabic;0640 +kasmallkatakana;30F5 +kasquare;3384 +kasraarabic;0650 +kasratanarabic;064D +kastrokecyrillic;049F +katahiraprolongmarkhalfwidth;FF70 +kaverticalstrokecyrillic;049D +kbopomofo;310E +kcalsquare;3389 +kcaron;01E9 +kcedilla;0137 +kcircle;24DA +kcommaaccent;0137 +kdotbelow;1E33 +keharmenian;0584 +kehiragana;3051 +kekatakana;30B1 +kekatakanahalfwidth;FF79 +kenarmenian;056F +kesmallkatakana;30F6 +kgreenlandic;0138 +khabengali;0996 +khacyrillic;0445 +khadeva;0916 +khagujarati;0A96 +khagurmukhi;0A16 +khaharabic;062E +khahfinalarabic;FEA6 +khahinitialarabic;FEA7 +khahmedialarabic;FEA8 +kheicoptic;03E7 +khhadeva;0959 +khhagurmukhi;0A59 +khieukhacirclekorean;3278 +khieukhaparenkorean;3218 +khieukhcirclekorean;326A +khieukhkorean;314B +khieukhparenkorean;320A +khokhaithai;0E02 +khokhonthai;0E05 +khokhuatthai;0E03 +khokhwaithai;0E04 +khomutthai;0E5B +khook;0199 +khorakhangthai;0E06 +khzsquare;3391 +kihiragana;304D +kikatakana;30AD +kikatakanahalfwidth;FF77 +kiroguramusquare;3315 +kiromeetorusquare;3316 +kirosquare;3314 +kiyeokacirclekorean;326E +kiyeokaparenkorean;320E +kiyeokcirclekorean;3260 +kiyeokkorean;3131 +kiyeokparenkorean;3200 +kiyeoksioskorean;3133 +kjecyrillic;045C +klinebelow;1E35 +klsquare;3398 +kmcubedsquare;33A6 +kmonospace;FF4B +kmsquaredsquare;33A2 +kohiragana;3053 +kohmsquare;33C0 +kokaithai;0E01 +kokatakana;30B3 +kokatakanahalfwidth;FF7A +kooposquare;331E +koppacyrillic;0481 +koreanstandardsymbol;327F +koroniscmb;0343 +kparen;24A6 +kpasquare;33AA +ksicyrillic;046F +ktsquare;33CF +kturned;029E +kuhiragana;304F +kukatakana;30AF +kukatakanahalfwidth;FF78 +kvsquare;33B8 +kwsquare;33BE +l;006C +labengali;09B2 +lacute;013A +ladeva;0932 +lagujarati;0AB2 +lagurmukhi;0A32 +lakkhangyaothai;0E45 +lamaleffinalarabic;FEFC +lamalefhamzaabovefinalarabic;FEF8 +lamalefhamzaaboveisolatedarabic;FEF7 +lamalefhamzabelowfinalarabic;FEFA +lamalefhamzabelowisolatedarabic;FEF9 +lamalefisolatedarabic;FEFB +lamalefmaddaabovefinalarabic;FEF6 +lamalefmaddaaboveisolatedarabic;FEF5 +lamarabic;0644 +lambda;03BB +lambdastroke;019B +lamed;05DC +lameddagesh;FB3C +lameddageshhebrew;FB3C +lamedhebrew;05DC +lamedholam;05DC 05B9 +lamedholamdagesh;05DC 05B9 05BC +lamedholamdageshhebrew;05DC 05B9 05BC +lamedholamhebrew;05DC 05B9 +lamfinalarabic;FEDE +lamhahinitialarabic;FCCA +laminitialarabic;FEDF +lamjeeminitialarabic;FCC9 +lamkhahinitialarabic;FCCB +lamlamhehisolatedarabic;FDF2 +lammedialarabic;FEE0 +lammeemhahinitialarabic;FD88 +lammeeminitialarabic;FCCC +lammeemjeeminitialarabic;FEDF FEE4 FEA0 +lammeemkhahinitialarabic;FEDF FEE4 FEA8 +largecircle;25EF +lbar;019A +lbelt;026C +lbopomofo;310C +lcaron;013E +lcedilla;013C +lcircle;24DB +lcircumflexbelow;1E3D +lcommaaccent;013C +ldot;0140 +ldotaccent;0140 +ldotbelow;1E37 +ldotbelowmacron;1E39 +leftangleabovecmb;031A +lefttackbelowcmb;0318 +less;003C +lessequal;2264 +lessequalorgreater;22DA +lessmonospace;FF1C +lessorequivalent;2272 +lessorgreater;2276 +lessoverequal;2266 +lesssmall;FE64 +lezh;026E +lfblock;258C +lhookretroflex;026D +lira;20A4 +liwnarmenian;056C +lj;01C9 +ljecyrillic;0459 +ll;F6C0 +lladeva;0933 +llagujarati;0AB3 +llinebelow;1E3B +llladeva;0934 +llvocalicbengali;09E1 +llvocalicdeva;0961 +llvocalicvowelsignbengali;09E3 +llvocalicvowelsigndeva;0963 +lmiddletilde;026B +lmonospace;FF4C +lmsquare;33D0 +lochulathai;0E2C +logicaland;2227 +logicalnot;00AC +logicalnotreversed;2310 +logicalor;2228 +lolingthai;0E25 +longs;017F +lowlinecenterline;FE4E +lowlinecmb;0332 +lowlinedashed;FE4D +lozenge;25CA +lparen;24A7 +lslash;0142 +lsquare;2113 +lsuperior;F6EE +ltshade;2591 +luthai;0E26 +lvocalicbengali;098C +lvocalicdeva;090C +lvocalicvowelsignbengali;09E2 +lvocalicvowelsigndeva;0962 +lxsquare;33D3 +m;006D +mabengali;09AE +macron;00AF +macronbelowcmb;0331 +macroncmb;0304 +macronlowmod;02CD +macronmonospace;FFE3 +macute;1E3F +madeva;092E +magujarati;0AAE +magurmukhi;0A2E +mahapakhhebrew;05A4 +mahapakhlefthebrew;05A4 +mahiragana;307E +maichattawalowleftthai;F895 +maichattawalowrightthai;F894 +maichattawathai;0E4B +maichattawaupperleftthai;F893 +maieklowleftthai;F88C +maieklowrightthai;F88B +maiekthai;0E48 +maiekupperleftthai;F88A +maihanakatleftthai;F884 +maihanakatthai;0E31 +maitaikhuleftthai;F889 +maitaikhuthai;0E47 +maitholowleftthai;F88F +maitholowrightthai;F88E +maithothai;0E49 +maithoupperleftthai;F88D +maitrilowleftthai;F892 +maitrilowrightthai;F891 +maitrithai;0E4A +maitriupperleftthai;F890 +maiyamokthai;0E46 +makatakana;30DE +makatakanahalfwidth;FF8F +male;2642 +mansyonsquare;3347 +maqafhebrew;05BE +mars;2642 +masoracirclehebrew;05AF +masquare;3383 +mbopomofo;3107 +mbsquare;33D4 +mcircle;24DC +mcubedsquare;33A5 +mdotaccent;1E41 +mdotbelow;1E43 +meemarabic;0645 +meemfinalarabic;FEE2 +meeminitialarabic;FEE3 +meemmedialarabic;FEE4 +meemmeeminitialarabic;FCD1 +meemmeemisolatedarabic;FC48 +meetorusquare;334D +mehiragana;3081 +meizierasquare;337E +mekatakana;30E1 +mekatakanahalfwidth;FF92 +mem;05DE +memdagesh;FB3E +memdageshhebrew;FB3E +memhebrew;05DE +menarmenian;0574 +merkhahebrew;05A5 +merkhakefulahebrew;05A6 +merkhakefulalefthebrew;05A6 +merkhalefthebrew;05A5 +mhook;0271 +mhzsquare;3392 +middledotkatakanahalfwidth;FF65 +middot;00B7 +mieumacirclekorean;3272 +mieumaparenkorean;3212 +mieumcirclekorean;3264 +mieumkorean;3141 +mieumpansioskorean;3170 +mieumparenkorean;3204 +mieumpieupkorean;316E +mieumsioskorean;316F +mihiragana;307F +mikatakana;30DF +mikatakanahalfwidth;FF90 +minus;2212 +minusbelowcmb;0320 +minuscircle;2296 +minusmod;02D7 +minusplus;2213 +minute;2032 +miribaarusquare;334A +mirisquare;3349 +mlonglegturned;0270 +mlsquare;3396 +mmcubedsquare;33A3 +mmonospace;FF4D +mmsquaredsquare;339F +mohiragana;3082 +mohmsquare;33C1 +mokatakana;30E2 +mokatakanahalfwidth;FF93 +molsquare;33D6 +momathai;0E21 +moverssquare;33A7 +moverssquaredsquare;33A8 +mparen;24A8 +mpasquare;33AB +mssquare;33B3 +msuperior;F6EF +mturned;026F +mu;00B5 +mu1;00B5 +muasquare;3382 +muchgreater;226B +muchless;226A +mufsquare;338C +mugreek;03BC +mugsquare;338D +muhiragana;3080 +mukatakana;30E0 +mukatakanahalfwidth;FF91 +mulsquare;3395 +multiply;00D7 +mumsquare;339B +munahhebrew;05A3 +munahlefthebrew;05A3 +musicalnote;266A +musicalnotedbl;266B +musicflatsign;266D +musicsharpsign;266F +mussquare;33B2 +muvsquare;33B6 +muwsquare;33BC +mvmegasquare;33B9 +mvsquare;33B7 +mwmegasquare;33BF +mwsquare;33BD +n;006E +nabengali;09A8 +nabla;2207 +nacute;0144 +nadeva;0928 +nagujarati;0AA8 +nagurmukhi;0A28 +nahiragana;306A +nakatakana;30CA +nakatakanahalfwidth;FF85 +napostrophe;0149 +nasquare;3381 +nbopomofo;310B +nbspace;00A0 +ncaron;0148 +ncedilla;0146 +ncircle;24DD +ncircumflexbelow;1E4B +ncommaaccent;0146 +ndotaccent;1E45 +ndotbelow;1E47 +nehiragana;306D +nekatakana;30CD +nekatakanahalfwidth;FF88 +newsheqelsign;20AA +nfsquare;338B +ngabengali;0999 +ngadeva;0919 +ngagujarati;0A99 +ngagurmukhi;0A19 +ngonguthai;0E07 +nhiragana;3093 +nhookleft;0272 +nhookretroflex;0273 +nieunacirclekorean;326F +nieunaparenkorean;320F +nieuncieuckorean;3135 +nieuncirclekorean;3261 +nieunhieuhkorean;3136 +nieunkorean;3134 +nieunpansioskorean;3168 +nieunparenkorean;3201 +nieunsioskorean;3167 +nieuntikeutkorean;3166 +nihiragana;306B +nikatakana;30CB +nikatakanahalfwidth;FF86 +nikhahitleftthai;F899 +nikhahitthai;0E4D +nine;0039 +ninearabic;0669 +ninebengali;09EF +ninecircle;2468 +ninecircleinversesansserif;2792 +ninedeva;096F +ninegujarati;0AEF +ninegurmukhi;0A6F +ninehackarabic;0669 +ninehangzhou;3029 +nineideographicparen;3228 +nineinferior;2089 +ninemonospace;FF19 +nineoldstyle;F739 +nineparen;247C +nineperiod;2490 +ninepersian;06F9 +nineroman;2178 +ninesuperior;2079 +nineteencircle;2472 +nineteenparen;2486 +nineteenperiod;249A +ninethai;0E59 +nj;01CC +njecyrillic;045A +nkatakana;30F3 +nkatakanahalfwidth;FF9D +nlegrightlong;019E +nlinebelow;1E49 +nmonospace;FF4E +nmsquare;339A +nnabengali;09A3 +nnadeva;0923 +nnagujarati;0AA3 +nnagurmukhi;0A23 +nnnadeva;0929 +nohiragana;306E +nokatakana;30CE +nokatakanahalfwidth;FF89 +nonbreakingspace;00A0 +nonenthai;0E13 +nonuthai;0E19 +noonarabic;0646 +noonfinalarabic;FEE6 +noonghunnaarabic;06BA +noonghunnafinalarabic;FB9F +noonhehinitialarabic;FEE7 FEEC +nooninitialarabic;FEE7 +noonjeeminitialarabic;FCD2 +noonjeemisolatedarabic;FC4B +noonmedialarabic;FEE8 +noonmeeminitialarabic;FCD5 +noonmeemisolatedarabic;FC4E +noonnoonfinalarabic;FC8D +notcontains;220C +notelement;2209 +notelementof;2209 +notequal;2260 +notgreater;226F +notgreaternorequal;2271 +notgreaternorless;2279 +notidentical;2262 +notless;226E +notlessnorequal;2270 +notparallel;2226 +notprecedes;2280 +notsubset;2284 +notsucceeds;2281 +notsuperset;2285 +nowarmenian;0576 +nparen;24A9 +nssquare;33B1 +nsuperior;207F +ntilde;00F1 +nu;03BD +nuhiragana;306C +nukatakana;30CC +nukatakanahalfwidth;FF87 +nuktabengali;09BC +nuktadeva;093C +nuktagujarati;0ABC +nuktagurmukhi;0A3C +numbersign;0023 +numbersignmonospace;FF03 +numbersignsmall;FE5F +numeralsigngreek;0374 +numeralsignlowergreek;0375 +numero;2116 +nun;05E0 +nundagesh;FB40 +nundageshhebrew;FB40 +nunhebrew;05E0 +nvsquare;33B5 +nwsquare;33BB +nyabengali;099E +nyadeva;091E +nyagujarati;0A9E +nyagurmukhi;0A1E +o;006F +oacute;00F3 +oangthai;0E2D +obarred;0275 +obarredcyrillic;04E9 +obarreddieresiscyrillic;04EB +obengali;0993 +obopomofo;311B +obreve;014F +ocandradeva;0911 +ocandragujarati;0A91 +ocandravowelsigndeva;0949 +ocandravowelsigngujarati;0AC9 +ocaron;01D2 +ocircle;24DE +ocircumflex;00F4 +ocircumflexacute;1ED1 +ocircumflexdotbelow;1ED9 +ocircumflexgrave;1ED3 +ocircumflexhookabove;1ED5 +ocircumflextilde;1ED7 +ocyrillic;043E +odblacute;0151 +odblgrave;020D +odeva;0913 +odieresis;00F6 +odieresiscyrillic;04E7 +odotbelow;1ECD +oe;0153 +oekorean;315A +ogonek;02DB +ogonekcmb;0328 +ograve;00F2 +ogujarati;0A93 +oharmenian;0585 +ohiragana;304A +ohookabove;1ECF +ohorn;01A1 +ohornacute;1EDB +ohorndotbelow;1EE3 +ohorngrave;1EDD +ohornhookabove;1EDF +ohorntilde;1EE1 +ohungarumlaut;0151 +oi;01A3 +oinvertedbreve;020F +okatakana;30AA +okatakanahalfwidth;FF75 +okorean;3157 +olehebrew;05AB +omacron;014D +omacronacute;1E53 +omacrongrave;1E51 +omdeva;0950 +omega;03C9 +omega1;03D6 +omegacyrillic;0461 +omegalatinclosed;0277 +omegaroundcyrillic;047B +omegatitlocyrillic;047D +omegatonos;03CE +omgujarati;0AD0 +omicron;03BF +omicrontonos;03CC +omonospace;FF4F +one;0031 +onearabic;0661 +onebengali;09E7 +onecircle;2460 +onecircleinversesansserif;278A +onedeva;0967 +onedotenleader;2024 +oneeighth;215B +onefitted;F6DC +onegujarati;0AE7 +onegurmukhi;0A67 +onehackarabic;0661 +onehalf;00BD +onehangzhou;3021 +oneideographicparen;3220 +oneinferior;2081 +onemonospace;FF11 +onenumeratorbengali;09F4 +oneoldstyle;F731 +oneparen;2474 +oneperiod;2488 +onepersian;06F1 +onequarter;00BC +oneroman;2170 +onesuperior;00B9 +onethai;0E51 +onethird;2153 +oogonek;01EB +oogonekmacron;01ED +oogurmukhi;0A13 +oomatragurmukhi;0A4B +oopen;0254 +oparen;24AA +openbullet;25E6 +option;2325 +ordfeminine;00AA +ordmasculine;00BA +orthogonal;221F +oshortdeva;0912 +oshortvowelsigndeva;094A +oslash;00F8 +oslashacute;01FF +osmallhiragana;3049 +osmallkatakana;30A9 +osmallkatakanahalfwidth;FF6B +ostrokeacute;01FF +osuperior;F6F0 +otcyrillic;047F +otilde;00F5 +otildeacute;1E4D +otildedieresis;1E4F +oubopomofo;3121 +overline;203E +overlinecenterline;FE4A +overlinecmb;0305 +overlinedashed;FE49 +overlinedblwavy;FE4C +overlinewavy;FE4B +overscore;00AF +ovowelsignbengali;09CB +ovowelsigndeva;094B +ovowelsigngujarati;0ACB +p;0070 +paampssquare;3380 +paasentosquare;332B +pabengali;09AA +pacute;1E55 +padeva;092A +pagedown;21DF +pageup;21DE +pagujarati;0AAA +pagurmukhi;0A2A +pahiragana;3071 +paiyannoithai;0E2F +pakatakana;30D1 +palatalizationcyrilliccmb;0484 +palochkacyrillic;04C0 +pansioskorean;317F +paragraph;00B6 +parallel;2225 +parenleft;0028 +parenleftaltonearabic;FD3E +parenleftbt;F8ED +parenleftex;F8EC +parenleftinferior;208D +parenleftmonospace;FF08 +parenleftsmall;FE59 +parenleftsuperior;207D +parenlefttp;F8EB +parenleftvertical;FE35 +parenright;0029 +parenrightaltonearabic;FD3F +parenrightbt;F8F8 +parenrightex;F8F7 +parenrightinferior;208E +parenrightmonospace;FF09 +parenrightsmall;FE5A +parenrightsuperior;207E +parenrighttp;F8F6 +parenrightvertical;FE36 +partialdiff;2202 +paseqhebrew;05C0 +pashtahebrew;0599 +pasquare;33A9 +patah;05B7 +patah11;05B7 +patah1d;05B7 +patah2a;05B7 +patahhebrew;05B7 +patahnarrowhebrew;05B7 +patahquarterhebrew;05B7 +patahwidehebrew;05B7 +pazerhebrew;05A1 +pbopomofo;3106 +pcircle;24DF +pdotaccent;1E57 +pe;05E4 +pecyrillic;043F +pedagesh;FB44 +pedageshhebrew;FB44 +peezisquare;333B +pefinaldageshhebrew;FB43 +peharabic;067E +peharmenian;057A +pehebrew;05E4 +pehfinalarabic;FB57 +pehinitialarabic;FB58 +pehiragana;307A +pehmedialarabic;FB59 +pekatakana;30DA +pemiddlehookcyrillic;04A7 +perafehebrew;FB4E +percent;0025 +percentarabic;066A +percentmonospace;FF05 +percentsmall;FE6A +period;002E +periodarmenian;0589 +periodcentered;00B7 +periodhalfwidth;FF61 +periodinferior;F6E7 +periodmonospace;FF0E +periodsmall;FE52 +periodsuperior;F6E8 +perispomenigreekcmb;0342 +perpendicular;22A5 +perthousand;2030 +peseta;20A7 +pfsquare;338A +phabengali;09AB +phadeva;092B +phagujarati;0AAB +phagurmukhi;0A2B +phi;03C6 +phi1;03D5 +phieuphacirclekorean;327A +phieuphaparenkorean;321A +phieuphcirclekorean;326C +phieuphkorean;314D +phieuphparenkorean;320C +philatin;0278 +phinthuthai;0E3A +phisymbolgreek;03D5 +phook;01A5 +phophanthai;0E1E +phophungthai;0E1C +phosamphaothai;0E20 +pi;03C0 +pieupacirclekorean;3273 +pieupaparenkorean;3213 +pieupcieuckorean;3176 +pieupcirclekorean;3265 +pieupkiyeokkorean;3172 +pieupkorean;3142 +pieupparenkorean;3205 +pieupsioskiyeokkorean;3174 +pieupsioskorean;3144 +pieupsiostikeutkorean;3175 +pieupthieuthkorean;3177 +pieuptikeutkorean;3173 +pihiragana;3074 +pikatakana;30D4 +pisymbolgreek;03D6 +piwrarmenian;0583 +plus;002B +plusbelowcmb;031F +pluscircle;2295 +plusminus;00B1 +plusmod;02D6 +plusmonospace;FF0B +plussmall;FE62 +plussuperior;207A +pmonospace;FF50 +pmsquare;33D8 +pohiragana;307D +pointingindexdownwhite;261F +pointingindexleftwhite;261C +pointingindexrightwhite;261E +pointingindexupwhite;261D +pokatakana;30DD +poplathai;0E1B +postalmark;3012 +postalmarkface;3020 +pparen;24AB +precedes;227A +prescription;211E +primemod;02B9 +primereversed;2035 +product;220F +projective;2305 +prolongedkana;30FC +propellor;2318 +propersubset;2282 +propersuperset;2283 +proportion;2237 +proportional;221D +psi;03C8 +psicyrillic;0471 +psilipneumatacyrilliccmb;0486 +pssquare;33B0 +puhiragana;3077 +pukatakana;30D7 +pvsquare;33B4 +pwsquare;33BA +q;0071 +qadeva;0958 +qadmahebrew;05A8 +qafarabic;0642 +qaffinalarabic;FED6 +qafinitialarabic;FED7 +qafmedialarabic;FED8 +qamats;05B8 +qamats10;05B8 +qamats1a;05B8 +qamats1c;05B8 +qamats27;05B8 +qamats29;05B8 +qamats33;05B8 +qamatsde;05B8 +qamatshebrew;05B8 +qamatsnarrowhebrew;05B8 +qamatsqatanhebrew;05B8 +qamatsqatannarrowhebrew;05B8 +qamatsqatanquarterhebrew;05B8 +qamatsqatanwidehebrew;05B8 +qamatsquarterhebrew;05B8 +qamatswidehebrew;05B8 +qarneyparahebrew;059F +qbopomofo;3111 +qcircle;24E0 +qhook;02A0 +qmonospace;FF51 +qof;05E7 +qofdagesh;FB47 +qofdageshhebrew;FB47 +qofhatafpatah;05E7 05B2 +qofhatafpatahhebrew;05E7 05B2 +qofhatafsegol;05E7 05B1 +qofhatafsegolhebrew;05E7 05B1 +qofhebrew;05E7 +qofhiriq;05E7 05B4 +qofhiriqhebrew;05E7 05B4 +qofholam;05E7 05B9 +qofholamhebrew;05E7 05B9 +qofpatah;05E7 05B7 +qofpatahhebrew;05E7 05B7 +qofqamats;05E7 05B8 +qofqamatshebrew;05E7 05B8 +qofqubuts;05E7 05BB +qofqubutshebrew;05E7 05BB +qofsegol;05E7 05B6 +qofsegolhebrew;05E7 05B6 +qofsheva;05E7 05B0 +qofshevahebrew;05E7 05B0 +qoftsere;05E7 05B5 +qoftserehebrew;05E7 05B5 +qparen;24AC +quarternote;2669 +qubuts;05BB +qubuts18;05BB +qubuts25;05BB +qubuts31;05BB +qubutshebrew;05BB +qubutsnarrowhebrew;05BB +qubutsquarterhebrew;05BB +qubutswidehebrew;05BB +question;003F +questionarabic;061F +questionarmenian;055E +questiondown;00BF +questiondownsmall;F7BF +questiongreek;037E +questionmonospace;FF1F +questionsmall;F73F +quotedbl;0022 +quotedblbase;201E +quotedblleft;201C +quotedblmonospace;FF02 +quotedblprime;301E +quotedblprimereversed;301D +quotedblright;201D +quoteleft;2018 +quoteleftreversed;201B +quotereversed;201B +quoteright;2019 +quoterightn;0149 +quotesinglbase;201A +quotesingle;0027 +quotesinglemonospace;FF07 +r;0072 +raarmenian;057C +rabengali;09B0 +racute;0155 +radeva;0930 +radical;221A +radicalex;F8E5 +radoverssquare;33AE +radoverssquaredsquare;33AF +radsquare;33AD +rafe;05BF +rafehebrew;05BF +ragujarati;0AB0 +ragurmukhi;0A30 +rahiragana;3089 +rakatakana;30E9 +rakatakanahalfwidth;FF97 +ralowerdiagonalbengali;09F1 +ramiddlediagonalbengali;09F0 +ramshorn;0264 +ratio;2236 +rbopomofo;3116 +rcaron;0159 +rcedilla;0157 +rcircle;24E1 +rcommaaccent;0157 +rdblgrave;0211 +rdotaccent;1E59 +rdotbelow;1E5B +rdotbelowmacron;1E5D +referencemark;203B +reflexsubset;2286 +reflexsuperset;2287 +registered;00AE +registersans;F8E8 +registerserif;F6DA +reharabic;0631 +reharmenian;0580 +rehfinalarabic;FEAE +rehiragana;308C +rehyehaleflamarabic;0631 FEF3 FE8E 0644 +rekatakana;30EC +rekatakanahalfwidth;FF9A +resh;05E8 +reshdageshhebrew;FB48 +reshhatafpatah;05E8 05B2 +reshhatafpatahhebrew;05E8 05B2 +reshhatafsegol;05E8 05B1 +reshhatafsegolhebrew;05E8 05B1 +reshhebrew;05E8 +reshhiriq;05E8 05B4 +reshhiriqhebrew;05E8 05B4 +reshholam;05E8 05B9 +reshholamhebrew;05E8 05B9 +reshpatah;05E8 05B7 +reshpatahhebrew;05E8 05B7 +reshqamats;05E8 05B8 +reshqamatshebrew;05E8 05B8 +reshqubuts;05E8 05BB +reshqubutshebrew;05E8 05BB +reshsegol;05E8 05B6 +reshsegolhebrew;05E8 05B6 +reshsheva;05E8 05B0 +reshshevahebrew;05E8 05B0 +reshtsere;05E8 05B5 +reshtserehebrew;05E8 05B5 +reversedtilde;223D +reviahebrew;0597 +reviamugrashhebrew;0597 +revlogicalnot;2310 +rfishhook;027E +rfishhookreversed;027F +rhabengali;09DD +rhadeva;095D +rho;03C1 +rhook;027D +rhookturned;027B +rhookturnedsuperior;02B5 +rhosymbolgreek;03F1 +rhotichookmod;02DE +rieulacirclekorean;3271 +rieulaparenkorean;3211 +rieulcirclekorean;3263 +rieulhieuhkorean;3140 +rieulkiyeokkorean;313A +rieulkiyeoksioskorean;3169 +rieulkorean;3139 +rieulmieumkorean;313B +rieulpansioskorean;316C +rieulparenkorean;3203 +rieulphieuphkorean;313F +rieulpieupkorean;313C +rieulpieupsioskorean;316B +rieulsioskorean;313D +rieulthieuthkorean;313E +rieultikeutkorean;316A +rieulyeorinhieuhkorean;316D +rightangle;221F +righttackbelowcmb;0319 +righttriangle;22BF +rihiragana;308A +rikatakana;30EA +rikatakanahalfwidth;FF98 +ring;02DA +ringbelowcmb;0325 +ringcmb;030A +ringhalfleft;02BF +ringhalfleftarmenian;0559 +ringhalfleftbelowcmb;031C +ringhalfleftcentered;02D3 +ringhalfright;02BE +ringhalfrightbelowcmb;0339 +ringhalfrightcentered;02D2 +rinvertedbreve;0213 +rittorusquare;3351 +rlinebelow;1E5F +rlongleg;027C +rlonglegturned;027A +rmonospace;FF52 +rohiragana;308D +rokatakana;30ED +rokatakanahalfwidth;FF9B +roruathai;0E23 +rparen;24AD +rrabengali;09DC +rradeva;0931 +rragurmukhi;0A5C +rreharabic;0691 +rrehfinalarabic;FB8D +rrvocalicbengali;09E0 +rrvocalicdeva;0960 +rrvocalicgujarati;0AE0 +rrvocalicvowelsignbengali;09C4 +rrvocalicvowelsigndeva;0944 +rrvocalicvowelsigngujarati;0AC4 +rsuperior;F6F1 +rtblock;2590 +rturned;0279 +rturnedsuperior;02B4 +ruhiragana;308B +rukatakana;30EB +rukatakanahalfwidth;FF99 +rupeemarkbengali;09F2 +rupeesignbengali;09F3 +rupiah;F6DD +ruthai;0E24 +rvocalicbengali;098B +rvocalicdeva;090B +rvocalicgujarati;0A8B +rvocalicvowelsignbengali;09C3 +rvocalicvowelsigndeva;0943 +rvocalicvowelsigngujarati;0AC3 +s;0073 +sabengali;09B8 +sacute;015B +sacutedotaccent;1E65 +sadarabic;0635 +sadeva;0938 +sadfinalarabic;FEBA +sadinitialarabic;FEBB +sadmedialarabic;FEBC +sagujarati;0AB8 +sagurmukhi;0A38 +sahiragana;3055 +sakatakana;30B5 +sakatakanahalfwidth;FF7B +sallallahoualayhewasallamarabic;FDFA +samekh;05E1 +samekhdagesh;FB41 +samekhdageshhebrew;FB41 +samekhhebrew;05E1 +saraaathai;0E32 +saraaethai;0E41 +saraaimaimalaithai;0E44 +saraaimaimuanthai;0E43 +saraamthai;0E33 +saraathai;0E30 +saraethai;0E40 +saraiileftthai;F886 +saraiithai;0E35 +saraileftthai;F885 +saraithai;0E34 +saraothai;0E42 +saraueeleftthai;F888 +saraueethai;0E37 +saraueleftthai;F887 +sarauethai;0E36 +sarauthai;0E38 +sarauuthai;0E39 +sbopomofo;3119 +scaron;0161 +scarondotaccent;1E67 +scedilla;015F +schwa;0259 +schwacyrillic;04D9 +schwadieresiscyrillic;04DB +schwahook;025A +scircle;24E2 +scircumflex;015D +scommaaccent;0219 +sdotaccent;1E61 +sdotbelow;1E63 +sdotbelowdotaccent;1E69 +seagullbelowcmb;033C +second;2033 +secondtonechinese;02CA +section;00A7 +seenarabic;0633 +seenfinalarabic;FEB2 +seeninitialarabic;FEB3 +seenmedialarabic;FEB4 +segol;05B6 +segol13;05B6 +segol1f;05B6 +segol2c;05B6 +segolhebrew;05B6 +segolnarrowhebrew;05B6 +segolquarterhebrew;05B6 +segoltahebrew;0592 +segolwidehebrew;05B6 +seharmenian;057D +sehiragana;305B +sekatakana;30BB +sekatakanahalfwidth;FF7E +semicolon;003B +semicolonarabic;061B +semicolonmonospace;FF1B +semicolonsmall;FE54 +semivoicedmarkkana;309C +semivoicedmarkkanahalfwidth;FF9F +sentisquare;3322 +sentosquare;3323 +seven;0037 +sevenarabic;0667 +sevenbengali;09ED +sevencircle;2466 +sevencircleinversesansserif;2790 +sevendeva;096D +seveneighths;215E +sevengujarati;0AED +sevengurmukhi;0A6D +sevenhackarabic;0667 +sevenhangzhou;3027 +sevenideographicparen;3226 +seveninferior;2087 +sevenmonospace;FF17 +sevenoldstyle;F737 +sevenparen;247A +sevenperiod;248E +sevenpersian;06F7 +sevenroman;2176 +sevensuperior;2077 +seventeencircle;2470 +seventeenparen;2484 +seventeenperiod;2498 +seventhai;0E57 +sfthyphen;00AD +shaarmenian;0577 +shabengali;09B6 +shacyrillic;0448 +shaddaarabic;0651 +shaddadammaarabic;FC61 +shaddadammatanarabic;FC5E +shaddafathaarabic;FC60 +shaddafathatanarabic;0651 064B +shaddakasraarabic;FC62 +shaddakasratanarabic;FC5F +shade;2592 +shadedark;2593 +shadelight;2591 +shademedium;2592 +shadeva;0936 +shagujarati;0AB6 +shagurmukhi;0A36 +shalshelethebrew;0593 +shbopomofo;3115 +shchacyrillic;0449 +sheenarabic;0634 +sheenfinalarabic;FEB6 +sheeninitialarabic;FEB7 +sheenmedialarabic;FEB8 +sheicoptic;03E3 +sheqel;20AA +sheqelhebrew;20AA +sheva;05B0 +sheva115;05B0 +sheva15;05B0 +sheva22;05B0 +sheva2e;05B0 +shevahebrew;05B0 +shevanarrowhebrew;05B0 +shevaquarterhebrew;05B0 +shevawidehebrew;05B0 +shhacyrillic;04BB +shimacoptic;03ED +shin;05E9 +shindagesh;FB49 +shindageshhebrew;FB49 +shindageshshindot;FB2C +shindageshshindothebrew;FB2C +shindageshsindot;FB2D +shindageshsindothebrew;FB2D +shindothebrew;05C1 +shinhebrew;05E9 +shinshindot;FB2A +shinshindothebrew;FB2A +shinsindot;FB2B +shinsindothebrew;FB2B +shook;0282 +sigma;03C3 +sigma1;03C2 +sigmafinal;03C2 +sigmalunatesymbolgreek;03F2 +sihiragana;3057 +sikatakana;30B7 +sikatakanahalfwidth;FF7C +siluqhebrew;05BD +siluqlefthebrew;05BD +similar;223C +sindothebrew;05C2 +siosacirclekorean;3274 +siosaparenkorean;3214 +sioscieuckorean;317E +sioscirclekorean;3266 +sioskiyeokkorean;317A +sioskorean;3145 +siosnieunkorean;317B +siosparenkorean;3206 +siospieupkorean;317D +siostikeutkorean;317C +six;0036 +sixarabic;0666 +sixbengali;09EC +sixcircle;2465 +sixcircleinversesansserif;278F +sixdeva;096C +sixgujarati;0AEC +sixgurmukhi;0A6C +sixhackarabic;0666 +sixhangzhou;3026 +sixideographicparen;3225 +sixinferior;2086 +sixmonospace;FF16 +sixoldstyle;F736 +sixparen;2479 +sixperiod;248D +sixpersian;06F6 +sixroman;2175 +sixsuperior;2076 +sixteencircle;246F +sixteencurrencydenominatorbengali;09F9 +sixteenparen;2483 +sixteenperiod;2497 +sixthai;0E56 +slash;002F +slashmonospace;FF0F +slong;017F +slongdotaccent;1E9B +smileface;263A +smonospace;FF53 +sofpasuqhebrew;05C3 +softhyphen;00AD +softsigncyrillic;044C +sohiragana;305D +sokatakana;30BD +sokatakanahalfwidth;FF7F +soliduslongoverlaycmb;0338 +solidusshortoverlaycmb;0337 +sorusithai;0E29 +sosalathai;0E28 +sosothai;0E0B +sosuathai;0E2A +space;0020 +spacehackarabic;0020 +spade;2660 +spadesuitblack;2660 +spadesuitwhite;2664 +sparen;24AE +squarebelowcmb;033B +squarecc;33C4 +squarecm;339D +squarediagonalcrosshatchfill;25A9 +squarehorizontalfill;25A4 +squarekg;338F +squarekm;339E +squarekmcapital;33CE +squareln;33D1 +squarelog;33D2 +squaremg;338E +squaremil;33D5 +squaremm;339C +squaremsquared;33A1 +squareorthogonalcrosshatchfill;25A6 +squareupperlefttolowerrightfill;25A7 +squareupperrighttolowerleftfill;25A8 +squareverticalfill;25A5 +squarewhitewithsmallblack;25A3 +srsquare;33DB +ssabengali;09B7 +ssadeva;0937 +ssagujarati;0AB7 +ssangcieuckorean;3149 +ssanghieuhkorean;3185 +ssangieungkorean;3180 +ssangkiyeokkorean;3132 +ssangnieunkorean;3165 +ssangpieupkorean;3143 +ssangsioskorean;3146 +ssangtikeutkorean;3138 +ssuperior;F6F2 +sterling;00A3 +sterlingmonospace;FFE1 +strokelongoverlaycmb;0336 +strokeshortoverlaycmb;0335 +subset;2282 +subsetnotequal;228A +subsetorequal;2286 +succeeds;227B +suchthat;220B +suhiragana;3059 +sukatakana;30B9 +sukatakanahalfwidth;FF7D +sukunarabic;0652 +summation;2211 +sun;263C +superset;2283 +supersetnotequal;228B +supersetorequal;2287 +svsquare;33DC +syouwaerasquare;337C +t;0074 +tabengali;09A4 +tackdown;22A4 +tackleft;22A3 +tadeva;0924 +tagujarati;0AA4 +tagurmukhi;0A24 +taharabic;0637 +tahfinalarabic;FEC2 +tahinitialarabic;FEC3 +tahiragana;305F +tahmedialarabic;FEC4 +taisyouerasquare;337D +takatakana;30BF +takatakanahalfwidth;FF80 +tatweelarabic;0640 +tau;03C4 +tav;05EA +tavdages;FB4A +tavdagesh;FB4A +tavdageshhebrew;FB4A +tavhebrew;05EA +tbar;0167 +tbopomofo;310A +tcaron;0165 +tccurl;02A8 +tcedilla;0163 +tcheharabic;0686 +tchehfinalarabic;FB7B +tchehinitialarabic;FB7C +tchehmedialarabic;FB7D +tchehmeeminitialarabic;FB7C FEE4 +tcircle;24E3 +tcircumflexbelow;1E71 +tcommaaccent;0163 +tdieresis;1E97 +tdotaccent;1E6B +tdotbelow;1E6D +tecyrillic;0442 +tedescendercyrillic;04AD +teharabic;062A +tehfinalarabic;FE96 +tehhahinitialarabic;FCA2 +tehhahisolatedarabic;FC0C +tehinitialarabic;FE97 +tehiragana;3066 +tehjeeminitialarabic;FCA1 +tehjeemisolatedarabic;FC0B +tehmarbutaarabic;0629 +tehmarbutafinalarabic;FE94 +tehmedialarabic;FE98 +tehmeeminitialarabic;FCA4 +tehmeemisolatedarabic;FC0E +tehnoonfinalarabic;FC73 +tekatakana;30C6 +tekatakanahalfwidth;FF83 +telephone;2121 +telephoneblack;260E +telishagedolahebrew;05A0 +telishaqetanahebrew;05A9 +tencircle;2469 +tenideographicparen;3229 +tenparen;247D +tenperiod;2491 +tenroman;2179 +tesh;02A7 +tet;05D8 +tetdagesh;FB38 +tetdageshhebrew;FB38 +tethebrew;05D8 +tetsecyrillic;04B5 +tevirhebrew;059B +tevirlefthebrew;059B +thabengali;09A5 +thadeva;0925 +thagujarati;0AA5 +thagurmukhi;0A25 +thalarabic;0630 +thalfinalarabic;FEAC +thanthakhatlowleftthai;F898 +thanthakhatlowrightthai;F897 +thanthakhatthai;0E4C +thanthakhatupperleftthai;F896 +theharabic;062B +thehfinalarabic;FE9A +thehinitialarabic;FE9B +thehmedialarabic;FE9C +thereexists;2203 +therefore;2234 +theta;03B8 +theta1;03D1 +thetasymbolgreek;03D1 +thieuthacirclekorean;3279 +thieuthaparenkorean;3219 +thieuthcirclekorean;326B +thieuthkorean;314C +thieuthparenkorean;320B +thirteencircle;246C +thirteenparen;2480 +thirteenperiod;2494 +thonangmonthothai;0E11 +thook;01AD +thophuthaothai;0E12 +thorn;00FE +thothahanthai;0E17 +thothanthai;0E10 +thothongthai;0E18 +thothungthai;0E16 +thousandcyrillic;0482 +thousandsseparatorarabic;066C +thousandsseparatorpersian;066C +three;0033 +threearabic;0663 +threebengali;09E9 +threecircle;2462 +threecircleinversesansserif;278C +threedeva;0969 +threeeighths;215C +threegujarati;0AE9 +threegurmukhi;0A69 +threehackarabic;0663 +threehangzhou;3023 +threeideographicparen;3222 +threeinferior;2083 +threemonospace;FF13 +threenumeratorbengali;09F6 +threeoldstyle;F733 +threeparen;2476 +threeperiod;248A +threepersian;06F3 +threequarters;00BE +threequartersemdash;F6DE +threeroman;2172 +threesuperior;00B3 +threethai;0E53 +thzsquare;3394 +tihiragana;3061 +tikatakana;30C1 +tikatakanahalfwidth;FF81 +tikeutacirclekorean;3270 +tikeutaparenkorean;3210 +tikeutcirclekorean;3262 +tikeutkorean;3137 +tikeutparenkorean;3202 +tilde;02DC +tildebelowcmb;0330 +tildecmb;0303 +tildecomb;0303 +tildedoublecmb;0360 +tildeoperator;223C +tildeoverlaycmb;0334 +tildeverticalcmb;033E +timescircle;2297 +tipehahebrew;0596 +tipehalefthebrew;0596 +tippigurmukhi;0A70 +titlocyrilliccmb;0483 +tiwnarmenian;057F +tlinebelow;1E6F +tmonospace;FF54 +toarmenian;0569 +tohiragana;3068 +tokatakana;30C8 +tokatakanahalfwidth;FF84 +tonebarextrahighmod;02E5 +tonebarextralowmod;02E9 +tonebarhighmod;02E6 +tonebarlowmod;02E8 +tonebarmidmod;02E7 +tonefive;01BD +tonesix;0185 +tonetwo;01A8 +tonos;0384 +tonsquare;3327 +topatakthai;0E0F +tortoiseshellbracketleft;3014 +tortoiseshellbracketleftsmall;FE5D +tortoiseshellbracketleftvertical;FE39 +tortoiseshellbracketright;3015 +tortoiseshellbracketrightsmall;FE5E +tortoiseshellbracketrightvertical;FE3A +totaothai;0E15 +tpalatalhook;01AB +tparen;24AF +trademark;2122 +trademarksans;F8EA +trademarkserif;F6DB +tretroflexhook;0288 +triagdn;25BC +triaglf;25C4 +triagrt;25BA +triagup;25B2 +ts;02A6 +tsadi;05E6 +tsadidagesh;FB46 +tsadidageshhebrew;FB46 +tsadihebrew;05E6 +tsecyrillic;0446 +tsere;05B5 +tsere12;05B5 +tsere1e;05B5 +tsere2b;05B5 +tserehebrew;05B5 +tserenarrowhebrew;05B5 +tserequarterhebrew;05B5 +tserewidehebrew;05B5 +tshecyrillic;045B +tsuperior;F6F3 +ttabengali;099F +ttadeva;091F +ttagujarati;0A9F +ttagurmukhi;0A1F +tteharabic;0679 +ttehfinalarabic;FB67 +ttehinitialarabic;FB68 +ttehmedialarabic;FB69 +tthabengali;09A0 +tthadeva;0920 +tthagujarati;0AA0 +tthagurmukhi;0A20 +tturned;0287 +tuhiragana;3064 +tukatakana;30C4 +tukatakanahalfwidth;FF82 +tusmallhiragana;3063 +tusmallkatakana;30C3 +tusmallkatakanahalfwidth;FF6F +twelvecircle;246B +twelveparen;247F +twelveperiod;2493 +twelveroman;217B +twentycircle;2473 +twentyhangzhou;5344 +twentyparen;2487 +twentyperiod;249B +two;0032 +twoarabic;0662 +twobengali;09E8 +twocircle;2461 +twocircleinversesansserif;278B +twodeva;0968 +twodotenleader;2025 +twodotleader;2025 +twodotleadervertical;FE30 +twogujarati;0AE8 +twogurmukhi;0A68 +twohackarabic;0662 +twohangzhou;3022 +twoideographicparen;3221 +twoinferior;2082 +twomonospace;FF12 +twonumeratorbengali;09F5 +twooldstyle;F732 +twoparen;2475 +twoperiod;2489 +twopersian;06F2 +tworoman;2171 +twostroke;01BB +twosuperior;00B2 +twothai;0E52 +twothirds;2154 +u;0075 +uacute;00FA +ubar;0289 +ubengali;0989 +ubopomofo;3128 +ubreve;016D +ucaron;01D4 +ucircle;24E4 +ucircumflex;00FB +ucircumflexbelow;1E77 +ucyrillic;0443 +udattadeva;0951 +udblacute;0171 +udblgrave;0215 +udeva;0909 +udieresis;00FC +udieresisacute;01D8 +udieresisbelow;1E73 +udieresiscaron;01DA +udieresiscyrillic;04F1 +udieresisgrave;01DC +udieresismacron;01D6 +udotbelow;1EE5 +ugrave;00F9 +ugujarati;0A89 +ugurmukhi;0A09 +uhiragana;3046 +uhookabove;1EE7 +uhorn;01B0 +uhornacute;1EE9 +uhorndotbelow;1EF1 +uhorngrave;1EEB +uhornhookabove;1EED +uhorntilde;1EEF +uhungarumlaut;0171 +uhungarumlautcyrillic;04F3 +uinvertedbreve;0217 +ukatakana;30A6 +ukatakanahalfwidth;FF73 +ukcyrillic;0479 +ukorean;315C +umacron;016B +umacroncyrillic;04EF +umacrondieresis;1E7B +umatragurmukhi;0A41 +umonospace;FF55 +underscore;005F +underscoredbl;2017 +underscoremonospace;FF3F +underscorevertical;FE33 +underscorewavy;FE4F +union;222A +universal;2200 +uogonek;0173 +uparen;24B0 +upblock;2580 +upperdothebrew;05C4 +upsilon;03C5 +upsilondieresis;03CB +upsilondieresistonos;03B0 +upsilonlatin;028A +upsilontonos;03CD +uptackbelowcmb;031D +uptackmod;02D4 +uragurmukhi;0A73 +uring;016F +ushortcyrillic;045E +usmallhiragana;3045 +usmallkatakana;30A5 +usmallkatakanahalfwidth;FF69 +ustraightcyrillic;04AF +ustraightstrokecyrillic;04B1 +utilde;0169 +utildeacute;1E79 +utildebelow;1E75 +uubengali;098A +uudeva;090A +uugujarati;0A8A +uugurmukhi;0A0A +uumatragurmukhi;0A42 +uuvowelsignbengali;09C2 +uuvowelsigndeva;0942 +uuvowelsigngujarati;0AC2 +uvowelsignbengali;09C1 +uvowelsigndeva;0941 +uvowelsigngujarati;0AC1 +v;0076 +vadeva;0935 +vagujarati;0AB5 +vagurmukhi;0A35 +vakatakana;30F7 +vav;05D5 +vavdagesh;FB35 +vavdagesh65;FB35 +vavdageshhebrew;FB35 +vavhebrew;05D5 +vavholam;FB4B +vavholamhebrew;FB4B +vavvavhebrew;05F0 +vavyodhebrew;05F1 +vcircle;24E5 +vdotbelow;1E7F +vecyrillic;0432 +veharabic;06A4 +vehfinalarabic;FB6B +vehinitialarabic;FB6C +vehmedialarabic;FB6D +vekatakana;30F9 +venus;2640 +verticalbar;007C +verticallineabovecmb;030D +verticallinebelowcmb;0329 +verticallinelowmod;02CC +verticallinemod;02C8 +vewarmenian;057E +vhook;028B +vikatakana;30F8 +viramabengali;09CD +viramadeva;094D +viramagujarati;0ACD +visargabengali;0983 +visargadeva;0903 +visargagujarati;0A83 +vmonospace;FF56 +voarmenian;0578 +voicediterationhiragana;309E +voicediterationkatakana;30FE +voicedmarkkana;309B +voicedmarkkanahalfwidth;FF9E +vokatakana;30FA +vparen;24B1 +vtilde;1E7D +vturned;028C +vuhiragana;3094 +vukatakana;30F4 +w;0077 +wacute;1E83 +waekorean;3159 +wahiragana;308F +wakatakana;30EF +wakatakanahalfwidth;FF9C +wakorean;3158 +wasmallhiragana;308E +wasmallkatakana;30EE +wattosquare;3357 +wavedash;301C +wavyunderscorevertical;FE34 +wawarabic;0648 +wawfinalarabic;FEEE +wawhamzaabovearabic;0624 +wawhamzaabovefinalarabic;FE86 +wbsquare;33DD +wcircle;24E6 +wcircumflex;0175 +wdieresis;1E85 +wdotaccent;1E87 +wdotbelow;1E89 +wehiragana;3091 +weierstrass;2118 +wekatakana;30F1 +wekorean;315E +weokorean;315D +wgrave;1E81 +whitebullet;25E6 +whitecircle;25CB +whitecircleinverse;25D9 +whitecornerbracketleft;300E +whitecornerbracketleftvertical;FE43 +whitecornerbracketright;300F +whitecornerbracketrightvertical;FE44 +whitediamond;25C7 +whitediamondcontainingblacksmalldiamond;25C8 +whitedownpointingsmalltriangle;25BF +whitedownpointingtriangle;25BD +whiteleftpointingsmalltriangle;25C3 +whiteleftpointingtriangle;25C1 +whitelenticularbracketleft;3016 +whitelenticularbracketright;3017 +whiterightpointingsmalltriangle;25B9 +whiterightpointingtriangle;25B7 +whitesmallsquare;25AB +whitesmilingface;263A +whitesquare;25A1 +whitestar;2606 +whitetelephone;260F +whitetortoiseshellbracketleft;3018 +whitetortoiseshellbracketright;3019 +whiteuppointingsmalltriangle;25B5 +whiteuppointingtriangle;25B3 +wihiragana;3090 +wikatakana;30F0 +wikorean;315F +wmonospace;FF57 +wohiragana;3092 +wokatakana;30F2 +wokatakanahalfwidth;FF66 +won;20A9 +wonmonospace;FFE6 +wowaenthai;0E27 +wparen;24B2 +wring;1E98 +wsuperior;02B7 +wturned;028D +wynn;01BF +x;0078 +xabovecmb;033D +xbopomofo;3112 +xcircle;24E7 +xdieresis;1E8D +xdotaccent;1E8B +xeharmenian;056D +xi;03BE +xmonospace;FF58 +xparen;24B3 +xsuperior;02E3 +y;0079 +yaadosquare;334E +yabengali;09AF +yacute;00FD +yadeva;092F +yaekorean;3152 +yagujarati;0AAF +yagurmukhi;0A2F +yahiragana;3084 +yakatakana;30E4 +yakatakanahalfwidth;FF94 +yakorean;3151 +yamakkanthai;0E4E +yasmallhiragana;3083 +yasmallkatakana;30E3 +yasmallkatakanahalfwidth;FF6C +yatcyrillic;0463 +ycircle;24E8 +ycircumflex;0177 +ydieresis;00FF +ydotaccent;1E8F +ydotbelow;1EF5 +yeharabic;064A +yehbarreearabic;06D2 +yehbarreefinalarabic;FBAF +yehfinalarabic;FEF2 +yehhamzaabovearabic;0626 +yehhamzaabovefinalarabic;FE8A +yehhamzaaboveinitialarabic;FE8B +yehhamzaabovemedialarabic;FE8C +yehinitialarabic;FEF3 +yehmedialarabic;FEF4 +yehmeeminitialarabic;FCDD +yehmeemisolatedarabic;FC58 +yehnoonfinalarabic;FC94 +yehthreedotsbelowarabic;06D1 +yekorean;3156 +yen;00A5 +yenmonospace;FFE5 +yeokorean;3155 +yeorinhieuhkorean;3186 +yerahbenyomohebrew;05AA +yerahbenyomolefthebrew;05AA +yericyrillic;044B +yerudieresiscyrillic;04F9 +yesieungkorean;3181 +yesieungpansioskorean;3183 +yesieungsioskorean;3182 +yetivhebrew;059A +ygrave;1EF3 +yhook;01B4 +yhookabove;1EF7 +yiarmenian;0575 +yicyrillic;0457 +yikorean;3162 +yinyang;262F +yiwnarmenian;0582 +ymonospace;FF59 +yod;05D9 +yoddagesh;FB39 +yoddageshhebrew;FB39 +yodhebrew;05D9 +yodyodhebrew;05F2 +yodyodpatahhebrew;FB1F +yohiragana;3088 +yoikorean;3189 +yokatakana;30E8 +yokatakanahalfwidth;FF96 +yokorean;315B +yosmallhiragana;3087 +yosmallkatakana;30E7 +yosmallkatakanahalfwidth;FF6E +yotgreek;03F3 +yoyaekorean;3188 +yoyakorean;3187 +yoyakthai;0E22 +yoyingthai;0E0D +yparen;24B4 +ypogegrammeni;037A +ypogegrammenigreekcmb;0345 +yr;01A6 +yring;1E99 +ysuperior;02B8 +ytilde;1EF9 +yturned;028E +yuhiragana;3086 +yuikorean;318C +yukatakana;30E6 +yukatakanahalfwidth;FF95 +yukorean;3160 +yusbigcyrillic;046B +yusbigiotifiedcyrillic;046D +yuslittlecyrillic;0467 +yuslittleiotifiedcyrillic;0469 +yusmallhiragana;3085 +yusmallkatakana;30E5 +yusmallkatakanahalfwidth;FF6D +yuyekorean;318B +yuyeokorean;318A +yyabengali;09DF +yyadeva;095F +z;007A +zaarmenian;0566 +zacute;017A +zadeva;095B +zagurmukhi;0A5B +zaharabic;0638 +zahfinalarabic;FEC6 +zahinitialarabic;FEC7 +zahiragana;3056 +zahmedialarabic;FEC8 +zainarabic;0632 +zainfinalarabic;FEB0 +zakatakana;30B6 +zaqefgadolhebrew;0595 +zaqefqatanhebrew;0594 +zarqahebrew;0598 +zayin;05D6 +zayindagesh;FB36 +zayindageshhebrew;FB36 +zayinhebrew;05D6 +zbopomofo;3117 +zcaron;017E +zcircle;24E9 +zcircumflex;1E91 +zcurl;0291 +zdot;017C +zdotaccent;017C +zdotbelow;1E93 +zecyrillic;0437 +zedescendercyrillic;0499 +zedieresiscyrillic;04DF +zehiragana;305C +zekatakana;30BC +zero;0030 +zeroarabic;0660 +zerobengali;09E6 +zerodeva;0966 +zerogujarati;0AE6 +zerogurmukhi;0A66 +zerohackarabic;0660 +zeroinferior;2080 +zeromonospace;FF10 +zerooldstyle;F730 +zeropersian;06F0 +zerosuperior;2070 +zerothai;0E50 +zerowidthjoiner;FEFF +zerowidthnonjoiner;200C +zerowidthspace;200B +zeta;03B6 +zhbopomofo;3113 +zhearmenian;056A +zhebrevecyrillic;04C2 +zhecyrillic;0436 +zhedescendercyrillic;0497 +zhedieresiscyrillic;04DD +zihiragana;3058 +zikatakana;30B8 +zinorhebrew;05AE +zlinebelow;1E95 +zmonospace;FF5A +zohiragana;305E +zokatakana;30BE +zparen;24B5 +zretroflexhook;0290 +zstroke;01B6 +zuhiragana;305A +zukatakana;30BA +# END +""" + + +_aglfnText = """\ +# ----------------------------------------------------------- +# Copyright 2002-2019 Adobe (http://www.adobe.com/). +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the +# following conditions are met: +# +# Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# Neither the name of Adobe nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------- +# Name: Adobe Glyph List For New Fonts +# Table version: 1.7 +# Date: November 6, 2008 +# URL: https://github.com/adobe-type-tools/agl-aglfn +# +# Description: +# +# AGLFN (Adobe Glyph List For New Fonts) provides a list of base glyph +# names that are recommended for new fonts, which are compatible with +# the AGL (Adobe Glyph List) Specification, and which should be used +# as described in Section 6 of that document. AGLFN comprises the set +# of glyph names from AGL that map via the AGL Specification rules to +# the semantically correct UV (Unicode Value). For example, "Asmall" +# is omitted because AGL maps this glyph name to the PUA (Private Use +# Area) value U+F761, rather than to the UV that maps from the glyph +# name "A." Also omitted is "ffi," because AGL maps this to the +# Alphabetic Presentation Forms value U+FB03, rather than decomposing +# it into the following sequence of three UVs: U+0066, U+0066, and +# U+0069. The name "arrowvertex" has been omitted because this glyph +# now has a real UV, and AGL is now incorrect in mapping it to the PUA +# value U+F8E6. If you do not find an appropriate name for your glyph +# in this list, then please refer to Section 6 of the AGL +# Specification. +# +# Format: three semicolon-delimited fields: +# (1) Standard UV or CUS UV--four uppercase hexadecimal digits +# (2) Glyph name--upper/lowercase letters and digits +# (3) Character names: Unicode character names for standard UVs, and +# descriptive names for CUS UVs--uppercase letters, hyphen, and +# space +# +# The records are sorted by glyph name in increasing ASCII order, +# entries with the same glyph name are sorted in decreasing priority +# order, the UVs and Unicode character names are provided for +# convenience, lines starting with "#" are comments, and blank lines +# should be ignored. +# +# Revision History: +# +# 1.7 [6 November 2008] +# - Reverted to the original 1.4 and earlier mappings for Delta, +# Omega, and mu. +# - Removed mappings for "afii" names. These should now be assigned +# "uni" names. +# - Removed mappings for "commaaccent" names. These should now be +# assigned "uni" names. +# +# 1.6 [30 January 2006] +# - Completed work intended in 1.5. +# +# 1.5 [23 November 2005] +# - Removed duplicated block at end of file. +# - Changed mappings: +# 2206;Delta;INCREMENT changed to 0394;Delta;GREEK CAPITAL LETTER DELTA +# 2126;Omega;OHM SIGN changed to 03A9;Omega;GREEK CAPITAL LETTER OMEGA +# 03BC;mu;MICRO SIGN changed to 03BC;mu;GREEK SMALL LETTER MU +# - Corrected statement above about why "ffi" is omitted. +# +# 1.4 [24 September 2003] +# - Changed version to 1.4, to avoid confusion with the AGL 1.3. +# - Fixed spelling errors in the header. +# - Fully removed "arrowvertex," as it is mapped only to a PUA Unicode +# value in some fonts. +# +# 1.1 [17 April 2003] +# - Renamed [Tt]cedilla back to [Tt]commaaccent. +# +# 1.0 [31 January 2003] +# - Original version. +# - Derived from the AGLv1.2 by: +# removing the PUA area codes; +# removing duplicate Unicode mappings; and +# renaming "tcommaaccent" to "tcedilla" and "Tcommaaccent" to "Tcedilla" +# +0041;A;LATIN CAPITAL LETTER A +00C6;AE;LATIN CAPITAL LETTER AE +01FC;AEacute;LATIN CAPITAL LETTER AE WITH ACUTE +00C1;Aacute;LATIN CAPITAL LETTER A WITH ACUTE +0102;Abreve;LATIN CAPITAL LETTER A WITH BREVE +00C2;Acircumflex;LATIN CAPITAL LETTER A WITH CIRCUMFLEX +00C4;Adieresis;LATIN CAPITAL LETTER A WITH DIAERESIS +00C0;Agrave;LATIN CAPITAL LETTER A WITH GRAVE +0391;Alpha;GREEK CAPITAL LETTER ALPHA +0386;Alphatonos;GREEK CAPITAL LETTER ALPHA WITH TONOS +0100;Amacron;LATIN CAPITAL LETTER A WITH MACRON +0104;Aogonek;LATIN CAPITAL LETTER A WITH OGONEK +00C5;Aring;LATIN CAPITAL LETTER A WITH RING ABOVE +01FA;Aringacute;LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +00C3;Atilde;LATIN CAPITAL LETTER A WITH TILDE +0042;B;LATIN CAPITAL LETTER B +0392;Beta;GREEK CAPITAL LETTER BETA +0043;C;LATIN CAPITAL LETTER C +0106;Cacute;LATIN CAPITAL LETTER C WITH ACUTE +010C;Ccaron;LATIN CAPITAL LETTER C WITH CARON +00C7;Ccedilla;LATIN CAPITAL LETTER C WITH CEDILLA +0108;Ccircumflex;LATIN CAPITAL LETTER C WITH CIRCUMFLEX +010A;Cdotaccent;LATIN CAPITAL LETTER C WITH DOT ABOVE +03A7;Chi;GREEK CAPITAL LETTER CHI +0044;D;LATIN CAPITAL LETTER D +010E;Dcaron;LATIN CAPITAL LETTER D WITH CARON +0110;Dcroat;LATIN CAPITAL LETTER D WITH STROKE +2206;Delta;INCREMENT +0045;E;LATIN CAPITAL LETTER E +00C9;Eacute;LATIN CAPITAL LETTER E WITH ACUTE +0114;Ebreve;LATIN CAPITAL LETTER E WITH BREVE +011A;Ecaron;LATIN CAPITAL LETTER E WITH CARON +00CA;Ecircumflex;LATIN CAPITAL LETTER E WITH CIRCUMFLEX +00CB;Edieresis;LATIN CAPITAL LETTER E WITH DIAERESIS +0116;Edotaccent;LATIN CAPITAL LETTER E WITH DOT ABOVE +00C8;Egrave;LATIN CAPITAL LETTER E WITH GRAVE +0112;Emacron;LATIN CAPITAL LETTER E WITH MACRON +014A;Eng;LATIN CAPITAL LETTER ENG +0118;Eogonek;LATIN CAPITAL LETTER E WITH OGONEK +0395;Epsilon;GREEK CAPITAL LETTER EPSILON +0388;Epsilontonos;GREEK CAPITAL LETTER EPSILON WITH TONOS +0397;Eta;GREEK CAPITAL LETTER ETA +0389;Etatonos;GREEK CAPITAL LETTER ETA WITH TONOS +00D0;Eth;LATIN CAPITAL LETTER ETH +20AC;Euro;EURO SIGN +0046;F;LATIN CAPITAL LETTER F +0047;G;LATIN CAPITAL LETTER G +0393;Gamma;GREEK CAPITAL LETTER GAMMA +011E;Gbreve;LATIN CAPITAL LETTER G WITH BREVE +01E6;Gcaron;LATIN CAPITAL LETTER G WITH CARON +011C;Gcircumflex;LATIN CAPITAL LETTER G WITH CIRCUMFLEX +0120;Gdotaccent;LATIN CAPITAL LETTER G WITH DOT ABOVE +0048;H;LATIN CAPITAL LETTER H +25CF;H18533;BLACK CIRCLE +25AA;H18543;BLACK SMALL SQUARE +25AB;H18551;WHITE SMALL SQUARE +25A1;H22073;WHITE SQUARE +0126;Hbar;LATIN CAPITAL LETTER H WITH STROKE +0124;Hcircumflex;LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0049;I;LATIN CAPITAL LETTER I +0132;IJ;LATIN CAPITAL LIGATURE IJ +00CD;Iacute;LATIN CAPITAL LETTER I WITH ACUTE +012C;Ibreve;LATIN CAPITAL LETTER I WITH BREVE +00CE;Icircumflex;LATIN CAPITAL LETTER I WITH CIRCUMFLEX +00CF;Idieresis;LATIN CAPITAL LETTER I WITH DIAERESIS +0130;Idotaccent;LATIN CAPITAL LETTER I WITH DOT ABOVE +2111;Ifraktur;BLACK-LETTER CAPITAL I +00CC;Igrave;LATIN CAPITAL LETTER I WITH GRAVE +012A;Imacron;LATIN CAPITAL LETTER I WITH MACRON +012E;Iogonek;LATIN CAPITAL LETTER I WITH OGONEK +0399;Iota;GREEK CAPITAL LETTER IOTA +03AA;Iotadieresis;GREEK CAPITAL LETTER IOTA WITH DIALYTIKA +038A;Iotatonos;GREEK CAPITAL LETTER IOTA WITH TONOS +0128;Itilde;LATIN CAPITAL LETTER I WITH TILDE +004A;J;LATIN CAPITAL LETTER J +0134;Jcircumflex;LATIN CAPITAL LETTER J WITH CIRCUMFLEX +004B;K;LATIN CAPITAL LETTER K +039A;Kappa;GREEK CAPITAL LETTER KAPPA +004C;L;LATIN CAPITAL LETTER L +0139;Lacute;LATIN CAPITAL LETTER L WITH ACUTE +039B;Lambda;GREEK CAPITAL LETTER LAMDA +013D;Lcaron;LATIN CAPITAL LETTER L WITH CARON +013F;Ldot;LATIN CAPITAL LETTER L WITH MIDDLE DOT +0141;Lslash;LATIN CAPITAL LETTER L WITH STROKE +004D;M;LATIN CAPITAL LETTER M +039C;Mu;GREEK CAPITAL LETTER MU +004E;N;LATIN CAPITAL LETTER N +0143;Nacute;LATIN CAPITAL LETTER N WITH ACUTE +0147;Ncaron;LATIN CAPITAL LETTER N WITH CARON +00D1;Ntilde;LATIN CAPITAL LETTER N WITH TILDE +039D;Nu;GREEK CAPITAL LETTER NU +004F;O;LATIN CAPITAL LETTER O +0152;OE;LATIN CAPITAL LIGATURE OE +00D3;Oacute;LATIN CAPITAL LETTER O WITH ACUTE +014E;Obreve;LATIN CAPITAL LETTER O WITH BREVE +00D4;Ocircumflex;LATIN CAPITAL LETTER O WITH CIRCUMFLEX +00D6;Odieresis;LATIN CAPITAL LETTER O WITH DIAERESIS +00D2;Ograve;LATIN CAPITAL LETTER O WITH GRAVE +01A0;Ohorn;LATIN CAPITAL LETTER O WITH HORN +0150;Ohungarumlaut;LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +014C;Omacron;LATIN CAPITAL LETTER O WITH MACRON +2126;Omega;OHM SIGN +038F;Omegatonos;GREEK CAPITAL LETTER OMEGA WITH TONOS +039F;Omicron;GREEK CAPITAL LETTER OMICRON +038C;Omicrontonos;GREEK CAPITAL LETTER OMICRON WITH TONOS +00D8;Oslash;LATIN CAPITAL LETTER O WITH STROKE +01FE;Oslashacute;LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +00D5;Otilde;LATIN CAPITAL LETTER O WITH TILDE +0050;P;LATIN CAPITAL LETTER P +03A6;Phi;GREEK CAPITAL LETTER PHI +03A0;Pi;GREEK CAPITAL LETTER PI +03A8;Psi;GREEK CAPITAL LETTER PSI +0051;Q;LATIN CAPITAL LETTER Q +0052;R;LATIN CAPITAL LETTER R +0154;Racute;LATIN CAPITAL LETTER R WITH ACUTE +0158;Rcaron;LATIN CAPITAL LETTER R WITH CARON +211C;Rfraktur;BLACK-LETTER CAPITAL R +03A1;Rho;GREEK CAPITAL LETTER RHO +0053;S;LATIN CAPITAL LETTER S +250C;SF010000;BOX DRAWINGS LIGHT DOWN AND RIGHT +2514;SF020000;BOX DRAWINGS LIGHT UP AND RIGHT +2510;SF030000;BOX DRAWINGS LIGHT DOWN AND LEFT +2518;SF040000;BOX DRAWINGS LIGHT UP AND LEFT +253C;SF050000;BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL +252C;SF060000;BOX DRAWINGS LIGHT DOWN AND HORIZONTAL +2534;SF070000;BOX DRAWINGS LIGHT UP AND HORIZONTAL +251C;SF080000;BOX DRAWINGS LIGHT VERTICAL AND RIGHT +2524;SF090000;BOX DRAWINGS LIGHT VERTICAL AND LEFT +2500;SF100000;BOX DRAWINGS LIGHT HORIZONTAL +2502;SF110000;BOX DRAWINGS LIGHT VERTICAL +2561;SF190000;BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE +2562;SF200000;BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE +2556;SF210000;BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE +2555;SF220000;BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE +2563;SF230000;BOX DRAWINGS DOUBLE VERTICAL AND LEFT +2551;SF240000;BOX DRAWINGS DOUBLE VERTICAL +2557;SF250000;BOX DRAWINGS DOUBLE DOWN AND LEFT +255D;SF260000;BOX DRAWINGS DOUBLE UP AND LEFT +255C;SF270000;BOX DRAWINGS UP DOUBLE AND LEFT SINGLE +255B;SF280000;BOX DRAWINGS UP SINGLE AND LEFT DOUBLE +255E;SF360000;BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE +255F;SF370000;BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE +255A;SF380000;BOX DRAWINGS DOUBLE UP AND RIGHT +2554;SF390000;BOX DRAWINGS DOUBLE DOWN AND RIGHT +2569;SF400000;BOX DRAWINGS DOUBLE UP AND HORIZONTAL +2566;SF410000;BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL +2560;SF420000;BOX DRAWINGS DOUBLE VERTICAL AND RIGHT +2550;SF430000;BOX DRAWINGS DOUBLE HORIZONTAL +256C;SF440000;BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL +2567;SF450000;BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE +2568;SF460000;BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE +2564;SF470000;BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE +2565;SF480000;BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE +2559;SF490000;BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE +2558;SF500000;BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE +2552;SF510000;BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE +2553;SF520000;BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE +256B;SF530000;BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE +256A;SF540000;BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE +015A;Sacute;LATIN CAPITAL LETTER S WITH ACUTE +0160;Scaron;LATIN CAPITAL LETTER S WITH CARON +015E;Scedilla;LATIN CAPITAL LETTER S WITH CEDILLA +015C;Scircumflex;LATIN CAPITAL LETTER S WITH CIRCUMFLEX +03A3;Sigma;GREEK CAPITAL LETTER SIGMA +0054;T;LATIN CAPITAL LETTER T +03A4;Tau;GREEK CAPITAL LETTER TAU +0166;Tbar;LATIN CAPITAL LETTER T WITH STROKE +0164;Tcaron;LATIN CAPITAL LETTER T WITH CARON +0398;Theta;GREEK CAPITAL LETTER THETA +00DE;Thorn;LATIN CAPITAL LETTER THORN +0055;U;LATIN CAPITAL LETTER U +00DA;Uacute;LATIN CAPITAL LETTER U WITH ACUTE +016C;Ubreve;LATIN CAPITAL LETTER U WITH BREVE +00DB;Ucircumflex;LATIN CAPITAL LETTER U WITH CIRCUMFLEX +00DC;Udieresis;LATIN CAPITAL LETTER U WITH DIAERESIS +00D9;Ugrave;LATIN CAPITAL LETTER U WITH GRAVE +01AF;Uhorn;LATIN CAPITAL LETTER U WITH HORN +0170;Uhungarumlaut;LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +016A;Umacron;LATIN CAPITAL LETTER U WITH MACRON +0172;Uogonek;LATIN CAPITAL LETTER U WITH OGONEK +03A5;Upsilon;GREEK CAPITAL LETTER UPSILON +03D2;Upsilon1;GREEK UPSILON WITH HOOK SYMBOL +03AB;Upsilondieresis;GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +038E;Upsilontonos;GREEK CAPITAL LETTER UPSILON WITH TONOS +016E;Uring;LATIN CAPITAL LETTER U WITH RING ABOVE +0168;Utilde;LATIN CAPITAL LETTER U WITH TILDE +0056;V;LATIN CAPITAL LETTER V +0057;W;LATIN CAPITAL LETTER W +1E82;Wacute;LATIN CAPITAL LETTER W WITH ACUTE +0174;Wcircumflex;LATIN CAPITAL LETTER W WITH CIRCUMFLEX +1E84;Wdieresis;LATIN CAPITAL LETTER W WITH DIAERESIS +1E80;Wgrave;LATIN CAPITAL LETTER W WITH GRAVE +0058;X;LATIN CAPITAL LETTER X +039E;Xi;GREEK CAPITAL LETTER XI +0059;Y;LATIN CAPITAL LETTER Y +00DD;Yacute;LATIN CAPITAL LETTER Y WITH ACUTE +0176;Ycircumflex;LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0178;Ydieresis;LATIN CAPITAL LETTER Y WITH DIAERESIS +1EF2;Ygrave;LATIN CAPITAL LETTER Y WITH GRAVE +005A;Z;LATIN CAPITAL LETTER Z +0179;Zacute;LATIN CAPITAL LETTER Z WITH ACUTE +017D;Zcaron;LATIN CAPITAL LETTER Z WITH CARON +017B;Zdotaccent;LATIN CAPITAL LETTER Z WITH DOT ABOVE +0396;Zeta;GREEK CAPITAL LETTER ZETA +0061;a;LATIN SMALL LETTER A +00E1;aacute;LATIN SMALL LETTER A WITH ACUTE +0103;abreve;LATIN SMALL LETTER A WITH BREVE +00E2;acircumflex;LATIN SMALL LETTER A WITH CIRCUMFLEX +00B4;acute;ACUTE ACCENT +0301;acutecomb;COMBINING ACUTE ACCENT +00E4;adieresis;LATIN SMALL LETTER A WITH DIAERESIS +00E6;ae;LATIN SMALL LETTER AE +01FD;aeacute;LATIN SMALL LETTER AE WITH ACUTE +00E0;agrave;LATIN SMALL LETTER A WITH GRAVE +2135;aleph;ALEF SYMBOL +03B1;alpha;GREEK SMALL LETTER ALPHA +03AC;alphatonos;GREEK SMALL LETTER ALPHA WITH TONOS +0101;amacron;LATIN SMALL LETTER A WITH MACRON +0026;ampersand;AMPERSAND +2220;angle;ANGLE +2329;angleleft;LEFT-POINTING ANGLE BRACKET +232A;angleright;RIGHT-POINTING ANGLE BRACKET +0387;anoteleia;GREEK ANO TELEIA +0105;aogonek;LATIN SMALL LETTER A WITH OGONEK +2248;approxequal;ALMOST EQUAL TO +00E5;aring;LATIN SMALL LETTER A WITH RING ABOVE +01FB;aringacute;LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +2194;arrowboth;LEFT RIGHT ARROW +21D4;arrowdblboth;LEFT RIGHT DOUBLE ARROW +21D3;arrowdbldown;DOWNWARDS DOUBLE ARROW +21D0;arrowdblleft;LEFTWARDS DOUBLE ARROW +21D2;arrowdblright;RIGHTWARDS DOUBLE ARROW +21D1;arrowdblup;UPWARDS DOUBLE ARROW +2193;arrowdown;DOWNWARDS ARROW +2190;arrowleft;LEFTWARDS ARROW +2192;arrowright;RIGHTWARDS ARROW +2191;arrowup;UPWARDS ARROW +2195;arrowupdn;UP DOWN ARROW +21A8;arrowupdnbse;UP DOWN ARROW WITH BASE +005E;asciicircum;CIRCUMFLEX ACCENT +007E;asciitilde;TILDE +002A;asterisk;ASTERISK +2217;asteriskmath;ASTERISK OPERATOR +0040;at;COMMERCIAL AT +00E3;atilde;LATIN SMALL LETTER A WITH TILDE +0062;b;LATIN SMALL LETTER B +005C;backslash;REVERSE SOLIDUS +007C;bar;VERTICAL LINE +03B2;beta;GREEK SMALL LETTER BETA +2588;block;FULL BLOCK +007B;braceleft;LEFT CURLY BRACKET +007D;braceright;RIGHT CURLY BRACKET +005B;bracketleft;LEFT SQUARE BRACKET +005D;bracketright;RIGHT SQUARE BRACKET +02D8;breve;BREVE +00A6;brokenbar;BROKEN BAR +2022;bullet;BULLET +0063;c;LATIN SMALL LETTER C +0107;cacute;LATIN SMALL LETTER C WITH ACUTE +02C7;caron;CARON +21B5;carriagereturn;DOWNWARDS ARROW WITH CORNER LEFTWARDS +010D;ccaron;LATIN SMALL LETTER C WITH CARON +00E7;ccedilla;LATIN SMALL LETTER C WITH CEDILLA +0109;ccircumflex;LATIN SMALL LETTER C WITH CIRCUMFLEX +010B;cdotaccent;LATIN SMALL LETTER C WITH DOT ABOVE +00B8;cedilla;CEDILLA +00A2;cent;CENT SIGN +03C7;chi;GREEK SMALL LETTER CHI +25CB;circle;WHITE CIRCLE +2297;circlemultiply;CIRCLED TIMES +2295;circleplus;CIRCLED PLUS +02C6;circumflex;MODIFIER LETTER CIRCUMFLEX ACCENT +2663;club;BLACK CLUB SUIT +003A;colon;COLON +20A1;colonmonetary;COLON SIGN +002C;comma;COMMA +2245;congruent;APPROXIMATELY EQUAL TO +00A9;copyright;COPYRIGHT SIGN +00A4;currency;CURRENCY SIGN +0064;d;LATIN SMALL LETTER D +2020;dagger;DAGGER +2021;daggerdbl;DOUBLE DAGGER +010F;dcaron;LATIN SMALL LETTER D WITH CARON +0111;dcroat;LATIN SMALL LETTER D WITH STROKE +00B0;degree;DEGREE SIGN +03B4;delta;GREEK SMALL LETTER DELTA +2666;diamond;BLACK DIAMOND SUIT +00A8;dieresis;DIAERESIS +0385;dieresistonos;GREEK DIALYTIKA TONOS +00F7;divide;DIVISION SIGN +2593;dkshade;DARK SHADE +2584;dnblock;LOWER HALF BLOCK +0024;dollar;DOLLAR SIGN +20AB;dong;DONG SIGN +02D9;dotaccent;DOT ABOVE +0323;dotbelowcomb;COMBINING DOT BELOW +0131;dotlessi;LATIN SMALL LETTER DOTLESS I +22C5;dotmath;DOT OPERATOR +0065;e;LATIN SMALL LETTER E +00E9;eacute;LATIN SMALL LETTER E WITH ACUTE +0115;ebreve;LATIN SMALL LETTER E WITH BREVE +011B;ecaron;LATIN SMALL LETTER E WITH CARON +00EA;ecircumflex;LATIN SMALL LETTER E WITH CIRCUMFLEX +00EB;edieresis;LATIN SMALL LETTER E WITH DIAERESIS +0117;edotaccent;LATIN SMALL LETTER E WITH DOT ABOVE +00E8;egrave;LATIN SMALL LETTER E WITH GRAVE +0038;eight;DIGIT EIGHT +2208;element;ELEMENT OF +2026;ellipsis;HORIZONTAL ELLIPSIS +0113;emacron;LATIN SMALL LETTER E WITH MACRON +2014;emdash;EM DASH +2205;emptyset;EMPTY SET +2013;endash;EN DASH +014B;eng;LATIN SMALL LETTER ENG +0119;eogonek;LATIN SMALL LETTER E WITH OGONEK +03B5;epsilon;GREEK SMALL LETTER EPSILON +03AD;epsilontonos;GREEK SMALL LETTER EPSILON WITH TONOS +003D;equal;EQUALS SIGN +2261;equivalence;IDENTICAL TO +212E;estimated;ESTIMATED SYMBOL +03B7;eta;GREEK SMALL LETTER ETA +03AE;etatonos;GREEK SMALL LETTER ETA WITH TONOS +00F0;eth;LATIN SMALL LETTER ETH +0021;exclam;EXCLAMATION MARK +203C;exclamdbl;DOUBLE EXCLAMATION MARK +00A1;exclamdown;INVERTED EXCLAMATION MARK +2203;existential;THERE EXISTS +0066;f;LATIN SMALL LETTER F +2640;female;FEMALE SIGN +2012;figuredash;FIGURE DASH +25A0;filledbox;BLACK SQUARE +25AC;filledrect;BLACK RECTANGLE +0035;five;DIGIT FIVE +215D;fiveeighths;VULGAR FRACTION FIVE EIGHTHS +0192;florin;LATIN SMALL LETTER F WITH HOOK +0034;four;DIGIT FOUR +2044;fraction;FRACTION SLASH +20A3;franc;FRENCH FRANC SIGN +0067;g;LATIN SMALL LETTER G +03B3;gamma;GREEK SMALL LETTER GAMMA +011F;gbreve;LATIN SMALL LETTER G WITH BREVE +01E7;gcaron;LATIN SMALL LETTER G WITH CARON +011D;gcircumflex;LATIN SMALL LETTER G WITH CIRCUMFLEX +0121;gdotaccent;LATIN SMALL LETTER G WITH DOT ABOVE +00DF;germandbls;LATIN SMALL LETTER SHARP S +2207;gradient;NABLA +0060;grave;GRAVE ACCENT +0300;gravecomb;COMBINING GRAVE ACCENT +003E;greater;GREATER-THAN SIGN +2265;greaterequal;GREATER-THAN OR EQUAL TO +00AB;guillemotleft;LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00BB;guillemotright;RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +2039;guilsinglleft;SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A;guilsinglright;SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +0068;h;LATIN SMALL LETTER H +0127;hbar;LATIN SMALL LETTER H WITH STROKE +0125;hcircumflex;LATIN SMALL LETTER H WITH CIRCUMFLEX +2665;heart;BLACK HEART SUIT +0309;hookabovecomb;COMBINING HOOK ABOVE +2302;house;HOUSE +02DD;hungarumlaut;DOUBLE ACUTE ACCENT +002D;hyphen;HYPHEN-MINUS +0069;i;LATIN SMALL LETTER I +00ED;iacute;LATIN SMALL LETTER I WITH ACUTE +012D;ibreve;LATIN SMALL LETTER I WITH BREVE +00EE;icircumflex;LATIN SMALL LETTER I WITH CIRCUMFLEX +00EF;idieresis;LATIN SMALL LETTER I WITH DIAERESIS +00EC;igrave;LATIN SMALL LETTER I WITH GRAVE +0133;ij;LATIN SMALL LIGATURE IJ +012B;imacron;LATIN SMALL LETTER I WITH MACRON +221E;infinity;INFINITY +222B;integral;INTEGRAL +2321;integralbt;BOTTOM HALF INTEGRAL +2320;integraltp;TOP HALF INTEGRAL +2229;intersection;INTERSECTION +25D8;invbullet;INVERSE BULLET +25D9;invcircle;INVERSE WHITE CIRCLE +263B;invsmileface;BLACK SMILING FACE +012F;iogonek;LATIN SMALL LETTER I WITH OGONEK +03B9;iota;GREEK SMALL LETTER IOTA +03CA;iotadieresis;GREEK SMALL LETTER IOTA WITH DIALYTIKA +0390;iotadieresistonos;GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +03AF;iotatonos;GREEK SMALL LETTER IOTA WITH TONOS +0129;itilde;LATIN SMALL LETTER I WITH TILDE +006A;j;LATIN SMALL LETTER J +0135;jcircumflex;LATIN SMALL LETTER J WITH CIRCUMFLEX +006B;k;LATIN SMALL LETTER K +03BA;kappa;GREEK SMALL LETTER KAPPA +0138;kgreenlandic;LATIN SMALL LETTER KRA +006C;l;LATIN SMALL LETTER L +013A;lacute;LATIN SMALL LETTER L WITH ACUTE +03BB;lambda;GREEK SMALL LETTER LAMDA +013E;lcaron;LATIN SMALL LETTER L WITH CARON +0140;ldot;LATIN SMALL LETTER L WITH MIDDLE DOT +003C;less;LESS-THAN SIGN +2264;lessequal;LESS-THAN OR EQUAL TO +258C;lfblock;LEFT HALF BLOCK +20A4;lira;LIRA SIGN +2227;logicaland;LOGICAL AND +00AC;logicalnot;NOT SIGN +2228;logicalor;LOGICAL OR +017F;longs;LATIN SMALL LETTER LONG S +25CA;lozenge;LOZENGE +0142;lslash;LATIN SMALL LETTER L WITH STROKE +2591;ltshade;LIGHT SHADE +006D;m;LATIN SMALL LETTER M +00AF;macron;MACRON +2642;male;MALE SIGN +2212;minus;MINUS SIGN +2032;minute;PRIME +00B5;mu;MICRO SIGN +00D7;multiply;MULTIPLICATION SIGN +266A;musicalnote;EIGHTH NOTE +266B;musicalnotedbl;BEAMED EIGHTH NOTES +006E;n;LATIN SMALL LETTER N +0144;nacute;LATIN SMALL LETTER N WITH ACUTE +0149;napostrophe;LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +0148;ncaron;LATIN SMALL LETTER N WITH CARON +0039;nine;DIGIT NINE +2209;notelement;NOT AN ELEMENT OF +2260;notequal;NOT EQUAL TO +2284;notsubset;NOT A SUBSET OF +00F1;ntilde;LATIN SMALL LETTER N WITH TILDE +03BD;nu;GREEK SMALL LETTER NU +0023;numbersign;NUMBER SIGN +006F;o;LATIN SMALL LETTER O +00F3;oacute;LATIN SMALL LETTER O WITH ACUTE +014F;obreve;LATIN SMALL LETTER O WITH BREVE +00F4;ocircumflex;LATIN SMALL LETTER O WITH CIRCUMFLEX +00F6;odieresis;LATIN SMALL LETTER O WITH DIAERESIS +0153;oe;LATIN SMALL LIGATURE OE +02DB;ogonek;OGONEK +00F2;ograve;LATIN SMALL LETTER O WITH GRAVE +01A1;ohorn;LATIN SMALL LETTER O WITH HORN +0151;ohungarumlaut;LATIN SMALL LETTER O WITH DOUBLE ACUTE +014D;omacron;LATIN SMALL LETTER O WITH MACRON +03C9;omega;GREEK SMALL LETTER OMEGA +03D6;omega1;GREEK PI SYMBOL +03CE;omegatonos;GREEK SMALL LETTER OMEGA WITH TONOS +03BF;omicron;GREEK SMALL LETTER OMICRON +03CC;omicrontonos;GREEK SMALL LETTER OMICRON WITH TONOS +0031;one;DIGIT ONE +2024;onedotenleader;ONE DOT LEADER +215B;oneeighth;VULGAR FRACTION ONE EIGHTH +00BD;onehalf;VULGAR FRACTION ONE HALF +00BC;onequarter;VULGAR FRACTION ONE QUARTER +2153;onethird;VULGAR FRACTION ONE THIRD +25E6;openbullet;WHITE BULLET +00AA;ordfeminine;FEMININE ORDINAL INDICATOR +00BA;ordmasculine;MASCULINE ORDINAL INDICATOR +221F;orthogonal;RIGHT ANGLE +00F8;oslash;LATIN SMALL LETTER O WITH STROKE +01FF;oslashacute;LATIN SMALL LETTER O WITH STROKE AND ACUTE +00F5;otilde;LATIN SMALL LETTER O WITH TILDE +0070;p;LATIN SMALL LETTER P +00B6;paragraph;PILCROW SIGN +0028;parenleft;LEFT PARENTHESIS +0029;parenright;RIGHT PARENTHESIS +2202;partialdiff;PARTIAL DIFFERENTIAL +0025;percent;PERCENT SIGN +002E;period;FULL STOP +00B7;periodcentered;MIDDLE DOT +22A5;perpendicular;UP TACK +2030;perthousand;PER MILLE SIGN +20A7;peseta;PESETA SIGN +03C6;phi;GREEK SMALL LETTER PHI +03D5;phi1;GREEK PHI SYMBOL +03C0;pi;GREEK SMALL LETTER PI +002B;plus;PLUS SIGN +00B1;plusminus;PLUS-MINUS SIGN +211E;prescription;PRESCRIPTION TAKE +220F;product;N-ARY PRODUCT +2282;propersubset;SUBSET OF +2283;propersuperset;SUPERSET OF +221D;proportional;PROPORTIONAL TO +03C8;psi;GREEK SMALL LETTER PSI +0071;q;LATIN SMALL LETTER Q +003F;question;QUESTION MARK +00BF;questiondown;INVERTED QUESTION MARK +0022;quotedbl;QUOTATION MARK +201E;quotedblbase;DOUBLE LOW-9 QUOTATION MARK +201C;quotedblleft;LEFT DOUBLE QUOTATION MARK +201D;quotedblright;RIGHT DOUBLE QUOTATION MARK +2018;quoteleft;LEFT SINGLE QUOTATION MARK +201B;quotereversed;SINGLE HIGH-REVERSED-9 QUOTATION MARK +2019;quoteright;RIGHT SINGLE QUOTATION MARK +201A;quotesinglbase;SINGLE LOW-9 QUOTATION MARK +0027;quotesingle;APOSTROPHE +0072;r;LATIN SMALL LETTER R +0155;racute;LATIN SMALL LETTER R WITH ACUTE +221A;radical;SQUARE ROOT +0159;rcaron;LATIN SMALL LETTER R WITH CARON +2286;reflexsubset;SUBSET OF OR EQUAL TO +2287;reflexsuperset;SUPERSET OF OR EQUAL TO +00AE;registered;REGISTERED SIGN +2310;revlogicalnot;REVERSED NOT SIGN +03C1;rho;GREEK SMALL LETTER RHO +02DA;ring;RING ABOVE +2590;rtblock;RIGHT HALF BLOCK +0073;s;LATIN SMALL LETTER S +015B;sacute;LATIN SMALL LETTER S WITH ACUTE +0161;scaron;LATIN SMALL LETTER S WITH CARON +015F;scedilla;LATIN SMALL LETTER S WITH CEDILLA +015D;scircumflex;LATIN SMALL LETTER S WITH CIRCUMFLEX +2033;second;DOUBLE PRIME +00A7;section;SECTION SIGN +003B;semicolon;SEMICOLON +0037;seven;DIGIT SEVEN +215E;seveneighths;VULGAR FRACTION SEVEN EIGHTHS +2592;shade;MEDIUM SHADE +03C3;sigma;GREEK SMALL LETTER SIGMA +03C2;sigma1;GREEK SMALL LETTER FINAL SIGMA +223C;similar;TILDE OPERATOR +0036;six;DIGIT SIX +002F;slash;SOLIDUS +263A;smileface;WHITE SMILING FACE +0020;space;SPACE +2660;spade;BLACK SPADE SUIT +00A3;sterling;POUND SIGN +220B;suchthat;CONTAINS AS MEMBER +2211;summation;N-ARY SUMMATION +263C;sun;WHITE SUN WITH RAYS +0074;t;LATIN SMALL LETTER T +03C4;tau;GREEK SMALL LETTER TAU +0167;tbar;LATIN SMALL LETTER T WITH STROKE +0165;tcaron;LATIN SMALL LETTER T WITH CARON +2234;therefore;THEREFORE +03B8;theta;GREEK SMALL LETTER THETA +03D1;theta1;GREEK THETA SYMBOL +00FE;thorn;LATIN SMALL LETTER THORN +0033;three;DIGIT THREE +215C;threeeighths;VULGAR FRACTION THREE EIGHTHS +00BE;threequarters;VULGAR FRACTION THREE QUARTERS +02DC;tilde;SMALL TILDE +0303;tildecomb;COMBINING TILDE +0384;tonos;GREEK TONOS +2122;trademark;TRADE MARK SIGN +25BC;triagdn;BLACK DOWN-POINTING TRIANGLE +25C4;triaglf;BLACK LEFT-POINTING POINTER +25BA;triagrt;BLACK RIGHT-POINTING POINTER +25B2;triagup;BLACK UP-POINTING TRIANGLE +0032;two;DIGIT TWO +2025;twodotenleader;TWO DOT LEADER +2154;twothirds;VULGAR FRACTION TWO THIRDS +0075;u;LATIN SMALL LETTER U +00FA;uacute;LATIN SMALL LETTER U WITH ACUTE +016D;ubreve;LATIN SMALL LETTER U WITH BREVE +00FB;ucircumflex;LATIN SMALL LETTER U WITH CIRCUMFLEX +00FC;udieresis;LATIN SMALL LETTER U WITH DIAERESIS +00F9;ugrave;LATIN SMALL LETTER U WITH GRAVE +01B0;uhorn;LATIN SMALL LETTER U WITH HORN +0171;uhungarumlaut;LATIN SMALL LETTER U WITH DOUBLE ACUTE +016B;umacron;LATIN SMALL LETTER U WITH MACRON +005F;underscore;LOW LINE +2017;underscoredbl;DOUBLE LOW LINE +222A;union;UNION +2200;universal;FOR ALL +0173;uogonek;LATIN SMALL LETTER U WITH OGONEK +2580;upblock;UPPER HALF BLOCK +03C5;upsilon;GREEK SMALL LETTER UPSILON +03CB;upsilondieresis;GREEK SMALL LETTER UPSILON WITH DIALYTIKA +03B0;upsilondieresistonos;GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS +03CD;upsilontonos;GREEK SMALL LETTER UPSILON WITH TONOS +016F;uring;LATIN SMALL LETTER U WITH RING ABOVE +0169;utilde;LATIN SMALL LETTER U WITH TILDE +0076;v;LATIN SMALL LETTER V +0077;w;LATIN SMALL LETTER W +1E83;wacute;LATIN SMALL LETTER W WITH ACUTE +0175;wcircumflex;LATIN SMALL LETTER W WITH CIRCUMFLEX +1E85;wdieresis;LATIN SMALL LETTER W WITH DIAERESIS +2118;weierstrass;SCRIPT CAPITAL P +1E81;wgrave;LATIN SMALL LETTER W WITH GRAVE +0078;x;LATIN SMALL LETTER X +03BE;xi;GREEK SMALL LETTER XI +0079;y;LATIN SMALL LETTER Y +00FD;yacute;LATIN SMALL LETTER Y WITH ACUTE +0177;ycircumflex;LATIN SMALL LETTER Y WITH CIRCUMFLEX +00FF;ydieresis;LATIN SMALL LETTER Y WITH DIAERESIS +00A5;yen;YEN SIGN +1EF3;ygrave;LATIN SMALL LETTER Y WITH GRAVE +007A;z;LATIN SMALL LETTER Z +017A;zacute;LATIN SMALL LETTER Z WITH ACUTE +017E;zcaron;LATIN SMALL LETTER Z WITH CARON +017C;zdotaccent;LATIN SMALL LETTER Z WITH DOT ABOVE +0030;zero;DIGIT ZERO +03B6;zeta;GREEK SMALL LETTER ZETA +# END +""" + + +class AGLError(Exception): + pass + + +LEGACY_AGL2UV = {} +AGL2UV = {} +UV2AGL = {} + + +def _builddicts(): + import re + + lines = _aglText.splitlines() + + parseAGL_RE = re.compile("([A-Za-z0-9]+);((?:[0-9A-F]{4})(?: (?:[0-9A-F]{4}))*)$") + + for line in lines: + if not line or line[:1] == "#": + continue + m = parseAGL_RE.match(line) + if not m: + raise AGLError("syntax error in glyphlist.txt: %s" % repr(line[:20])) + unicodes = m.group(2) + assert len(unicodes) % 5 == 4 + unicodes = [int(unicode, 16) for unicode in unicodes.split()] + glyphName = tostr(m.group(1)) + LEGACY_AGL2UV[glyphName] = unicodes + + lines = _aglfnText.splitlines() + + parseAGLFN_RE = re.compile("([0-9A-F]{4});([A-Za-z0-9]+);.*?$") + + for line in lines: + if not line or line[:1] == "#": + continue + m = parseAGLFN_RE.match(line) + if not m: + raise AGLError("syntax error in aglfn.txt: %s" % repr(line[:20])) + unicode = m.group(1) + assert len(unicode) == 4 + unicode = int(unicode, 16) + glyphName = tostr(m.group(2)) + AGL2UV[glyphName] = unicode + UV2AGL[unicode] = glyphName + + +_builddicts() + + +def toUnicode(glyph, isZapfDingbats=False): + """Convert glyph names to Unicode, such as ``'longs_t.oldstyle'`` --> ``u'ſt'`` + + If ``isZapfDingbats`` is ``True``, the implementation recognizes additional + glyph names (as required by the AGL specification). + """ + # https://github.com/adobe-type-tools/agl-specification#2-the-mapping + # + # 1. Drop all the characters from the glyph name starting with + # the first occurrence of a period (U+002E; FULL STOP), if any. + glyph = glyph.split(".", 1)[0] + + # 2. Split the remaining string into a sequence of components, + # using underscore (U+005F; LOW LINE) as the delimiter. + components = glyph.split("_") + + # 3. Map each component to a character string according to the + # procedure below, and concatenate those strings; the result + # is the character string to which the glyph name is mapped. + result = [_glyphComponentToUnicode(c, isZapfDingbats) for c in components] + return "".join(result) + + +def _glyphComponentToUnicode(component, isZapfDingbats): + # If the font is Zapf Dingbats (PostScript FontName: ZapfDingbats), + # and the component is in the ITC Zapf Dingbats Glyph List, then + # map it to the corresponding character in that list. + dingbat = _zapfDingbatsToUnicode(component) if isZapfDingbats else None + if dingbat: + return dingbat + + # Otherwise, if the component is in AGL, then map it + # to the corresponding character in that list. + uchars = LEGACY_AGL2UV.get(component) + if uchars: + return "".join(map(chr, uchars)) + + # Otherwise, if the component is of the form "uni" (U+0075, + # U+006E, and U+0069) followed by a sequence of uppercase + # hexadecimal digits (0–9 and A–F, meaning U+0030 through + # U+0039 and U+0041 through U+0046), if the length of that + # sequence is a multiple of four, and if each group of four + # digits represents a value in the ranges 0000 through D7FF + # or E000 through FFFF, then interpret each as a Unicode scalar + # value and map the component to the string made of those + # scalar values. Note that the range and digit-length + # restrictions mean that the "uni" glyph name prefix can be + # used only with UVs in the Basic Multilingual Plane (BMP). + uni = _uniToUnicode(component) + if uni: + return uni + + # Otherwise, if the component is of the form "u" (U+0075) + # followed by a sequence of four to six uppercase hexadecimal + # digits (0–9 and A–F, meaning U+0030 through U+0039 and + # U+0041 through U+0046), and those digits represents a value + # in the ranges 0000 through D7FF or E000 through 10FFFF, then + # interpret it as a Unicode scalar value and map the component + # to the string made of this scalar value. + uni = _uToUnicode(component) + if uni: + return uni + + # Otherwise, map the component to an empty string. + return "" + + +# https://github.com/adobe-type-tools/agl-aglfn/blob/master/zapfdingbats.txt +_AGL_ZAPF_DINGBATS = ( + " ✁✂✄☎✆✝✞✟✠✡☛☞✌✍✎✏✑✒✓✔✕✖✗✘✙✚✛✜✢✣✤✥✦✧★✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀" + "❁❂❃❄❅❆❇❈❉❊❋●❍■❏❑▲▼◆❖ ◗❘❙❚❯❱❲❳❨❩❬❭❪❫❴❵❛❜❝❞❡❢❣❤✐❥❦❧♠♥♦♣ ✉✈✇" + "①②③④⑤⑥⑦⑧⑨⑩❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔→➣↔" + "↕➙➛➜➝➞➟➠➡➢➤➥➦➧➨➩➫➭➯➲➳➵➸➺➻➼➽➾➚➪➶➹➘➴➷➬➮➱✃❐❒❮❰" +) + + +def _zapfDingbatsToUnicode(glyph): + """Helper for toUnicode().""" + if len(glyph) < 2 or glyph[0] != "a": + return None + try: + gid = int(glyph[1:]) + except ValueError: + return None + if gid < 0 or gid >= len(_AGL_ZAPF_DINGBATS): + return None + uchar = _AGL_ZAPF_DINGBATS[gid] + return uchar if uchar != " " else None + + +_re_uni = re.compile("^uni([0-9A-F]+)$") + + +def _uniToUnicode(component): + """Helper for toUnicode() to handle "uniABCD" components.""" + match = _re_uni.match(component) + if match is None: + return None + digits = match.group(1) + if len(digits) % 4 != 0: + return None + chars = [int(digits[i : i + 4], 16) for i in range(0, len(digits), 4)] + if any(c >= 0xD800 and c <= 0xDFFF for c in chars): + # The AGL specification explicitly excluded surrogate pairs. + return None + return "".join([chr(c) for c in chars]) + + +_re_u = re.compile("^u([0-9A-F]{4,6})$") + + +def _uToUnicode(component): + """Helper for toUnicode() to handle "u1ABCD" components.""" + match = _re_u.match(component) + if match is None: + return None + digits = match.group(1) + try: + value = int(digits, 16) + except ValueError: + return None + if (value >= 0x0000 and value <= 0xD7FF) or (value >= 0xE000 and value <= 0x10FFFF): + return chr(value) + return None diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/__init__.py new file mode 100644 index 0000000..bb62673 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/__init__.py @@ -0,0 +1,3830 @@ +"""cffLib: read/write Adobe CFF fonts + +OpenType fonts with PostScript outlines contain a completely independent +font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts +requires also dealing with CFF. This module allows you to read and write +fonts written in the CFF format. + +In 2016, OpenType 1.8 introduced the `CFF2 `_ +format which, along with other changes, extended the CFF format to deal with +the demands of variable fonts. This module parses both original CFF and CFF2. + +""" + +from fontTools.misc import sstruct +from fontTools.misc import psCharStrings +from fontTools.misc.arrayTools import unionRect, intRect +from fontTools.misc.textTools import ( + bytechr, + byteord, + bytesjoin, + tobytes, + tostr, + safeEval, +) +from fontTools.ttLib import TTFont +from fontTools.ttLib.tables.otBase import OTTableWriter +from fontTools.ttLib.tables.otBase import OTTableReader +from fontTools.ttLib.tables import otTables as ot +from io import BytesIO +import struct +import logging +import re + +# mute cffLib debug messages when running ttx in verbose mode +DEBUG = logging.DEBUG - 1 +log = logging.getLogger(__name__) + +cffHeaderFormat = """ + major: B + minor: B + hdrSize: B +""" + +maxStackLimit = 513 +# maxstack operator has been deprecated. max stack is now always 513. + + +class StopHintCountEvent(Exception): + pass + + +class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler): + stop_hintcount_ops = ( + "op_hintmask", + "op_cntrmask", + "op_rmoveto", + "op_hmoveto", + "op_vmoveto", + ) + + def __init__(self, localSubrs, globalSubrs, private=None): + psCharStrings.SimpleT2Decompiler.__init__( + self, localSubrs, globalSubrs, private + ) + + def execute(self, charString): + self.need_hintcount = True # until proven otherwise + for op_name in self.stop_hintcount_ops: + setattr(self, op_name, self.stop_hint_count) + + if hasattr(charString, "_desubroutinized"): + # If a charstring has already been desubroutinized, we will still + # need to execute it if we need to count hints in order to + # compute the byte length for mask arguments, and haven't finished + # counting hints pairs. + if self.need_hintcount and self.callingStack: + try: + psCharStrings.SimpleT2Decompiler.execute(self, charString) + except StopHintCountEvent: + del self.callingStack[-1] + return + + charString._patches = [] + psCharStrings.SimpleT2Decompiler.execute(self, charString) + desubroutinized = charString.program[:] + for idx, expansion in reversed(charString._patches): + assert idx >= 2 + assert desubroutinized[idx - 1] in [ + "callsubr", + "callgsubr", + ], desubroutinized[idx - 1] + assert type(desubroutinized[idx - 2]) == int + if expansion[-1] == "return": + expansion = expansion[:-1] + desubroutinized[idx - 2 : idx] = expansion + if not self.private.in_cff2: + if "endchar" in desubroutinized: + # Cut off after first endchar + desubroutinized = desubroutinized[ + : desubroutinized.index("endchar") + 1 + ] + else: + if not len(desubroutinized) or desubroutinized[-1] != "return": + desubroutinized.append("return") + + charString._desubroutinized = desubroutinized + del charString._patches + + def op_callsubr(self, index): + subr = self.localSubrs[self.operandStack[-1] + self.localBias] + psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) + self.processSubr(index, subr) + + def op_callgsubr(self, index): + subr = self.globalSubrs[self.operandStack[-1] + self.globalBias] + psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) + self.processSubr(index, subr) + + def stop_hint_count(self, *args): + self.need_hintcount = False + for op_name in self.stop_hintcount_ops: + setattr(self, op_name, None) + cs = self.callingStack[-1] + if hasattr(cs, "_desubroutinized"): + raise StopHintCountEvent() + + def op_hintmask(self, index): + psCharStrings.SimpleT2Decompiler.op_hintmask(self, index) + if self.need_hintcount: + self.stop_hint_count() + + def processSubr(self, index, subr): + cs = self.callingStack[-1] + if not hasattr(cs, "_desubroutinized"): + cs._patches.append((index, subr._desubroutinized)) + + +class CFFFontSet(object): + """A CFF font "file" can contain more than one font, although this is + extremely rare (and not allowed within OpenType fonts). + + This class is the entry point for parsing a CFF table. To actually + manipulate the data inside the CFF font, you will want to access the + ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet`` + object can either be treated as a dictionary (with appropriate + ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict` + objects, or as a list. + + .. code:: python + + from fontTools import ttLib + tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf") + tt["CFF "].cff + # + tt["CFF "].cff[0] # Here's your actual font data + # + + """ + + def decompile(self, file, otFont, isCFF2=None): + """Parse a binary CFF file into an internal representation. ``file`` + should be a file handle object. ``otFont`` is the top-level + :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. + + If ``isCFF2`` is passed and set to ``True`` or ``False``, then the + library makes an assertion that the CFF header is of the appropriate + version. + """ + + self.otFont = otFont + sstruct.unpack(cffHeaderFormat, file.read(3), self) + if isCFF2 is not None: + # called from ttLib: assert 'major' as read from file matches the + # expected version + expected_major = 2 if isCFF2 else 1 + if self.major != expected_major: + raise ValueError( + "Invalid CFF 'major' version: expected %d, found %d" + % (expected_major, self.major) + ) + else: + # use 'major' version from file to determine if isCFF2 + assert self.major in (1, 2), "Unknown CFF format" + isCFF2 = self.major == 2 + if not isCFF2: + self.offSize = struct.unpack("B", file.read(1))[0] + file.seek(self.hdrSize) + self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2)) + self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2) + self.strings = IndexedStrings(file) + else: # isCFF2 + self.topDictSize = struct.unpack(">H", file.read(2))[0] + file.seek(self.hdrSize) + self.fontNames = ["CFF2Font"] + cff2GetGlyphOrder = otFont.getGlyphOrder + # in CFF2, offsetSize is the size of the TopDict data. + self.topDictIndex = TopDictIndex( + file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2 + ) + self.strings = None + self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2) + self.topDictIndex.strings = self.strings + self.topDictIndex.GlobalSubrs = self.GlobalSubrs + + def __len__(self): + return len(self.fontNames) + + def keys(self): + return list(self.fontNames) + + def values(self): + return self.topDictIndex + + def __getitem__(self, nameOrIndex): + """Return TopDict instance identified by name (str) or index (int + or any object that implements `__index__`). + """ + if hasattr(nameOrIndex, "__index__"): + index = nameOrIndex.__index__() + elif isinstance(nameOrIndex, str): + name = nameOrIndex + try: + index = self.fontNames.index(name) + except ValueError: + raise KeyError(nameOrIndex) + else: + raise TypeError(nameOrIndex) + return self.topDictIndex[index] + + def compile(self, file, otFont, isCFF2=None): + """Write the object back into binary representation onto the given file. + ``file`` should be a file handle object. ``otFont`` is the top-level + :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. + + If ``isCFF2`` is passed and set to ``True`` or ``False``, then the + library makes an assertion that the CFF header is of the appropriate + version. + """ + self.otFont = otFont + if isCFF2 is not None: + # called from ttLib: assert 'major' value matches expected version + expected_major = 2 if isCFF2 else 1 + if self.major != expected_major: + raise ValueError( + "Invalid CFF 'major' version: expected %d, found %d" + % (expected_major, self.major) + ) + else: + # use current 'major' value to determine output format + assert self.major in (1, 2), "Unknown CFF format" + isCFF2 = self.major == 2 + + if otFont.recalcBBoxes and not isCFF2: + for topDict in self.topDictIndex: + topDict.recalcFontBBox() + + if not isCFF2: + strings = IndexedStrings() + else: + strings = None + writer = CFFWriter(isCFF2) + topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2) + if isCFF2: + self.hdrSize = 5 + writer.add(sstruct.pack(cffHeaderFormat, self)) + # Note: topDictSize will most likely change in CFFWriter.toFile(). + self.topDictSize = topCompiler.getDataLength() + writer.add(struct.pack(">H", self.topDictSize)) + else: + self.hdrSize = 4 + self.offSize = 4 # will most likely change in CFFWriter.toFile(). + writer.add(sstruct.pack(cffHeaderFormat, self)) + writer.add(struct.pack("B", self.offSize)) + if not isCFF2: + fontNames = Index() + for name in self.fontNames: + fontNames.append(name) + writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2)) + writer.add(topCompiler) + if not isCFF2: + writer.add(strings.getCompiler()) + writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2)) + + for topDict in self.topDictIndex: + if not hasattr(topDict, "charset") or topDict.charset is None: + charset = otFont.getGlyphOrder() + topDict.charset = charset + children = topCompiler.getChildren(strings) + for child in children: + writer.add(child) + + writer.toFile(file) + + def toXML(self, xmlWriter): + """Write the object into XML representation onto the given + :class:`fontTools.misc.xmlWriter.XMLWriter`. + + .. code:: python + + writer = xmlWriter.XMLWriter(sys.stdout) + tt["CFF "].cff.toXML(writer) + + """ + + xmlWriter.simpletag("major", value=self.major) + xmlWriter.newline() + xmlWriter.simpletag("minor", value=self.minor) + xmlWriter.newline() + for fontName in self.fontNames: + xmlWriter.begintag("CFFFont", name=tostr(fontName)) + xmlWriter.newline() + font = self[fontName] + font.toXML(xmlWriter) + xmlWriter.endtag("CFFFont") + xmlWriter.newline() + xmlWriter.newline() + xmlWriter.begintag("GlobalSubrs") + xmlWriter.newline() + self.GlobalSubrs.toXML(xmlWriter) + xmlWriter.endtag("GlobalSubrs") + xmlWriter.newline() + + def fromXML(self, name, attrs, content, otFont=None): + """Reads data from the XML element into the ``CFFFontSet`` object.""" + self.otFont = otFont + + # set defaults. These will be replaced if there are entries for them + # in the XML file. + if not hasattr(self, "major"): + self.major = 1 + if not hasattr(self, "minor"): + self.minor = 0 + + if name == "CFFFont": + if self.major == 1: + if not hasattr(self, "offSize"): + # this will be recalculated when the cff is compiled. + self.offSize = 4 + if not hasattr(self, "hdrSize"): + self.hdrSize = 4 + if not hasattr(self, "GlobalSubrs"): + self.GlobalSubrs = GlobalSubrsIndex() + if not hasattr(self, "fontNames"): + self.fontNames = [] + self.topDictIndex = TopDictIndex() + fontName = attrs["name"] + self.fontNames.append(fontName) + topDict = TopDict(GlobalSubrs=self.GlobalSubrs) + topDict.charset = None # gets filled in later + elif self.major == 2: + if not hasattr(self, "hdrSize"): + self.hdrSize = 5 + if not hasattr(self, "GlobalSubrs"): + self.GlobalSubrs = GlobalSubrsIndex() + if not hasattr(self, "fontNames"): + self.fontNames = ["CFF2Font"] + cff2GetGlyphOrder = self.otFont.getGlyphOrder + topDict = TopDict( + GlobalSubrs=self.GlobalSubrs, cff2GetGlyphOrder=cff2GetGlyphOrder + ) + self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder) + self.topDictIndex.append(topDict) + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + topDict.fromXML(name, attrs, content) + + if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None: + fdArray = topDict.FDArray + for fontDict in fdArray: + if hasattr(fontDict, "Private"): + fontDict.Private.vstore = topDict.VarStore + + elif name == "GlobalSubrs": + subrCharStringClass = psCharStrings.T2CharString + if not hasattr(self, "GlobalSubrs"): + self.GlobalSubrs = GlobalSubrsIndex() + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + subr = subrCharStringClass() + subr.fromXML(name, attrs, content) + self.GlobalSubrs.append(subr) + elif name == "major": + self.major = int(attrs["value"]) + elif name == "minor": + self.minor = int(attrs["value"]) + + def convertCFFToCFF2(self, otFont): + """Converts this object from CFF format to CFF2 format. This conversion + is done 'in-place'. The conversion cannot be reversed. + + This assumes a decompiled CFF table. (i.e. that the object has been + filled via :meth:`decompile`.)""" + self.major = 2 + cff2GetGlyphOrder = self.otFont.getGlyphOrder + topDictData = TopDictIndex(None, cff2GetGlyphOrder) + topDictData.items = self.topDictIndex.items + self.topDictIndex = topDictData + topDict = topDictData[0] + if hasattr(topDict, "Private"): + privateDict = topDict.Private + else: + privateDict = None + opOrder = buildOrder(topDictOperators2) + topDict.order = opOrder + topDict.cff2GetGlyphOrder = cff2GetGlyphOrder + for entry in topDictOperators: + key = entry[1] + if key not in opOrder: + if key in topDict.rawDict: + del topDict.rawDict[key] + if hasattr(topDict, key): + delattr(topDict, key) + + if not hasattr(topDict, "FDArray"): + fdArray = topDict.FDArray = FDArrayIndex() + fdArray.strings = None + fdArray.GlobalSubrs = topDict.GlobalSubrs + topDict.GlobalSubrs.fdArray = fdArray + charStrings = topDict.CharStrings + if charStrings.charStringsAreIndexed: + charStrings.charStringsIndex.fdArray = fdArray + else: + charStrings.fdArray = fdArray + fontDict = FontDict() + fontDict.setCFF2(True) + fdArray.append(fontDict) + fontDict.Private = privateDict + privateOpOrder = buildOrder(privateDictOperators2) + for entry in privateDictOperators: + key = entry[1] + if key not in privateOpOrder: + if key in privateDict.rawDict: + # print "Removing private dict", key + del privateDict.rawDict[key] + if hasattr(privateDict, key): + delattr(privateDict, key) + # print "Removing privateDict attr", key + else: + # clean up the PrivateDicts in the fdArray + fdArray = topDict.FDArray + privateOpOrder = buildOrder(privateDictOperators2) + for fontDict in fdArray: + fontDict.setCFF2(True) + for key in fontDict.rawDict.keys(): + if key not in fontDict.order: + del fontDict.rawDict[key] + if hasattr(fontDict, key): + delattr(fontDict, key) + + privateDict = fontDict.Private + for entry in privateDictOperators: + key = entry[1] + if key not in privateOpOrder: + if key in privateDict.rawDict: + # print "Removing private dict", key + del privateDict.rawDict[key] + if hasattr(privateDict, key): + delattr(privateDict, key) + # print "Removing privateDict attr", key + # At this point, the Subrs and Charstrings are all still T2Charstring class + # easiest to fix this by compiling, then decompiling again + file = BytesIO() + self.compile(file, otFont, isCFF2=True) + file.seek(0) + self.decompile(file, otFont, isCFF2=True) + + def desubroutinize(self): + for fontName in self.fontNames: + font = self[fontName] + cs = font.CharStrings + for g in font.charset: + c, _ = cs.getItemAndSelector(g) + c.decompile() + subrs = getattr(c.private, "Subrs", []) + decompiler = _DesubroutinizingT2Decompiler( + subrs, c.globalSubrs, c.private + ) + decompiler.execute(c) + c.program = c._desubroutinized + del c._desubroutinized + # Delete all the local subrs + if hasattr(font, "FDArray"): + for fd in font.FDArray: + pd = fd.Private + if hasattr(pd, "Subrs"): + del pd.Subrs + if "Subrs" in pd.rawDict: + del pd.rawDict["Subrs"] + else: + pd = font.Private + if hasattr(pd, "Subrs"): + del pd.Subrs + if "Subrs" in pd.rawDict: + del pd.rawDict["Subrs"] + # as well as the global subrs + self.GlobalSubrs.clear() + + +class CFFWriter(object): + """Helper class for serializing CFF data to binary. Used by + :meth:`CFFFontSet.compile`.""" + + def __init__(self, isCFF2): + self.data = [] + self.isCFF2 = isCFF2 + + def add(self, table): + self.data.append(table) + + def toFile(self, file): + lastPosList = None + count = 1 + while True: + log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count) + count = count + 1 + pos = 0 + posList = [pos] + for item in self.data: + if hasattr(item, "getDataLength"): + endPos = pos + item.getDataLength() + if isinstance(item, TopDictIndexCompiler) and item.isCFF2: + self.topDictSize = item.getDataLength() + else: + endPos = pos + len(item) + if hasattr(item, "setPos"): + item.setPos(pos, endPos) + pos = endPos + posList.append(pos) + if posList == lastPosList: + break + lastPosList = posList + log.log(DEBUG, "CFFWriter.toFile() writing to file.") + begin = file.tell() + if self.isCFF2: + self.data[1] = struct.pack(">H", self.topDictSize) + else: + self.offSize = calcOffSize(lastPosList[-1]) + self.data[1] = struct.pack("B", self.offSize) + posList = [0] + for item in self.data: + if hasattr(item, "toFile"): + item.toFile(file) + else: + file.write(item) + posList.append(file.tell() - begin) + assert posList == lastPosList + + +def calcOffSize(largestOffset): + if largestOffset < 0x100: + offSize = 1 + elif largestOffset < 0x10000: + offSize = 2 + elif largestOffset < 0x1000000: + offSize = 3 + else: + offSize = 4 + return offSize + + +class IndexCompiler(object): + """Base class for writing CFF `INDEX data `_ + to binary.""" + + def __init__(self, items, strings, parent, isCFF2=None): + if isCFF2 is None and hasattr(parent, "isCFF2"): + isCFF2 = parent.isCFF2 + assert isCFF2 is not None + self.isCFF2 = isCFF2 + self.items = self.getItems(items, strings) + self.parent = parent + + def getItems(self, items, strings): + return items + + def getOffsets(self): + # An empty INDEX contains only the count field. + if self.items: + pos = 1 + offsets = [pos] + for item in self.items: + if hasattr(item, "getDataLength"): + pos = pos + item.getDataLength() + else: + pos = pos + len(item) + offsets.append(pos) + else: + offsets = [] + return offsets + + def getDataLength(self): + if self.isCFF2: + countSize = 4 + else: + countSize = 2 + + if self.items: + lastOffset = self.getOffsets()[-1] + offSize = calcOffSize(lastOffset) + dataLength = ( + countSize + + 1 # count + + (len(self.items) + 1) * offSize # offSize + + lastOffset # the offsets + - 1 # size of object data + ) + else: + # count. For empty INDEX tables, this is the only entry. + dataLength = countSize + + return dataLength + + def toFile(self, file): + offsets = self.getOffsets() + if self.isCFF2: + writeCard32(file, len(self.items)) + else: + writeCard16(file, len(self.items)) + # An empty INDEX contains only the count field. + if self.items: + offSize = calcOffSize(offsets[-1]) + writeCard8(file, offSize) + offSize = -offSize + pack = struct.pack + for offset in offsets: + binOffset = pack(">l", offset)[offSize:] + assert len(binOffset) == -offSize + file.write(binOffset) + for item in self.items: + if hasattr(item, "toFile"): + item.toFile(file) + else: + data = tobytes(item, encoding="latin1") + file.write(data) + + +class IndexedStringsCompiler(IndexCompiler): + def getItems(self, items, strings): + return items.strings + + +class TopDictIndexCompiler(IndexCompiler): + """Helper class for writing the TopDict to binary.""" + + def getItems(self, items, strings): + out = [] + for item in items: + out.append(item.getCompiler(strings, self)) + return out + + def getChildren(self, strings): + children = [] + for topDict in self.items: + children.extend(topDict.getChildren(strings)) + return children + + def getOffsets(self): + if self.isCFF2: + offsets = [0, self.items[0].getDataLength()] + return offsets + else: + return super(TopDictIndexCompiler, self).getOffsets() + + def getDataLength(self): + if self.isCFF2: + dataLength = self.items[0].getDataLength() + return dataLength + else: + return super(TopDictIndexCompiler, self).getDataLength() + + def toFile(self, file): + if self.isCFF2: + self.items[0].toFile(file) + else: + super(TopDictIndexCompiler, self).toFile(file) + + +class FDArrayIndexCompiler(IndexCompiler): + """Helper class for writing the + `Font DICT INDEX `_ + to binary.""" + + def getItems(self, items, strings): + out = [] + for item in items: + out.append(item.getCompiler(strings, self)) + return out + + def getChildren(self, strings): + children = [] + for fontDict in self.items: + children.extend(fontDict.getChildren(strings)) + return children + + def toFile(self, file): + offsets = self.getOffsets() + if self.isCFF2: + writeCard32(file, len(self.items)) + else: + writeCard16(file, len(self.items)) + offSize = calcOffSize(offsets[-1]) + writeCard8(file, offSize) + offSize = -offSize + pack = struct.pack + for offset in offsets: + binOffset = pack(">l", offset)[offSize:] + assert len(binOffset) == -offSize + file.write(binOffset) + for item in self.items: + if hasattr(item, "toFile"): + item.toFile(file) + else: + file.write(item) + + def setPos(self, pos, endPos): + self.parent.rawDict["FDArray"] = pos + + +class GlobalSubrsCompiler(IndexCompiler): + """Helper class for writing the `global subroutine INDEX `_ + to binary.""" + + def getItems(self, items, strings): + out = [] + for cs in items: + cs.compile(self.isCFF2) + out.append(cs.bytecode) + return out + + +class SubrsCompiler(GlobalSubrsCompiler): + """Helper class for writing the `local subroutine INDEX `_ + to binary.""" + + def setPos(self, pos, endPos): + offset = pos - self.parent.pos + self.parent.rawDict["Subrs"] = offset + + +class CharStringsCompiler(GlobalSubrsCompiler): + """Helper class for writing the `CharStrings INDEX `_ + to binary.""" + + def getItems(self, items, strings): + out = [] + for cs in items: + cs.compile(self.isCFF2) + out.append(cs.bytecode) + return out + + def setPos(self, pos, endPos): + self.parent.rawDict["CharStrings"] = pos + + +class Index(object): + """This class represents what the CFF spec calls an INDEX (an array of + variable-sized objects). `Index` items can be addressed and set using + Python list indexing.""" + + compilerClass = IndexCompiler + + def __init__(self, file=None, isCFF2=None): + assert (isCFF2 is None) == (file is None) + self.items = [] + name = self.__class__.__name__ + if file is None: + return + self._isCFF2 = isCFF2 + log.log(DEBUG, "loading %s at %s", name, file.tell()) + self.file = file + if isCFF2: + count = readCard32(file) + else: + count = readCard16(file) + if count == 0: + return + self.items = [None] * count + offSize = readCard8(file) + log.log(DEBUG, " index count: %s offSize: %s", count, offSize) + assert offSize <= 4, "offSize too large: %s" % offSize + self.offsets = offsets = [] + pad = b"\0" * (4 - offSize) + for index in range(count + 1): + chunk = file.read(offSize) + chunk = pad + chunk + (offset,) = struct.unpack(">L", chunk) + offsets.append(int(offset)) + self.offsetBase = file.tell() - 1 + file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot + log.log(DEBUG, " end of %s at %s", name, file.tell()) + + def __len__(self): + return len(self.items) + + def __getitem__(self, index): + item = self.items[index] + if item is not None: + return item + offset = self.offsets[index] + self.offsetBase + size = self.offsets[index + 1] - self.offsets[index] + file = self.file + file.seek(offset) + data = file.read(size) + assert len(data) == size + item = self.produceItem(index, data, file, offset) + self.items[index] = item + return item + + def __setitem__(self, index, item): + self.items[index] = item + + def produceItem(self, index, data, file, offset): + return data + + def append(self, item): + """Add an item to an INDEX.""" + self.items.append(item) + + def getCompiler(self, strings, parent, isCFF2=None): + return self.compilerClass(self, strings, parent, isCFF2=isCFF2) + + def clear(self): + """Empty the INDEX.""" + del self.items[:] + + +class GlobalSubrsIndex(Index): + """This index contains all the global subroutines in the font. A global + subroutine is a set of ``CharString`` data which is accessible to any + glyph in the font, and are used to store repeated instructions - for + example, components may be encoded as global subroutines, but so could + hinting instructions. + + Remember that when interpreting a ``callgsubr`` instruction (or indeed + a ``callsubr`` instruction) that you will need to add the "subroutine + number bias" to number given: + + .. code:: python + + tt = ttLib.TTFont("Almendra-Bold.otf") + u = tt["CFF "].cff[0].CharStrings["udieresis"] + u.decompile() + + u.toXML(XMLWriter(sys.stdout)) + # + # -64 callgsubr <-- Subroutine which implements the dieresis mark + # + + tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG + # + + tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT + # + + ("The bias applied depends on the number of subrs (gsubrs). If the number of + subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less + than 33900, it is 1131; otherwise it is 32768.", + `Subroutine Operators `) + """ + + compilerClass = GlobalSubrsCompiler + subrClass = psCharStrings.T2CharString + charStringClass = psCharStrings.T2CharString + + def __init__( + self, + file=None, + globalSubrs=None, + private=None, + fdSelect=None, + fdArray=None, + isCFF2=None, + ): + super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2) + self.globalSubrs = globalSubrs + self.private = private + if fdSelect: + self.fdSelect = fdSelect + if fdArray: + self.fdArray = fdArray + + def produceItem(self, index, data, file, offset): + if self.private is not None: + private = self.private + elif hasattr(self, "fdArray") and self.fdArray is not None: + if hasattr(self, "fdSelect") and self.fdSelect is not None: + fdIndex = self.fdSelect[index] + else: + fdIndex = 0 + private = self.fdArray[fdIndex].Private + else: + private = None + return self.subrClass(data, private=private, globalSubrs=self.globalSubrs) + + def toXML(self, xmlWriter): + """Write the subroutines index into XML representation onto the given + :class:`fontTools.misc.xmlWriter.XMLWriter`. + + .. code:: python + + writer = xmlWriter.XMLWriter(sys.stdout) + tt["CFF "].cff[0].GlobalSubrs.toXML(writer) + + """ + xmlWriter.comment( + "The 'index' attribute is only for humans; " "it is ignored when parsed." + ) + xmlWriter.newline() + for i in range(len(self)): + subr = self[i] + if subr.needsDecompilation(): + xmlWriter.begintag("CharString", index=i, raw=1) + else: + xmlWriter.begintag("CharString", index=i) + xmlWriter.newline() + subr.toXML(xmlWriter) + xmlWriter.endtag("CharString") + xmlWriter.newline() + + def fromXML(self, name, attrs, content): + if name != "CharString": + return + subr = self.subrClass() + subr.fromXML(name, attrs, content) + self.append(subr) + + def getItemAndSelector(self, index): + sel = None + if hasattr(self, "fdSelect"): + sel = self.fdSelect[index] + return self[index], sel + + +class SubrsIndex(GlobalSubrsIndex): + """This index contains a glyph's local subroutines. A local subroutine is a + private set of ``CharString`` data which is accessible only to the glyph to + which the index is attached.""" + + compilerClass = SubrsCompiler + + +class TopDictIndex(Index): + """This index represents the array of ``TopDict`` structures in the font + (again, usually only one entry is present). Hence the following calls are + equivalent: + + .. code:: python + + tt["CFF "].cff[0] + # + tt["CFF "].cff.topDictIndex[0] + # + + """ + + compilerClass = TopDictIndexCompiler + + def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0, isCFF2=None): + assert (isCFF2 is None) == (file is None) + self.cff2GetGlyphOrder = cff2GetGlyphOrder + if file is not None and isCFF2: + self._isCFF2 = isCFF2 + self.items = [] + name = self.__class__.__name__ + log.log(DEBUG, "loading %s at %s", name, file.tell()) + self.file = file + count = 1 + self.items = [None] * count + self.offsets = [0, topSize] + self.offsetBase = file.tell() + # pretend we've read the whole lot + file.seek(self.offsetBase + topSize) + log.log(DEBUG, " end of %s at %s", name, file.tell()) + else: + super(TopDictIndex, self).__init__(file, isCFF2=isCFF2) + + def produceItem(self, index, data, file, offset): + top = TopDict( + self.strings, + file, + offset, + self.GlobalSubrs, + self.cff2GetGlyphOrder, + isCFF2=self._isCFF2, + ) + top.decompile(data) + return top + + def toXML(self, xmlWriter): + for i in range(len(self)): + xmlWriter.begintag("FontDict", index=i) + xmlWriter.newline() + self[i].toXML(xmlWriter) + xmlWriter.endtag("FontDict") + xmlWriter.newline() + + +class FDArrayIndex(Index): + compilerClass = FDArrayIndexCompiler + + def toXML(self, xmlWriter): + for i in range(len(self)): + xmlWriter.begintag("FontDict", index=i) + xmlWriter.newline() + self[i].toXML(xmlWriter) + xmlWriter.endtag("FontDict") + xmlWriter.newline() + + def produceItem(self, index, data, file, offset): + fontDict = FontDict( + self.strings, + file, + offset, + self.GlobalSubrs, + isCFF2=self._isCFF2, + vstore=self.vstore, + ) + fontDict.decompile(data) + return fontDict + + def fromXML(self, name, attrs, content): + if name != "FontDict": + return + fontDict = FontDict() + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + fontDict.fromXML(name, attrs, content) + self.append(fontDict) + + +class VarStoreData(object): + def __init__(self, file=None, otVarStore=None): + self.file = file + self.data = None + self.otVarStore = otVarStore + self.font = TTFont() # dummy font for the decompile function. + + def decompile(self): + if self.file: + # read data in from file. Assume position is correct. + length = readCard16(self.file) + self.data = self.file.read(length) + globalState = {} + reader = OTTableReader(self.data, globalState) + self.otVarStore = ot.VarStore() + self.otVarStore.decompile(reader, self.font) + return self + + def compile(self): + writer = OTTableWriter() + self.otVarStore.compile(writer, self.font) + # Note that this omits the initial Card16 length from the CFF2 + # VarStore data block + self.data = writer.getAllData() + + def writeXML(self, xmlWriter, name): + self.otVarStore.toXML(xmlWriter, self.font) + + def xmlRead(self, name, attrs, content, parent): + self.otVarStore = ot.VarStore() + for element in content: + if isinstance(element, tuple): + name, attrs, content = element + self.otVarStore.fromXML(name, attrs, content, self.font) + else: + pass + return None + + def __len__(self): + return len(self.data) + + def getNumRegions(self, vsIndex): + if vsIndex is None: + vsIndex = 0 + varData = self.otVarStore.VarData[vsIndex] + numRegions = varData.VarRegionCount + return numRegions + + +class FDSelect(object): + def __init__(self, file=None, numGlyphs=None, format=None): + if file: + # read data in from file + self.format = readCard8(file) + if self.format == 0: + from array import array + + self.gidArray = array("B", file.read(numGlyphs)).tolist() + elif self.format == 3: + gidArray = [None] * numGlyphs + nRanges = readCard16(file) + fd = None + prev = None + for i in range(nRanges): + first = readCard16(file) + if prev is not None: + for glyphID in range(prev, first): + gidArray[glyphID] = fd + prev = first + fd = readCard8(file) + if prev is not None: + first = readCard16(file) + for glyphID in range(prev, first): + gidArray[glyphID] = fd + self.gidArray = gidArray + elif self.format == 4: + gidArray = [None] * numGlyphs + nRanges = readCard32(file) + fd = None + prev = None + for i in range(nRanges): + first = readCard32(file) + if prev is not None: + for glyphID in range(prev, first): + gidArray[glyphID] = fd + prev = first + fd = readCard16(file) + if prev is not None: + first = readCard32(file) + for glyphID in range(prev, first): + gidArray[glyphID] = fd + self.gidArray = gidArray + else: + assert False, "unsupported FDSelect format: %s" % format + else: + # reading from XML. Make empty gidArray, and leave format as passed in. + # format is None will result in the smallest representation being used. + self.format = format + self.gidArray = [] + + def __len__(self): + return len(self.gidArray) + + def __getitem__(self, index): + return self.gidArray[index] + + def __setitem__(self, index, fdSelectValue): + self.gidArray[index] = fdSelectValue + + def append(self, fdSelectValue): + self.gidArray.append(fdSelectValue) + + +class CharStrings(object): + """The ``CharStrings`` in the font represent the instructions for drawing + each glyph. This object presents a dictionary interface to the font's + CharStrings, indexed by glyph name: + + .. code:: python + + tt["CFF "].cff[0].CharStrings["a"] + # + + See :class:`fontTools.misc.psCharStrings.T1CharString` and + :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile, + compile and interpret the glyph drawing instructions in the returned objects. + + """ + + def __init__( + self, + file, + charset, + globalSubrs, + private, + fdSelect, + fdArray, + isCFF2=None, + varStore=None, + ): + self.globalSubrs = globalSubrs + self.varStore = varStore + if file is not None: + self.charStringsIndex = SubrsIndex( + file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2 + ) + self.charStrings = charStrings = {} + for i in range(len(charset)): + charStrings[charset[i]] = i + # read from OTF file: charStrings.values() are indices into + # charStringsIndex. + self.charStringsAreIndexed = 1 + else: + self.charStrings = {} + # read from ttx file: charStrings.values() are actual charstrings + self.charStringsAreIndexed = 0 + self.private = private + if fdSelect is not None: + self.fdSelect = fdSelect + if fdArray is not None: + self.fdArray = fdArray + + def keys(self): + return list(self.charStrings.keys()) + + def values(self): + if self.charStringsAreIndexed: + return self.charStringsIndex + else: + return list(self.charStrings.values()) + + def has_key(self, name): + return name in self.charStrings + + __contains__ = has_key + + def __len__(self): + return len(self.charStrings) + + def __getitem__(self, name): + charString = self.charStrings[name] + if self.charStringsAreIndexed: + charString = self.charStringsIndex[charString] + return charString + + def __setitem__(self, name, charString): + if self.charStringsAreIndexed: + index = self.charStrings[name] + self.charStringsIndex[index] = charString + else: + self.charStrings[name] = charString + + def getItemAndSelector(self, name): + if self.charStringsAreIndexed: + index = self.charStrings[name] + return self.charStringsIndex.getItemAndSelector(index) + else: + if hasattr(self, "fdArray"): + if hasattr(self, "fdSelect"): + sel = self.charStrings[name].fdSelectIndex + else: + sel = 0 + else: + sel = None + return self.charStrings[name], sel + + def toXML(self, xmlWriter): + names = sorted(self.keys()) + for name in names: + charStr, fdSelectIndex = self.getItemAndSelector(name) + if charStr.needsDecompilation(): + raw = [("raw", 1)] + else: + raw = [] + if fdSelectIndex is None: + xmlWriter.begintag("CharString", [("name", name)] + raw) + else: + xmlWriter.begintag( + "CharString", + [("name", name), ("fdSelectIndex", fdSelectIndex)] + raw, + ) + xmlWriter.newline() + charStr.toXML(xmlWriter) + xmlWriter.endtag("CharString") + xmlWriter.newline() + + def fromXML(self, name, attrs, content): + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + if name != "CharString": + continue + fdID = -1 + if hasattr(self, "fdArray"): + try: + fdID = safeEval(attrs["fdSelectIndex"]) + except KeyError: + fdID = 0 + private = self.fdArray[fdID].Private + else: + private = self.private + + glyphName = attrs["name"] + charStringClass = psCharStrings.T2CharString + charString = charStringClass(private=private, globalSubrs=self.globalSubrs) + charString.fromXML(name, attrs, content) + if fdID >= 0: + charString.fdSelectIndex = fdID + self[glyphName] = charString + + +def readCard8(file): + return byteord(file.read(1)) + + +def readCard16(file): + (value,) = struct.unpack(">H", file.read(2)) + return value + + +def readCard32(file): + (value,) = struct.unpack(">L", file.read(4)) + return value + + +def writeCard8(file, value): + file.write(bytechr(value)) + + +def writeCard16(file, value): + file.write(struct.pack(">H", value)) + + +def writeCard32(file, value): + file.write(struct.pack(">L", value)) + + +def packCard8(value): + return bytechr(value) + + +def packCard16(value): + return struct.pack(">H", value) + + +def packCard32(value): + return struct.pack(">L", value) + + +def buildOperatorDict(table): + d = {} + for op, name, arg, default, conv in table: + d[op] = (name, arg) + return d + + +def buildOpcodeDict(table): + d = {} + for op, name, arg, default, conv in table: + if isinstance(op, tuple): + op = bytechr(op[0]) + bytechr(op[1]) + else: + op = bytechr(op) + d[name] = (op, arg) + return d + + +def buildOrder(table): + l = [] + for op, name, arg, default, conv in table: + l.append(name) + return l + + +def buildDefaults(table): + d = {} + for op, name, arg, default, conv in table: + if default is not None: + d[name] = default + return d + + +def buildConverters(table): + d = {} + for op, name, arg, default, conv in table: + d[name] = conv + return d + + +class SimpleConverter(object): + def read(self, parent, value): + if not hasattr(parent, "file"): + return self._read(parent, value) + file = parent.file + pos = file.tell() + try: + return self._read(parent, value) + finally: + file.seek(pos) + + def _read(self, parent, value): + return value + + def write(self, parent, value): + return value + + def xmlWrite(self, xmlWriter, name, value): + xmlWriter.simpletag(name, value=value) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + return attrs["value"] + + +class ASCIIConverter(SimpleConverter): + def _read(self, parent, value): + return tostr(value, encoding="ascii") + + def write(self, parent, value): + return tobytes(value, encoding="ascii") + + def xmlWrite(self, xmlWriter, name, value): + xmlWriter.simpletag(name, value=tostr(value, encoding="ascii")) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + return tobytes(attrs["value"], encoding=("ascii")) + + +class Latin1Converter(SimpleConverter): + def _read(self, parent, value): + return tostr(value, encoding="latin1") + + def write(self, parent, value): + return tobytes(value, encoding="latin1") + + def xmlWrite(self, xmlWriter, name, value): + value = tostr(value, encoding="latin1") + if name in ["Notice", "Copyright"]: + value = re.sub(r"[\r\n]\s+", " ", value) + xmlWriter.simpletag(name, value=value) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + return tobytes(attrs["value"], encoding=("latin1")) + + +def parseNum(s): + try: + value = int(s) + except: + value = float(s) + return value + + +def parseBlendList(s): + valueList = [] + for element in s: + if isinstance(element, str): + continue + name, attrs, content = element + blendList = attrs["value"].split() + blendList = [eval(val) for val in blendList] + valueList.append(blendList) + if len(valueList) == 1: + valueList = valueList[0] + return valueList + + +class NumberConverter(SimpleConverter): + def xmlWrite(self, xmlWriter, name, value): + if isinstance(value, list): + xmlWriter.begintag(name) + xmlWriter.newline() + xmlWriter.indent() + blendValue = " ".join([str(val) for val in value]) + xmlWriter.simpletag(kBlendDictOpName, value=blendValue) + xmlWriter.newline() + xmlWriter.dedent() + xmlWriter.endtag(name) + xmlWriter.newline() + else: + xmlWriter.simpletag(name, value=value) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + valueString = attrs.get("value", None) + if valueString is None: + value = parseBlendList(content) + else: + value = parseNum(attrs["value"]) + return value + + +class ArrayConverter(SimpleConverter): + def xmlWrite(self, xmlWriter, name, value): + if value and isinstance(value[0], list): + xmlWriter.begintag(name) + xmlWriter.newline() + xmlWriter.indent() + for valueList in value: + blendValue = " ".join([str(val) for val in valueList]) + xmlWriter.simpletag(kBlendDictOpName, value=blendValue) + xmlWriter.newline() + xmlWriter.dedent() + xmlWriter.endtag(name) + xmlWriter.newline() + else: + value = " ".join([str(val) for val in value]) + xmlWriter.simpletag(name, value=value) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + valueString = attrs.get("value", None) + if valueString is None: + valueList = parseBlendList(content) + else: + values = valueString.split() + valueList = [parseNum(value) for value in values] + return valueList + + +class TableConverter(SimpleConverter): + def xmlWrite(self, xmlWriter, name, value): + xmlWriter.begintag(name) + xmlWriter.newline() + value.toXML(xmlWriter) + xmlWriter.endtag(name) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + ob = self.getClass()() + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + ob.fromXML(name, attrs, content) + return ob + + +class PrivateDictConverter(TableConverter): + def getClass(self): + return PrivateDict + + def _read(self, parent, value): + size, offset = value + file = parent.file + isCFF2 = parent._isCFF2 + try: + vstore = parent.vstore + except AttributeError: + vstore = None + priv = PrivateDict(parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore) + file.seek(offset) + data = file.read(size) + assert len(data) == size + priv.decompile(data) + return priv + + def write(self, parent, value): + return (0, 0) # dummy value + + +class SubrsConverter(TableConverter): + def getClass(self): + return SubrsIndex + + def _read(self, parent, value): + file = parent.file + isCFF2 = parent._isCFF2 + file.seek(parent.offset + value) # Offset(self) + return SubrsIndex(file, isCFF2=isCFF2) + + def write(self, parent, value): + return 0 # dummy value + + +class CharStringsConverter(TableConverter): + def _read(self, parent, value): + file = parent.file + isCFF2 = parent._isCFF2 + charset = parent.charset + varStore = getattr(parent, "VarStore", None) + globalSubrs = parent.GlobalSubrs + if hasattr(parent, "FDArray"): + fdArray = parent.FDArray + if hasattr(parent, "FDSelect"): + fdSelect = parent.FDSelect + else: + fdSelect = None + private = None + else: + fdSelect, fdArray = None, None + private = parent.Private + file.seek(value) # Offset(0) + charStrings = CharStrings( + file, + charset, + globalSubrs, + private, + fdSelect, + fdArray, + isCFF2=isCFF2, + varStore=varStore, + ) + return charStrings + + def write(self, parent, value): + return 0 # dummy value + + def xmlRead(self, name, attrs, content, parent): + if hasattr(parent, "FDArray"): + # if it is a CID-keyed font, then the private Dict is extracted from the + # parent.FDArray + fdArray = parent.FDArray + if hasattr(parent, "FDSelect"): + fdSelect = parent.FDSelect + else: + fdSelect = None + private = None + else: + # if it is a name-keyed font, then the private dict is in the top dict, + # and + # there is no fdArray. + private, fdSelect, fdArray = parent.Private, None, None + charStrings = CharStrings( + None, + None, + parent.GlobalSubrs, + private, + fdSelect, + fdArray, + varStore=getattr(parent, "VarStore", None), + ) + charStrings.fromXML(name, attrs, content) + return charStrings + + +class CharsetConverter(SimpleConverter): + def _read(self, parent, value): + isCID = hasattr(parent, "ROS") + if value > 2: + numGlyphs = parent.numGlyphs + file = parent.file + file.seek(value) + log.log(DEBUG, "loading charset at %s", value) + format = readCard8(file) + if format == 0: + charset = parseCharset0(numGlyphs, file, parent.strings, isCID) + elif format == 1 or format == 2: + charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) + else: + raise NotImplementedError + assert len(charset) == numGlyphs + log.log(DEBUG, " charset end at %s", file.tell()) + # make sure glyph names are unique + allNames = {} + newCharset = [] + for glyphName in charset: + if glyphName in allNames: + # make up a new glyphName that's unique + n = allNames[glyphName] + while (glyphName + "#" + str(n)) in allNames: + n += 1 + allNames[glyphName] = n + 1 + glyphName = glyphName + "#" + str(n) + allNames[glyphName] = 1 + newCharset.append(glyphName) + charset = newCharset + else: # offset == 0 -> no charset data. + if isCID or "CharStrings" not in parent.rawDict: + # We get here only when processing fontDicts from the FDArray of + # CFF-CID fonts. Only the real topDict references the chrset. + assert value == 0 + charset = None + elif value == 0: + charset = cffISOAdobeStrings + elif value == 1: + charset = cffIExpertStrings + elif value == 2: + charset = cffExpertSubsetStrings + if charset and (len(charset) != parent.numGlyphs): + charset = charset[: parent.numGlyphs] + return charset + + def write(self, parent, value): + return 0 # dummy value + + def xmlWrite(self, xmlWriter, name, value): + # XXX only write charset when not in OT/TTX context, where we + # dump charset as a separate "GlyphOrder" table. + # # xmlWriter.simpletag("charset") + xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + pass + + +class CharsetCompiler(object): + def __init__(self, strings, charset, parent): + assert charset[0] == ".notdef" + isCID = hasattr(parent.dictObj, "ROS") + data0 = packCharset0(charset, isCID, strings) + data = packCharset(charset, isCID, strings) + if len(data) < len(data0): + self.data = data + else: + self.data = data0 + self.parent = parent + + def setPos(self, pos, endPos): + self.parent.rawDict["charset"] = pos + + def getDataLength(self): + return len(self.data) + + def toFile(self, file): + file.write(self.data) + + +def getStdCharSet(charset): + # check to see if we can use a predefined charset value. + predefinedCharSetVal = None + predefinedCharSets = [ + (cffISOAdobeStringCount, cffISOAdobeStrings, 0), + (cffExpertStringCount, cffIExpertStrings, 1), + (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2), + ] + lcs = len(charset) + for cnt, pcs, csv in predefinedCharSets: + if predefinedCharSetVal is not None: + break + if lcs > cnt: + continue + predefinedCharSetVal = csv + for i in range(lcs): + if charset[i] != pcs[i]: + predefinedCharSetVal = None + break + return predefinedCharSetVal + + +def getCIDfromName(name, strings): + return int(name[3:]) + + +def getSIDfromName(name, strings): + return strings.getSID(name) + + +def packCharset0(charset, isCID, strings): + fmt = 0 + data = [packCard8(fmt)] + if isCID: + getNameID = getCIDfromName + else: + getNameID = getSIDfromName + + for name in charset[1:]: + data.append(packCard16(getNameID(name, strings))) + return bytesjoin(data) + + +def packCharset(charset, isCID, strings): + fmt = 1 + ranges = [] + first = None + end = 0 + if isCID: + getNameID = getCIDfromName + else: + getNameID = getSIDfromName + + for name in charset[1:]: + SID = getNameID(name, strings) + if first is None: + first = SID + elif end + 1 != SID: + nLeft = end - first + if nLeft > 255: + fmt = 2 + ranges.append((first, nLeft)) + first = SID + end = SID + if end: + nLeft = end - first + if nLeft > 255: + fmt = 2 + ranges.append((first, nLeft)) + + data = [packCard8(fmt)] + if fmt == 1: + nLeftFunc = packCard8 + else: + nLeftFunc = packCard16 + for first, nLeft in ranges: + data.append(packCard16(first) + nLeftFunc(nLeft)) + return bytesjoin(data) + + +def parseCharset0(numGlyphs, file, strings, isCID): + charset = [".notdef"] + if isCID: + for i in range(numGlyphs - 1): + CID = readCard16(file) + charset.append("cid" + str(CID).zfill(5)) + else: + for i in range(numGlyphs - 1): + SID = readCard16(file) + charset.append(strings[SID]) + return charset + + +def parseCharset(numGlyphs, file, strings, isCID, fmt): + charset = [".notdef"] + count = 1 + if fmt == 1: + nLeftFunc = readCard8 + else: + nLeftFunc = readCard16 + while count < numGlyphs: + first = readCard16(file) + nLeft = nLeftFunc(file) + if isCID: + for CID in range(first, first + nLeft + 1): + charset.append("cid" + str(CID).zfill(5)) + else: + for SID in range(first, first + nLeft + 1): + charset.append(strings[SID]) + count = count + nLeft + 1 + return charset + + +class EncodingCompiler(object): + def __init__(self, strings, encoding, parent): + assert not isinstance(encoding, str) + data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) + data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) + if len(data0) < len(data1): + self.data = data0 + else: + self.data = data1 + self.parent = parent + + def setPos(self, pos, endPos): + self.parent.rawDict["Encoding"] = pos + + def getDataLength(self): + return len(self.data) + + def toFile(self, file): + file.write(self.data) + + +class EncodingConverter(SimpleConverter): + def _read(self, parent, value): + if value == 0: + return "StandardEncoding" + elif value == 1: + return "ExpertEncoding" + else: + assert value > 1 + file = parent.file + file.seek(value) + log.log(DEBUG, "loading Encoding at %s", value) + fmt = readCard8(file) + haveSupplement = fmt & 0x80 + if haveSupplement: + raise NotImplementedError("Encoding supplements are not yet supported") + fmt = fmt & 0x7F + if fmt == 0: + encoding = parseEncoding0( + parent.charset, file, haveSupplement, parent.strings + ) + elif fmt == 1: + encoding = parseEncoding1( + parent.charset, file, haveSupplement, parent.strings + ) + return encoding + + def write(self, parent, value): + if value == "StandardEncoding": + return 0 + elif value == "ExpertEncoding": + return 1 + return 0 # dummy value + + def xmlWrite(self, xmlWriter, name, value): + if value in ("StandardEncoding", "ExpertEncoding"): + xmlWriter.simpletag(name, name=value) + xmlWriter.newline() + return + xmlWriter.begintag(name) + xmlWriter.newline() + for code in range(len(value)): + glyphName = value[code] + if glyphName != ".notdef": + xmlWriter.simpletag("map", code=hex(code), name=glyphName) + xmlWriter.newline() + xmlWriter.endtag(name) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + if "name" in attrs: + return attrs["name"] + encoding = [".notdef"] * 256 + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + code = safeEval(attrs["code"]) + glyphName = attrs["name"] + encoding[code] = glyphName + return encoding + + +def parseEncoding0(charset, file, haveSupplement, strings): + nCodes = readCard8(file) + encoding = [".notdef"] * 256 + for glyphID in range(1, nCodes + 1): + code = readCard8(file) + if code != 0: + encoding[code] = charset[glyphID] + return encoding + + +def parseEncoding1(charset, file, haveSupplement, strings): + nRanges = readCard8(file) + encoding = [".notdef"] * 256 + glyphID = 1 + for i in range(nRanges): + code = readCard8(file) + nLeft = readCard8(file) + for glyphID in range(glyphID, glyphID + nLeft + 1): + encoding[code] = charset[glyphID] + code = code + 1 + glyphID = glyphID + 1 + return encoding + + +def packEncoding0(charset, encoding, strings): + fmt = 0 + m = {} + for code in range(len(encoding)): + name = encoding[code] + if name != ".notdef": + m[name] = code + codes = [] + for name in charset[1:]: + code = m.get(name) + codes.append(code) + + while codes and codes[-1] is None: + codes.pop() + + data = [packCard8(fmt), packCard8(len(codes))] + for code in codes: + if code is None: + code = 0 + data.append(packCard8(code)) + return bytesjoin(data) + + +def packEncoding1(charset, encoding, strings): + fmt = 1 + m = {} + for code in range(len(encoding)): + name = encoding[code] + if name != ".notdef": + m[name] = code + ranges = [] + first = None + end = 0 + for name in charset[1:]: + code = m.get(name, -1) + if first is None: + first = code + elif end + 1 != code: + nLeft = end - first + ranges.append((first, nLeft)) + first = code + end = code + nLeft = end - first + ranges.append((first, nLeft)) + + # remove unencoded glyphs at the end. + while ranges and ranges[-1][0] == -1: + ranges.pop() + + data = [packCard8(fmt), packCard8(len(ranges))] + for first, nLeft in ranges: + if first == -1: # unencoded + first = 0 + data.append(packCard8(first) + packCard8(nLeft)) + return bytesjoin(data) + + +class FDArrayConverter(TableConverter): + def _read(self, parent, value): + try: + vstore = parent.VarStore + except AttributeError: + vstore = None + file = parent.file + isCFF2 = parent._isCFF2 + file.seek(value) + fdArray = FDArrayIndex(file, isCFF2=isCFF2) + fdArray.vstore = vstore + fdArray.strings = parent.strings + fdArray.GlobalSubrs = parent.GlobalSubrs + return fdArray + + def write(self, parent, value): + return 0 # dummy value + + def xmlRead(self, name, attrs, content, parent): + fdArray = FDArrayIndex() + for element in content: + if isinstance(element, str): + continue + name, attrs, content = element + fdArray.fromXML(name, attrs, content) + return fdArray + + +class FDSelectConverter(SimpleConverter): + def _read(self, parent, value): + file = parent.file + file.seek(value) + fdSelect = FDSelect(file, parent.numGlyphs) + return fdSelect + + def write(self, parent, value): + return 0 # dummy value + + # The FDSelect glyph data is written out to XML in the charstring keys, + # so we write out only the format selector + def xmlWrite(self, xmlWriter, name, value): + xmlWriter.simpletag(name, [("format", value.format)]) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + fmt = safeEval(attrs["format"]) + file = None + numGlyphs = None + fdSelect = FDSelect(file, numGlyphs, fmt) + return fdSelect + + +class VarStoreConverter(SimpleConverter): + def _read(self, parent, value): + file = parent.file + file.seek(value) + varStore = VarStoreData(file) + varStore.decompile() + return varStore + + def write(self, parent, value): + return 0 # dummy value + + def xmlWrite(self, xmlWriter, name, value): + value.writeXML(xmlWriter, name) + + def xmlRead(self, name, attrs, content, parent): + varStore = VarStoreData() + varStore.xmlRead(name, attrs, content, parent) + return varStore + + +def packFDSelect0(fdSelectArray): + fmt = 0 + data = [packCard8(fmt)] + for index in fdSelectArray: + data.append(packCard8(index)) + return bytesjoin(data) + + +def packFDSelect3(fdSelectArray): + fmt = 3 + fdRanges = [] + lenArray = len(fdSelectArray) + lastFDIndex = -1 + for i in range(lenArray): + fdIndex = fdSelectArray[i] + if lastFDIndex != fdIndex: + fdRanges.append([i, fdIndex]) + lastFDIndex = fdIndex + sentinelGID = i + 1 + + data = [packCard8(fmt)] + data.append(packCard16(len(fdRanges))) + for fdRange in fdRanges: + data.append(packCard16(fdRange[0])) + data.append(packCard8(fdRange[1])) + data.append(packCard16(sentinelGID)) + return bytesjoin(data) + + +def packFDSelect4(fdSelectArray): + fmt = 4 + fdRanges = [] + lenArray = len(fdSelectArray) + lastFDIndex = -1 + for i in range(lenArray): + fdIndex = fdSelectArray[i] + if lastFDIndex != fdIndex: + fdRanges.append([i, fdIndex]) + lastFDIndex = fdIndex + sentinelGID = i + 1 + + data = [packCard8(fmt)] + data.append(packCard32(len(fdRanges))) + for fdRange in fdRanges: + data.append(packCard32(fdRange[0])) + data.append(packCard16(fdRange[1])) + data.append(packCard32(sentinelGID)) + return bytesjoin(data) + + +class FDSelectCompiler(object): + def __init__(self, fdSelect, parent): + fmt = fdSelect.format + fdSelectArray = fdSelect.gidArray + if fmt == 0: + self.data = packFDSelect0(fdSelectArray) + elif fmt == 3: + self.data = packFDSelect3(fdSelectArray) + elif fmt == 4: + self.data = packFDSelect4(fdSelectArray) + else: + # choose smaller of the two formats + data0 = packFDSelect0(fdSelectArray) + data3 = packFDSelect3(fdSelectArray) + if len(data0) < len(data3): + self.data = data0 + fdSelect.format = 0 + else: + self.data = data3 + fdSelect.format = 3 + + self.parent = parent + + def setPos(self, pos, endPos): + self.parent.rawDict["FDSelect"] = pos + + def getDataLength(self): + return len(self.data) + + def toFile(self, file): + file.write(self.data) + + +class VarStoreCompiler(object): + def __init__(self, varStoreData, parent): + self.parent = parent + if not varStoreData.data: + varStoreData.compile() + data = [packCard16(len(varStoreData.data)), varStoreData.data] + self.data = bytesjoin(data) + + def setPos(self, pos, endPos): + self.parent.rawDict["VarStore"] = pos + + def getDataLength(self): + return len(self.data) + + def toFile(self, file): + file.write(self.data) + + +class ROSConverter(SimpleConverter): + def xmlWrite(self, xmlWriter, name, value): + registry, order, supplement = value + xmlWriter.simpletag( + name, + [ + ("Registry", tostr(registry)), + ("Order", tostr(order)), + ("Supplement", supplement), + ], + ) + xmlWriter.newline() + + def xmlRead(self, name, attrs, content, parent): + return (attrs["Registry"], attrs["Order"], safeEval(attrs["Supplement"])) + + +topDictOperators = [ + # opcode name argument type default converter + (25, "maxstack", "number", None, None), + ((12, 30), "ROS", ("SID", "SID", "number"), None, ROSConverter()), + ((12, 20), "SyntheticBase", "number", None, None), + (0, "version", "SID", None, None), + (1, "Notice", "SID", None, Latin1Converter()), + ((12, 0), "Copyright", "SID", None, Latin1Converter()), + (2, "FullName", "SID", None, None), + ((12, 38), "FontName", "SID", None, None), + (3, "FamilyName", "SID", None, None), + (4, "Weight", "SID", None, None), + ((12, 1), "isFixedPitch", "number", 0, None), + ((12, 2), "ItalicAngle", "number", 0, None), + ((12, 3), "UnderlinePosition", "number", -100, None), + ((12, 4), "UnderlineThickness", "number", 50, None), + ((12, 5), "PaintType", "number", 0, None), + ((12, 6), "CharstringType", "number", 2, None), + ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None), + (13, "UniqueID", "number", None, None), + (5, "FontBBox", "array", [0, 0, 0, 0], None), + ((12, 8), "StrokeWidth", "number", 0, None), + (14, "XUID", "array", None, None), + ((12, 21), "PostScript", "SID", None, None), + ((12, 22), "BaseFontName", "SID", None, None), + ((12, 23), "BaseFontBlend", "delta", None, None), + ((12, 31), "CIDFontVersion", "number", 0, None), + ((12, 32), "CIDFontRevision", "number", 0, None), + ((12, 33), "CIDFontType", "number", 0, None), + ((12, 34), "CIDCount", "number", 8720, None), + (15, "charset", "number", None, CharsetConverter()), + ((12, 35), "UIDBase", "number", None, None), + (16, "Encoding", "number", 0, EncodingConverter()), + (18, "Private", ("number", "number"), None, PrivateDictConverter()), + ((12, 37), "FDSelect", "number", None, FDSelectConverter()), + ((12, 36), "FDArray", "number", None, FDArrayConverter()), + (17, "CharStrings", "number", None, CharStringsConverter()), + (24, "VarStore", "number", None, VarStoreConverter()), +] + +topDictOperators2 = [ + # opcode name argument type default converter + (25, "maxstack", "number", None, None), + ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None), + ((12, 37), "FDSelect", "number", None, FDSelectConverter()), + ((12, 36), "FDArray", "number", None, FDArrayConverter()), + (17, "CharStrings", "number", None, CharStringsConverter()), + (24, "VarStore", "number", None, VarStoreConverter()), +] + +# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, +# in order for the font to compile back from xml. + +kBlendDictOpName = "blend" +blendOp = 23 + +privateDictOperators = [ + # opcode name argument type default converter + (22, "vsindex", "number", None, None), + ( + blendOp, + kBlendDictOpName, + "blendList", + None, + None, + ), # This is for reading to/from XML: it not written to CFF. + (6, "BlueValues", "delta", None, None), + (7, "OtherBlues", "delta", None, None), + (8, "FamilyBlues", "delta", None, None), + (9, "FamilyOtherBlues", "delta", None, None), + ((12, 9), "BlueScale", "number", 0.039625, None), + ((12, 10), "BlueShift", "number", 7, None), + ((12, 11), "BlueFuzz", "number", 1, None), + (10, "StdHW", "number", None, None), + (11, "StdVW", "number", None, None), + ((12, 12), "StemSnapH", "delta", None, None), + ((12, 13), "StemSnapV", "delta", None, None), + ((12, 14), "ForceBold", "number", 0, None), + ((12, 15), "ForceBoldThreshold", "number", None, None), # deprecated + ((12, 16), "lenIV", "number", None, None), # deprecated + ((12, 17), "LanguageGroup", "number", 0, None), + ((12, 18), "ExpansionFactor", "number", 0.06, None), + ((12, 19), "initialRandomSeed", "number", 0, None), + (20, "defaultWidthX", "number", 0, None), + (21, "nominalWidthX", "number", 0, None), + (19, "Subrs", "number", None, SubrsConverter()), +] + +privateDictOperators2 = [ + # opcode name argument type default converter + (22, "vsindex", "number", None, None), + ( + blendOp, + kBlendDictOpName, + "blendList", + None, + None, + ), # This is for reading to/from XML: it not written to CFF. + (6, "BlueValues", "delta", None, None), + (7, "OtherBlues", "delta", None, None), + (8, "FamilyBlues", "delta", None, None), + (9, "FamilyOtherBlues", "delta", None, None), + ((12, 9), "BlueScale", "number", 0.039625, None), + ((12, 10), "BlueShift", "number", 7, None), + ((12, 11), "BlueFuzz", "number", 1, None), + (10, "StdHW", "number", None, None), + (11, "StdVW", "number", None, None), + ((12, 12), "StemSnapH", "delta", None, None), + ((12, 13), "StemSnapV", "delta", None, None), + ((12, 17), "LanguageGroup", "number", 0, None), + ((12, 18), "ExpansionFactor", "number", 0.06, None), + (19, "Subrs", "number", None, SubrsConverter()), +] + + +def addConverters(table): + for i in range(len(table)): + op, name, arg, default, conv = table[i] + if conv is not None: + continue + if arg in ("delta", "array"): + conv = ArrayConverter() + elif arg == "number": + conv = NumberConverter() + elif arg == "SID": + conv = ASCIIConverter() + elif arg == "blendList": + conv = None + else: + assert False + table[i] = op, name, arg, default, conv + + +addConverters(privateDictOperators) +addConverters(topDictOperators) + + +class TopDictDecompiler(psCharStrings.DictDecompiler): + operators = buildOperatorDict(topDictOperators) + + +class PrivateDictDecompiler(psCharStrings.DictDecompiler): + operators = buildOperatorDict(privateDictOperators) + + +class DictCompiler(object): + maxBlendStack = 0 + + def __init__(self, dictObj, strings, parent, isCFF2=None): + if strings: + assert isinstance(strings, IndexedStrings) + if isCFF2 is None and hasattr(parent, "isCFF2"): + isCFF2 = parent.isCFF2 + assert isCFF2 is not None + self.isCFF2 = isCFF2 + self.dictObj = dictObj + self.strings = strings + self.parent = parent + rawDict = {} + for name in dictObj.order: + value = getattr(dictObj, name, None) + if value is None: + continue + conv = dictObj.converters[name] + value = conv.write(dictObj, value) + if value == dictObj.defaults.get(name): + continue + rawDict[name] = value + self.rawDict = rawDict + + def setPos(self, pos, endPos): + pass + + def getDataLength(self): + return len(self.compile("getDataLength")) + + def compile(self, reason): + log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason) + rawDict = self.rawDict + data = [] + for name in self.dictObj.order: + value = rawDict.get(name) + if value is None: + continue + op, argType = self.opcodes[name] + if isinstance(argType, tuple): + l = len(argType) + assert len(value) == l, "value doesn't match arg type" + for i in range(l): + arg = argType[i] + v = value[i] + arghandler = getattr(self, "arg_" + arg) + data.append(arghandler(v)) + else: + arghandler = getattr(self, "arg_" + argType) + data.append(arghandler(value)) + data.append(op) + data = bytesjoin(data) + return data + + def toFile(self, file): + data = self.compile("toFile") + file.write(data) + + def arg_number(self, num): + if isinstance(num, list): + data = [encodeNumber(val) for val in num] + data.append(encodeNumber(1)) + data.append(bytechr(blendOp)) + datum = bytesjoin(data) + else: + datum = encodeNumber(num) + return datum + + def arg_SID(self, s): + return psCharStrings.encodeIntCFF(self.strings.getSID(s)) + + def arg_array(self, value): + data = [] + for num in value: + data.append(self.arg_number(num)) + return bytesjoin(data) + + def arg_delta(self, value): + if not value: + return b"" + val0 = value[0] + if isinstance(val0, list): + data = self.arg_delta_blend(value) + else: + out = [] + last = 0 + for v in value: + out.append(v - last) + last = v + data = [] + for num in out: + data.append(encodeNumber(num)) + return bytesjoin(data) + + def arg_delta_blend(self, value): + """A delta list with blend lists has to be *all* blend lists. + + The value is a list is arranged as follows:: + + [ + [V0, d0..dn] + [V1, d0..dn] + ... + [Vm, d0..dn] + ] + + ``V`` is the absolute coordinate value from the default font, and ``d0-dn`` + are the delta values from the *n* regions. Each ``V`` is an absolute + coordinate from the default font. + + We want to return a list:: + + [ + [v0, v1..vm] + [d0..dn] + ... + [d0..dn] + numBlends + blendOp + ] + + where each ``v`` is relative to the previous default font value. + """ + numMasters = len(value[0]) + numBlends = len(value) + numStack = (numBlends * numMasters) + 1 + if numStack > self.maxBlendStack: + # Figure out the max number of value we can blend + # and divide this list up into chunks of that size. + + numBlendValues = int((self.maxBlendStack - 1) / numMasters) + out = [] + while True: + numVal = min(len(value), numBlendValues) + if numVal == 0: + break + valList = value[0:numVal] + out1 = self.arg_delta_blend(valList) + out.extend(out1) + value = value[numVal:] + else: + firstList = [0] * numBlends + deltaList = [None] * numBlends + i = 0 + prevVal = 0 + while i < numBlends: + # For PrivateDict BlueValues, the default font + # values are absolute, not relative. + # Must convert these back to relative coordinates + # befor writing to CFF2. + defaultValue = value[i][0] + firstList[i] = defaultValue - prevVal + prevVal = defaultValue + deltaList[i] = value[i][1:] + i += 1 + + relValueList = firstList + for blendList in deltaList: + relValueList.extend(blendList) + out = [encodeNumber(val) for val in relValueList] + out.append(encodeNumber(numBlends)) + out.append(bytechr(blendOp)) + return out + + +def encodeNumber(num): + if isinstance(num, float): + return psCharStrings.encodeFloat(num) + else: + return psCharStrings.encodeIntCFF(num) + + +class TopDictCompiler(DictCompiler): + opcodes = buildOpcodeDict(topDictOperators) + + def getChildren(self, strings): + isCFF2 = self.isCFF2 + children = [] + if self.dictObj.cff2GetGlyphOrder is None: + if hasattr(self.dictObj, "charset") and self.dictObj.charset: + if hasattr(self.dictObj, "ROS"): # aka isCID + charsetCode = None + else: + charsetCode = getStdCharSet(self.dictObj.charset) + if charsetCode is None: + children.append( + CharsetCompiler(strings, self.dictObj.charset, self) + ) + else: + self.rawDict["charset"] = charsetCode + if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding: + encoding = self.dictObj.Encoding + if not isinstance(encoding, str): + children.append(EncodingCompiler(strings, encoding, self)) + else: + if hasattr(self.dictObj, "VarStore"): + varStoreData = self.dictObj.VarStore + varStoreComp = VarStoreCompiler(varStoreData, self) + children.append(varStoreComp) + if hasattr(self.dictObj, "FDSelect"): + # I have not yet supported merging a ttx CFF-CID font, as there are + # interesting issues about merging the FDArrays. Here I assume that + # either the font was read from XML, and the FDSelect indices are all + # in the charstring data, or the FDSelect array is already fully defined. + fdSelect = self.dictObj.FDSelect + # probably read in from XML; assume fdIndex in CharString data + if len(fdSelect) == 0: + charStrings = self.dictObj.CharStrings + for name in self.dictObj.charset: + fdSelect.append(charStrings[name].fdSelectIndex) + fdSelectComp = FDSelectCompiler(fdSelect, self) + children.append(fdSelectComp) + if hasattr(self.dictObj, "CharStrings"): + items = [] + charStrings = self.dictObj.CharStrings + for name in self.dictObj.charset: + items.append(charStrings[name]) + charStringsComp = CharStringsCompiler(items, strings, self, isCFF2=isCFF2) + children.append(charStringsComp) + if hasattr(self.dictObj, "FDArray"): + # I have not yet supported merging a ttx CFF-CID font, as there are + # interesting issues about merging the FDArrays. Here I assume that the + # FDArray info is correct and complete. + fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) + children.append(fdArrayIndexComp) + children.extend(fdArrayIndexComp.getChildren(strings)) + if hasattr(self.dictObj, "Private"): + privComp = self.dictObj.Private.getCompiler(strings, self) + children.append(privComp) + children.extend(privComp.getChildren(strings)) + return children + + +class FontDictCompiler(DictCompiler): + opcodes = buildOpcodeDict(topDictOperators) + + def __init__(self, dictObj, strings, parent, isCFF2=None): + super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2) + # + # We now take some effort to detect if there were any key/value pairs + # supplied that were ignored in the FontDict context, and issue a warning + # for those cases. + # + ignoredNames = [] + dictObj = self.dictObj + for name in sorted(set(dictObj.converters) - set(dictObj.order)): + if name in dictObj.rawDict: + # The font was directly read from binary. In this + # case, we want to report *all* "useless" key/value + # pairs that are in the font, not just the ones that + # are different from the default. + ignoredNames.append(name) + else: + # The font was probably read from a TTX file. We only + # warn about keys whos value is not the default. The + # ones that have the default value will not be written + # to binary anyway. + default = dictObj.defaults.get(name) + if default is not None: + conv = dictObj.converters[name] + default = conv.read(dictObj, default) + if getattr(dictObj, name, None) != default: + ignoredNames.append(name) + if ignoredNames: + log.warning( + "Some CFF FDArray/FontDict keys were ignored upon compile: " + + " ".join(sorted(ignoredNames)) + ) + + def getChildren(self, strings): + children = [] + if hasattr(self.dictObj, "Private"): + privComp = self.dictObj.Private.getCompiler(strings, self) + children.append(privComp) + children.extend(privComp.getChildren(strings)) + return children + + +class PrivateDictCompiler(DictCompiler): + maxBlendStack = maxStackLimit + opcodes = buildOpcodeDict(privateDictOperators) + + def setPos(self, pos, endPos): + size = endPos - pos + self.parent.rawDict["Private"] = size, pos + self.pos = pos + + def getChildren(self, strings): + children = [] + if hasattr(self.dictObj, "Subrs"): + children.append(self.dictObj.Subrs.getCompiler(strings, self)) + return children + + +class BaseDict(object): + def __init__(self, strings=None, file=None, offset=None, isCFF2=None): + assert (isCFF2 is None) == (file is None) + self.rawDict = {} + self.skipNames = [] + self.strings = strings + if file is None: + return + self._isCFF2 = isCFF2 + self.file = file + if offset is not None: + log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset) + self.offset = offset + + def decompile(self, data): + log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data)) + dec = self.decompilerClass(self.strings, self) + dec.decompile(data) + self.rawDict = dec.getDict() + self.postDecompile() + + def postDecompile(self): + pass + + def getCompiler(self, strings, parent, isCFF2=None): + return self.compilerClass(self, strings, parent, isCFF2=isCFF2) + + def __getattr__(self, name): + if name[:2] == name[-2:] == "__": + # to make deepcopy() and pickle.load() work, we need to signal with + # AttributeError that dunder methods like '__deepcopy__' or '__getstate__' + # aren't implemented. For more details, see: + # https://github.com/fonttools/fonttools/pull/1488 + raise AttributeError(name) + value = self.rawDict.get(name, None) + if value is None: + value = self.defaults.get(name) + if value is None: + raise AttributeError(name) + conv = self.converters[name] + value = conv.read(self, value) + setattr(self, name, value) + return value + + def toXML(self, xmlWriter): + for name in self.order: + if name in self.skipNames: + continue + value = getattr(self, name, None) + # XXX For "charset" we never skip calling xmlWrite even if the + # value is None, so we always write the following XML comment: + # + # + # + # Charset is None when 'CFF ' table is imported from XML into an + # empty TTFont(). By writing this comment all the time, we obtain + # the same XML output whether roundtripping XML-to-XML or + # dumping binary-to-XML + if value is None and name != "charset": + continue + conv = self.converters[name] + conv.xmlWrite(xmlWriter, name, value) + ignoredNames = set(self.rawDict) - set(self.order) + if ignoredNames: + xmlWriter.comment( + "some keys were ignored: %s" % " ".join(sorted(ignoredNames)) + ) + xmlWriter.newline() + + def fromXML(self, name, attrs, content): + conv = self.converters[name] + value = conv.xmlRead(name, attrs, content, self) + setattr(self, name, value) + + +class TopDict(BaseDict): + """The ``TopDict`` represents the top-level dictionary holding font + information. CFF2 tables contain a restricted set of top-level entries + as described `here `_, + but CFF tables may contain a wider range of information. This information + can be accessed through attributes or through the dictionary returned + through the ``rawDict`` property: + + .. code:: python + + font = tt["CFF "].cff[0] + font.FamilyName + # 'Linux Libertine O' + font.rawDict["FamilyName"] + # 'Linux Libertine O' + + More information is available in the CFF file's private dictionary, accessed + via the ``Private`` property: + + .. code:: python + + tt["CFF "].cff[0].Private.BlueValues + # [-15, 0, 515, 515, 666, 666] + + """ + + defaults = buildDefaults(topDictOperators) + converters = buildConverters(topDictOperators) + compilerClass = TopDictCompiler + order = buildOrder(topDictOperators) + decompilerClass = TopDictDecompiler + + def __init__( + self, + strings=None, + file=None, + offset=None, + GlobalSubrs=None, + cff2GetGlyphOrder=None, + isCFF2=None, + ): + super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2) + self.cff2GetGlyphOrder = cff2GetGlyphOrder + self.GlobalSubrs = GlobalSubrs + if isCFF2: + self.defaults = buildDefaults(topDictOperators2) + self.charset = cff2GetGlyphOrder() + self.order = buildOrder(topDictOperators2) + else: + self.defaults = buildDefaults(topDictOperators) + self.order = buildOrder(topDictOperators) + + def getGlyphOrder(self): + """Returns a list of glyph names in the CFF font.""" + return self.charset + + def postDecompile(self): + offset = self.rawDict.get("CharStrings") + if offset is None: + return + # get the number of glyphs beforehand. + self.file.seek(offset) + if self._isCFF2: + self.numGlyphs = readCard32(self.file) + else: + self.numGlyphs = readCard16(self.file) + + def toXML(self, xmlWriter): + if hasattr(self, "CharStrings"): + self.decompileAllCharStrings() + if hasattr(self, "ROS"): + self.skipNames = ["Encoding"] + if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): + # these values have default values, but I only want them to show up + # in CID fonts. + self.skipNames = [ + "CIDFontVersion", + "CIDFontRevision", + "CIDFontType", + "CIDCount", + ] + BaseDict.toXML(self, xmlWriter) + + def decompileAllCharStrings(self): + # Make sure that all the Private Dicts have been instantiated. + for i, charString in enumerate(self.CharStrings.values()): + try: + charString.decompile() + except: + log.error("Error in charstring %s", i) + raise + + def recalcFontBBox(self): + fontBBox = None + for charString in self.CharStrings.values(): + bounds = charString.calcBounds(self.CharStrings) + if bounds is not None: + if fontBBox is not None: + fontBBox = unionRect(fontBBox, bounds) + else: + fontBBox = bounds + + if fontBBox is None: + self.FontBBox = self.defaults["FontBBox"][:] + else: + self.FontBBox = list(intRect(fontBBox)) + + +class FontDict(BaseDict): + # + # Since fonttools used to pass a lot of fields that are not relevant in the FDArray + # FontDict, there are 'ttx' files in the wild that contain all these. These got in + # the ttx files because fonttools writes explicit values for all the TopDict default + # values. These are not actually illegal in the context of an FDArray FontDict - you + # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are + # useless since current major company CFF interpreters ignore anything but the set + # listed in this file. So, we just silently skip them. An exception is Weight: this + # is not used by any interpreter, but some foundries have asked that this be + # supported in FDArray FontDicts just to preserve information about the design when + # the font is being inspected. + # + # On top of that, there are fonts out there that contain such useless FontDict values. + # + # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading + # from binary or when reading from XML, but by overriding `order` with a limited + # list of names, we ensure that only the useful names ever get exported to XML and + # ever get compiled into the binary font. + # + # We override compilerClass so we can warn about "useless" key/value pairs, either + # from the original binary font or from TTX input. + # + # See: + # - https://github.com/fonttools/fonttools/issues/740 + # - https://github.com/fonttools/fonttools/issues/601 + # - https://github.com/adobe-type-tools/afdko/issues/137 + # + defaults = {} + converters = buildConverters(topDictOperators) + compilerClass = FontDictCompiler + orderCFF = ["FontName", "FontMatrix", "Weight", "Private"] + orderCFF2 = ["Private"] + decompilerClass = TopDictDecompiler + + def __init__( + self, + strings=None, + file=None, + offset=None, + GlobalSubrs=None, + isCFF2=None, + vstore=None, + ): + super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2) + self.vstore = vstore + self.setCFF2(isCFF2) + + def setCFF2(self, isCFF2): + # isCFF2 may be None. + if isCFF2: + self.order = self.orderCFF2 + self._isCFF2 = True + else: + self.order = self.orderCFF + self._isCFF2 = False + + +class PrivateDict(BaseDict): + defaults = buildDefaults(privateDictOperators) + converters = buildConverters(privateDictOperators) + order = buildOrder(privateDictOperators) + decompilerClass = PrivateDictDecompiler + compilerClass = PrivateDictCompiler + + def __init__(self, strings=None, file=None, offset=None, isCFF2=None, vstore=None): + super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2) + self.vstore = vstore + if isCFF2: + self.defaults = buildDefaults(privateDictOperators2) + self.order = buildOrder(privateDictOperators2) + # Provide dummy values. This avoids needing to provide + # an isCFF2 state in a lot of places. + self.nominalWidthX = self.defaultWidthX = None + else: + self.defaults = buildDefaults(privateDictOperators) + self.order = buildOrder(privateDictOperators) + + @property + def in_cff2(self): + return self._isCFF2 + + def getNumRegions(self, vi=None): # called from misc/psCharStrings.py + # if getNumRegions is being called, we can assume that VarStore exists. + if vi is None: + if hasattr(self, "vsindex"): + vi = self.vsindex + else: + vi = 0 + numRegions = self.vstore.getNumRegions(vi) + return numRegions + + +class IndexedStrings(object): + + """SID -> string mapping.""" + + def __init__(self, file=None): + if file is None: + strings = [] + else: + strings = [tostr(s, encoding="latin1") for s in Index(file, isCFF2=False)] + self.strings = strings + + def getCompiler(self): + return IndexedStringsCompiler(self, None, self, isCFF2=False) + + def __len__(self): + return len(self.strings) + + def __getitem__(self, SID): + if SID < cffStandardStringCount: + return cffStandardStrings[SID] + else: + return self.strings[SID - cffStandardStringCount] + + def getSID(self, s): + if not hasattr(self, "stringMapping"): + self.buildStringMapping() + s = tostr(s, encoding="latin1") + if s in cffStandardStringMapping: + SID = cffStandardStringMapping[s] + elif s in self.stringMapping: + SID = self.stringMapping[s] + else: + SID = len(self.strings) + cffStandardStringCount + self.strings.append(s) + self.stringMapping[s] = SID + return SID + + def getStrings(self): + return self.strings + + def buildStringMapping(self): + self.stringMapping = {} + for index in range(len(self.strings)): + self.stringMapping[self.strings[index]] = index + cffStandardStringCount + + +# The 391 Standard Strings as used in the CFF format. +# from Adobe Technical None #5176, version 1.0, 18 March 1998 + +cffStandardStrings = [ + ".notdef", + "space", + "exclam", + "quotedbl", + "numbersign", + "dollar", + "percent", + "ampersand", + "quoteright", + "parenleft", + "parenright", + "asterisk", + "plus", + "comma", + "hyphen", + "period", + "slash", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "colon", + "semicolon", + "less", + "equal", + "greater", + "question", + "at", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "bracketleft", + "backslash", + "bracketright", + "asciicircum", + "underscore", + "quoteleft", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "braceleft", + "bar", + "braceright", + "asciitilde", + "exclamdown", + "cent", + "sterling", + "fraction", + "yen", + "florin", + "section", + "currency", + "quotesingle", + "quotedblleft", + "guillemotleft", + "guilsinglleft", + "guilsinglright", + "fi", + "fl", + "endash", + "dagger", + "daggerdbl", + "periodcentered", + "paragraph", + "bullet", + "quotesinglbase", + "quotedblbase", + "quotedblright", + "guillemotright", + "ellipsis", + "perthousand", + "questiondown", + "grave", + "acute", + "circumflex", + "tilde", + "macron", + "breve", + "dotaccent", + "dieresis", + "ring", + "cedilla", + "hungarumlaut", + "ogonek", + "caron", + "emdash", + "AE", + "ordfeminine", + "Lslash", + "Oslash", + "OE", + "ordmasculine", + "ae", + "dotlessi", + "lslash", + "oslash", + "oe", + "germandbls", + "onesuperior", + "logicalnot", + "mu", + "trademark", + "Eth", + "onehalf", + "plusminus", + "Thorn", + "onequarter", + "divide", + "brokenbar", + "degree", + "thorn", + "threequarters", + "twosuperior", + "registered", + "minus", + "eth", + "multiply", + "threesuperior", + "copyright", + "Aacute", + "Acircumflex", + "Adieresis", + "Agrave", + "Aring", + "Atilde", + "Ccedilla", + "Eacute", + "Ecircumflex", + "Edieresis", + "Egrave", + "Iacute", + "Icircumflex", + "Idieresis", + "Igrave", + "Ntilde", + "Oacute", + "Ocircumflex", + "Odieresis", + "Ograve", + "Otilde", + "Scaron", + "Uacute", + "Ucircumflex", + "Udieresis", + "Ugrave", + "Yacute", + "Ydieresis", + "Zcaron", + "aacute", + "acircumflex", + "adieresis", + "agrave", + "aring", + "atilde", + "ccedilla", + "eacute", + "ecircumflex", + "edieresis", + "egrave", + "iacute", + "icircumflex", + "idieresis", + "igrave", + "ntilde", + "oacute", + "ocircumflex", + "odieresis", + "ograve", + "otilde", + "scaron", + "uacute", + "ucircumflex", + "udieresis", + "ugrave", + "yacute", + "ydieresis", + "zcaron", + "exclamsmall", + "Hungarumlautsmall", + "dollaroldstyle", + "dollarsuperior", + "ampersandsmall", + "Acutesmall", + "parenleftsuperior", + "parenrightsuperior", + "twodotenleader", + "onedotenleader", + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + "commasuperior", + "threequartersemdash", + "periodsuperior", + "questionsmall", + "asuperior", + "bsuperior", + "centsuperior", + "dsuperior", + "esuperior", + "isuperior", + "lsuperior", + "msuperior", + "nsuperior", + "osuperior", + "rsuperior", + "ssuperior", + "tsuperior", + "ff", + "ffi", + "ffl", + "parenleftinferior", + "parenrightinferior", + "Circumflexsmall", + "hyphensuperior", + "Gravesmall", + "Asmall", + "Bsmall", + "Csmall", + "Dsmall", + "Esmall", + "Fsmall", + "Gsmall", + "Hsmall", + "Ismall", + "Jsmall", + "Ksmall", + "Lsmall", + "Msmall", + "Nsmall", + "Osmall", + "Psmall", + "Qsmall", + "Rsmall", + "Ssmall", + "Tsmall", + "Usmall", + "Vsmall", + "Wsmall", + "Xsmall", + "Ysmall", + "Zsmall", + "colonmonetary", + "onefitted", + "rupiah", + "Tildesmall", + "exclamdownsmall", + "centoldstyle", + "Lslashsmall", + "Scaronsmall", + "Zcaronsmall", + "Dieresissmall", + "Brevesmall", + "Caronsmall", + "Dotaccentsmall", + "Macronsmall", + "figuredash", + "hypheninferior", + "Ogoneksmall", + "Ringsmall", + "Cedillasmall", + "questiondownsmall", + "oneeighth", + "threeeighths", + "fiveeighths", + "seveneighths", + "onethird", + "twothirds", + "zerosuperior", + "foursuperior", + "fivesuperior", + "sixsuperior", + "sevensuperior", + "eightsuperior", + "ninesuperior", + "zeroinferior", + "oneinferior", + "twoinferior", + "threeinferior", + "fourinferior", + "fiveinferior", + "sixinferior", + "seveninferior", + "eightinferior", + "nineinferior", + "centinferior", + "dollarinferior", + "periodinferior", + "commainferior", + "Agravesmall", + "Aacutesmall", + "Acircumflexsmall", + "Atildesmall", + "Adieresissmall", + "Aringsmall", + "AEsmall", + "Ccedillasmall", + "Egravesmall", + "Eacutesmall", + "Ecircumflexsmall", + "Edieresissmall", + "Igravesmall", + "Iacutesmall", + "Icircumflexsmall", + "Idieresissmall", + "Ethsmall", + "Ntildesmall", + "Ogravesmall", + "Oacutesmall", + "Ocircumflexsmall", + "Otildesmall", + "Odieresissmall", + "OEsmall", + "Oslashsmall", + "Ugravesmall", + "Uacutesmall", + "Ucircumflexsmall", + "Udieresissmall", + "Yacutesmall", + "Thornsmall", + "Ydieresissmall", + "001.000", + "001.001", + "001.002", + "001.003", + "Black", + "Bold", + "Book", + "Light", + "Medium", + "Regular", + "Roman", + "Semibold", +] + +cffStandardStringCount = 391 +assert len(cffStandardStrings) == cffStandardStringCount +# build reverse mapping +cffStandardStringMapping = {} +for _i in range(cffStandardStringCount): + cffStandardStringMapping[cffStandardStrings[_i]] = _i + +cffISOAdobeStrings = [ + ".notdef", + "space", + "exclam", + "quotedbl", + "numbersign", + "dollar", + "percent", + "ampersand", + "quoteright", + "parenleft", + "parenright", + "asterisk", + "plus", + "comma", + "hyphen", + "period", + "slash", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "colon", + "semicolon", + "less", + "equal", + "greater", + "question", + "at", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "bracketleft", + "backslash", + "bracketright", + "asciicircum", + "underscore", + "quoteleft", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "braceleft", + "bar", + "braceright", + "asciitilde", + "exclamdown", + "cent", + "sterling", + "fraction", + "yen", + "florin", + "section", + "currency", + "quotesingle", + "quotedblleft", + "guillemotleft", + "guilsinglleft", + "guilsinglright", + "fi", + "fl", + "endash", + "dagger", + "daggerdbl", + "periodcentered", + "paragraph", + "bullet", + "quotesinglbase", + "quotedblbase", + "quotedblright", + "guillemotright", + "ellipsis", + "perthousand", + "questiondown", + "grave", + "acute", + "circumflex", + "tilde", + "macron", + "breve", + "dotaccent", + "dieresis", + "ring", + "cedilla", + "hungarumlaut", + "ogonek", + "caron", + "emdash", + "AE", + "ordfeminine", + "Lslash", + "Oslash", + "OE", + "ordmasculine", + "ae", + "dotlessi", + "lslash", + "oslash", + "oe", + "germandbls", + "onesuperior", + "logicalnot", + "mu", + "trademark", + "Eth", + "onehalf", + "plusminus", + "Thorn", + "onequarter", + "divide", + "brokenbar", + "degree", + "thorn", + "threequarters", + "twosuperior", + "registered", + "minus", + "eth", + "multiply", + "threesuperior", + "copyright", + "Aacute", + "Acircumflex", + "Adieresis", + "Agrave", + "Aring", + "Atilde", + "Ccedilla", + "Eacute", + "Ecircumflex", + "Edieresis", + "Egrave", + "Iacute", + "Icircumflex", + "Idieresis", + "Igrave", + "Ntilde", + "Oacute", + "Ocircumflex", + "Odieresis", + "Ograve", + "Otilde", + "Scaron", + "Uacute", + "Ucircumflex", + "Udieresis", + "Ugrave", + "Yacute", + "Ydieresis", + "Zcaron", + "aacute", + "acircumflex", + "adieresis", + "agrave", + "aring", + "atilde", + "ccedilla", + "eacute", + "ecircumflex", + "edieresis", + "egrave", + "iacute", + "icircumflex", + "idieresis", + "igrave", + "ntilde", + "oacute", + "ocircumflex", + "odieresis", + "ograve", + "otilde", + "scaron", + "uacute", + "ucircumflex", + "udieresis", + "ugrave", + "yacute", + "ydieresis", + "zcaron", +] + +cffISOAdobeStringCount = 229 +assert len(cffISOAdobeStrings) == cffISOAdobeStringCount + +cffIExpertStrings = [ + ".notdef", + "space", + "exclamsmall", + "Hungarumlautsmall", + "dollaroldstyle", + "dollarsuperior", + "ampersandsmall", + "Acutesmall", + "parenleftsuperior", + "parenrightsuperior", + "twodotenleader", + "onedotenleader", + "comma", + "hyphen", + "period", + "fraction", + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + "colon", + "semicolon", + "commasuperior", + "threequartersemdash", + "periodsuperior", + "questionsmall", + "asuperior", + "bsuperior", + "centsuperior", + "dsuperior", + "esuperior", + "isuperior", + "lsuperior", + "msuperior", + "nsuperior", + "osuperior", + "rsuperior", + "ssuperior", + "tsuperior", + "ff", + "fi", + "fl", + "ffi", + "ffl", + "parenleftinferior", + "parenrightinferior", + "Circumflexsmall", + "hyphensuperior", + "Gravesmall", + "Asmall", + "Bsmall", + "Csmall", + "Dsmall", + "Esmall", + "Fsmall", + "Gsmall", + "Hsmall", + "Ismall", + "Jsmall", + "Ksmall", + "Lsmall", + "Msmall", + "Nsmall", + "Osmall", + "Psmall", + "Qsmall", + "Rsmall", + "Ssmall", + "Tsmall", + "Usmall", + "Vsmall", + "Wsmall", + "Xsmall", + "Ysmall", + "Zsmall", + "colonmonetary", + "onefitted", + "rupiah", + "Tildesmall", + "exclamdownsmall", + "centoldstyle", + "Lslashsmall", + "Scaronsmall", + "Zcaronsmall", + "Dieresissmall", + "Brevesmall", + "Caronsmall", + "Dotaccentsmall", + "Macronsmall", + "figuredash", + "hypheninferior", + "Ogoneksmall", + "Ringsmall", + "Cedillasmall", + "onequarter", + "onehalf", + "threequarters", + "questiondownsmall", + "oneeighth", + "threeeighths", + "fiveeighths", + "seveneighths", + "onethird", + "twothirds", + "zerosuperior", + "onesuperior", + "twosuperior", + "threesuperior", + "foursuperior", + "fivesuperior", + "sixsuperior", + "sevensuperior", + "eightsuperior", + "ninesuperior", + "zeroinferior", + "oneinferior", + "twoinferior", + "threeinferior", + "fourinferior", + "fiveinferior", + "sixinferior", + "seveninferior", + "eightinferior", + "nineinferior", + "centinferior", + "dollarinferior", + "periodinferior", + "commainferior", + "Agravesmall", + "Aacutesmall", + "Acircumflexsmall", + "Atildesmall", + "Adieresissmall", + "Aringsmall", + "AEsmall", + "Ccedillasmall", + "Egravesmall", + "Eacutesmall", + "Ecircumflexsmall", + "Edieresissmall", + "Igravesmall", + "Iacutesmall", + "Icircumflexsmall", + "Idieresissmall", + "Ethsmall", + "Ntildesmall", + "Ogravesmall", + "Oacutesmall", + "Ocircumflexsmall", + "Otildesmall", + "Odieresissmall", + "OEsmall", + "Oslashsmall", + "Ugravesmall", + "Uacutesmall", + "Ucircumflexsmall", + "Udieresissmall", + "Yacutesmall", + "Thornsmall", + "Ydieresissmall", +] + +cffExpertStringCount = 166 +assert len(cffIExpertStrings) == cffExpertStringCount + +cffExpertSubsetStrings = [ + ".notdef", + "space", + "dollaroldstyle", + "dollarsuperior", + "parenleftsuperior", + "parenrightsuperior", + "twodotenleader", + "onedotenleader", + "comma", + "hyphen", + "period", + "fraction", + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + "colon", + "semicolon", + "commasuperior", + "threequartersemdash", + "periodsuperior", + "asuperior", + "bsuperior", + "centsuperior", + "dsuperior", + "esuperior", + "isuperior", + "lsuperior", + "msuperior", + "nsuperior", + "osuperior", + "rsuperior", + "ssuperior", + "tsuperior", + "ff", + "fi", + "fl", + "ffi", + "ffl", + "parenleftinferior", + "parenrightinferior", + "hyphensuperior", + "colonmonetary", + "onefitted", + "rupiah", + "centoldstyle", + "figuredash", + "hypheninferior", + "onequarter", + "onehalf", + "threequarters", + "oneeighth", + "threeeighths", + "fiveeighths", + "seveneighths", + "onethird", + "twothirds", + "zerosuperior", + "onesuperior", + "twosuperior", + "threesuperior", + "foursuperior", + "fivesuperior", + "sixsuperior", + "sevensuperior", + "eightsuperior", + "ninesuperior", + "zeroinferior", + "oneinferior", + "twoinferior", + "threeinferior", + "fourinferior", + "fiveinferior", + "sixinferior", + "seveninferior", + "eightinferior", + "nineinferior", + "centinferior", + "dollarinferior", + "periodinferior", + "commainferior", +] + +cffExpertSubsetStringCount = 87 +assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/specializer.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/specializer.py new file mode 100644 index 0000000..f3645bf --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/specializer.py @@ -0,0 +1,849 @@ +# -*- coding: utf-8 -*- + +"""T2CharString operator specializer and generalizer. + +PostScript glyph drawing operations can be expressed in multiple different +ways. For example, as well as the ``lineto`` operator, there is also a +``hlineto`` operator which draws a horizontal line, removing the need to +specify a ``dx`` coordinate, and a ``vlineto`` operator which draws a +vertical line, removing the need to specify a ``dy`` coordinate. As well +as decompiling :class:`fontTools.misc.psCharStrings.T2CharString` objects +into lists of operations, this module allows for conversion between general +and specific forms of the operation. + +""" + +from fontTools.cffLib import maxStackLimit + + +def stringToProgram(string): + if isinstance(string, str): + string = string.split() + program = [] + for token in string: + try: + token = int(token) + except ValueError: + try: + token = float(token) + except ValueError: + pass + program.append(token) + return program + + +def programToString(program): + return " ".join(str(x) for x in program) + + +def programToCommands(program, getNumRegions=None): + """Takes a T2CharString program list and returns list of commands. + Each command is a two-tuple of commandname,arg-list. The commandname might + be empty string if no commandname shall be emitted (used for glyph width, + hintmask/cntrmask argument, as well as stray arguments at the end of the + program (¯\_(ツ)_/¯). + 'getNumRegions' may be None, or a callable object. It must return the + number of regions. 'getNumRegions' takes a single argument, vsindex. If + the vsindex argument is None, getNumRegions returns the default number + of regions for the charstring, else it returns the numRegions for + the vsindex. + The Charstring may or may not start with a width value. If the first + non-blend operator has an odd number of arguments, then the first argument is + a width, and is popped off. This is complicated with blend operators, as + there may be more than one before the first hint or moveto operator, and each + one reduces several arguments to just one list argument. We have to sum the + number of arguments that are not part of the blend arguments, and all the + 'numBlends' values. We could instead have said that by definition, if there + is a blend operator, there is no width value, since CFF2 Charstrings don't + have width values. I discussed this with Behdad, and we are allowing for an + initial width value in this case because developers may assemble a CFF2 + charstring from CFF Charstrings, which could have width values. + """ + + seenWidthOp = False + vsIndex = None + lenBlendStack = 0 + lastBlendIndex = 0 + commands = [] + stack = [] + it = iter(program) + + for token in it: + if not isinstance(token, str): + stack.append(token) + continue + + if token == "blend": + assert getNumRegions is not None + numSourceFonts = 1 + getNumRegions(vsIndex) + # replace the blend op args on the stack with a single list + # containing all the blend op args. + numBlends = stack[-1] + numBlendArgs = numBlends * numSourceFonts + 1 + # replace first blend op by a list of the blend ops. + stack[-numBlendArgs:] = [stack[-numBlendArgs:]] + lenBlendStack += numBlends + len(stack) - 1 + lastBlendIndex = len(stack) + # if a blend op exists, this is or will be a CFF2 charstring. + continue + + elif token == "vsindex": + vsIndex = stack[-1] + assert type(vsIndex) is int + + elif (not seenWidthOp) and token in { + "hstem", + "hstemhm", + "vstem", + "vstemhm", + "cntrmask", + "hintmask", + "hmoveto", + "vmoveto", + "rmoveto", + "endchar", + }: + seenWidthOp = True + parity = token in {"hmoveto", "vmoveto"} + if lenBlendStack: + # lenBlendStack has the number of args represented by the last blend + # arg and all the preceding args. We need to now add the number of + # args following the last blend arg. + numArgs = lenBlendStack + len(stack[lastBlendIndex:]) + else: + numArgs = len(stack) + if numArgs and (numArgs % 2) ^ parity: + width = stack.pop(0) + commands.append(("", [width])) + + if token in {"hintmask", "cntrmask"}: + if stack: + commands.append(("", stack)) + commands.append((token, [])) + commands.append(("", [next(it)])) + else: + commands.append((token, stack)) + stack = [] + if stack: + commands.append(("", stack)) + return commands + + +def _flattenBlendArgs(args): + token_list = [] + for arg in args: + if isinstance(arg, list): + token_list.extend(arg) + token_list.append("blend") + else: + token_list.append(arg) + return token_list + + +def commandsToProgram(commands): + """Takes a commands list as returned by programToCommands() and converts + it back to a T2CharString program list.""" + program = [] + for op, args in commands: + if any(isinstance(arg, list) for arg in args): + args = _flattenBlendArgs(args) + program.extend(args) + if op: + program.append(op) + return program + + +def _everyN(el, n): + """Group the list el into groups of size n""" + if len(el) % n != 0: + raise ValueError(el) + for i in range(0, len(el), n): + yield el[i : i + n] + + +class _GeneralizerDecombinerCommandsMap(object): + @staticmethod + def rmoveto(args): + if len(args) != 2: + raise ValueError(args) + yield ("rmoveto", args) + + @staticmethod + def hmoveto(args): + if len(args) != 1: + raise ValueError(args) + yield ("rmoveto", [args[0], 0]) + + @staticmethod + def vmoveto(args): + if len(args) != 1: + raise ValueError(args) + yield ("rmoveto", [0, args[0]]) + + @staticmethod + def rlineto(args): + if not args: + raise ValueError(args) + for args in _everyN(args, 2): + yield ("rlineto", args) + + @staticmethod + def hlineto(args): + if not args: + raise ValueError(args) + it = iter(args) + try: + while True: + yield ("rlineto", [next(it), 0]) + yield ("rlineto", [0, next(it)]) + except StopIteration: + pass + + @staticmethod + def vlineto(args): + if not args: + raise ValueError(args) + it = iter(args) + try: + while True: + yield ("rlineto", [0, next(it)]) + yield ("rlineto", [next(it), 0]) + except StopIteration: + pass + + @staticmethod + def rrcurveto(args): + if not args: + raise ValueError(args) + for args in _everyN(args, 6): + yield ("rrcurveto", args) + + @staticmethod + def hhcurveto(args): + if len(args) < 4 or len(args) % 4 > 1: + raise ValueError(args) + if len(args) % 2 == 1: + yield ("rrcurveto", [args[1], args[0], args[2], args[3], args[4], 0]) + args = args[5:] + for args in _everyN(args, 4): + yield ("rrcurveto", [args[0], 0, args[1], args[2], args[3], 0]) + + @staticmethod + def vvcurveto(args): + if len(args) < 4 or len(args) % 4 > 1: + raise ValueError(args) + if len(args) % 2 == 1: + yield ("rrcurveto", [args[0], args[1], args[2], args[3], 0, args[4]]) + args = args[5:] + for args in _everyN(args, 4): + yield ("rrcurveto", [0, args[0], args[1], args[2], 0, args[3]]) + + @staticmethod + def hvcurveto(args): + if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}: + raise ValueError(args) + last_args = None + if len(args) % 2 == 1: + lastStraight = len(args) % 8 == 5 + args, last_args = args[:-5], args[-5:] + it = _everyN(args, 4) + try: + while True: + args = next(it) + yield ("rrcurveto", [args[0], 0, args[1], args[2], 0, args[3]]) + args = next(it) + yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], 0]) + except StopIteration: + pass + if last_args: + args = last_args + if lastStraight: + yield ("rrcurveto", [args[0], 0, args[1], args[2], args[4], args[3]]) + else: + yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], args[4]]) + + @staticmethod + def vhcurveto(args): + if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}: + raise ValueError(args) + last_args = None + if len(args) % 2 == 1: + lastStraight = len(args) % 8 == 5 + args, last_args = args[:-5], args[-5:] + it = _everyN(args, 4) + try: + while True: + args = next(it) + yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], 0]) + args = next(it) + yield ("rrcurveto", [args[0], 0, args[1], args[2], 0, args[3]]) + except StopIteration: + pass + if last_args: + args = last_args + if lastStraight: + yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], args[4]]) + else: + yield ("rrcurveto", [args[0], 0, args[1], args[2], args[4], args[3]]) + + @staticmethod + def rcurveline(args): + if len(args) < 8 or len(args) % 6 != 2: + raise ValueError(args) + args, last_args = args[:-2], args[-2:] + for args in _everyN(args, 6): + yield ("rrcurveto", args) + yield ("rlineto", last_args) + + @staticmethod + def rlinecurve(args): + if len(args) < 8 or len(args) % 2 != 0: + raise ValueError(args) + args, last_args = args[:-6], args[-6:] + for args in _everyN(args, 2): + yield ("rlineto", args) + yield ("rrcurveto", last_args) + + +def _convertBlendOpToArgs(blendList): + # args is list of blend op args. Since we are supporting + # recursive blend op calls, some of these args may also + # be a list of blend op args, and need to be converted before + # we convert the current list. + if any([isinstance(arg, list) for arg in blendList]): + args = [ + i + for e in blendList + for i in (_convertBlendOpToArgs(e) if isinstance(e, list) else [e]) + ] + else: + args = blendList + + # We now know that blendList contains a blend op argument list, even if + # some of the args are lists that each contain a blend op argument list. + # Convert from: + # [default font arg sequence x0,...,xn] + [delta tuple for x0] + ... + [delta tuple for xn] + # to: + # [ [x0] + [delta tuple for x0], + # ..., + # [xn] + [delta tuple for xn] ] + numBlends = args[-1] + # Can't use args.pop() when the args are being used in a nested list + # comprehension. See calling context + args = args[:-1] + + numRegions = len(args) // numBlends - 1 + if not (numBlends * (numRegions + 1) == len(args)): + raise ValueError(blendList) + + defaultArgs = [[arg] for arg in args[:numBlends]] + deltaArgs = args[numBlends:] + numDeltaValues = len(deltaArgs) + deltaList = [ + deltaArgs[i : i + numRegions] for i in range(0, numDeltaValues, numRegions) + ] + blend_args = [a + b + [1] for a, b in zip(defaultArgs, deltaList)] + return blend_args + + +def generalizeCommands(commands, ignoreErrors=False): + result = [] + mapping = _GeneralizerDecombinerCommandsMap + for op, args in commands: + # First, generalize any blend args in the arg list. + if any([isinstance(arg, list) for arg in args]): + try: + args = [ + n + for arg in args + for n in ( + _convertBlendOpToArgs(arg) if isinstance(arg, list) else [arg] + ) + ] + except ValueError: + if ignoreErrors: + # Store op as data, such that consumers of commands do not have to + # deal with incorrect number of arguments. + result.append(("", args)) + result.append(("", [op])) + else: + raise + + func = getattr(mapping, op, None) + if not func: + result.append((op, args)) + continue + try: + for command in func(args): + result.append(command) + except ValueError: + if ignoreErrors: + # Store op as data, such that consumers of commands do not have to + # deal with incorrect number of arguments. + result.append(("", args)) + result.append(("", [op])) + else: + raise + return result + + +def generalizeProgram(program, getNumRegions=None, **kwargs): + return commandsToProgram( + generalizeCommands(programToCommands(program, getNumRegions), **kwargs) + ) + + +def _categorizeVector(v): + """ + Takes X,Y vector v and returns one of r, h, v, or 0 depending on which + of X and/or Y are zero, plus tuple of nonzero ones. If both are zero, + it returns a single zero still. + + >>> _categorizeVector((0,0)) + ('0', (0,)) + >>> _categorizeVector((1,0)) + ('h', (1,)) + >>> _categorizeVector((0,2)) + ('v', (2,)) + >>> _categorizeVector((1,2)) + ('r', (1, 2)) + """ + if not v[0]: + if not v[1]: + return "0", v[:1] + else: + return "v", v[1:] + else: + if not v[1]: + return "h", v[:1] + else: + return "r", v + + +def _mergeCategories(a, b): + if a == "0": + return b + if b == "0": + return a + if a == b: + return a + return None + + +def _negateCategory(a): + if a == "h": + return "v" + if a == "v": + return "h" + assert a in "0r" + return a + + +def _convertToBlendCmds(args): + # return a list of blend commands, and + # the remaining non-blended args, if any. + num_args = len(args) + stack_use = 0 + new_args = [] + i = 0 + while i < num_args: + arg = args[i] + if not isinstance(arg, list): + new_args.append(arg) + i += 1 + stack_use += 1 + else: + prev_stack_use = stack_use + # The arg is a tuple of blend values. + # These are each (master 0,delta 1..delta n, 1) + # Combine as many successive tuples as we can, + # up to the max stack limit. + num_sources = len(arg) - 1 + blendlist = [arg] + i += 1 + stack_use += 1 + num_sources # 1 for the num_blends arg + while (i < num_args) and isinstance(args[i], list): + blendlist.append(args[i]) + i += 1 + stack_use += num_sources + if stack_use + num_sources > maxStackLimit: + # if we are here, max stack is the CFF2 max stack. + # I use the CFF2 max stack limit here rather than + # the 'maxstack' chosen by the client, as the default + # maxstack may have been used unintentionally. For all + # the other operators, this just produces a little less + # optimization, but here it puts a hard (and low) limit + # on the number of source fonts that can be used. + break + # blendList now contains as many single blend tuples as can be + # combined without exceeding the CFF2 stack limit. + num_blends = len(blendlist) + # append the 'num_blends' default font values + blend_args = [] + for arg in blendlist: + blend_args.append(arg[0]) + for arg in blendlist: + assert arg[-1] == 1 + blend_args.extend(arg[1:-1]) + blend_args.append(num_blends) + new_args.append(blend_args) + stack_use = prev_stack_use + num_blends + + return new_args + + +def _addArgs(a, b): + if isinstance(b, list): + if isinstance(a, list): + if len(a) != len(b) or a[-1] != b[-1]: + raise ValueError() + return [_addArgs(va, vb) for va, vb in zip(a[:-1], b[:-1])] + [a[-1]] + else: + a, b = b, a + if isinstance(a, list): + assert a[-1] == 1 + return [_addArgs(a[0], b)] + a[1:] + return a + b + + +def specializeCommands( + commands, + ignoreErrors=False, + generalizeFirst=True, + preserveTopology=False, + maxstack=48, +): + # We perform several rounds of optimizations. They are carefully ordered and are: + # + # 0. Generalize commands. + # This ensures that they are in our expected simple form, with each line/curve only + # having arguments for one segment, and using the generic form (rlineto/rrcurveto). + # If caller is sure the input is in this form, they can turn off generalization to + # save time. + # + # 1. Combine successive rmoveto operations. + # + # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants. + # We specialize into some, made-up, variants as well, which simplifies following + # passes. + # + # 3. Merge or delete redundant operations, to the extent requested. + # OpenType spec declares point numbers in CFF undefined. As such, we happily + # change topology. If client relies on point numbers (in GPOS anchors, or for + # hinting purposes(what?)) they can turn this off. + # + # 4. Peephole optimization to revert back some of the h/v variants back into their + # original "relative" operator (rline/rrcurveto) if that saves a byte. + # + # 5. Combine adjacent operators when possible, minding not to go over max stack size. + # + # 6. Resolve any remaining made-up operators into real operators. + # + # I have convinced myself that this produces optimal bytecode (except for, possibly + # one byte each time maxstack size prohibits combining.) YMMV, but you'd be wrong. :-) + # A dynamic-programming approach can do the same but would be significantly slower. + # + # 7. For any args which are blend lists, convert them to a blend command. + + # 0. Generalize commands. + if generalizeFirst: + commands = generalizeCommands(commands, ignoreErrors=ignoreErrors) + else: + commands = list(commands) # Make copy since we modify in-place later. + + # 1. Combine successive rmoveto operations. + for i in range(len(commands) - 1, 0, -1): + if "rmoveto" == commands[i][0] == commands[i - 1][0]: + v1, v2 = commands[i - 1][1], commands[i][1] + commands[i - 1] = ("rmoveto", [v1[0] + v2[0], v1[1] + v2[1]]) + del commands[i] + + # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants. + # + # We, in fact, specialize into more, made-up, variants that special-case when both + # X and Y components are zero. This simplifies the following optimization passes. + # This case is rare, but OCD does not let me skip it. + # + # After this round, we will have four variants that use the following mnemonics: + # + # - 'r' for relative, ie. non-zero X and non-zero Y, + # - 'h' for horizontal, ie. zero X and non-zero Y, + # - 'v' for vertical, ie. non-zero X and zero Y, + # - '0' for zeros, ie. zero X and zero Y. + # + # The '0' pseudo-operators are not part of the spec, but help simplify the following + # optimization rounds. We resolve them at the end. So, after this, we will have four + # moveto and four lineto variants: + # + # - 0moveto, 0lineto + # - hmoveto, hlineto + # - vmoveto, vlineto + # - rmoveto, rlineto + # + # and sixteen curveto variants. For example, a '0hcurveto' operator means a curve + # dx0,dy0,dx1,dy1,dx2,dy2,dx3,dy3 where dx0, dx1, and dy3 are zero but not dx3. + # An 'rvcurveto' means dx3 is zero but not dx0,dy0,dy3. + # + # There are nine different variants of curves without the '0'. Those nine map exactly + # to the existing curve variants in the spec: rrcurveto, and the four variants hhcurveto, + # vvcurveto, hvcurveto, and vhcurveto each cover two cases, one with an odd number of + # arguments and one without. Eg. an hhcurveto with an extra argument (odd number of + # arguments) is in fact an rhcurveto. The operators in the spec are designed such that + # all four of rhcurveto, rvcurveto, hrcurveto, and vrcurveto are encodable for one curve. + # + # Of the curve types with '0', the 00curveto is equivalent to a lineto variant. The rest + # of the curve types with a 0 need to be encoded as a h or v variant. Ie. a '0' can be + # thought of a "don't care" and can be used as either an 'h' or a 'v'. As such, we always + # encode a number 0 as argument when we use a '0' variant. Later on, we can just substitute + # the '0' with either 'h' or 'v' and it works. + # + # When we get to curve splines however, things become more complicated... XXX finish this. + # There's one more complexity with splines. If one side of the spline is not horizontal or + # vertical (or zero), ie. if it's 'r', then it limits which spline types we can encode. + # Only hhcurveto and vvcurveto operators can encode a spline starting with 'r', and + # only hvcurveto and vhcurveto operators can encode a spline ending with 'r'. + # This limits our merge opportunities later. + # + for i in range(len(commands)): + op, args = commands[i] + + if op in {"rmoveto", "rlineto"}: + c, args = _categorizeVector(args) + commands[i] = c + op[1:], args + continue + + if op == "rrcurveto": + c1, args1 = _categorizeVector(args[:2]) + c2, args2 = _categorizeVector(args[-2:]) + commands[i] = c1 + c2 + "curveto", args1 + args[2:4] + args2 + continue + + # 3. Merge or delete redundant operations, to the extent requested. + # + # TODO + # A 0moveto that comes before all other path operations can be removed. + # though I find conflicting evidence for this. + # + # TODO + # "If hstem and vstem hints are both declared at the beginning of a + # CharString, and this sequence is followed directly by the hintmask or + # cntrmask operators, then the vstem hint operator (or, if applicable, + # the vstemhm operator) need not be included." + # + # "The sequence and form of a CFF2 CharString program may be represented as: + # {hs* vs* cm* hm* mt subpath}? {mt subpath}*" + # + # https://www.microsoft.com/typography/otspec/cff2charstr.htm#section3.1 + # + # For Type2 CharStrings the sequence is: + # w? {hs* vs* cm* hm* mt subpath}? {mt subpath}* endchar" + + # Some other redundancies change topology (point numbers). + if not preserveTopology: + for i in range(len(commands) - 1, -1, -1): + op, args = commands[i] + + # A 00curveto is demoted to a (specialized) lineto. + if op == "00curveto": + assert len(args) == 4 + c, args = _categorizeVector(args[1:3]) + op = c + "lineto" + commands[i] = op, args + # and then... + + # A 0lineto can be deleted. + if op == "0lineto": + del commands[i] + continue + + # Merge adjacent hlineto's and vlineto's. + # In CFF2 charstrings from variable fonts, each + # arg item may be a list of blendable values, one from + # each source font. + if i and op in {"hlineto", "vlineto"} and (op == commands[i - 1][0]): + _, other_args = commands[i - 1] + assert len(args) == 1 and len(other_args) == 1 + try: + new_args = [_addArgs(args[0], other_args[0])] + except ValueError: + continue + commands[i - 1] = (op, new_args) + del commands[i] + continue + + # 4. Peephole optimization to revert back some of the h/v variants back into their + # original "relative" operator (rline/rrcurveto) if that saves a byte. + for i in range(1, len(commands) - 1): + op, args = commands[i] + prv, nxt = commands[i - 1][0], commands[i + 1][0] + + if op in {"0lineto", "hlineto", "vlineto"} and prv == nxt == "rlineto": + assert len(args) == 1 + args = [0, args[0]] if op[0] == "v" else [args[0], 0] + commands[i] = ("rlineto", args) + continue + + if op[2:] == "curveto" and len(args) == 5 and prv == nxt == "rrcurveto": + assert (op[0] == "r") ^ (op[1] == "r") + if op[0] == "v": + pos = 0 + elif op[0] != "r": + pos = 1 + elif op[1] == "v": + pos = 4 + else: + pos = 5 + # Insert, while maintaining the type of args (can be tuple or list). + args = args[:pos] + type(args)((0,)) + args[pos:] + commands[i] = ("rrcurveto", args) + continue + + # 5. Combine adjacent operators when possible, minding not to go over max stack size. + for i in range(len(commands) - 1, 0, -1): + op1, args1 = commands[i - 1] + op2, args2 = commands[i] + new_op = None + + # Merge logic... + if {op1, op2} <= {"rlineto", "rrcurveto"}: + if op1 == op2: + new_op = op1 + else: + if op2 == "rrcurveto" and len(args2) == 6: + new_op = "rlinecurve" + elif len(args2) == 2: + new_op = "rcurveline" + + elif (op1, op2) in {("rlineto", "rlinecurve"), ("rrcurveto", "rcurveline")}: + new_op = op2 + + elif {op1, op2} == {"vlineto", "hlineto"}: + new_op = op1 + + elif "curveto" == op1[2:] == op2[2:]: + d0, d1 = op1[:2] + d2, d3 = op2[:2] + + if d1 == "r" or d2 == "r" or d0 == d3 == "r": + continue + + d = _mergeCategories(d1, d2) + if d is None: + continue + if d0 == "r": + d = _mergeCategories(d, d3) + if d is None: + continue + new_op = "r" + d + "curveto" + elif d3 == "r": + d0 = _mergeCategories(d0, _negateCategory(d)) + if d0 is None: + continue + new_op = d0 + "r" + "curveto" + else: + d0 = _mergeCategories(d0, d3) + if d0 is None: + continue + new_op = d0 + d + "curveto" + + # Make sure the stack depth does not exceed (maxstack - 1), so + # that subroutinizer can insert subroutine calls at any point. + if new_op and len(args1) + len(args2) < maxstack: + commands[i - 1] = (new_op, args1 + args2) + del commands[i] + + # 6. Resolve any remaining made-up operators into real operators. + for i in range(len(commands)): + op, args = commands[i] + + if op in {"0moveto", "0lineto"}: + commands[i] = "h" + op[1:], args + continue + + if op[2:] == "curveto" and op[:2] not in {"rr", "hh", "vv", "vh", "hv"}: + op0, op1 = op[:2] + if (op0 == "r") ^ (op1 == "r"): + assert len(args) % 2 == 1 + if op0 == "0": + op0 = "h" + if op1 == "0": + op1 = "h" + if op0 == "r": + op0 = op1 + if op1 == "r": + op1 = _negateCategory(op0) + assert {op0, op1} <= {"h", "v"}, (op0, op1) + + if len(args) % 2: + if op0 != op1: # vhcurveto / hvcurveto + if (op0 == "h") ^ (len(args) % 8 == 1): + # Swap last two args order + args = args[:-2] + args[-1:] + args[-2:-1] + else: # hhcurveto / vvcurveto + if op0 == "h": # hhcurveto + # Swap first two args order + args = args[1:2] + args[:1] + args[2:] + + commands[i] = op0 + op1 + "curveto", args + continue + + # 7. For any series of args which are blend lists, convert the series to a single blend arg. + for i in range(len(commands)): + op, args = commands[i] + if any(isinstance(arg, list) for arg in args): + commands[i] = op, _convertToBlendCmds(args) + + return commands + + +def specializeProgram(program, getNumRegions=None, **kwargs): + return commandsToProgram( + specializeCommands(programToCommands(program, getNumRegions), **kwargs) + ) + + +if __name__ == "__main__": + import sys + + if len(sys.argv) == 1: + import doctest + + sys.exit(doctest.testmod().failed) + + import argparse + + parser = argparse.ArgumentParser( + "fonttools cffLib.specialer", + description="CFF CharString generalizer/specializer", + ) + parser.add_argument("program", metavar="command", nargs="*", help="Commands.") + parser.add_argument( + "--num-regions", + metavar="NumRegions", + nargs="*", + default=None, + help="Number of variable-font regions for blend opertaions.", + ) + + options = parser.parse_args(sys.argv[1:]) + + getNumRegions = ( + None + if options.num_regions is None + else lambda vsIndex: int(options.num_regions[0 if vsIndex is None else vsIndex]) + ) + + program = stringToProgram(options.program) + print("Program:") + print(programToString(program)) + commands = programToCommands(program, getNumRegions) + print("Commands:") + print(commands) + program2 = commandsToProgram(commands) + print("Program from commands:") + print(programToString(program2)) + assert program == program2 + print("Generalized program:") + print(programToString(generalizeProgram(program, getNumRegions))) + print("Specialized program:") + print(programToString(specializeProgram(program, getNumRegions))) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/width.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/width.py new file mode 100644 index 0000000..0ba3ed3 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cffLib/width.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- + +"""T2CharString glyph width optimizer. + +CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX`` +value do not need to specify their width in their charstring, saving bytes. +This module determines the optimum ``defaultWidthX`` and ``nominalWidthX`` +values for a font, when provided with a list of glyph widths.""" + +from fontTools.ttLib import TTFont +from collections import defaultdict +from operator import add +from functools import reduce + + +class missingdict(dict): + def __init__(self, missing_func): + self.missing_func = missing_func + + def __missing__(self, v): + return self.missing_func(v) + + +def cumSum(f, op=add, start=0, decreasing=False): + keys = sorted(f.keys()) + minx, maxx = keys[0], keys[-1] + + total = reduce(op, f.values(), start) + + if decreasing: + missing = lambda x: start if x > maxx else total + domain = range(maxx, minx - 1, -1) + else: + missing = lambda x: start if x < minx else total + domain = range(minx, maxx + 1) + + out = missingdict(missing) + + v = start + for x in domain: + v = op(v, f[x]) + out[x] = v + + return out + + +def byteCost(widths, default, nominal): + if not hasattr(widths, "items"): + d = defaultdict(int) + for w in widths: + d[w] += 1 + widths = d + + cost = 0 + for w, freq in widths.items(): + if w == default: + continue + diff = abs(w - nominal) + if diff <= 107: + cost += freq + elif diff <= 1131: + cost += freq * 2 + else: + cost += freq * 5 + return cost + + +def optimizeWidthsBruteforce(widths): + """Bruteforce version. Veeeeeeeeeeeeeeeeery slow. Only works for smallests of fonts.""" + + d = defaultdict(int) + for w in widths: + d[w] += 1 + + # Maximum number of bytes using default can possibly save + maxDefaultAdvantage = 5 * max(d.values()) + + minw, maxw = min(widths), max(widths) + domain = list(range(minw, maxw + 1)) + + bestCostWithoutDefault = min(byteCost(widths, None, nominal) for nominal in domain) + + bestCost = len(widths) * 5 + 1 + for nominal in domain: + if byteCost(widths, None, nominal) > bestCost + maxDefaultAdvantage: + continue + for default in domain: + cost = byteCost(widths, default, nominal) + if cost < bestCost: + bestCost = cost + bestDefault = default + bestNominal = nominal + + return bestDefault, bestNominal + + +def optimizeWidths(widths): + """Given a list of glyph widths, or dictionary mapping glyph width to number of + glyphs having that, returns a tuple of best CFF default and nominal glyph widths. + + This algorithm is linear in UPEM+numGlyphs.""" + + if not hasattr(widths, "items"): + d = defaultdict(int) + for w in widths: + d[w] += 1 + widths = d + + keys = sorted(widths.keys()) + minw, maxw = keys[0], keys[-1] + domain = list(range(minw, maxw + 1)) + + # Cumulative sum/max forward/backward. + cumFrqU = cumSum(widths, op=add) + cumMaxU = cumSum(widths, op=max) + cumFrqD = cumSum(widths, op=add, decreasing=True) + cumMaxD = cumSum(widths, op=max, decreasing=True) + + # Cost per nominal choice, without default consideration. + nomnCostU = missingdict( + lambda x: cumFrqU[x] + cumFrqU[x - 108] + cumFrqU[x - 1132] * 3 + ) + nomnCostD = missingdict( + lambda x: cumFrqD[x] + cumFrqD[x + 108] + cumFrqD[x + 1132] * 3 + ) + nomnCost = missingdict(lambda x: nomnCostU[x] + nomnCostD[x] - widths[x]) + + # Cost-saving per nominal choice, by best default choice. + dfltCostU = missingdict( + lambda x: max(cumMaxU[x], cumMaxU[x - 108] * 2, cumMaxU[x - 1132] * 5) + ) + dfltCostD = missingdict( + lambda x: max(cumMaxD[x], cumMaxD[x + 108] * 2, cumMaxD[x + 1132] * 5) + ) + dfltCost = missingdict(lambda x: max(dfltCostU[x], dfltCostD[x])) + + # Combined cost per nominal choice. + bestCost = missingdict(lambda x: nomnCost[x] - dfltCost[x]) + + # Best nominal. + nominal = min(domain, key=lambda x: bestCost[x]) + + # Work back the best default. + bestC = bestCost[nominal] + dfltC = nomnCost[nominal] - bestCost[nominal] + ends = [] + if dfltC == dfltCostU[nominal]: + starts = [nominal, nominal - 108, nominal - 1132] + for start in starts: + while cumMaxU[start] and cumMaxU[start] == cumMaxU[start - 1]: + start -= 1 + ends.append(start) + else: + starts = [nominal, nominal + 108, nominal + 1132] + for start in starts: + while cumMaxD[start] and cumMaxD[start] == cumMaxD[start + 1]: + start += 1 + ends.append(start) + default = min(ends, key=lambda default: byteCost(widths, default, nominal)) + + return default, nominal + + +def main(args=None): + """Calculate optimum defaultWidthX/nominalWidthX values""" + + import argparse + + parser = argparse.ArgumentParser( + "fonttools cffLib.width", + description=main.__doc__, + ) + parser.add_argument( + "inputs", metavar="FILE", type=str, nargs="+", help="Input TTF files" + ) + parser.add_argument( + "-b", + "--brute-force", + dest="brute", + action="store_true", + help="Use brute-force approach (VERY slow)", + ) + + args = parser.parse_args(args) + + for fontfile in args.inputs: + font = TTFont(fontfile) + hmtx = font["hmtx"] + widths = [m[0] for m in hmtx.metrics.values()] + if args.brute: + default, nominal = optimizeWidthsBruteforce(widths) + else: + default, nominal = optimizeWidths(widths) + print( + "glyphs=%d default=%d nominal=%d byteCost=%d" + % (len(widths), default, nominal, byteCost(widths, default, nominal)) + ) + + +if __name__ == "__main__": + import sys + + if len(sys.argv) == 1: + import doctest + + sys.exit(doctest.testmod().failed) + main() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/builder.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/builder.py new file mode 100644 index 0000000..442bc20 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/builder.py @@ -0,0 +1,659 @@ +""" +colorLib.builder: Build COLR/CPAL tables from scratch + +""" +import collections +import copy +import enum +from functools import partial +from math import ceil, log +from typing import ( + Any, + Dict, + Generator, + Iterable, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, +) +from fontTools.misc.arrayTools import intRect +from fontTools.misc.fixedTools import fixedToFloat +from fontTools.misc.treeTools import build_n_ary_tree +from fontTools.ttLib.tables import C_O_L_R_ +from fontTools.ttLib.tables import C_P_A_L_ +from fontTools.ttLib.tables import _n_a_m_e +from fontTools.ttLib.tables import otTables as ot +from fontTools.ttLib.tables.otTables import ExtendMode, CompositeMode +from .errors import ColorLibError +from .geometry import round_start_circle_stable_containment +from .table_builder import BuildCallback, TableBuilder + + +# TODO move type aliases to colorLib.types? +T = TypeVar("T") +_Kwargs = Mapping[str, Any] +_PaintInput = Union[int, _Kwargs, ot.Paint, Tuple[str, "_PaintInput"]] +_PaintInputList = Sequence[_PaintInput] +_ColorGlyphsDict = Dict[str, Union[_PaintInputList, _PaintInput]] +_ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]] +_ClipBoxInput = Union[ + Tuple[int, int, int, int, int], # format 1, variable + Tuple[int, int, int, int], # format 0, non-variable + ot.ClipBox, +] + + +MAX_PAINT_COLR_LAYER_COUNT = 255 +_DEFAULT_ALPHA = 1.0 +_MAX_REUSE_LEN = 32 + + +def _beforeBuildPaintRadialGradient(paint, source): + x0 = source["x0"] + y0 = source["y0"] + r0 = source["r0"] + x1 = source["x1"] + y1 = source["y1"] + r1 = source["r1"] + + # TODO apparently no builder_test confirms this works (?) + + # avoid abrupt change after rounding when c0 is near c1's perimeter + c = round_start_circle_stable_containment((x0, y0), r0, (x1, y1), r1) + x0, y0 = c.centre + r0 = c.radius + + # update source to ensure paint is built with corrected values + source["x0"] = x0 + source["y0"] = y0 + source["r0"] = r0 + source["x1"] = x1 + source["y1"] = y1 + source["r1"] = r1 + + return paint, source + + +def _defaultColorStop(): + colorStop = ot.ColorStop() + colorStop.Alpha = _DEFAULT_ALPHA + return colorStop + + +def _defaultVarColorStop(): + colorStop = ot.VarColorStop() + colorStop.Alpha = _DEFAULT_ALPHA + return colorStop + + +def _defaultColorLine(): + colorLine = ot.ColorLine() + colorLine.Extend = ExtendMode.PAD + return colorLine + + +def _defaultVarColorLine(): + colorLine = ot.VarColorLine() + colorLine.Extend = ExtendMode.PAD + return colorLine + + +def _defaultPaintSolid(): + paint = ot.Paint() + paint.Alpha = _DEFAULT_ALPHA + return paint + + +def _buildPaintCallbacks(): + return { + ( + BuildCallback.BEFORE_BUILD, + ot.Paint, + ot.PaintFormat.PaintRadialGradient, + ): _beforeBuildPaintRadialGradient, + ( + BuildCallback.BEFORE_BUILD, + ot.Paint, + ot.PaintFormat.PaintVarRadialGradient, + ): _beforeBuildPaintRadialGradient, + (BuildCallback.CREATE_DEFAULT, ot.ColorStop): _defaultColorStop, + (BuildCallback.CREATE_DEFAULT, ot.VarColorStop): _defaultVarColorStop, + (BuildCallback.CREATE_DEFAULT, ot.ColorLine): _defaultColorLine, + (BuildCallback.CREATE_DEFAULT, ot.VarColorLine): _defaultVarColorLine, + ( + BuildCallback.CREATE_DEFAULT, + ot.Paint, + ot.PaintFormat.PaintSolid, + ): _defaultPaintSolid, + ( + BuildCallback.CREATE_DEFAULT, + ot.Paint, + ot.PaintFormat.PaintVarSolid, + ): _defaultPaintSolid, + } + + +def populateCOLRv0( + table: ot.COLR, + colorGlyphsV0: _ColorGlyphsV0Dict, + glyphMap: Optional[Mapping[str, int]] = None, +): + """Build v0 color layers and add to existing COLR table. + + Args: + table: a raw ``otTables.COLR()`` object (not ttLib's ``table_C_O_L_R_``). + colorGlyphsV0: map of base glyph names to lists of (layer glyph names, + color palette index) tuples. Can be empty. + glyphMap: a map from glyph names to glyph indices, as returned from + ``TTFont.getReverseGlyphMap()``, to optionally sort base records by GID. + """ + if glyphMap is not None: + colorGlyphItems = sorted( + colorGlyphsV0.items(), key=lambda item: glyphMap[item[0]] + ) + else: + colorGlyphItems = colorGlyphsV0.items() + baseGlyphRecords = [] + layerRecords = [] + for baseGlyph, layers in colorGlyphItems: + baseRec = ot.BaseGlyphRecord() + baseRec.BaseGlyph = baseGlyph + baseRec.FirstLayerIndex = len(layerRecords) + baseRec.NumLayers = len(layers) + baseGlyphRecords.append(baseRec) + + for layerGlyph, paletteIndex in layers: + layerRec = ot.LayerRecord() + layerRec.LayerGlyph = layerGlyph + layerRec.PaletteIndex = paletteIndex + layerRecords.append(layerRec) + + table.BaseGlyphRecordArray = table.LayerRecordArray = None + if baseGlyphRecords: + table.BaseGlyphRecordArray = ot.BaseGlyphRecordArray() + table.BaseGlyphRecordArray.BaseGlyphRecord = baseGlyphRecords + if layerRecords: + table.LayerRecordArray = ot.LayerRecordArray() + table.LayerRecordArray.LayerRecord = layerRecords + table.BaseGlyphRecordCount = len(baseGlyphRecords) + table.LayerRecordCount = len(layerRecords) + + +def buildCOLR( + colorGlyphs: _ColorGlyphsDict, + version: Optional[int] = None, + *, + glyphMap: Optional[Mapping[str, int]] = None, + varStore: Optional[ot.VarStore] = None, + varIndexMap: Optional[ot.DeltaSetIndexMap] = None, + clipBoxes: Optional[Dict[str, _ClipBoxInput]] = None, + allowLayerReuse: bool = True, +) -> C_O_L_R_.table_C_O_L_R_: + """Build COLR table from color layers mapping. + + Args: + + colorGlyphs: map of base glyph name to, either list of (layer glyph name, + color palette index) tuples for COLRv0; or a single ``Paint`` (dict) or + list of ``Paint`` for COLRv1. + version: the version of COLR table. If None, the version is determined + by the presence of COLRv1 paints or variation data (varStore), which + require version 1; otherwise, if all base glyphs use only simple color + layers, version 0 is used. + glyphMap: a map from glyph names to glyph indices, as returned from + TTFont.getReverseGlyphMap(), to optionally sort base records by GID. + varStore: Optional ItemVarationStore for deltas associated with v1 layer. + varIndexMap: Optional DeltaSetIndexMap for deltas associated with v1 layer. + clipBoxes: Optional map of base glyph name to clip box 4- or 5-tuples: + (xMin, yMin, xMax, yMax) or (xMin, yMin, xMax, yMax, varIndexBase). + + Returns: + A new COLR table. + """ + self = C_O_L_R_.table_C_O_L_R_() + + if varStore is not None and version == 0: + raise ValueError("Can't add VarStore to COLRv0") + + if version in (None, 0) and not varStore: + # split color glyphs into v0 and v1 and encode separately + colorGlyphsV0, colorGlyphsV1 = _split_color_glyphs_by_version(colorGlyphs) + if version == 0 and colorGlyphsV1: + raise ValueError("Can't encode COLRv1 glyphs in COLRv0") + else: + # unless explicitly requested for v1 or have variations, in which case + # we encode all color glyph as v1 + colorGlyphsV0, colorGlyphsV1 = {}, colorGlyphs + + colr = ot.COLR() + + populateCOLRv0(colr, colorGlyphsV0, glyphMap) + + colr.LayerList, colr.BaseGlyphList = buildColrV1( + colorGlyphsV1, + glyphMap, + allowLayerReuse=allowLayerReuse, + ) + + if version is None: + version = 1 if (varStore or colorGlyphsV1) else 0 + elif version not in (0, 1): + raise NotImplementedError(version) + self.version = colr.Version = version + + if version == 0: + self.ColorLayers = self._decompileColorLayersV0(colr) + else: + colr.ClipList = buildClipList(clipBoxes) if clipBoxes else None + colr.VarIndexMap = varIndexMap + colr.VarStore = varStore + self.table = colr + + return self + + +def buildClipList(clipBoxes: Dict[str, _ClipBoxInput]) -> ot.ClipList: + clipList = ot.ClipList() + clipList.Format = 1 + clipList.clips = {name: buildClipBox(box) for name, box in clipBoxes.items()} + return clipList + + +def buildClipBox(clipBox: _ClipBoxInput) -> ot.ClipBox: + if isinstance(clipBox, ot.ClipBox): + return clipBox + n = len(clipBox) + clip = ot.ClipBox() + if n not in (4, 5): + raise ValueError(f"Invalid ClipBox: expected 4 or 5 values, found {n}") + clip.xMin, clip.yMin, clip.xMax, clip.yMax = intRect(clipBox[:4]) + clip.Format = int(n == 5) + 1 + if n == 5: + clip.VarIndexBase = int(clipBox[4]) + return clip + + +class ColorPaletteType(enum.IntFlag): + USABLE_WITH_LIGHT_BACKGROUND = 0x0001 + USABLE_WITH_DARK_BACKGROUND = 0x0002 + + @classmethod + def _missing_(cls, value): + # enforce reserved bits + if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0): + raise ValueError(f"{value} is not a valid {cls.__name__}") + return super()._missing_(value) + + +# None, 'abc' or {'en': 'abc', 'de': 'xyz'} +_OptionalLocalizedString = Union[None, str, Dict[str, str]] + + +def buildPaletteLabels( + labels: Iterable[_OptionalLocalizedString], nameTable: _n_a_m_e.table__n_a_m_e +) -> List[Optional[int]]: + return [ + nameTable.addMultilingualName(l, mac=False) + if isinstance(l, dict) + else C_P_A_L_.table_C_P_A_L_.NO_NAME_ID + if l is None + else nameTable.addMultilingualName({"en": l}, mac=False) + for l in labels + ] + + +def buildCPAL( + palettes: Sequence[Sequence[Tuple[float, float, float, float]]], + paletteTypes: Optional[Sequence[ColorPaletteType]] = None, + paletteLabels: Optional[Sequence[_OptionalLocalizedString]] = None, + paletteEntryLabels: Optional[Sequence[_OptionalLocalizedString]] = None, + nameTable: Optional[_n_a_m_e.table__n_a_m_e] = None, +) -> C_P_A_L_.table_C_P_A_L_: + """Build CPAL table from list of color palettes. + + Args: + palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats + in the range [0..1]. + paletteTypes: optional list of ColorPaletteType, one for each palette. + paletteLabels: optional list of palette labels. Each lable can be either: + None (no label), a string (for for default English labels), or a + localized string (as a dict keyed with BCP47 language codes). + paletteEntryLabels: optional list of palette entry labels, one for each + palette entry (see paletteLabels). + nameTable: optional name table where to store palette and palette entry + labels. Required if either paletteLabels or paletteEntryLabels is set. + + Return: + A new CPAL v0 or v1 table, if custom palette types or labels are specified. + """ + if len({len(p) for p in palettes}) != 1: + raise ColorLibError("color palettes have different lengths") + + if (paletteLabels or paletteEntryLabels) and not nameTable: + raise TypeError( + "nameTable is required if palette or palette entries have labels" + ) + + cpal = C_P_A_L_.table_C_P_A_L_() + cpal.numPaletteEntries = len(palettes[0]) + + cpal.palettes = [] + for i, palette in enumerate(palettes): + colors = [] + for j, color in enumerate(palette): + if not isinstance(color, tuple) or len(color) != 4: + raise ColorLibError( + f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}" + ) + if any(v > 1 or v < 0 for v in color): + raise ColorLibError( + f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}" + ) + # input colors are RGBA, CPAL encodes them as BGRA + red, green, blue, alpha = color + colors.append( + C_P_A_L_.Color(*(round(v * 255) for v in (blue, green, red, alpha))) + ) + cpal.palettes.append(colors) + + if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)): + cpal.version = 1 + + if paletteTypes is not None: + if len(paletteTypes) != len(palettes): + raise ColorLibError( + f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}" + ) + cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes] + else: + cpal.paletteTypes = [C_P_A_L_.table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len( + palettes + ) + + if paletteLabels is not None: + if len(paletteLabels) != len(palettes): + raise ColorLibError( + f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}" + ) + cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable) + else: + cpal.paletteLabels = [C_P_A_L_.table_C_P_A_L_.NO_NAME_ID] * len(palettes) + + if paletteEntryLabels is not None: + if len(paletteEntryLabels) != cpal.numPaletteEntries: + raise ColorLibError( + f"Expected {cpal.numPaletteEntries} paletteEntryLabels, " + f"got {len(paletteEntryLabels)}" + ) + cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable) + else: + cpal.paletteEntryLabels = [ + C_P_A_L_.table_C_P_A_L_.NO_NAME_ID + ] * cpal.numPaletteEntries + else: + cpal.version = 0 + + return cpal + + +# COLR v1 tables +# See draft proposal at: https://github.com/googlefonts/colr-gradients-spec + + +def _is_colrv0_layer(layer: Any) -> bool: + # Consider as COLRv0 layer any sequence of length 2 (be it tuple or list) in which + # the first element is a str (the layerGlyph) and the second element is an int + # (CPAL paletteIndex). + # https://github.com/googlefonts/ufo2ft/issues/426 + try: + layerGlyph, paletteIndex = layer + except (TypeError, ValueError): + return False + else: + return isinstance(layerGlyph, str) and isinstance(paletteIndex, int) + + +def _split_color_glyphs_by_version( + colorGlyphs: _ColorGlyphsDict, +) -> Tuple[_ColorGlyphsV0Dict, _ColorGlyphsDict]: + colorGlyphsV0 = {} + colorGlyphsV1 = {} + for baseGlyph, layers in colorGlyphs.items(): + if all(_is_colrv0_layer(l) for l in layers): + colorGlyphsV0[baseGlyph] = layers + else: + colorGlyphsV1[baseGlyph] = layers + + # sanity check + assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1)) + + return colorGlyphsV0, colorGlyphsV1 + + +def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]: + # TODO feels like something itertools might have already + for lbound in range(num_layers): + # Reuse of very large #s of layers is relatively unlikely + # +2: we want sequences of at least 2 + # otData handles single-record duplication + for ubound in range( + lbound + 2, min(num_layers + 1, lbound + 2 + _MAX_REUSE_LEN) + ): + yield (lbound, ubound) + + +class LayerReuseCache: + reusePool: Mapping[Tuple[Any, ...], int] + tuples: Mapping[int, Tuple[Any, ...]] + keepAlive: List[ot.Paint] # we need id to remain valid + + def __init__(self): + self.reusePool = {} + self.tuples = {} + self.keepAlive = [] + + def _paint_tuple(self, paint: ot.Paint): + # start simple, who even cares about cyclic graphs or interesting field types + def _tuple_safe(value): + if isinstance(value, enum.Enum): + return value + elif hasattr(value, "__dict__"): + return tuple( + (k, _tuple_safe(v)) for k, v in sorted(value.__dict__.items()) + ) + elif isinstance(value, collections.abc.MutableSequence): + return tuple(_tuple_safe(e) for e in value) + return value + + # Cache the tuples for individual Paint instead of the whole sequence + # because the seq could be a transient slice + result = self.tuples.get(id(paint), None) + if result is None: + result = _tuple_safe(paint) + self.tuples[id(paint)] = result + self.keepAlive.append(paint) + return result + + def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]: + return tuple(self._paint_tuple(p) for p in paints) + + def try_reuse(self, layers: List[ot.Paint]) -> List[ot.Paint]: + found_reuse = True + while found_reuse: + found_reuse = False + + ranges = sorted( + _reuse_ranges(len(layers)), + key=lambda t: (t[1] - t[0], t[1], t[0]), + reverse=True, + ) + for lbound, ubound in ranges: + reuse_lbound = self.reusePool.get( + self._as_tuple(layers[lbound:ubound]), -1 + ) + if reuse_lbound == -1: + continue + new_slice = ot.Paint() + new_slice.Format = int(ot.PaintFormat.PaintColrLayers) + new_slice.NumLayers = ubound - lbound + new_slice.FirstLayerIndex = reuse_lbound + layers = layers[:lbound] + [new_slice] + layers[ubound:] + found_reuse = True + break + return layers + + def add(self, layers: List[ot.Paint], first_layer_index: int): + for lbound, ubound in _reuse_ranges(len(layers)): + self.reusePool[self._as_tuple(layers[lbound:ubound])] = ( + lbound + first_layer_index + ) + + +class LayerListBuilder: + layers: List[ot.Paint] + cache: LayerReuseCache + allowLayerReuse: bool + + def __init__(self, *, allowLayerReuse=True): + self.layers = [] + if allowLayerReuse: + self.cache = LayerReuseCache() + else: + self.cache = None + + # We need to intercept construction of PaintColrLayers + callbacks = _buildPaintCallbacks() + callbacks[ + ( + BuildCallback.BEFORE_BUILD, + ot.Paint, + ot.PaintFormat.PaintColrLayers, + ) + ] = self._beforeBuildPaintColrLayers + self.tableBuilder = TableBuilder(callbacks) + + # COLR layers is unusual in that it modifies shared state + # so we need a callback into an object + def _beforeBuildPaintColrLayers(self, dest, source): + # Sketchy gymnastics: a sequence input will have dropped it's layers + # into NumLayers; get it back + if isinstance(source.get("NumLayers", None), collections.abc.Sequence): + layers = source["NumLayers"] + else: + layers = source["Layers"] + + # Convert maps seqs or whatever into typed objects + layers = [self.buildPaint(l) for l in layers] + + # No reason to have a colr layers with just one entry + if len(layers) == 1: + return layers[0], {} + + if self.cache is not None: + # Look for reuse, with preference to longer sequences + # This may make the layer list smaller + layers = self.cache.try_reuse(layers) + + # The layer list is now final; if it's too big we need to tree it + is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT + layers = build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT) + + # We now have a tree of sequences with Paint leaves. + # Convert the sequences into PaintColrLayers. + def listToColrLayers(layer): + if isinstance(layer, collections.abc.Sequence): + return self.buildPaint( + { + "Format": ot.PaintFormat.PaintColrLayers, + "Layers": [listToColrLayers(l) for l in layer], + } + ) + return layer + + layers = [listToColrLayers(l) for l in layers] + + # No reason to have a colr layers with just one entry + if len(layers) == 1: + return layers[0], {} + + paint = ot.Paint() + paint.Format = int(ot.PaintFormat.PaintColrLayers) + paint.NumLayers = len(layers) + paint.FirstLayerIndex = len(self.layers) + self.layers.extend(layers) + + # Register our parts for reuse provided we aren't a tree + # If we are a tree the leaves registered for reuse and that will suffice + if self.cache is not None and not is_tree: + self.cache.add(layers, paint.FirstLayerIndex) + + # we've fully built dest; empty source prevents generalized build from kicking in + return paint, {} + + def buildPaint(self, paint: _PaintInput) -> ot.Paint: + return self.tableBuilder.build(ot.Paint, paint) + + def build(self) -> Optional[ot.LayerList]: + if not self.layers: + return None + layers = ot.LayerList() + layers.LayerCount = len(self.layers) + layers.Paint = self.layers + return layers + + +def buildBaseGlyphPaintRecord( + baseGlyph: str, layerBuilder: LayerListBuilder, paint: _PaintInput +) -> ot.BaseGlyphList: + self = ot.BaseGlyphPaintRecord() + self.BaseGlyph = baseGlyph + self.Paint = layerBuilder.buildPaint(paint) + return self + + +def _format_glyph_errors(errors: Mapping[str, Exception]) -> str: + lines = [] + for baseGlyph, error in sorted(errors.items()): + lines.append(f" {baseGlyph} => {type(error).__name__}: {error}") + return "\n".join(lines) + + +def buildColrV1( + colorGlyphs: _ColorGlyphsDict, + glyphMap: Optional[Mapping[str, int]] = None, + *, + allowLayerReuse: bool = True, +) -> Tuple[Optional[ot.LayerList], ot.BaseGlyphList]: + if glyphMap is not None: + colorGlyphItems = sorted( + colorGlyphs.items(), key=lambda item: glyphMap[item[0]] + ) + else: + colorGlyphItems = colorGlyphs.items() + + errors = {} + baseGlyphs = [] + layerBuilder = LayerListBuilder(allowLayerReuse=allowLayerReuse) + for baseGlyph, paint in colorGlyphItems: + try: + baseGlyphs.append(buildBaseGlyphPaintRecord(baseGlyph, layerBuilder, paint)) + + except (ColorLibError, OverflowError, ValueError, TypeError) as e: + errors[baseGlyph] = e + + if errors: + failed_glyphs = _format_glyph_errors(errors) + exc = ColorLibError(f"Failed to build BaseGlyphList:\n{failed_glyphs}") + exc.errors = errors + raise exc from next(iter(errors.values())) + + layers = layerBuilder.build() + glyphs = ot.BaseGlyphList() + glyphs.BaseGlyphCount = len(baseGlyphs) + glyphs.BaseGlyphPaintRecord = baseGlyphs + return (layers, glyphs) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/errors.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/errors.py new file mode 100644 index 0000000..18cbebb --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/errors.py @@ -0,0 +1,2 @@ +class ColorLibError(Exception): + pass diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/geometry.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/geometry.py new file mode 100644 index 0000000..1ce161b --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/geometry.py @@ -0,0 +1,143 @@ +"""Helpers for manipulating 2D points and vectors in COLR table.""" + +from math import copysign, cos, hypot, isclose, pi +from fontTools.misc.roundTools import otRound + + +def _vector_between(origin, target): + return (target[0] - origin[0], target[1] - origin[1]) + + +def _round_point(pt): + return (otRound(pt[0]), otRound(pt[1])) + + +def _unit_vector(vec): + length = hypot(*vec) + if length == 0: + return None + return (vec[0] / length, vec[1] / length) + + +_CIRCLE_INSIDE_TOLERANCE = 1e-4 + + +# The unit vector's X and Y components are respectively +# U = (cos(α), sin(α)) +# where α is the angle between the unit vector and the positive x axis. +_UNIT_VECTOR_THRESHOLD = cos(3 / 8 * pi) # == sin(1/8 * pi) == 0.38268343236508984 + + +def _rounding_offset(direction): + # Return 2-tuple of -/+ 1.0 or 0.0 approximately based on the direction vector. + # We divide the unit circle in 8 equal slices oriented towards the cardinal + # (N, E, S, W) and intermediate (NE, SE, SW, NW) directions. To each slice we + # map one of the possible cases: -1, 0, +1 for either X and Y coordinate. + # E.g. Return (+1.0, -1.0) if unit vector is oriented towards SE, or + # (-1.0, 0.0) if it's pointing West, etc. + uv = _unit_vector(direction) + if not uv: + return (0, 0) + + result = [] + for uv_component in uv: + if -_UNIT_VECTOR_THRESHOLD <= uv_component < _UNIT_VECTOR_THRESHOLD: + # unit vector component near 0: direction almost orthogonal to the + # direction of the current axis, thus keep coordinate unchanged + result.append(0) + else: + # nudge coord by +/- 1.0 in direction of unit vector + result.append(copysign(1.0, uv_component)) + return tuple(result) + + +class Circle: + def __init__(self, centre, radius): + self.centre = centre + self.radius = radius + + def __repr__(self): + return f"Circle(centre={self.centre}, radius={self.radius})" + + def round(self): + return Circle(_round_point(self.centre), otRound(self.radius)) + + def inside(self, outer_circle, tolerance=_CIRCLE_INSIDE_TOLERANCE): + dist = self.radius + hypot(*_vector_between(self.centre, outer_circle.centre)) + return ( + isclose(outer_circle.radius, dist, rel_tol=_CIRCLE_INSIDE_TOLERANCE) + or outer_circle.radius > dist + ) + + def concentric(self, other): + return self.centre == other.centre + + def move(self, dx, dy): + self.centre = (self.centre[0] + dx, self.centre[1] + dy) + + +def round_start_circle_stable_containment(c0, r0, c1, r1): + """Round start circle so that it stays inside/outside end circle after rounding. + + The rounding of circle coordinates to integers may cause an abrupt change + if the start circle c0 is so close to the end circle c1's perimiter that + it ends up falling outside (or inside) as a result of the rounding. + To keep the gradient unchanged, we nudge it in the right direction. + + See: + https://github.com/googlefonts/colr-gradients-spec/issues/204 + https://github.com/googlefonts/picosvg/issues/158 + """ + start, end = Circle(c0, r0), Circle(c1, r1) + + inside_before_round = start.inside(end) + + round_start = start.round() + round_end = end.round() + inside_after_round = round_start.inside(round_end) + + if inside_before_round == inside_after_round: + return round_start + elif inside_after_round: + # start was outside before rounding: we need to push start away from end + direction = _vector_between(round_end.centre, round_start.centre) + radius_delta = +1.0 + else: + # start was inside before rounding: we need to push start towards end + direction = _vector_between(round_start.centre, round_end.centre) + radius_delta = -1.0 + dx, dy = _rounding_offset(direction) + + # At most 2 iterations ought to be enough to converge. Before the loop, we + # know the start circle didn't keep containment after normal rounding; thus + # we continue adjusting by -/+ 1.0 until containment is restored. + # Normal rounding can at most move each coordinates -/+0.5; in the worst case + # both the start and end circle's centres and radii will be rounded in opposite + # directions, e.g. when they move along a 45 degree diagonal: + # c0 = (1.5, 1.5) ===> (2.0, 2.0) + # r0 = 0.5 ===> 1.0 + # c1 = (0.499, 0.499) ===> (0.0, 0.0) + # r1 = 2.499 ===> 2.0 + # In this example, the relative distance between the circles, calculated + # as r1 - (r0 + distance(c0, c1)) is initially 0.57437 (c0 is inside c1), and + # -1.82842 after rounding (c0 is now outside c1). Nudging c0 by -1.0 on both + # x and y axes moves it towards c1 by hypot(-1.0, -1.0) = 1.41421. Two of these + # moves cover twice that distance, which is enough to restore containment. + max_attempts = 2 + for _ in range(max_attempts): + if round_start.concentric(round_end): + # can't move c0 towards c1 (they are the same), so we change the radius + round_start.radius += radius_delta + assert round_start.radius >= 0 + else: + round_start.move(dx, dy) + if inside_before_round == round_start.inside(round_end): + break + else: # likely a bug + raise AssertionError( + f"Rounding circle {start} " + f"{'inside' if inside_before_round else 'outside'} " + f"{end} failed after {max_attempts} attempts!" + ) + + return round_start diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/table_builder.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/table_builder.py new file mode 100644 index 0000000..f1e182c --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/table_builder.py @@ -0,0 +1,223 @@ +""" +colorLib.table_builder: Generic helper for filling in BaseTable derivatives from tuples and maps and such. + +""" + +import collections +import enum +from fontTools.ttLib.tables.otBase import ( + BaseTable, + FormatSwitchingBaseTable, + UInt8FormatSwitchingBaseTable, +) +from fontTools.ttLib.tables.otConverters import ( + ComputedInt, + SimpleValue, + Struct, + Short, + UInt8, + UShort, + IntValue, + FloatValue, + OptionalValue, +) +from fontTools.misc.roundTools import otRound + + +class BuildCallback(enum.Enum): + """Keyed on (BEFORE_BUILD, class[, Format if available]). + Receives (dest, source). + Should return (dest, source), which can be new objects. + """ + + BEFORE_BUILD = enum.auto() + + """Keyed on (AFTER_BUILD, class[, Format if available]). + Receives (dest). + Should return dest, which can be a new object. + """ + AFTER_BUILD = enum.auto() + + """Keyed on (CREATE_DEFAULT, class[, Format if available]). + Receives no arguments. + Should return a new instance of class. + """ + CREATE_DEFAULT = enum.auto() + + +def _assignable(convertersByName): + return {k: v for k, v in convertersByName.items() if not isinstance(v, ComputedInt)} + + +def _isNonStrSequence(value): + return isinstance(value, collections.abc.Sequence) and not isinstance(value, str) + + +def _split_format(cls, source): + if _isNonStrSequence(source): + assert len(source) > 0, f"{cls} needs at least format from {source}" + fmt, remainder = source[0], source[1:] + elif isinstance(source, collections.abc.Mapping): + assert "Format" in source, f"{cls} needs at least Format from {source}" + remainder = source.copy() + fmt = remainder.pop("Format") + else: + raise ValueError(f"Not sure how to populate {cls} from {source}") + + assert isinstance( + fmt, collections.abc.Hashable + ), f"{cls} Format is not hashable: {fmt!r}" + assert fmt in cls.convertersByName, f"{cls} invalid Format: {fmt!r}" + + return fmt, remainder + + +class TableBuilder: + """ + Helps to populate things derived from BaseTable from maps, tuples, etc. + + A table of lifecycle callbacks may be provided to add logic beyond what is possible + based on otData info for the target class. See BuildCallbacks. + """ + + def __init__(self, callbackTable=None): + if callbackTable is None: + callbackTable = {} + self._callbackTable = callbackTable + + def _convert(self, dest, field, converter, value): + enumClass = getattr(converter, "enumClass", None) + + if enumClass: + if isinstance(value, enumClass): + pass + elif isinstance(value, str): + try: + value = getattr(enumClass, value.upper()) + except AttributeError: + raise ValueError(f"{value} is not a valid {enumClass}") + else: + value = enumClass(value) + + elif isinstance(converter, IntValue): + value = otRound(value) + elif isinstance(converter, FloatValue): + value = float(value) + + elif isinstance(converter, Struct): + if converter.repeat: + if _isNonStrSequence(value): + value = [self.build(converter.tableClass, v) for v in value] + else: + value = [self.build(converter.tableClass, value)] + setattr(dest, converter.repeat, len(value)) + else: + value = self.build(converter.tableClass, value) + elif callable(converter): + value = converter(value) + + setattr(dest, field, value) + + def build(self, cls, source): + assert issubclass(cls, BaseTable) + + if isinstance(source, cls): + return source + + callbackKey = (cls,) + fmt = None + if issubclass(cls, FormatSwitchingBaseTable): + fmt, source = _split_format(cls, source) + callbackKey = (cls, fmt) + + dest = self._callbackTable.get( + (BuildCallback.CREATE_DEFAULT,) + callbackKey, lambda: cls() + )() + assert isinstance(dest, cls) + + convByName = _assignable(cls.convertersByName) + skippedFields = set() + + # For format switchers we need to resolve converters based on format + if issubclass(cls, FormatSwitchingBaseTable): + dest.Format = fmt + convByName = _assignable(convByName[dest.Format]) + skippedFields.add("Format") + + # Convert sequence => mapping so before thunk only has to handle one format + if _isNonStrSequence(source): + # Sequence (typically list or tuple) assumed to match fields in declaration order + assert len(source) <= len( + convByName + ), f"Sequence of {len(source)} too long for {cls}; expected <= {len(convByName)} values" + source = dict(zip(convByName.keys(), source)) + + dest, source = self._callbackTable.get( + (BuildCallback.BEFORE_BUILD,) + callbackKey, lambda d, s: (d, s) + )(dest, source) + + if isinstance(source, collections.abc.Mapping): + for field, value in source.items(): + if field in skippedFields: + continue + converter = convByName.get(field, None) + if not converter: + raise ValueError( + f"Unrecognized field {field} for {cls}; expected one of {sorted(convByName.keys())}" + ) + self._convert(dest, field, converter, value) + else: + # let's try as a 1-tuple + dest = self.build(cls, (source,)) + + for field, conv in convByName.items(): + if not hasattr(dest, field) and isinstance(conv, OptionalValue): + setattr(dest, field, conv.DEFAULT) + + dest = self._callbackTable.get( + (BuildCallback.AFTER_BUILD,) + callbackKey, lambda d: d + )(dest) + + return dest + + +class TableUnbuilder: + def __init__(self, callbackTable=None): + if callbackTable is None: + callbackTable = {} + self._callbackTable = callbackTable + + def unbuild(self, table): + assert isinstance(table, BaseTable) + + source = {} + + callbackKey = (type(table),) + if isinstance(table, FormatSwitchingBaseTable): + source["Format"] = int(table.Format) + callbackKey += (table.Format,) + + for converter in table.getConverters(): + if isinstance(converter, ComputedInt): + continue + value = getattr(table, converter.name) + + enumClass = getattr(converter, "enumClass", None) + if enumClass: + source[converter.name] = value.name.lower() + elif isinstance(converter, Struct): + if converter.repeat: + source[converter.name] = [self.unbuild(v) for v in value] + else: + source[converter.name] = self.unbuild(value) + elif isinstance(converter, SimpleValue): + # "simple" values (e.g. int, float, str) need no further un-building + source[converter.name] = value + else: + raise NotImplementedError( + "Don't know how unbuild {value!r} with {converter!r}" + ) + + source = self._callbackTable.get(callbackKey, lambda s: s)(source) + + return source diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/unbuilder.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/unbuilder.py new file mode 100644 index 0000000..ac24355 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/colorLib/unbuilder.py @@ -0,0 +1,81 @@ +from fontTools.ttLib.tables import otTables as ot +from .table_builder import TableUnbuilder + + +def unbuildColrV1(layerList, baseGlyphList): + layers = [] + if layerList: + layers = layerList.Paint + unbuilder = LayerListUnbuilder(layers) + return { + rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint) + for rec in baseGlyphList.BaseGlyphPaintRecord + } + + +def _flatten_layers(lst): + for paint in lst: + if paint["Format"] == ot.PaintFormat.PaintColrLayers: + yield from _flatten_layers(paint["Layers"]) + else: + yield paint + + +class LayerListUnbuilder: + def __init__(self, layers): + self.layers = layers + + callbacks = { + ( + ot.Paint, + ot.PaintFormat.PaintColrLayers, + ): self._unbuildPaintColrLayers, + } + self.tableUnbuilder = TableUnbuilder(callbacks) + + def unbuildPaint(self, paint): + assert isinstance(paint, ot.Paint) + return self.tableUnbuilder.unbuild(paint) + + def _unbuildPaintColrLayers(self, source): + assert source["Format"] == ot.PaintFormat.PaintColrLayers + + layers = list( + _flatten_layers( + [ + self.unbuildPaint(childPaint) + for childPaint in self.layers[ + source["FirstLayerIndex"] : source["FirstLayerIndex"] + + source["NumLayers"] + ] + ] + ) + ) + + if len(layers) == 1: + return layers[0] + + return {"Format": source["Format"], "Layers": layers} + + +if __name__ == "__main__": + from pprint import pprint + import sys + from fontTools.ttLib import TTFont + + try: + fontfile = sys.argv[1] + except IndexError: + sys.exit("usage: fonttools colorLib.unbuilder FONTFILE") + + font = TTFont(fontfile) + colr = font["COLR"] + if colr.version < 1: + sys.exit(f"error: No COLR table version=1 found in {fontfile}") + + colorGlyphs = unbuildColrV1( + colr.table.LayerList, + colr.table.BaseGlyphList, + ) + + pprint(colorGlyphs) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/config/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/config/__init__.py new file mode 100644 index 0000000..c106fe5 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/config/__init__.py @@ -0,0 +1,74 @@ +""" +Define all configuration options that can affect the working of fontTools +modules. E.g. optimization levels of varLib IUP, otlLib GPOS compression level, +etc. If this file gets too big, split it into smaller files per-module. + +An instance of the Config class can be attached to a TTFont object, so that +the various modules can access their configuration options from it. +""" +from textwrap import dedent + +from fontTools.misc.configTools import * + + +class Config(AbstractConfig): + options = Options() + + +OPTIONS = Config.options + + +Config.register_option( + name="fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL", + help=dedent( + """\ + GPOS Lookup type 2 (PairPos) compression level: + 0 = do not attempt to compact PairPos lookups; + 1 to 8 = create at most 1 to 8 new subtables for each existing + subtable, provided that it would yield a 50%% file size saving; + 9 = create as many new subtables as needed to yield a file size saving. + Default: 0. + + This compaction aims to save file size, by splitting large class + kerning subtables (Format 2) that contain many zero values into + smaller and denser subtables. It's a trade-off between the overhead + of several subtables versus the sparseness of one big subtable. + + See the pull request: https://github.com/fonttools/fonttools/pull/2326 + """ + ), + default=0, + parse=int, + validate=lambda v: v in range(10), +) + +Config.register_option( + name="fontTools.ttLib.tables.otBase:USE_HARFBUZZ_REPACKER", + help=dedent( + """\ + FontTools tries to use the HarfBuzz Repacker to serialize GPOS/GSUB tables + if the uharfbuzz python bindings are importable, otherwise falls back to its + slower, less efficient serializer. Set to False to always use the latter. + Set to True to explicitly request the HarfBuzz Repacker (will raise an + error if uharfbuzz cannot be imported). + """ + ), + default=None, + parse=Option.parse_optional_bool, + validate=Option.validate_optional_bool, +) + +Config.register_option( + name="fontTools.otlLib.builder:WRITE_GPOS7", + help=dedent( + """\ + macOS before 13.2 didn’t support GPOS LookupType 7 (non-chaining + ContextPos lookups), so FontTools.otlLib.builder disables a file size + optimisation that would use LookupType 7 instead of 8 when there is no + chaining (no prefix or suffix). Set to True to enable the optimization. + """ + ), + default=False, + parse=Option.parse_optional_bool, + validate=Option.validate_optional_bool, +) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__init__.py new file mode 100644 index 0000000..4ae6356 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .cu2qu import * diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__main__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__main__.py new file mode 100644 index 0000000..084bf8f --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/__main__.py @@ -0,0 +1,6 @@ +import sys +from .cli import main + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/benchmark.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/benchmark.py new file mode 100644 index 0000000..2ab1e96 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/benchmark.py @@ -0,0 +1,55 @@ +"""Benchmark the cu2qu algorithm performance.""" + +from .cu2qu import * +import random +import timeit + +MAX_ERR = 0.05 + + +def generate_curve(): + return [ + tuple(float(random.randint(0, 2048)) for coord in range(2)) + for point in range(4) + ] + + +def setup_curve_to_quadratic(): + return generate_curve(), MAX_ERR + + +def setup_curves_to_quadratic(): + num_curves = 3 + return ([generate_curve() for curve in range(num_curves)], [MAX_ERR] * num_curves) + + +def run_benchmark(module, function, setup_suffix="", repeat=5, number=1000): + setup_func = "setup_" + function + if setup_suffix: + print("%s with %s:" % (function, setup_suffix), end="") + setup_func += "_" + setup_suffix + else: + print("%s:" % function, end="") + + def wrapper(function, setup_func): + function = globals()[function] + setup_func = globals()[setup_func] + + def wrapped(): + return function(*setup_func()) + + return wrapped + + results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number) + print("\t%5.1fus" % (min(results) * 1000000.0 / number)) + + +def main(): + """Benchmark the cu2qu algorithm performance.""" + run_benchmark("cu2qu", "curve_to_quadratic") + run_benchmark("cu2qu", "curves_to_quadratic") + + +if __name__ == "__main__": + random.seed(1) + main() diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cli.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cli.py new file mode 100644 index 0000000..9144043 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cli.py @@ -0,0 +1,198 @@ +import os +import argparse +import logging +import shutil +import multiprocessing as mp +from contextlib import closing +from functools import partial + +import fontTools +from .ufo import font_to_quadratic, fonts_to_quadratic + +ufo_module = None +try: + import ufoLib2 as ufo_module +except ImportError: + try: + import defcon as ufo_module + except ImportError as e: + pass + + +logger = logging.getLogger("fontTools.cu2qu") + + +def _cpu_count(): + try: + return mp.cpu_count() + except NotImplementedError: # pragma: no cover + return 1 + + +def open_ufo(path): + if hasattr(ufo_module.Font, "open"): # ufoLib2 + return ufo_module.Font.open(path) + return ufo_module.Font(path) # defcon + + +def _font_to_quadratic(input_path, output_path=None, **kwargs): + ufo = open_ufo(input_path) + logger.info("Converting curves for %s", input_path) + if font_to_quadratic(ufo, **kwargs): + logger.info("Saving %s", output_path) + if output_path: + ufo.save(output_path) + else: + ufo.save() # save in-place + elif output_path: + _copytree(input_path, output_path) + + +def _samepath(path1, path2): + # TODO on python3+, there's os.path.samefile + path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1))) + path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2))) + return path1 == path2 + + +def _copytree(input_path, output_path): + if _samepath(input_path, output_path): + logger.debug("input and output paths are the same file; skipped copy") + return + if os.path.exists(output_path): + shutil.rmtree(output_path) + shutil.copytree(input_path, output_path) + + +def main(args=None): + """Convert a UFO font from cubic to quadratic curves""" + parser = argparse.ArgumentParser(prog="cu2qu") + parser.add_argument("--version", action="version", version=fontTools.__version__) + parser.add_argument( + "infiles", + nargs="+", + metavar="INPUT", + help="one or more input UFO source file(s).", + ) + parser.add_argument("-v", "--verbose", action="count", default=0) + parser.add_argument( + "-e", + "--conversion-error", + type=float, + metavar="ERROR", + default=None, + help="maxiumum approximation error measured in EM (default: 0.001)", + ) + parser.add_argument( + "-m", + "--mixed", + default=False, + action="store_true", + help="whether to used mixed quadratic and cubic curves", + ) + parser.add_argument( + "--keep-direction", + dest="reverse_direction", + action="store_false", + help="do not reverse the contour direction", + ) + + mode_parser = parser.add_mutually_exclusive_group() + mode_parser.add_argument( + "-i", + "--interpolatable", + action="store_true", + help="whether curve conversion should keep interpolation compatibility", + ) + mode_parser.add_argument( + "-j", + "--jobs", + type=int, + nargs="?", + default=1, + const=_cpu_count(), + metavar="N", + help="Convert using N multiple processes (default: %(default)s)", + ) + + output_parser = parser.add_mutually_exclusive_group() + output_parser.add_argument( + "-o", + "--output-file", + default=None, + metavar="OUTPUT", + help=( + "output filename for the converted UFO. By default fonts are " + "modified in place. This only works with a single input." + ), + ) + output_parser.add_argument( + "-d", + "--output-dir", + default=None, + metavar="DIRECTORY", + help="output directory where to save converted UFOs", + ) + + options = parser.parse_args(args) + + if ufo_module is None: + parser.error("Either ufoLib2 or defcon are required to run this script.") + + if not options.verbose: + level = "WARNING" + elif options.verbose == 1: + level = "INFO" + else: + level = "DEBUG" + logging.basicConfig(level=level) + + if len(options.infiles) > 1 and options.output_file: + parser.error("-o/--output-file can't be used with multile inputs") + + if options.output_dir: + output_dir = options.output_dir + if not os.path.exists(output_dir): + os.mkdir(output_dir) + elif not os.path.isdir(output_dir): + parser.error("'%s' is not a directory" % output_dir) + output_paths = [ + os.path.join(output_dir, os.path.basename(p)) for p in options.infiles + ] + elif options.output_file: + output_paths = [options.output_file] + else: + # save in-place + output_paths = [None] * len(options.infiles) + + kwargs = dict( + dump_stats=options.verbose > 0, + max_err_em=options.conversion_error, + reverse_direction=options.reverse_direction, + all_quadratic=False if options.mixed else True, + ) + + if options.interpolatable: + logger.info("Converting curves compatibly") + ufos = [open_ufo(infile) for infile in options.infiles] + if fonts_to_quadratic(ufos, **kwargs): + for ufo, output_path in zip(ufos, output_paths): + logger.info("Saving %s", output_path) + if output_path: + ufo.save(output_path) + else: + ufo.save() + else: + for input_path, output_path in zip(options.infiles, output_paths): + if output_path: + _copytree(input_path, output_path) + else: + jobs = min(len(options.infiles), options.jobs) if options.jobs > 1 else 1 + if jobs > 1: + func = partial(_font_to_quadratic, **kwargs) + logger.info("Running %d parallel processes", jobs) + with closing(mp.Pool(jobs)) as pool: + pool.starmap(func, zip(options.infiles, output_paths)) + else: + for input_path, output_path in zip(options.infiles, output_paths): + _font_to_quadratic(input_path, output_path, **kwargs) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cu2qu.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cu2qu.py new file mode 100644 index 0000000..e620b48 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/cu2qu.py @@ -0,0 +1,534 @@ +# cython: language_level=3 +# distutils: define_macros=CYTHON_TRACE_NOGIL=1 + +# Copyright 2015 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + import cython + + COMPILED = cython.compiled +except (AttributeError, ImportError): + # if cython not installed, use mock module with no-op decorators and types + from fontTools.misc import cython + + COMPILED = False + +import math + +from .errors import Error as Cu2QuError, ApproxNotFoundError + + +__all__ = ["curve_to_quadratic", "curves_to_quadratic"] + +MAX_N = 100 + +NAN = float("NaN") + + +@cython.cfunc +@cython.inline +@cython.returns(cython.double) +@cython.locals(v1=cython.complex, v2=cython.complex) +def dot(v1, v2): + """Return the dot product of two vectors. + + Args: + v1 (complex): First vector. + v2 (complex): Second vector. + + Returns: + double: Dot product. + """ + return (v1 * v2.conjugate()).real + + +@cython.cfunc +@cython.inline +@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) +@cython.locals( + _1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex +) +def calc_cubic_points(a, b, c, d): + _1 = d + _2 = (c / 3.0) + d + _3 = (b + c) / 3.0 + _2 + _4 = a + d + c + b + return _1, _2, _3, _4 + + +@cython.cfunc +@cython.inline +@cython.locals( + p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex +) +@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) +def calc_cubic_parameters(p0, p1, p2, p3): + c = (p1 - p0) * 3.0 + b = (p2 - p1) * 3.0 - c + d = p0 + a = p3 - d - c - b + return a, b, c, d + + +@cython.cfunc +@cython.inline +@cython.locals( + p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex +) +def split_cubic_into_n_iter(p0, p1, p2, p3, n): + """Split a cubic Bezier into n equal parts. + + Splits the curve into `n` equal parts by curve time. + (t=0..1/n, t=1/n..2/n, ...) + + Args: + p0 (complex): Start point of curve. + p1 (complex): First handle of curve. + p2 (complex): Second handle of curve. + p3 (complex): End point of curve. + + Returns: + An iterator yielding the control points (four complex values) of the + subcurves. + """ + # Hand-coded special-cases + if n == 2: + return iter(split_cubic_into_two(p0, p1, p2, p3)) + if n == 3: + return iter(split_cubic_into_three(p0, p1, p2, p3)) + if n == 4: + a, b = split_cubic_into_two(p0, p1, p2, p3) + return iter( + split_cubic_into_two(a[0], a[1], a[2], a[3]) + + split_cubic_into_two(b[0], b[1], b[2], b[3]) + ) + if n == 6: + a, b = split_cubic_into_two(p0, p1, p2, p3) + return iter( + split_cubic_into_three(a[0], a[1], a[2], a[3]) + + split_cubic_into_three(b[0], b[1], b[2], b[3]) + ) + + return _split_cubic_into_n_gen(p0, p1, p2, p3, n) + + +@cython.locals( + p0=cython.complex, + p1=cython.complex, + p2=cython.complex, + p3=cython.complex, + n=cython.int, +) +@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) +@cython.locals( + dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int +) +@cython.locals( + a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex +) +def _split_cubic_into_n_gen(p0, p1, p2, p3, n): + a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3) + dt = 1 / n + delta_2 = dt * dt + delta_3 = dt * delta_2 + for i in range(n): + t1 = i * dt + t1_2 = t1 * t1 + # calc new a, b, c and d + a1 = a * delta_3 + b1 = (3 * a * t1 + b) * delta_2 + c1 = (2 * b * t1 + c + 3 * a * t1_2) * dt + d1 = a * t1 * t1_2 + b * t1_2 + c * t1 + d + yield calc_cubic_points(a1, b1, c1, d1) + + +@cython.cfunc +@cython.inline +@cython.locals( + p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex +) +@cython.locals(mid=cython.complex, deriv3=cython.complex) +def split_cubic_into_two(p0, p1, p2, p3): + """Split a cubic Bezier into two equal parts. + + Splits the curve into two equal parts at t = 0.5 + + Args: + p0 (complex): Start point of curve. + p1 (complex): First handle of curve. + p2 (complex): Second handle of curve. + p3 (complex): End point of curve. + + Returns: + tuple: Two cubic Beziers (each expressed as a tuple of four complex + values). + """ + mid = (p0 + 3 * (p1 + p2) + p3) * 0.125 + deriv3 = (p3 + p2 - p1 - p0) * 0.125 + return ( + (p0, (p0 + p1) * 0.5, mid - deriv3, mid), + (mid, mid + deriv3, (p2 + p3) * 0.5, p3), + ) + + +@cython.cfunc +@cython.inline +@cython.locals( + p0=cython.complex, + p1=cython.complex, + p2=cython.complex, + p3=cython.complex, +) +@cython.locals( + mid1=cython.complex, + deriv1=cython.complex, + mid2=cython.complex, + deriv2=cython.complex, +) +def split_cubic_into_three(p0, p1, p2, p3): + """Split a cubic Bezier into three equal parts. + + Splits the curve into three equal parts at t = 1/3 and t = 2/3 + + Args: + p0 (complex): Start point of curve. + p1 (complex): First handle of curve. + p2 (complex): Second handle of curve. + p3 (complex): End point of curve. + + Returns: + tuple: Three cubic Beziers (each expressed as a tuple of four complex + values). + """ + mid1 = (8 * p0 + 12 * p1 + 6 * p2 + p3) * (1 / 27) + deriv1 = (p3 + 3 * p2 - 4 * p0) * (1 / 27) + mid2 = (p0 + 6 * p1 + 12 * p2 + 8 * p3) * (1 / 27) + deriv2 = (4 * p3 - 3 * p1 - p0) * (1 / 27) + return ( + (p0, (2 * p0 + p1) / 3.0, mid1 - deriv1, mid1), + (mid1, mid1 + deriv1, mid2 - deriv2, mid2), + (mid2, mid2 + deriv2, (p2 + 2 * p3) / 3.0, p3), + ) + + +@cython.cfunc +@cython.inline +@cython.returns(cython.complex) +@cython.locals( + t=cython.double, + p0=cython.complex, + p1=cython.complex, + p2=cython.complex, + p3=cython.complex, +) +@cython.locals(_p1=cython.complex, _p2=cython.complex) +def cubic_approx_control(t, p0, p1, p2, p3): + """Approximate a cubic Bezier using a quadratic one. + + Args: + t (double): Position of control point. + p0 (complex): Start point of curve. + p1 (complex): First handle of curve. + p2 (complex): Second handle of curve. + p3 (complex): End point of curve. + + Returns: + complex: Location of candidate control point on quadratic curve. + """ + _p1 = p0 + (p1 - p0) * 1.5 + _p2 = p3 + (p2 - p3) * 1.5 + return _p1 + (_p2 - _p1) * t + + +@cython.cfunc +@cython.inline +@cython.returns(cython.complex) +@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) +@cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double) +def calc_intersect(a, b, c, d): + """Calculate the intersection of two lines. + + Args: + a (complex): Start point of first line. + b (complex): End point of first line. + c (complex): Start point of second line. + d (complex): End point of second line. + + Returns: + complex: Location of intersection if one present, ``complex(NaN,NaN)`` + if no intersection was found. + """ + ab = b - a + cd = d - c + p = ab * 1j + try: + h = dot(p, a - c) / dot(p, cd) + except ZeroDivisionError: + return complex(NAN, NAN) + return c + cd * h + + +@cython.cfunc +@cython.returns(cython.int) +@cython.locals( + tolerance=cython.double, + p0=cython.complex, + p1=cython.complex, + p2=cython.complex, + p3=cython.complex, +) +@cython.locals(mid=cython.complex, deriv3=cython.complex) +def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance): + """Check if a cubic Bezier lies within a given distance of the origin. + + "Origin" means *the* origin (0,0), not the start of the curve. Note that no + checks are made on the start and end positions of the curve; this function + only checks the inside of the curve. + + Args: + p0 (complex): Start point of curve. + p1 (complex): First handle of curve. + p2 (complex): Second handle of curve. + p3 (complex): End point of curve. + tolerance (double): Distance from origin. + + Returns: + bool: True if the cubic Bezier ``p`` entirely lies within a distance + ``tolerance`` of the origin, False otherwise. + """ + # First check p2 then p1, as p2 has higher error early on. + if abs(p2) <= tolerance and abs(p1) <= tolerance: + return True + + # Split. + mid = (p0 + 3 * (p1 + p2) + p3) * 0.125 + if abs(mid) > tolerance: + return False + deriv3 = (p3 + p2 - p1 - p0) * 0.125 + return cubic_farthest_fit_inside( + p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance + ) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance) + + +@cython.cfunc +@cython.inline +@cython.locals(tolerance=cython.double) +@cython.locals( + q1=cython.complex, + c0=cython.complex, + c1=cython.complex, + c2=cython.complex, + c3=cython.complex, +) +def cubic_approx_quadratic(cubic, tolerance): + """Approximate a cubic Bezier with a single quadratic within a given tolerance. + + Args: + cubic (sequence): Four complex numbers representing control points of + the cubic Bezier curve. + tolerance (double): Permitted deviation from the original curve. + + Returns: + Three complex numbers representing control points of the quadratic + curve if it fits within the given tolerance, or ``None`` if no suitable + curve could be calculated. + """ + + q1 = calc_intersect(cubic[0], cubic[1], cubic[2], cubic[3]) + if math.isnan(q1.imag): + return None + c0 = cubic[0] + c3 = cubic[3] + c1 = c0 + (q1 - c0) * (2 / 3) + c2 = c3 + (q1 - c3) * (2 / 3) + if not cubic_farthest_fit_inside(0, c1 - cubic[1], c2 - cubic[2], 0, tolerance): + return None + return c0, q1, c3 + + +@cython.cfunc +@cython.locals(n=cython.int, tolerance=cython.double) +@cython.locals(i=cython.int) +@cython.locals(all_quadratic=cython.int) +@cython.locals( + c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex +) +@cython.locals( + q0=cython.complex, + q1=cython.complex, + next_q1=cython.complex, + q2=cython.complex, + d1=cython.complex, +) +def cubic_approx_spline(cubic, n, tolerance, all_quadratic): + """Approximate a cubic Bezier curve with a spline of n quadratics. + + Args: + cubic (sequence): Four complex numbers representing control points of + the cubic Bezier curve. + n (int): Number of quadratic Bezier curves in the spline. + tolerance (double): Permitted deviation from the original curve. + + Returns: + A list of ``n+2`` complex numbers, representing control points of the + quadratic spline if it fits within the given tolerance, or ``None`` if + no suitable spline could be calculated. + """ + + if n == 1: + return cubic_approx_quadratic(cubic, tolerance) + if n == 2 and all_quadratic == False: + return cubic + + cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n) + + # calculate the spline of quadratics and check errors at the same time. + next_cubic = next(cubics) + next_q1 = cubic_approx_control( + 0, next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3] + ) + q2 = cubic[0] + d1 = 0j + spline = [cubic[0], next_q1] + for i in range(1, n + 1): + # Current cubic to convert + c0, c1, c2, c3 = next_cubic + + # Current quadratic approximation of current cubic + q0 = q2 + q1 = next_q1 + if i < n: + next_cubic = next(cubics) + next_q1 = cubic_approx_control( + i / (n - 1), next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3] + ) + spline.append(next_q1) + q2 = (q1 + next_q1) * 0.5 + else: + q2 = c3 + + # End-point deltas + d0 = d1 + d1 = q2 - c3 + + if abs(d1) > tolerance or not cubic_farthest_fit_inside( + d0, + q0 + (q1 - q0) * (2 / 3) - c1, + q2 + (q1 - q2) * (2 / 3) - c2, + d1, + tolerance, + ): + return None + spline.append(cubic[3]) + + return spline + + +@cython.locals(max_err=cython.double) +@cython.locals(n=cython.int) +@cython.locals(all_quadratic=cython.int) +def curve_to_quadratic(curve, max_err, all_quadratic=True): + """Approximate a cubic Bezier curve with a spline of n quadratics. + + Args: + cubic (sequence): Four 2D tuples representing control points of + the cubic Bezier curve. + max_err (double): Permitted deviation from the original curve. + all_quadratic (bool): If True (default) returned value is a + quadratic spline. If False, it's either a single quadratic + curve or a single cubic curve. + + Returns: + If all_quadratic is True: A list of 2D tuples, representing + control points of the quadratic spline if it fits within the + given tolerance, or ``None`` if no suitable spline could be + calculated. + + If all_quadratic is False: Either a quadratic curve (if length + of output is 3), or a cubic curve (if length of output is 4). + """ + + curve = [complex(*p) for p in curve] + + for n in range(1, MAX_N + 1): + spline = cubic_approx_spline(curve, n, max_err, all_quadratic) + if spline is not None: + # done. go home + return [(s.real, s.imag) for s in spline] + + raise ApproxNotFoundError(curve) + + +@cython.locals(l=cython.int, last_i=cython.int, i=cython.int) +@cython.locals(all_quadratic=cython.int) +def curves_to_quadratic(curves, max_errors, all_quadratic=True): + """Return quadratic Bezier splines approximating the input cubic Beziers. + + Args: + curves: A sequence of *n* curves, each curve being a sequence of four + 2D tuples. + max_errors: A sequence of *n* floats representing the maximum permissible + deviation from each of the cubic Bezier curves. + all_quadratic (bool): If True (default) returned values are a + quadratic spline. If False, they are either a single quadratic + curve or a single cubic curve. + + Example:: + + >>> curves_to_quadratic( [ + ... [ (50,50), (100,100), (150,100), (200,50) ], + ... [ (75,50), (120,100), (150,75), (200,60) ] + ... ], [1,1] ) + [[(50.0, 50.0), (75.0, 75.0), (125.0, 91.66666666666666), (175.0, 75.0), (200.0, 50.0)], [(75.0, 50.0), (97.5, 75.0), (135.41666666666666, 82.08333333333333), (175.0, 67.5), (200.0, 60.0)]] + + The returned splines have "implied oncurve points" suitable for use in + TrueType ``glif`` outlines - i.e. in the first spline returned above, + the first quadratic segment runs from (50,50) to + ( (75 + 125)/2 , (120 + 91.666..)/2 ) = (100, 83.333...). + + Returns: + If all_quadratic is True, a list of splines, each spline being a list + of 2D tuples. + + If all_quadratic is False, a list of curves, each curve being a quadratic + (length 3), or cubic (length 4). + + Raises: + fontTools.cu2qu.Errors.ApproxNotFoundError: if no suitable approximation + can be found for all curves with the given parameters. + """ + + curves = [[complex(*p) for p in curve] for curve in curves] + assert len(max_errors) == len(curves) + + l = len(curves) + splines = [None] * l + last_i = i = 0 + n = 1 + while True: + spline = cubic_approx_spline(curves[i], n, max_errors[i], all_quadratic) + if spline is None: + if n == MAX_N: + break + n += 1 + last_i = i + continue + splines[i] = spline + i = (i + 1) % l + if i == last_i: + # done. go home + return [[(s.real, s.imag) for s in spline] for spline in splines] + + raise ApproxNotFoundError(curves) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/errors.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/errors.py new file mode 100644 index 0000000..fa3dc42 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/errors.py @@ -0,0 +1,77 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class Error(Exception): + """Base Cu2Qu exception class for all other errors.""" + + +class ApproxNotFoundError(Error): + def __init__(self, curve): + message = "no approximation found: %s" % curve + super().__init__(message) + self.curve = curve + + +class UnequalZipLengthsError(Error): + pass + + +class IncompatibleGlyphsError(Error): + def __init__(self, glyphs): + assert len(glyphs) > 1 + self.glyphs = glyphs + names = set(repr(g.name) for g in glyphs) + if len(names) > 1: + self.combined_name = "{%s}" % ", ".join(sorted(names)) + else: + self.combined_name = names.pop() + + def __repr__(self): + return "<%s %s>" % (type(self).__name__, self.combined_name) + + +class IncompatibleSegmentNumberError(IncompatibleGlyphsError): + def __str__(self): + return "Glyphs named %s have different number of segments" % ( + self.combined_name + ) + + +class IncompatibleSegmentTypesError(IncompatibleGlyphsError): + def __init__(self, glyphs, segments): + IncompatibleGlyphsError.__init__(self, glyphs) + self.segments = segments + + def __str__(self): + lines = [] + ndigits = len(str(max(self.segments))) + for i, tags in sorted(self.segments.items()): + lines.append( + "%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags)) + ) + return "Glyphs named %s have incompatible segment types:\n %s" % ( + self.combined_name, + "\n ".join(lines), + ) + + +class IncompatibleFontsError(Error): + def __init__(self, glyph_errors): + self.glyph_errors = glyph_errors + + def __str__(self): + return "fonts contains incompatible glyphs: %s" % ( + ", ".join(repr(g) for g in sorted(self.glyph_errors.keys())) + ) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/ufo.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/ufo.py new file mode 100644 index 0000000..10367cf --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/cu2qu/ufo.py @@ -0,0 +1,349 @@ +# Copyright 2015 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Converts cubic bezier curves to quadratic splines. + +Conversion is performed such that the quadratic splines keep the same end-curve +tangents as the original cubics. The approach is iterative, increasing the +number of segments for a spline until the error gets below a bound. + +Respective curves from multiple fonts will be converted at once to ensure that +the resulting splines are interpolation-compatible. +""" + +import logging +from fontTools.pens.basePen import AbstractPen +from fontTools.pens.pointPen import PointToSegmentPen +from fontTools.pens.reverseContourPen import ReverseContourPen + +from . import curves_to_quadratic +from .errors import ( + UnequalZipLengthsError, + IncompatibleSegmentNumberError, + IncompatibleSegmentTypesError, + IncompatibleGlyphsError, + IncompatibleFontsError, +) + + +__all__ = ["fonts_to_quadratic", "font_to_quadratic"] + +# The default approximation error below is a relative value (1/1000 of the EM square). +# Later on, we convert it to absolute font units by multiplying it by a font's UPEM +# (see fonts_to_quadratic). +DEFAULT_MAX_ERR = 0.001 +CURVE_TYPE_LIB_KEY = "com.github.googlei18n.cu2qu.curve_type" + +logger = logging.getLogger(__name__) + + +_zip = zip + + +def zip(*args): + """Ensure each argument to zip has the same length. Also make sure a list is + returned for python 2/3 compatibility. + """ + + if len(set(len(a) for a in args)) != 1: + raise UnequalZipLengthsError(*args) + return list(_zip(*args)) + + +class GetSegmentsPen(AbstractPen): + """Pen to collect segments into lists of points for conversion. + + Curves always include their initial on-curve point, so some points are + duplicated between segments. + """ + + def __init__(self): + self._last_pt = None + self.segments = [] + + def _add_segment(self, tag, *args): + if tag in ["move", "line", "qcurve", "curve"]: + self._last_pt = args[-1] + self.segments.append((tag, args)) + + def moveTo(self, pt): + self._add_segment("move", pt) + + def lineTo(self, pt): + self._add_segment("line", pt) + + def qCurveTo(self, *points): + self._add_segment("qcurve", self._last_pt, *points) + + def curveTo(self, *points): + self._add_segment("curve", self._last_pt, *points) + + def closePath(self): + self._add_segment("close") + + def endPath(self): + self._add_segment("end") + + def addComponent(self, glyphName, transformation): + pass + + +def _get_segments(glyph): + """Get a glyph's segments as extracted by GetSegmentsPen.""" + + pen = GetSegmentsPen() + # glyph.draw(pen) + # We can't simply draw the glyph with the pen, but we must initialize the + # PointToSegmentPen explicitly with outputImpliedClosingLine=True. + # By default PointToSegmentPen does not outputImpliedClosingLine -- unless + # last and first point on closed contour are duplicated. Because we are + # converting multiple glyphs at the same time, we want to make sure + # this function returns the same number of segments, whether or not + # the last and first point overlap. + # https://github.com/googlefonts/fontmake/issues/572 + # https://github.com/fonttools/fonttools/pull/1720 + pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=True) + glyph.drawPoints(pointPen) + return pen.segments + + +def _set_segments(glyph, segments, reverse_direction): + """Draw segments as extracted by GetSegmentsPen back to a glyph.""" + + glyph.clearContours() + pen = glyph.getPen() + if reverse_direction: + pen = ReverseContourPen(pen) + for tag, args in segments: + if tag == "move": + pen.moveTo(*args) + elif tag == "line": + pen.lineTo(*args) + elif tag == "curve": + pen.curveTo(*args[1:]) + elif tag == "qcurve": + pen.qCurveTo(*args[1:]) + elif tag == "close": + pen.closePath() + elif tag == "end": + pen.endPath() + else: + raise AssertionError('Unhandled segment type "%s"' % tag) + + +def _segments_to_quadratic(segments, max_err, stats, all_quadratic=True): + """Return quadratic approximations of cubic segments.""" + + assert all(s[0] == "curve" for s in segments), "Non-cubic given to convert" + + new_points = curves_to_quadratic([s[1] for s in segments], max_err, all_quadratic) + n = len(new_points[0]) + assert all(len(s) == n for s in new_points[1:]), "Converted incompatibly" + + spline_length = str(n - 2) + stats[spline_length] = stats.get(spline_length, 0) + 1 + + if all_quadratic or n == 3: + return [("qcurve", p) for p in new_points] + else: + return [("curve", p) for p in new_points] + + +def _glyphs_to_quadratic(glyphs, max_err, reverse_direction, stats, all_quadratic=True): + """Do the actual conversion of a set of compatible glyphs, after arguments + have been set up. + + Return True if the glyphs were modified, else return False. + """ + + try: + segments_by_location = zip(*[_get_segments(g) for g in glyphs]) + except UnequalZipLengthsError: + raise IncompatibleSegmentNumberError(glyphs) + if not any(segments_by_location): + return False + + # always modify input glyphs if reverse_direction is True + glyphs_modified = reverse_direction + + new_segments_by_location = [] + incompatible = {} + for i, segments in enumerate(segments_by_location): + tag = segments[0][0] + if not all(s[0] == tag for s in segments[1:]): + incompatible[i] = [s[0] for s in segments] + elif tag == "curve": + new_segments = _segments_to_quadratic( + segments, max_err, stats, all_quadratic + ) + if all_quadratic or new_segments != segments: + glyphs_modified = True + segments = new_segments + new_segments_by_location.append(segments) + + if glyphs_modified: + new_segments_by_glyph = zip(*new_segments_by_location) + for glyph, new_segments in zip(glyphs, new_segments_by_glyph): + _set_segments(glyph, new_segments, reverse_direction) + + if incompatible: + raise IncompatibleSegmentTypesError(glyphs, segments=incompatible) + return glyphs_modified + + +def glyphs_to_quadratic( + glyphs, max_err=None, reverse_direction=False, stats=None, all_quadratic=True +): + """Convert the curves of a set of compatible of glyphs to quadratic. + + All curves will be converted to quadratic at once, ensuring interpolation + compatibility. If this is not required, calling glyphs_to_quadratic with one + glyph at a time may yield slightly more optimized results. + + Return True if glyphs were modified, else return False. + + Raises IncompatibleGlyphsError if glyphs have non-interpolatable outlines. + """ + if stats is None: + stats = {} + + if not max_err: + # assume 1000 is the default UPEM + max_err = DEFAULT_MAX_ERR * 1000 + + if isinstance(max_err, (list, tuple)): + max_errors = max_err + else: + max_errors = [max_err] * len(glyphs) + assert len(max_errors) == len(glyphs) + + return _glyphs_to_quadratic( + glyphs, max_errors, reverse_direction, stats, all_quadratic + ) + + +def fonts_to_quadratic( + fonts, + max_err_em=None, + max_err=None, + reverse_direction=False, + stats=None, + dump_stats=False, + remember_curve_type=True, + all_quadratic=True, +): + """Convert the curves of a collection of fonts to quadratic. + + All curves will be converted to quadratic at once, ensuring interpolation + compatibility. If this is not required, calling fonts_to_quadratic with one + font at a time may yield slightly more optimized results. + + Return True if fonts were modified, else return False. + + By default, cu2qu stores the curve type in the fonts' lib, under a private + key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert + them again if the curve type is already set to "quadratic". + Setting 'remember_curve_type' to False disables this optimization. + + Raises IncompatibleFontsError if same-named glyphs from different fonts + have non-interpolatable outlines. + """ + + if remember_curve_type: + curve_types = {f.lib.get(CURVE_TYPE_LIB_KEY, "cubic") for f in fonts} + if len(curve_types) == 1: + curve_type = next(iter(curve_types)) + if curve_type in ("quadratic", "mixed"): + logger.info("Curves already converted to quadratic") + return False + elif curve_type == "cubic": + pass # keep converting + else: + raise NotImplementedError(curve_type) + elif len(curve_types) > 1: + # going to crash later if they do differ + logger.warning("fonts may contain different curve types") + + if stats is None: + stats = {} + + if max_err_em and max_err: + raise TypeError("Only one of max_err and max_err_em can be specified.") + if not (max_err_em or max_err): + max_err_em = DEFAULT_MAX_ERR + + if isinstance(max_err, (list, tuple)): + assert len(max_err) == len(fonts) + max_errors = max_err + elif max_err: + max_errors = [max_err] * len(fonts) + + if isinstance(max_err_em, (list, tuple)): + assert len(fonts) == len(max_err_em) + max_errors = [f.info.unitsPerEm * e for f, e in zip(fonts, max_err_em)] + elif max_err_em: + max_errors = [f.info.unitsPerEm * max_err_em for f in fonts] + + modified = False + glyph_errors = {} + for name in set().union(*(f.keys() for f in fonts)): + glyphs = [] + cur_max_errors = [] + for font, error in zip(fonts, max_errors): + if name in font: + glyphs.append(font[name]) + cur_max_errors.append(error) + try: + modified |= _glyphs_to_quadratic( + glyphs, cur_max_errors, reverse_direction, stats, all_quadratic + ) + except IncompatibleGlyphsError as exc: + logger.error(exc) + glyph_errors[name] = exc + + if glyph_errors: + raise IncompatibleFontsError(glyph_errors) + + if modified and dump_stats: + spline_lengths = sorted(stats.keys()) + logger.info( + "New spline lengths: %s" + % (", ".join("%s: %d" % (l, stats[l]) for l in spline_lengths)) + ) + + if remember_curve_type: + for font in fonts: + curve_type = font.lib.get(CURVE_TYPE_LIB_KEY, "cubic") + new_curve_type = "quadratic" if all_quadratic else "mixed" + if curve_type != new_curve_type: + font.lib[CURVE_TYPE_LIB_KEY] = new_curve_type + modified = True + return modified + + +def glyph_to_quadratic(glyph, **kwargs): + """Convenience wrapper around glyphs_to_quadratic, for just one glyph. + Return True if the glyph was modified, else return False. + """ + + return glyphs_to_quadratic([glyph], **kwargs) + + +def font_to_quadratic(font, **kwargs): + """Convenience wrapper around fonts_to_quadratic, for just one font. + Return True if the font was modified, else return False. + """ + + return fonts_to_quadratic([font], **kwargs) diff --git a/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/__init__.py b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/__init__.py new file mode 100644 index 0000000..1c71fd0 --- /dev/null +++ b/dotfiles/inkscape/extensions/org.inkscape.extension.30306/scientific_inkscape/inkex1_3_0/site-packages/fontTools/designspaceLib/__init__.py @@ -0,0 +1,3283 @@ +from __future__ import annotations + +import collections +import copy +import itertools +import math +import os +import posixpath +from io import BytesIO, StringIO +from textwrap import indent +from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union, cast + +from fontTools.misc import etree as ET +from fontTools.misc import plistlib +from fontTools.misc.loggingTools import LogMixin +from fontTools.misc.textTools import tobytes, tostr + +""" + designSpaceDocument + + - read and write designspace files +""" + +__all__ = [ + "AxisDescriptor", + "AxisLabelDescriptor", + "AxisMappingDescriptor", + "BaseDocReader", + "BaseDocWriter", + "DesignSpaceDocument", + "DesignSpaceDocumentError", + "DiscreteAxisDescriptor", + "InstanceDescriptor", + "LocationLabelDescriptor", + "RangeAxisSubsetDescriptor", + "RuleDescriptor", + "SourceDescriptor", + "ValueAxisSubsetDescriptor", + "VariableFontDescriptor", +] + +# ElementTree allows to find namespace-prefixed elements, but not attributes +# so we have to do it ourselves for 'xml:lang' +XML_NS = "{http://www.w3.org/XML/1998/namespace}" +XML_LANG = XML_NS + "lang" + + +def posix(path): + """Normalize paths using forward slash to work also on Windows.""" + new_path = posixpath.join(*path.split(os.path.sep)) + if path.startswith("/"): + # The above transformation loses absolute paths + new_path = "/" + new_path + elif path.startswith(r"\\"): + # The above transformation loses leading slashes of UNC path mounts + new_path = "//" + new_path + return new_path + + +def posixpath_property(private_name): + """Generate a propery that holds a path always using forward slashes.""" + + def getter(self): + # Normal getter + return getattr(self, private_name) + + def setter(self, value): + # The setter rewrites paths using forward slashes + if value is not None: + value = posix(value) + setattr(self, private_name, value) + + return property(getter, setter) + + +class DesignSpaceDocumentError(Exception): + def __init__(self, msg, obj=None): + self.msg = msg + self.obj = obj + + def __str__(self): + return str(self.msg) + (": %r" % self.obj if self.obj is not None else "") + + +class AsDictMixin(object): + def asdict(self): + d = {} + for attr, value in self.__dict__.items(): + if attr.startswith("_"): + continue + if hasattr(value, "asdict"): + value = value.asdict() + elif isinstance(value, list): + value = [v.asdict() if hasattr(v, "asdict") else v for v in value] + d[attr] = value + return d + + +class SimpleDescriptor(AsDictMixin): + """Containers for a bunch of attributes""" + + # XXX this is ugly. The 'print' is inappropriate here, and instead of + # assert, it should simply return True/False + def compare(self, other): + # test if this object contains the same data as the other + for attr in self._attrs: + try: + assert getattr(self, attr) == getattr(other, attr) + except AssertionError: + print( + "failed attribute", + attr, + getattr(self, attr), + "!=", + getattr(other, attr), + ) + + def __repr__(self): + attrs = [f"{a}={repr(getattr(self, a))}," for a in self._attrs] + attrs = indent("\n".join(attrs), " ") + return f"{self.__class__.__name__}(\n{attrs}\n)" + + +class SourceDescriptor(SimpleDescriptor): + """Simple container for data related to the source + + .. code:: python + + doc = DesignSpaceDocument() + s1 = SourceDescriptor() + s1.path = masterPath1 + s1.name = "master.ufo1" + s1.font = defcon.Font("master.ufo1") + s1.location = dict(weight=0) + s1.familyName = "MasterFamilyName" + s1.styleName = "MasterStyleNameOne" + s1.localisedFamilyName = dict(fr="Caractère") + s1.mutedGlyphNames.append("A") + s1.mutedGlyphNames.append("Z") + doc.addSource(s1) + + """ + + flavor = "source" + _attrs = [ + "filename", + "path", + "name", + "layerName", + "location", + "copyLib", + "copyGroups", + "copyFeatures", + "muteKerning", + "muteInfo", + "mutedGlyphNames", + "familyName", + "styleName", + "localisedFamilyName", + ] + + filename = posixpath_property("_filename") + path = posixpath_property("_path") + + def __init__( + self, + *, + filename=None, + path=None, + font=None, + name=None, + location=None, + designLocation=None, + layerName=None, + familyName=None, + styleName=None, + localisedFamilyName=None, + copyLib=False, + copyInfo=False, + copyGroups=False, + copyFeatures=False, + muteKerning=False, + muteInfo=False, + mutedGlyphNames=None, + ): + self.filename = filename + """string. A relative path to the source file, **as it is in the document**. + + MutatorMath + VarLib. + """ + self.path = path + """The absolute path, calculated from filename.""" + + self.font = font + """Any Python object. Optional. Points to a representation of this + source font that is loaded in memory, as a Python object (e.g. a + ``defcon.Font`` or a ``fontTools.ttFont.TTFont``). + + The default document reader will not fill-in this attribute, and the + default writer will not use this attribute. It is up to the user of + ``designspaceLib`` to either load the resource identified by + ``filename`` and store it in this field, or write the contents of + this field to the disk and make ```filename`` point to that. + """ + + self.name = name + """string. Optional. Unique identifier name for this source. + + MutatorMath + varLib. + """ + + self.designLocation = ( + designLocation if designLocation is not None else location or {} + ) + """dict. Axis values for this source, in design space coordinates. + + MutatorMath + varLib. + + This may be only part of the full design location. + See :meth:`getFullDesignLocation()` + + .. versionadded:: 5.0 + """ + + self.layerName = layerName + """string. The name of the layer in the source to look for + outline data. Default ``None`` which means ``foreground``. + """ + self.familyName = familyName + """string. Family name of this source. Though this data + can be extracted from the font, it can be efficient to have it right + here. + + varLib. + """ + self.styleName = styleName + """string. Style name of this source. Though this data + can be extracted from the font, it can be efficient to have it right + here. + + varLib. + """ + self.localisedFamilyName = localisedFamilyName or {} + """dict. A dictionary of localised family name strings, keyed by + language code. + + If present, will be used to build localized names for all instances. + + .. versionadded:: 5.0 + """ + + self.copyLib = copyLib + """bool. Indicates if the contents of the font.lib need to + be copied to the instances. + + MutatorMath. + + .. deprecated:: 5.0 + """ + self.copyInfo = copyInfo + """bool. Indicates if the non-interpolating font.info needs + to be copied to the instances. + + MutatorMath. + + .. deprecated:: 5.0 + """ + self.copyGroups = copyGroups + """bool. Indicates if the groups need to be copied to the + instances. + + MutatorMath. + + .. deprecated:: 5.0 + """ + self.copyFeatures = copyFeatures + """bool. Indicates if the feature text needs to be + copied to the instances. + + MutatorMath. + + .. deprecated:: 5.0 + """ + self.muteKerning = muteKerning + """bool. Indicates if the kerning data from this source + needs to be muted (i.e. not be part of the calculations). + + MutatorMath only. + """ + self.muteInfo = muteInfo + """bool. Indicated if the interpolating font.info data for + this source needs to be muted. + + MutatorMath only. + """ + self.mutedGlyphNames = mutedGlyphNames or [] + """list. Glyphnames that need to be muted in the + instances. + + MutatorMath only. + """ + + @property + def location(self): + """dict. Axis values for this source, in design space coordinates. + + MutatorMath + varLib. + + .. deprecated:: 5.0 + Use the more explicit alias for this property :attr:`designLocation`. + """ + return self.designLocation + + @location.setter + def location(self, location: Optional[AnisotropicLocationDict]): + self.designLocation = location or {} + + def setFamilyName(self, familyName, languageCode="en"): + """Setter for :attr:`localisedFamilyName` + + .. versionadded:: 5.0 + """ + self.localisedFamilyName[languageCode] = tostr(familyName) + + def getFamilyName(self, languageCode="en"): + """Getter for :attr:`localisedFamilyName` + + .. versionadded:: 5.0 + """ + return self.localisedFamilyName.get(languageCode) + + def getFullDesignLocation( + self, doc: "DesignSpaceDocument" + ) -> AnisotropicLocationDict: + """Get the complete design location of this source, from its + :attr:`designLocation` and the document's axis defaults. + + .. versionadded:: 5.0 + """ + result: AnisotropicLocationDict = {} + for axis in doc.axes: + if axis.name in self.designLocation: + result[axis.name] = self.designLocation[axis.name] + else: + result[axis.name] = axis.map_forward(axis.default) + return result + + +class RuleDescriptor(SimpleDescriptor): + """Represents the rule descriptor element: a set of glyph substitutions to + trigger conditionally in some parts of the designspace. + + .. code:: python + + r1 = RuleDescriptor() + r1.name = "unique.rule.name" + r1.conditionSets.append([dict(name="weight", minimum=-10, maximum=10), dict(...)]) + r1.conditionSets.append([dict(...), dict(...)]) + r1.subs.append(("a", "a.alt")) + + .. code:: xml + + + + + + + + + + + + + + """ + + _attrs = ["name", "conditionSets", "subs"] # what do we need here + + def __init__(self, *, name=None, conditionSets=None, subs=None): + self.name = name + """string. Unique name for this rule. Can be used to reference this rule data.""" + # list of lists of dict(name='aaaa', minimum=0, maximum=1000) + self.conditionSets = conditionSets or [] + """a list of conditionsets. + + - Each conditionset is a list of conditions. + - Each condition is a dict with ``name``, ``minimum`` and ``maximum`` keys. + """ + # list of substitutions stored as tuples of glyphnames ("a", "a.alt") + self.subs = subs or [] + """list of substitutions. + + - Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt"). + - Note: By default, rules are applied first, before other text + shaping/OpenType layout, as they are part of the + `Required Variation Alternates OpenType feature `_. + See ref:`rules-element` § Attributes. + """ + + +def evaluateRule(rule, location): + """Return True if any of the rule's conditionsets matches the given location.""" + return any(evaluateConditions(c, location) for c in rule.conditionSets) + + +def evaluateConditions(conditions, location): + """Return True if all the conditions matches the given location. + + - If a condition has no minimum, check for < maximum. + - If a condition has no maximum, check for > minimum. + """ + for cd in conditions: + value = location[cd["name"]] + if cd.get("minimum") is None: + if value > cd["maximum"]: + return False + elif cd.get("maximum") is None: + if cd["minimum"] > value: + return False + elif not cd["minimum"] <= value <= cd["maximum"]: + return False + return True + + +def processRules(rules, location, glyphNames): + """Apply these rules at this location to these glyphnames. + + Return a new list of glyphNames with substitutions applied. + + - rule order matters + """ + newNames = [] + for rule in rules: + if evaluateRule(rule, location): + for name in glyphNames: + swap = False + for a, b in rule.subs: + if name == a: + swap = True + break + if swap: + newNames.append(b) + else: + newNames.append(name) + glyphNames = newNames + newNames = [] + return glyphNames + + +AnisotropicLocationDict = Dict[str, Union[float, Tuple[float, float]]] +SimpleLocationDict = Dict[str, float] + + +class AxisMappingDescriptor(SimpleDescriptor): + """Represents the axis mapping element: mapping an input location + to an output location in the designspace. + + .. code:: python + + m1 = AxisMappingDescriptor() + m1.inputLocation = {"weight": 900, "width": 150} + m1.outputLocation = {"weight": 870} + + .. code:: xml + + + + + + + + + + + + + """ + + _attrs = ["inputLocation", "outputLocation"] + + def __init__(self, *, inputLocation=None, outputLocation=None): + self.inputLocation: SimpleLocationDict = inputLocation or {} + """dict. Axis values for the input of the mapping, in design space coordinates. + + varLib. + + .. versionadded:: 5.1 + """ + self.outputLocation: SimpleLocationDict = outputLocation or {} + """dict. Axis values for the output of the mapping, in design space coordinates. + + varLib. + + .. versionadded:: 5.1 + """ + + +class InstanceDescriptor(SimpleDescriptor): + """Simple container for data related to the instance + + + .. code:: python + + i2 = InstanceDescriptor() + i2.path = instancePath2 + i2.familyName = "InstanceFamilyName" + i2.styleName = "InstanceStyleName" + i2.name = "instance.ufo2" + # anisotropic location + i2.designLocation = dict(weight=500, width=(400,300)) + i2.postScriptFontName = "InstancePostscriptName" + i2.styleMapFamilyName = "InstanceStyleMapFamilyName" + i2.styleMapStyleName = "InstanceStyleMapStyleName" + i2.lib['com.coolDesignspaceApp.specimenText'] = 'Hamburgerwhatever' + doc.addInstance(i2) + """ + + flavor = "instance" + _defaultLanguageCode = "en" + _attrs = [ + "filename", + "path", + "name", + "locationLabel", + "designLocation", + "userLocation", + "familyName", + "styleName", + "postScriptFontName", + "styleMapFamilyName", + "styleMapStyleName", + "localisedFamilyName", + "localisedStyleName", + "localisedStyleMapFamilyName", + "localisedStyleMapStyleName", + "glyphs", + "kerning", + "info", + "lib", + ] + + filename = posixpath_property("_filename") + path = posixpath_property("_path") + + def __init__( + self, + *, + filename=None, + path=None, + font=None, + name=None, + location=None, + locationLabel=None, + designLocation=None, + userLocation=None, + familyName=None, + styleName=None, + postScriptFontName=None, + styleMapFamilyName=None, + styleMapStyleName=None, + localisedFamilyName=None, + localisedStyleName=None, + localisedStyleMapFamilyName=None, + localisedStyleMapStyleName=None, + glyphs=None, + kerning=True, + info=True, + lib=None, + ): + self.filename = filename + """string. Relative path to the instance file, **as it is + in the document**. The file may or may not exist. + + MutatorMath + VarLib. + """ + self.path = path + """string. Absolute path to the instance file, calculated from + the document path and the string in the filename attr. The file may + or may not exist. + + MutatorMath. + """ + self.font = font + """Same as :attr:`SourceDescriptor.font` + + .. seealso:: :attr:`SourceDescriptor.font` + """ + self.name = name + """string. Unique identifier name of the instance, used to + identify it if it needs to be referenced from elsewhere in the + document. + """ + self.locationLabel = locationLabel + """Name of a :class:`LocationLabelDescriptor`. If + provided, the instance should have the same location as the + LocationLabel. + + .. seealso:: + :meth:`getFullDesignLocation` + :meth:`getFullUserLocation` + + .. versionadded:: 5.0 + """ + self.designLocation: AnisotropicLocationDict = ( + designLocation if designLocation is not None else (location or {}) + ) + """dict. Axis values for this instance, in design space coordinates. + + MutatorMath + varLib. + + .. seealso:: This may be only part of the full location. See: + :meth:`getFullDesignLocation` + :meth:`getFullUserLocation` + + .. versionadded:: 5.0 + """ + self.userLocation: SimpleLocationDict = userLocation or {} + """dict. Axis values for this instance, in user space coordinates. + + MutatorMath + varLib. + + .. seealso:: This may be only part of the full location. See: + :meth:`getFullDesignLocation` + :meth:`getFullUserLocation` + + .. versionadded:: 5.0 + """ + self.familyName = familyName + """string. Family name of this instance. + + MutatorMath + varLib. + """ + self.styleName = styleName + """string. Style name of this instance. + + MutatorMath + varLib. + """ + self.postScriptFontName = postScriptFontName + """string. Postscript fontname for this instance. + + MutatorMath + varLib. + """ + self.styleMapFamilyName = styleMapFamilyName + """string. StyleMap familyname for this instance. + + MutatorMath + varLib. + """ + self.styleMapStyleName = styleMapStyleName + """string. StyleMap stylename for this instance. + + MutatorMath + varLib. + """ + self.localisedFamilyName = localisedFamilyName or {} + """dict. A dictionary of localised family name + strings, keyed by language code. + """ + self.localisedStyleName = localisedStyleName or {} + """dict. A dictionary of localised stylename + strings, keyed by language code. + """ + self.localisedStyleMapFamilyName = localisedStyleMapFamilyName or {} + """A dictionary of localised style map + familyname strings, keyed by language code. + """ + self.localisedStyleMapStyleName = localisedStyleMapStyleName or {} + """A dictionary of localised style map + stylename strings, keyed by language code. + """ + self.glyphs = glyphs or {} + """dict for special master definitions for glyphs. If glyphs + need special masters (to record the results of executed rules for + example). + + MutatorMath. + + .. deprecated:: 5.0 + Use rules or sparse sources instead. + """ + self.kerning = kerning + """ bool. Indicates if this instance needs its kerning + calculated. + + MutatorMath. + + .. deprecated:: 5.0 + """ + self.info = info + """bool. Indicated if this instance needs the interpolating + font.info calculated. + + .. deprecated:: 5.0 + """ + + self.lib = lib or {} + """Custom data associated with this instance.""" + + @property + def location(self): + """dict. Axis values for this instance. + + MutatorMath + varLib. + + .. deprecated:: 5.0 + Use the more explicit alias for this property :attr:`designLocation`. + """ + return self.designLocation + + @location.setter + def location(self, location: Optional[AnisotropicLocationDict]): + self.designLocation = location or {} + + def setStyleName(self, styleName, languageCode="en"): + """These methods give easier access to the localised names.""" + self.localisedStyleName[languageCode] = tostr(styleName) + + def getStyleName(self, languageCode="en"): + return self.localisedStyleName.get(languageCode) + + def setFamilyName(self, familyName, languageCode="en"): + self.localisedFamilyName[languageCode] = tostr(familyName) + + def getFamilyName(self, languageCode="en"): + return self.localisedFamilyName.get(languageCode) + + def setStyleMapStyleName(self, styleMapStyleName, languageCode="en"): + self.localisedStyleMapStyleName[languageCode] = tostr(styleMapStyleName) + + def getStyleMapStyleName(self, languageCode="en"): + return self.localisedStyleMapStyleName.get(languageCode) + + def setStyleMapFamilyName(self, styleMapFamilyName, languageCode="en"): + self.localisedStyleMapFamilyName[languageCode] = tostr(styleMapFamilyName) + + def getStyleMapFamilyName(self, languageCode="en"): + return self.localisedStyleMapFamilyName.get(languageCode) + + def clearLocation(self, axisName: Optional[str] = None): + """Clear all location-related fields. Ensures that + :attr:``designLocation`` and :attr:``userLocation`` are dictionaries + (possibly empty if clearing everything). + + In order to update the location of this instance wholesale, a user + should first clear all the fields, then change the field(s) for which + they have data. + + .. code:: python + + instance.clearLocation() + instance.designLocation = {'Weight': (34, 36.5), 'Width': 100} + instance.userLocation = {'Opsz': 16} + + In order to update a single axis location, the user should only clear + that axis, then edit the values: + + .. code:: python + + instance.clearLocation('Weight') + instance.designLocation['Weight'] = (34, 36.5) + + Args: + axisName: if provided, only clear the location for that axis. + + .. versionadded:: 5.0 + """ + self.locationLabel = None + if axisName is None: + self.designLocation = {} + self.userLocation = {} + else: + if self.designLocation is None: + self.designLocation = {} + if axisName in self.designLocation: + del self.designLocation[axisName] + if self.userLocation is None: + self.userLocation = {} + if axisName in self.userLocation: + del self.userLocation[axisName] + + def getLocationLabelDescriptor( + self, doc: "DesignSpaceDocument" + ) -> Optional[LocationLabelDescriptor]: + """Get the :class:`LocationLabelDescriptor` instance that matches + this instances's :attr:`locationLabel`. + + Raises if the named label can't be found. + + .. versionadded:: 5.0 + """ + if self.locationLabel is None: + return None + label = doc.getLocationLabel(self.locationLabel) + if label is None: + raise DesignSpaceDocumentError( + "InstanceDescriptor.getLocationLabelDescriptor(): " + f"unknown location label `{self.locationLabel}` in instance `{self.name}`." + ) + return label + + def getFullDesignLocation( + self, doc: "DesignSpaceDocument" + ) -> AnisotropicLocationDict: + """Get the complete design location of this instance, by combining data + from the various location fields, default axis values and mappings, and + top-level location labels. + + The source of truth for this instance's location is determined for each + axis independently by taking the first not-None field in this list: + + - ``locationLabel``: the location along this axis is the same as the + matching STAT format 4 label. No anisotropy. + - ``designLocation[axisName]``: the explicit design location along this + axis, possibly anisotropic. + - ``userLocation[axisName]``: the explicit user location along this + axis. No anisotropy. + - ``axis.default``: default axis value. No anisotropy. + + .. versionadded:: 5.0 + """ + label = self.getLocationLabelDescriptor(doc) + if label is not None: + return doc.map_forward(label.userLocation) # type: ignore + result: AnisotropicLocationDict = {} + for axis in doc.axes: + if axis.name in self.designLocation: + result[axis.name] = self.designLocation[axis.name] + elif axis.name in self.userLocation: + result[axis.name] = axis.map_forward(self.userLocation[axis.name]) + else: + result[axis.name] = axis.map_forward(axis.default) + return result + + def getFullUserLocation(self, doc: "DesignSpaceDocument") -> SimpleLocationDict: + """Get the complete user location for this instance. + + .. seealso:: :meth:`getFullDesignLocation` + + .. versionadded:: 5.0 + """ + return doc.map_backward(self.getFullDesignLocation(doc)) + + +def tagForAxisName(name): + # try to find or make a tag name for this axis name + names = { + "weight": ("wght", dict(en="Weight")), + "width": ("wdth", dict(en="Width")), + "optical": ("opsz", dict(en="Optical Size")), + "slant": ("slnt", dict(en="Slant")), + "italic": ("ital", dict(en="Italic")), + } + if name.lower() in names: + return names[name.lower()] + if len(name) < 4: + tag = name + "*" * (4 - len(name)) + else: + tag = name[:4] + return tag, dict(en=name) + + +class AbstractAxisDescriptor(SimpleDescriptor): + flavor = "axis" + + def __init__( + self, + *, + tag=None, + name=None, + labelNames=None, + hidden=False, + map=None, + axisOrdering=None, + axisLabels=None, + ): + # opentype tag for this axis + self.tag = tag + """string. Four letter tag for this axis. Some might be + registered at the `OpenType + specification `__. + Privately-defined axis tags must begin with an uppercase letter and + use only uppercase letters or digits. + """ + # name of the axis used in locations + self.name = name + """string. Name of the axis as it is used in the location dicts. + + MutatorMath + varLib. + """ + # names for UI purposes, if this is not a standard axis, + self.labelNames = labelNames or {} + """dict. When defining a non-registered axis, it will be + necessary to define user-facing readable names for the axis. Keyed by + xml:lang code. Values are required to be ``unicode`` strings, even if + they only contain ASCII characters. + """ + self.hidden = hidden + """bool. Whether this axis should be hidden in user interfaces. + """ + self.map = map or [] + """list of input / output values that can describe a warp of user space + to design space coordinates. If no map values are present, it is assumed + user space is the same as design space, as in [(minimum, minimum), + (maximum, maximum)]. + + varLib. + """ + self.axisOrdering = axisOrdering + """STAT table field ``axisOrdering``. + + See: `OTSpec STAT Axis Record `_ + + .. versionadded:: 5.0 + """ + self.axisLabels: List[AxisLabelDescriptor] = axisLabels or [] + """STAT table entries for Axis Value Tables format 1, 2, 3. + + See: `OTSpec STAT Axis Value Tables `_ + + .. versionadded:: 5.0 + """ + + +class AxisDescriptor(AbstractAxisDescriptor): + """Simple container for the axis data. + + Add more localisations? + + .. code:: python + + a1 = AxisDescriptor() + a1.minimum = 1 + a1.maximum = 1000 + a1.default = 400 + a1.name = "weight" + a1.tag = "wght" + a1.labelNames['fa-IR'] = "قطر" + a1.labelNames['en'] = "Wéíght" + a1.map = [(1.0, 10.0), (400.0, 66.0), (1000.0, 990.0)] + a1.axisOrdering = 1 + a1.axisLabels = [ + AxisLabelDescriptor(name="Regular", userValue=400, elidable=True) + ] + doc.addAxis(a1) + """ + + _attrs = [ + "tag", + "name", + "maximum", + "minimum", + "default", + "map", + "axisOrdering", + "axisLabels", + ] + + def __init__( + self, + *, + tag=None, + name=None, + labelNames=None, + minimum=None, + default=None, + maximum=None, + hidden=False, + map=None, + axisOrdering=None, + axisLabels=None, + ): + super().__init__( + tag=tag, + name=name, + labelNames=labelNames, + hidden=hidden, + map=map, + axisOrdering=axisOrdering, + axisLabels=axisLabels, + ) + self.minimum = minimum + """number. The minimum value for this axis in user space. + + MutatorMath + varLib. + """ + self.maximum = maximum + """number. The maximum value for this axis in user space. + + MutatorMath + varLib. + """ + self.default = default + """number. The default value for this axis, i.e. when a new location is + created, this is the value this axis will get in user space. + + MutatorMath + varLib. + """ + + def serialize(self): + # output to a dict, used in testing + return dict( + tag=self.tag, + name=self.name, + labelNames=self.labelNames, + maximum=self.maximum, + minimum=self.minimum, + default=self.default, + hidden=self.hidden, + map=self.map, + axisOrdering=self.axisOrdering, + axisLabels=self.axisLabels, + ) + + def map_forward(self, v): + """Maps value from axis mapping's input (user) to output (design).""" + from fontTools.varLib.models import piecewiseLinearMap + + if not self.map: + return v + return piecewiseLinearMap(v, {k: v for k, v in self.map}) + + def map_backward(self, v): + """Maps value from axis mapping's output (design) to input (user).""" + from fontTools.varLib.models import piecewiseLinearMap + + if isinstance(v, tuple): + v = v[0] + if not self.map: + return v + return piecewiseLinearMap(v, {v: k for k, v in self.map}) + + +class DiscreteAxisDescriptor(AbstractAxisDescriptor): + """Container for discrete axis data. + + Use this for axes that do not interpolate. The main difference from a + continuous axis is that a continuous axis has a ``minimum`` and ``maximum``, + while a discrete axis has a list of ``values``. + + Example: an Italic axis with 2 stops, Roman and Italic, that are not + compatible. The axis still allows to bind together the full font family, + which is useful for the STAT table, however it can't become a variation + axis in a VF. + + .. code:: python + + a2 = DiscreteAxisDescriptor() + a2.values = [0, 1] + a2.default = 0 + a2.name = "Italic" + a2.tag = "ITAL" + a2.labelNames['fr'] = "Italique" + a2.map = [(0, 0), (1, -11)] + a2.axisOrdering = 2 + a2.axisLabels = [ + AxisLabelDescriptor(name="Roman", userValue=0, elidable=True) + ] + doc.addAxis(a2) + + .. versionadded:: 5.0 + """ + + flavor = "axis" + _attrs = ("tag", "name", "values", "default", "map", "axisOrdering", "axisLabels") + + def __init__( + self, + *, + tag=None, + name=None, + labelNames=None, + values=None, + default=None, + hidden=False, + map=None, + axisOrdering=None, + axisLabels=None, + ): + super().__init__( + tag=tag, + name=name, + labelNames=labelNames, + hidden=hidden, + map=map, + axisOrdering=axisOrdering, + axisLabels=axisLabels, + ) + self.default: float = default + """The default value for this axis, i.e. when a new location is + created, this is the value this axis will get in user space. + + However, this default value is less important than in continuous axes: + + - it doesn't define the "neutral" version of outlines from which + deltas would apply, as this axis does not interpolate. + - it doesn't provide the reference glyph set for the designspace, as + fonts at each value can have different glyph sets. + """ + self.values: List[float] = values or [] + """List of possible values for this axis. Contrary to continuous axes, + only the values in this list can be taken by the axis, nothing in-between. + """ + + def map_forward(self, value): + """Maps value from axis mapping's input to output. + + Returns value unchanged if no mapping entry is found. + + Note: for discrete axes, each value must have its mapping entry, if + you intend that value to be mapped. + """ + return next((v for k, v in self.map if k == value), value) + + def map_backward(self, value): + """Maps value from axis mapping's output to input. + + Returns value unchanged if no mapping entry is found. + + Note: for discrete axes, each value must have its mapping entry, if + you intend that value to be mapped. + """ + if isinstance(value, tuple): + value = value[0] + return next((k for k, v in self.map if v == value), value) + + +class AxisLabelDescriptor(SimpleDescriptor): + """Container for axis label data. + + Analogue of OpenType's STAT data for a single axis (formats 1, 2 and 3). + All values are user values. + See: `OTSpec STAT Axis value table, format 1, 2, 3 `_ + + The STAT format of the Axis value depends on which field are filled-in, + see :meth:`getFormat` + + .. versionadded:: 5.0 + """ + + flavor = "label" + _attrs = ( + "userMinimum", + "userValue", + "userMaximum", + "name", + "elidable", + "olderSibling", + "linkedUserValue", + "labelNames", + ) + + def __init__( + self, + *, + name, + userValue, + userMinimum=None, + userMaximum=None, + elidable=False, + olderSibling=False, + linkedUserValue=None, + labelNames=None, + ): + self.userMinimum: Optional[float] = userMinimum + """STAT field ``rangeMinValue`` (format 2).""" + self.userValue: float = userValue + """STAT field ``value`` (format 1, 3) or ``nominalValue`` (format 2).""" + self.userMaximum: Optional[float] = userMaximum + """STAT field ``rangeMaxValue`` (format 2).""" + self.name: str = name + """Label for this axis location, STAT field ``valueNameID``.""" + self.elidable: bool = elidable + """STAT flag ``ELIDABLE_AXIS_VALUE_NAME``. + + See: `OTSpec STAT Flags `_ + """ + self.olderSibling: bool = olderSibling + """STAT flag ``OLDER_SIBLING_FONT_ATTRIBUTE``. + + See: `OTSpec STAT Flags `_ + """ + self.linkedUserValue: Optional[float] = linkedUserValue + """STAT field ``linkedValue`` (format 3).""" + self.labelNames: MutableMapping[str, str] = labelNames or {} + """User-facing translations of this location's label. Keyed by + ``xml:lang`` code. + """ + + def getFormat(self) -> int: + """Determine which format of STAT Axis value to use to encode this label. + + =========== ========= =========== =========== =============== + STAT Format userValue userMinimum userMaximum linkedUserValue + =========== ========= =========== =========== =============== + 1 ✅ ❌ ❌ ❌ + 2 ✅ ✅ ✅ ❌ + 3 ✅ ❌ ❌ ✅ + =========== ========= =========== =========== =============== + """ + if self.linkedUserValue is not None: + return 3 + if self.userMinimum is not None or self.userMaximum is not None: + return 2 + return 1 + + @property + def defaultName(self) -> str: + """Return the English name from :attr:`labelNames` or the :attr:`name`.""" + return self.labelNames.get("en") or self.name + + +class LocationLabelDescriptor(SimpleDescriptor): + """Container for location label data. + + Analogue of OpenType's STAT data for a free-floating location (format 4). + All values are user values. + + See: `OTSpec STAT Axis value table, format 4 `_ + + .. versionadded:: 5.0 + """ + + flavor = "label" + _attrs = ("name", "elidable", "olderSibling", "userLocation", "labelNames") + + def __init__( + self, + *, + name, + userLocation, + elidable=False, + olderSibling=False, + labelNames=None, + ): + self.name: str = name + """Label for this named location, STAT field ``valueNameID``.""" + self.userLocation: SimpleLocationDict = userLocation or {} + """Location in user coordinates along each axis. + + If an axis is not mentioned, it is assumed to be at its default location. + + .. seealso:: This may be only part of the full location. See: + :meth:`getFullUserLocation` + """ + self.elidable: bool = elidable + """STAT flag ``ELIDABLE_AXIS_VALUE_NAME``. + + See: `OTSpec STAT Flags `_ + """ + self.olderSibling: bool = olderSibling + """STAT flag ``OLDER_SIBLING_FONT_ATTRIBUTE``. + + See: `OTSpec STAT Flags `_ + """ + self.labelNames: Dict[str, str] = labelNames or {} + """User-facing translations of this location's label. Keyed by + xml:lang code. + """ + + @property + def defaultName(self) -> str: + """Return the English name from :attr:`labelNames` or the :attr:`name`.""" + return self.labelNames.get("en") or self.name + + def getFullUserLocation(self, doc: "DesignSpaceDocument") -> SimpleLocationDict: + """Get the complete user location of this label, by combining data + from the explicit user location and default axis values. + + .. versionadded:: 5.0 + """ + return { + axis.name: self.userLocation.get(axis.name, axis.default) + for axis in doc.axes + } + + +class VariableFontDescriptor(SimpleDescriptor): + """Container for variable fonts, sub-spaces of the Designspace. + + Use-cases: + + - From a single DesignSpace with discrete axes, define 1 variable font + per value on the discrete axes. Before version 5, you would have needed + 1 DesignSpace per such variable font, and a lot of data duplication. + - From a big variable font with many axes, define subsets of that variable + font that only include some axes and freeze other axes at a given location. + + .. versionadded:: 5.0 + """ + + flavor = "variable-font" + _attrs = ("filename", "axisSubsets", "lib") + + filename = posixpath_property("_filename") + + def __init__(self, *, name, filename=None, axisSubsets=None, lib=None): + self.name: str = name + """string, required. Name of this variable to identify it during the + build process and from other parts of the document, and also as a + filename in case the filename property is empty. + + VarLib. + """ + self.filename: str = filename + """string, optional. Relative path to the variable font file, **as it is + in the document**. The file may or may not exist. + + If not specified, the :attr:`name` will be used as a basename for the file. + """ + self.axisSubsets: List[ + Union[RangeAxisSubsetDescriptor, ValueAxisSubsetDescriptor] + ] = (axisSubsets or []) + """Axis subsets to include in this variable font. + + If an axis is not mentioned, assume that we only want the default + location of that axis (same as a :class:`ValueAxisSubsetDescriptor`). + """ + self.lib: MutableMapping[str, Any] = lib or {} + """Custom data associated with this variable font.""" + + +class RangeAxisSubsetDescriptor(SimpleDescriptor): + """Subset of a continuous axis to include in a variable font. + + .. versionadded:: 5.0 + """ + + flavor = "axis-subset" + _attrs = ("name", "userMinimum", "userDefault", "userMaximum") + + def __init__( + self, *, name, userMinimum=-math.inf, userDefault=None, userMaximum=math.inf + ): + self.name: str = name + """Name of the :class:`AxisDescriptor` to subset.""" + self.userMinimum: float = userMinimum + """New minimum value of the axis in the target variable font. + If not specified, assume the same minimum value as the full axis. + (default = ``-math.inf``) + """ + self.userDefault: Optional[float] = userDefault + """New default value of the axis in the target variable font. + If not specified, assume the same default value as the full axis. + (default = ``None``) + """ + self.userMaximum: float = userMaximum + """New maximum value of the axis in the target variable font. + If not specified, assume the same maximum value as the full axis. + (default = ``math.inf``) + """ + + +class ValueAxisSubsetDescriptor(SimpleDescriptor): + """Single value of a discrete or continuous axis to use in a variable font. + + .. versionadded:: 5.0 + """ + + flavor = "axis-subset" + _attrs = ("name", "userValue") + + def __init__(self, *, name, userValue): + self.name: str = name + """Name of the :class:`AxisDescriptor` or :class:`DiscreteAxisDescriptor` + to "snapshot" or "freeze". + """ + self.userValue: float = userValue + """Value in user coordinates at which to freeze the given axis.""" + + +class BaseDocWriter(object): + _whiteSpace = " " + axisDescriptorClass = AxisDescriptor + discreteAxisDescriptorClass = DiscreteAxisDescriptor + axisLabelDescriptorClass = AxisLabelDescriptor + axisMappingDescriptorClass = AxisMappingDescriptor + locationLabelDescriptorClass = LocationLabelDescriptor + ruleDescriptorClass = RuleDescriptor + sourceDescriptorClass = SourceDescriptor + variableFontDescriptorClass = VariableFontDescriptor + valueAxisSubsetDescriptorClass = ValueAxisSubsetDescriptor + rangeAxisSubsetDescriptorClass = RangeAxisSubsetDescriptor + instanceDescriptorClass = InstanceDescriptor + + @classmethod + def getAxisDecriptor(cls): + return cls.axisDescriptorClass() + + @classmethod + def getAxisMappingDescriptor(cls): + return cls.axisMappingDescriptorClass() + + @classmethod + def getSourceDescriptor(cls): + return cls.sourceDescriptorClass() + + @classmethod + def getInstanceDescriptor(cls): + return cls.instanceDescriptorClass() + + @classmethod + def getRuleDescriptor(cls): + return cls.ruleDescriptorClass() + + def __init__(self, documentPath, documentObject: DesignSpaceDocument): + self.path = documentPath + self.documentObject = documentObject + self.effectiveFormatTuple = self._getEffectiveFormatTuple() + self.root = ET.Element("designspace") + + def write(self, pretty=True, encoding="UTF-8", xml_declaration=True): + self.root.attrib["format"] = ".".join(str(i) for i in self.effectiveFormatTuple) + + if ( + self.documentObject.axes + or self.documentObject.axisMappings + or self.documentObject.elidedFallbackName is not None + ): + axesElement = ET.Element("axes") + if self.documentObject.elidedFallbackName is not None: + axesElement.attrib[ + "elidedfallbackname" + ] = self.documentObject.elidedFallbackName + self.root.append(axesElement) + for axisObject in self.documentObject.axes: + self._addAxis(axisObject) + + if self.documentObject.axisMappings: + mappingsElement = ET.Element("mappings") + self.root.findall(".axes")[0].append(mappingsElement) + for mappingObject in self.documentObject.axisMappings: + self._addAxisMapping(mappingsElement, mappingObject) + + if self.documentObject.locationLabels: + labelsElement = ET.Element("labels") + for labelObject in self.documentObject.locationLabels: + self._addLocationLabel(labelsElement, labelObject) + self.root.append(labelsElement) + + if self.documentObject.rules: + if getattr(self.documentObject, "rulesProcessingLast", False): + attributes = {"processing": "last"} + else: + attributes = {} + self.root.append(ET.Element("rules", attributes)) + for ruleObject in self.documentObject.rules: + self._addRule(ruleObject) + + if self.documentObject.sources: + self.root.append(ET.Element("sources")) + for sourceObject in self.documentObject.sources: + self._addSource(sourceObject) + + if self.documentObject.variableFonts: + variableFontsElement = ET.Element("variable-fonts") + for variableFont in self.documentObject.variableFonts: + self._addVariableFont(variableFontsElement, variableFont) + self.root.append(variableFontsElement) + + if self.documentObject.instances: + self.root.append(ET.Element("instances")) + for instanceObject in self.documentObject.instances: + self._addInstance(instanceObject) + + if self.documentObject.lib: + self._addLib(self.root, self.documentObject.lib, 2) + + tree = ET.ElementTree(self.root) + tree.write( + self.path, + encoding=encoding, + method="xml", + xml_declaration=xml_declaration, + pretty_print=pretty, + ) + + def _getEffectiveFormatTuple(self): + """Try to use the version specified in the document, or a sufficiently + recent version to be able to encode what the document contains. + """ + minVersion = self.documentObject.formatTuple + if ( + any( + hasattr(axis, "values") + or axis.axisOrdering is not None + or axis.axisLabels + for axis in self.documentObject.axes + ) + or self.documentObject.locationLabels + or any(source.localisedFamilyName for source in self.documentObject.sources) + or self.documentObject.variableFonts + or any( + instance.locationLabel or instance.userLocation + for instance in self.documentObject.instances + ) + ): + if minVersion < (5, 0): + minVersion = (5, 0) + if self.documentObject.axisMappings: + if minVersion < (5, 1): + minVersion = (5, 1) + return minVersion + + def _makeLocationElement(self, locationObject, name=None): + """Convert Location dict to a locationElement.""" + locElement = ET.Element("location") + if name is not None: + locElement.attrib["name"] = name + validatedLocation = self.documentObject.newDefaultLocation() + for axisName, axisValue in locationObject.items(): + if axisName in validatedLocation: + # only accept values we know + validatedLocation[axisName] = axisValue + for dimensionName, dimensionValue in validatedLocation.items(): + dimElement = ET.Element("dimension") + dimElement.attrib["name"] = dimensionName + if type(dimensionValue) == tuple: + dimElement.attrib["xvalue"] = self.intOrFloat(dimensionValue[0]) + dimElement.attrib["yvalue"] = self.intOrFloat(dimensionValue[1]) + else: + dimElement.attrib["xvalue"] = self.intOrFloat(dimensionValue) + locElement.append(dimElement) + return locElement, validatedLocation + + def intOrFloat(self, num): + if int(num) == num: + return "%d" % num + return ("%f" % num).rstrip("0").rstrip(".") + + def _addRule(self, ruleObject): + # if none of the conditions have minimum or maximum values, do not add the rule. + ruleElement = ET.Element("rule") + if ruleObject.name is not None: + ruleElement.attrib["name"] = ruleObject.name + for conditions in ruleObject.conditionSets: + conditionsetElement = ET.Element("conditionset") + for cond in conditions: + if cond.get("minimum") is None and cond.get("maximum") is None: + # neither is defined, don't add this condition + continue + conditionElement = ET.Element("condition") + conditionElement.attrib["name"] = cond.get("name") + if cond.get("minimum") is not None: + conditionElement.attrib["minimum"] = self.intOrFloat( + cond.get("minimum") + ) + if cond.get("maximum") is not None: + conditionElement.attrib["maximum"] = self.intOrFloat( + cond.get("maximum") + ) + conditionsetElement.append(conditionElement) + if len(conditionsetElement): + ruleElement.append(conditionsetElement) + for sub in ruleObject.subs: + subElement = ET.Element("sub") + subElement.attrib["name"] = sub[0] + subElement.attrib["with"] = sub[1] + ruleElement.append(subElement) + if len(ruleElement): + self.root.findall(".rules")[0].append(ruleElement) + + def _addAxis(self, axisObject): + axisElement = ET.Element("axis") + axisElement.attrib["tag"] = axisObject.tag + axisElement.attrib["name"] = axisObject.name + self._addLabelNames(axisElement, axisObject.labelNames) + if axisObject.map: + for inputValue, outputValue in axisObject.map: + mapElement = ET.Element("map") + mapElement.attrib["input"] = self.intOrFloat(inputValue) + mapElement.attrib["output"] = self.intOrFloat(outputValue) + axisElement.append(mapElement) + if axisObject.axisOrdering or axisObject.axisLabels: + labelsElement = ET.Element("labels") + if axisObject.axisOrdering is not None: + labelsElement.attrib["ordering"] = str(axisObject.axisOrdering) + for label in axisObject.axisLabels: + self._addAxisLabel(labelsElement, label) + axisElement.append(labelsElement) + if hasattr(axisObject, "minimum"): + axisElement.attrib["minimum"] = self.intOrFloat(axisObject.minimum) + axisElement.attrib["maximum"] = self.intOrFloat(axisObject.maximum) + elif hasattr(axisObject, "values"): + axisElement.attrib["values"] = " ".join( + self.intOrFloat(v) for v in axisObject.values + ) + axisElement.attrib["default"] = self.intOrFloat(axisObject.default) + if axisObject.hidden: + axisElement.attrib["hidden"] = "1" + self.root.findall(".axes")[0].append(axisElement) + + def _addAxisMapping(self, mappingsElement, mappingObject): + mappingElement = ET.Element("mapping") + for what in ("inputLocation", "outputLocation"): + whatObject = getattr(mappingObject, what, None) + if whatObject is None: + continue + whatElement = ET.Element(what[:-8]) + mappingElement.append(whatElement) + + for name, value in whatObject.items(): + dimensionElement = ET.Element("dimension") + dimensionElement.attrib["name"] = name + dimensionElement.attrib["xvalue"] = self.intOrFloat(value) + whatElement.append(dimensionElement) + + mappingsElement.append(mappingElement) + + def _addAxisLabel( + self, axisElement: ET.Element, label: AxisLabelDescriptor + ) -> None: + labelElement = ET.Element("label") + labelElement.attrib["uservalue"] = self.intOrFloat(label.userValue) + if label.userMinimum is not None: + labelElement.attrib["userminimum"] = self.intOrFloat(label.userMinimum) + if label.userMaximum is not None: + labelElement.attrib["usermaximum"] = self.intOrFloat(label.userMaximum) + labelElement.attrib["name"] = label.name + if label.elidable: + labelElement.attrib["elidable"] = "true" + if label.olderSibling: + labelElement.attrib["oldersibling"] = "true" + if label.linkedUserValue is not None: + labelElement.attrib["linkeduservalue"] = self.intOrFloat( + label.linkedUserValue + ) + self._addLabelNames(labelElement, label.labelNames) + axisElement.append(labelElement) + + def _addLabelNames(self, parentElement, labelNames): + for languageCode, labelName in sorted(labelNames.items()): + languageElement = ET.Element("labelname") + languageElement.attrib[XML_LANG] = languageCode + languageElement.text = labelName + parentElement.append(languageElement) + + def _addLocationLabel( + self, parentElement: ET.Element, label: LocationLabelDescriptor + ) -> None: + labelElement = ET.Element("label") + labelElement.attrib["name"] = label.name + if label.elidable: + labelElement.attrib["elidable"] = "true" + if label.olderSibling: + labelElement.attrib["oldersibling"] = "true" + self._addLabelNames(labelElement, label.labelNames) + self._addLocationElement(labelElement, userLocation=label.userLocation) + parentElement.append(labelElement) + + def _addLocationElement( + self, + parentElement, + *, + designLocation: AnisotropicLocationDict = None, + userLocation: SimpleLocationDict = None, + ): + locElement = ET.Element("location") + for axis in self.documentObject.axes: + if designLocation is not None and axis.name in designLocation: + dimElement = ET.Element("dimension") + dimElement.attrib["name"] = axis.name + value = designLocation[axis.name] + if isinstance(value, tuple): + dimElement.attrib["xvalue"] = self.intOrFloat(value[0]) + dimElement.attrib["yvalue"] = self.intOrFloat(value[1]) + else: + dimElement.attrib["xvalue"] = self.intOrFloat(value) + locElement.append(dimElement) + elif userLocation is not None and axis.name in userLocation: + dimElement = ET.Element("dimension") + dimElement.attrib["name"] = axis.name + value = userLocation[axis.name] + dimElement.attrib["uservalue"] = self.intOrFloat(value) + locElement.append(dimElement) + if len(locElement) > 0: + parentElement.append(locElement) + + def _addInstance(self, instanceObject): + instanceElement = ET.Element("instance") + if instanceObject.name is not None: + instanceElement.attrib["name"] = instanceObject.name + if instanceObject.locationLabel is not None: + instanceElement.attrib["location"] = instanceObject.locationLabel + if instanceObject.familyName is not None: + instanceElement.attrib["familyname"] = instanceObject.familyName + if instanceObject.styleName is not None: + instanceElement.attrib["stylename"] = instanceObject.styleName + # add localisations + if instanceObject.localisedStyleName: + languageCodes = list(instanceObject.localisedStyleName.keys()) + languageCodes.sort() + for code in languageCodes: + if code == "en": + continue # already stored in the element attribute + localisedStyleNameElement = ET.Element("stylename") + localisedStyleNameElement.attrib[XML_LANG] = code + localisedStyleNameElement.text = instanceObject.getStyleName(code) + instanceElement.append(localisedStyleNameElement) + if instanceObject.localisedFamilyName: + languageCodes = list(instanceObject.localisedFamilyName.keys()) + languageCodes.sort() + for code in languageCodes: + if code == "en": + continue # already stored in the element attribute + localisedFamilyNameElement = ET.Element("familyname") + localisedFamilyNameElement.attrib[XML_LANG] = code + localisedFamilyNameElement.text = instanceObject.getFamilyName(code) + instanceElement.append(localisedFamilyNameElement) + if instanceObject.localisedStyleMapStyleName: + languageCodes = list(instanceObject.localisedStyleMapStyleName.keys()) + languageCodes.sort() + for code in languageCodes: + if code == "en": + continue + localisedStyleMapStyleNameElement = ET.Element("stylemapstylename") + localisedStyleMapStyleNameElement.attrib[XML_LANG] = code + localisedStyleMapStyleNameElement.text = ( + instanceObject.getStyleMapStyleName(code) + ) + instanceElement.append(localisedStyleMapStyleNameElement) + if instanceObject.localisedStyleMapFamilyName: + languageCodes = list(instanceObject.localisedStyleMapFamilyName.keys()) + languageCodes.sort() + for code in languageCodes: + if code == "en": + continue + localisedStyleMapFamilyNameElement = ET.Element("stylemapfamilyname") + localisedStyleMapFamilyNameElement.attrib[XML_LANG] = code + localisedStyleMapFamilyNameElement.text = ( + instanceObject.getStyleMapFamilyName(code) + ) + instanceElement.append(localisedStyleMapFamilyNameElement) + + if self.effectiveFormatTuple >= (5, 0): + if instanceObject.locationLabel is None: + self._addLocationElement( + instanceElement, + designLocation=instanceObject.designLocation, + userLocation=instanceObject.userLocation, + ) + else: + # Pre-version 5.0 code was validating and filling in the location + # dict while writing it out, as preserved below. + if instanceObject.location is not None: + locationElement, instanceObject.location = self._makeLocationElement( + instanceObject.location + ) + instanceElement.append(locationElement) + if instanceObject.filename is not None: + instanceElement.attrib["filename"] = instanceObject.filename + if instanceObject.postScriptFontName is not None: + instanceElement.attrib[ + "postscriptfontname" + ] = instanceObject.postScriptFontName + if instanceObject.styleMapFamilyName is not None: + instanceElement.attrib[ + "stylemapfamilyname" + ] = instanceObject.styleMapFamilyName + if instanceObject.styleMapStyleName is not None: + instanceElement.attrib[ + "stylemapstylename" + ] = instanceObject.styleMapStyleName + if self.effectiveFormatTuple < (5, 0): + # Deprecated members as of version 5.0 + if instanceObject.glyphs: + if instanceElement.findall(".glyphs") == []: + glyphsElement = ET.Element("glyphs") + instanceElement.append(glyphsElement) + glyphsElement = instanceElement.findall(".glyphs")[0] + for glyphName, data in sorted(instanceObject.glyphs.items()): + glyphElement = self._writeGlyphElement( + instanceElement, instanceObject, glyphName, data + ) + glyphsElement.append(glyphElement) + if instanceObject.kerning: + kerningElement = ET.Element("kerning") + instanceElement.append(kerningElement) + if instanceObject.info: + infoElement = ET.Element("info") + instanceElement.append(infoElement) + self._addLib(instanceElement, instanceObject.lib, 4) + self.root.findall(".instances")[0].append(instanceElement) + + def _addSource(self, sourceObject): + sourceElement = ET.Element("source") + if sourceObject.filename is not None: + sourceElement.attrib["filename"] = sourceObject.filename + if sourceObject.name is not None: + if sourceObject.name.find("temp_master") != 0: + # do not save temporary source names + sourceElement.attrib["name"] = sourceObject.name + if sourceObject.familyName is not None: + sourceElement.attrib["familyname"] = sourceObject.familyName + if sourceObject.styleName is not None: + sourceElement.attrib["stylename"] = sourceObject.styleName + if sourceObject.layerName is not None: + sourceElement.attrib["layer"] = sourceObject.layerName + if sourceObject.localisedFamilyName: + languageCodes = list(sourceObject.localisedFamilyName.keys()) + languageCodes.sort() + for code in languageCodes: + if code == "en": + continue # already stored in the element attribute + localisedFamilyNameElement = ET.Element("familyname") + localisedFamilyNameElement.attrib[XML_LANG] = code + localisedFamilyNameElement.text = sourceObject.getFamilyName(code) + sourceElement.append(localisedFamilyNameElement) + if sourceObject.copyLib: + libElement = ET.Element("lib") + libElement.attrib["copy"] = "1" + sourceElement.append(libElement) + if sourceObject.copyGroups: + groupsElement = ET.Element("groups") + groupsElement.attrib["copy"] = "1" + sourceElement.append(groupsElement) + if sourceObject.copyFeatures: + featuresElement = ET.Element("features") + featuresElement.attrib["copy"] = "1" + sourceElement.append(featuresElement) + if sourceObject.copyInfo or sourceObject.muteInfo: + infoElement = ET.Element("info") + if sourceObject.copyInfo: + infoElement.attrib["copy"] = "1" + if sourceObject.muteInfo: + infoElement.attrib["mute"] = "1" + sourceElement.append(infoElement) + if sourceObject.muteKerning: + kerningElement = ET.Element("kerning") + kerningElement.attrib["mute"] = "1" + sourceElement.append(kerningElement) + if sourceObject.mutedGlyphNames: + for name in sourceObject.mutedGlyphNames: + glyphElement = ET.Element("glyph") + glyphElement.attrib["name"] = name + glyphElement.attrib["mute"] = "1" + sourceElement.append(glyphElement) + if self.effectiveFormatTuple >= (5, 0): + self._addLocationElement( + sourceElement, designLocation=sourceObject.location + ) + else: + # Pre-version 5.0 code was validating and filling in the location + # dict while writing it out, as preserved below. + locationElement, sourceObject.location = self._makeLocationElement( + sourceObject.location + ) + sourceElement.append(locationElement) + self.root.findall(".sources")[0].append(sourceElement) + + def _addVariableFont( + self, parentElement: ET.Element, vf: VariableFontDescriptor + ) -> None: + vfElement = ET.Element("variable-font") + vfElement.attrib["name"] = vf.name + if vf.filename is not None: + vfElement.attrib["filename"] = vf.filename + if vf.axisSubsets: + subsetsElement = ET.Element("axis-subsets") + for subset in vf.axisSubsets: + subsetElement = ET.Element("axis-subset") + subsetElement.attrib["name"] = subset.name + # Mypy doesn't support narrowing union types via hasattr() + # https://mypy.readthedocs.io/en/stable/type_narrowing.html + # TODO(Python 3.10): use TypeGuard + if hasattr(subset, "userMinimum"): + subset = cast(RangeAxisSubsetDescriptor, subset) + if subset.userMinimum != -math.inf: + subsetElement.attrib["userminimum"] = self.intOrFloat( + subset.userMinimum + ) + if subset.userMaximum != math.inf: + subsetElement.attrib["usermaximum"] = self.intOrFloat( + subset.userMaximum + ) + if subset.userDefault is not None: + subsetElement.attrib["userdefault"] = self.intOrFloat( + subset.userDefault + ) + elif hasattr(subset, "userValue"): + subset = cast(ValueAxisSubsetDescriptor, subset) + subsetElement.attrib["uservalue"] = self.intOrFloat( + subset.userValue + ) + subsetsElement.append(subsetElement) + vfElement.append(subsetsElement) + self._addLib(vfElement, vf.lib, 4) + parentElement.append(vfElement) + + def _addLib(self, parentElement: ET.Element, data: Any, indent_level: int) -> None: + if not data: + return + libElement = ET.Element("lib") + libElement.append(plistlib.totree(data, indent_level=indent_level)) + parentElement.append(libElement) + + def _writeGlyphElement(self, instanceElement, instanceObject, glyphName, data): + glyphElement = ET.Element("glyph") + if data.get("mute"): + glyphElement.attrib["mute"] = "1" + if data.get("unicodes") is not None: + glyphElement.attrib["unicode"] = " ".join( + [hex(u) for u in data.get("unicodes")] + ) + if data.get("instanceLocation") is not None: + locationElement, data["instanceLocation"] = self._makeLocationElement( + data.get("instanceLocation") + ) + glyphElement.append(locationElement) + if glyphName is not None: + glyphElement.attrib["name"] = glyphName + if data.get("note") is not None: + noteElement = ET.Element("note") + noteElement.text = data.get("note") + glyphElement.append(noteElement) + if data.get("masters") is not None: + mastersElement = ET.Element("masters") + for m in data.get("masters"): + masterElement = ET.Element("master") + if m.get("glyphName") is not None: + masterElement.attrib["glyphname"] = m.get("glyphName") + if m.get("font") is not None: + masterElement.attrib["source"] = m.get("font") + if m.get("location") is not None: + locationElement, m["location"] = self._makeLocationElement( + m.get("location") + ) + masterElement.append(locationElement) + mastersElement.append(masterElement) + glyphElement.append(mastersElement) + return glyphElement + + +class BaseDocReader(LogMixin): + axisDescriptorClass = AxisDescriptor + discreteAxisDescriptorClass = DiscreteAxisDescriptor + axisLabelDescriptorClass = AxisLabelDescriptor + axisMappingDescriptorClass = AxisMappingDescriptor + locationLabelDescriptorClass = LocationLabelDescriptor + ruleDescriptorClass = RuleDescriptor + sourceDescriptorClass = SourceDescriptor + variableFontsDescriptorClass = VariableFontDescriptor + valueAxisSubsetDescriptorClass = ValueAxisSubsetDescriptor + rangeAxisSubsetDescriptorClass = RangeAxisSubsetDescriptor + instanceDescriptorClass = InstanceDescriptor + + def __init__(self, documentPath, documentObject): + self.path = documentPath + self.documentObject = documentObject + tree = ET.parse(self.path) + self.root = tree.getroot() + self.documentObject.formatVersion = self.root.attrib.get("format", "3.0") + self._axes = [] + self.rules = [] + self.sources = [] + self.instances = [] + self.axisDefaults = {} + self._strictAxisNames = True + + @classmethod + def fromstring(cls, string, documentObject): + f = BytesIO(tobytes(string, encoding="utf-8")) + self = cls(f, documentObject) + self.path = None + return self + + def read(self): + self.readAxes() + self.readLabels() + self.readRules() + self.readVariableFonts() + self.readSources() + self.readInstances() + self.readLib() + + def readRules(self): + # we also need to read any conditions that are outside of a condition set. + rules = [] + rulesElement = self.root.find(".rules") + if rulesElement is not None: + processingValue = rulesElement.attrib.get("processing", "first") + if processingValue not in {"first", "last"}: + raise DesignSpaceDocumentError( + " processing attribute value is not valid: %r, " + "expected 'first' or 'last'" % processingValue + ) + self.documentObject.rulesProcessingLast = processingValue == "last" + for ruleElement in self.root.findall(".rules/rule"): + ruleObject = self.ruleDescriptorClass() + ruleName = ruleObject.name = ruleElement.attrib.get("name") + # read any stray conditions outside a condition set + externalConditions = self._readConditionElements( + ruleElement, + ruleName, + ) + if externalConditions: + ruleObject.conditionSets.append(externalConditions) + self.log.info( + "Found stray rule conditions outside a conditionset. " + "Wrapped them in a new conditionset." + ) + # read the conditionsets + for conditionSetElement in ruleElement.findall(".conditionset"): + conditionSet = self._readConditionElements( + conditionSetElement, + ruleName, + ) + if conditionSet is not None: + ruleObject.conditionSets.append(conditionSet) + for subElement in ruleElement.findall(".sub"): + a = subElement.attrib["name"] + b = subElement.attrib["with"] + ruleObject.subs.append((a, b)) + rules.append(ruleObject) + self.documentObject.rules = rules + + def _readConditionElements(self, parentElement, ruleName=None): + cds = [] + for conditionElement in parentElement.findall(".condition"): + cd = {} + cdMin = conditionElement.attrib.get("minimum") + if cdMin is not None: + cd["minimum"] = float(cdMin) + else: + # will allow these to be None, assume axis.minimum + cd["minimum"] = None + cdMax = conditionElement.attrib.get("maximum") + if cdMax is not None: + cd["maximum"] = float(cdMax) + else: + # will allow these to be None, assume axis.maximum + cd["maximum"] = None + cd["name"] = conditionElement.attrib.get("name") + # # test for things + if cd.get("minimum") is None and cd.get("maximum") is None: + raise DesignSpaceDocumentError( + "condition missing required minimum or maximum in rule" + + (" '%s'" % ruleName if ruleName is not None else "") + ) + cds.append(cd) + return cds + + def readAxes(self): + # read the axes elements, including the warp map. + axesElement = self.root.find(".axes") + if axesElement is not None and "elidedfallbackname" in axesElement.attrib: + self.documentObject.elidedFallbackName = axesElement.attrib[ + "elidedfallbackname" + ] + axisElements = self.root.findall(".axes/axis") + if not axisElements: + return + for axisElement in axisElements: + if ( + self.documentObject.formatTuple >= (5, 0) + and "values" in axisElement.attrib + ): + axisObject = self.discreteAxisDescriptorClass() + axisObject.values = [ + float(s) for s in axisElement.attrib["values"].split(" ") + ] + else: + axisObject = self.axisDescriptorClass() + axisObject.minimum = float(axisElement.attrib.get("minimum")) + axisObject.maximum = float(axisElement.attrib.get("maximum")) + axisObject.default = float(axisElement.attrib.get("default")) + axisObject.name = axisElement.attrib.get("name") + if axisElement.attrib.get("hidden", False): + axisObject.hidden = True + axisObject.tag = axisElement.attrib.get("tag") + for mapElement in axisElement.findall("map"): + a = float(mapElement.attrib["input"]) + b = float(mapElement.attrib["output"]) + axisObject.map.append((a, b)) + for labelNameElement in axisElement.findall("labelname"): + # Note: elementtree reads the "xml:lang" attribute name as + # '{http://www.w3.org/XML/1998/namespace}lang' + for key, lang in labelNameElement.items(): + if key == XML_LANG: + axisObject.labelNames[lang] = tostr(labelNameElement.text) + labelElement = axisElement.find(".labels") + if labelElement is not None: + if "ordering" in labelElement.attrib: + axisObject.axisOrdering = int(labelElement.attrib["ordering"]) + for label in labelElement.findall(".label"): + axisObject.axisLabels.append(self.readAxisLabel(label)) + self.documentObject.axes.append(axisObject) + self.axisDefaults[axisObject.name] = axisObject.default + + mappingsElement = self.root.find(".axes/mappings") + self.documentObject.axisMappings = [] + if mappingsElement is not None: + for mappingElement in mappingsElement.findall("mapping"): + inputElement = mappingElement.find("input") + outputElement = mappingElement.find("output") + inputLoc = {} + outputLoc = {} + for dimElement in inputElement.findall(".dimension"): + name = dimElement.attrib["name"] + value = float(dimElement.attrib["xvalue"]) + inputLoc[name] = value + for dimElement in outputElement.findall(".dimension"): + name = dimElement.attrib["name"] + value = float(dimElement.attrib["xvalue"]) + outputLoc[name] = value + axisMappingObject = self.axisMappingDescriptorClass( + inputLocation=inputLoc, outputLocation=outputLoc + ) + self.documentObject.axisMappings.append(axisMappingObject) + + def readAxisLabel(self, element: ET.Element): + xml_attrs = { + "userminimum", + "uservalue", + "usermaximum", + "name", + "elidable", + "oldersibling", + "linkeduservalue", + } + unknown_attrs = set(element.attrib) - xml_attrs + if unknown_attrs: + raise DesignSpaceDocumentError( + f"label element contains unknown attributes: {', '.join(unknown_attrs)}" + ) + + name = element.get("name") + if name is None: + raise DesignSpaceDocumentError("label element must have a name attribute.") + valueStr = element.get("uservalue") + if valueStr is None: + raise DesignSpaceDocumentError( + "label element must have a uservalue attribute." + ) + value = float(valueStr) + minimumStr = element.get("userminimum") + minimum = float(minimumStr) if minimumStr is not None else None + maximumStr = element.get("usermaximum") + maximum = float(maximumStr) if maximumStr is not None else None + linkedValueStr = element.get("linkeduservalue") + linkedValue = float(linkedValueStr) if linkedValueStr is not None else None + elidable = True if element.get("elidable") == "true" else False + olderSibling = True if element.get("oldersibling") == "true" else False + labelNames = { + lang: label_name.text or "" + for label_name in element.findall("labelname") + for attr, lang in label_name.items() + if attr == XML_LANG + # Note: elementtree reads the "xml:lang" attribute name as + # '{http://www.w3.org/XML/1998/namespace}lang' + } + return self.axisLabelDescriptorClass( + name=name, + userValue=value, + userMinimum=minimum, + userMaximum=maximum, + elidable=elidable, + olderSibling=olderSibling, + linkedUserValue=linkedValue, + labelNames=labelNames, + ) + + def readLabels(self): + if self.documentObject.formatTuple < (5, 0): + return + + xml_attrs = {"name", "elidable", "oldersibling"} + for labelElement in self.root.findall(".labels/label"): + unknown_attrs = set(labelElement.attrib) - xml_attrs + if unknown_attrs: + raise DesignSpaceDocumentError( + f"Label element contains unknown attributes: {', '.join(unknown_attrs)}" + ) + + name = labelElement.get("name") + if name is None: + raise DesignSpaceDocumentError( + "label element must have a name attribute." + ) + designLocation, userLocation = self.locationFromElement(labelElement) + if designLocation: + raise DesignSpaceDocumentError( + f'