meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meerk40t/__init__.py +1 -1
- meerk40t/balormk/balor_params.py +167 -167
- meerk40t/balormk/clone_loader.py +457 -457
- meerk40t/balormk/controller.py +1566 -1512
- meerk40t/balormk/cylindermod.py +64 -0
- meerk40t/balormk/device.py +966 -1959
- meerk40t/balormk/driver.py +778 -591
- meerk40t/balormk/galvo_commands.py +1194 -0
- meerk40t/balormk/gui/balorconfig.py +237 -111
- meerk40t/balormk/gui/balorcontroller.py +191 -184
- meerk40t/balormk/gui/baloroperationproperties.py +116 -115
- meerk40t/balormk/gui/corscene.py +845 -0
- meerk40t/balormk/gui/gui.py +179 -147
- meerk40t/balormk/livelightjob.py +466 -382
- meerk40t/balormk/mock_connection.py +131 -109
- meerk40t/balormk/plugin.py +133 -135
- meerk40t/balormk/usb_connection.py +306 -301
- meerk40t/camera/__init__.py +1 -1
- meerk40t/camera/camera.py +514 -397
- meerk40t/camera/gui/camerapanel.py +1241 -1095
- meerk40t/camera/gui/gui.py +58 -58
- meerk40t/camera/plugin.py +441 -399
- meerk40t/ch341/__init__.py +27 -27
- meerk40t/ch341/ch341device.py +628 -628
- meerk40t/ch341/libusb.py +595 -589
- meerk40t/ch341/mock.py +171 -171
- meerk40t/ch341/windriver.py +157 -157
- meerk40t/constants.py +13 -0
- meerk40t/core/__init__.py +1 -1
- meerk40t/core/bindalias.py +550 -539
- meerk40t/core/core.py +47 -47
- meerk40t/core/cutcode/cubiccut.py +73 -73
- meerk40t/core/cutcode/cutcode.py +315 -312
- meerk40t/core/cutcode/cutgroup.py +141 -137
- meerk40t/core/cutcode/cutobject.py +192 -185
- meerk40t/core/cutcode/dwellcut.py +37 -37
- meerk40t/core/cutcode/gotocut.py +29 -29
- meerk40t/core/cutcode/homecut.py +29 -29
- meerk40t/core/cutcode/inputcut.py +34 -34
- meerk40t/core/cutcode/linecut.py +33 -33
- meerk40t/core/cutcode/outputcut.py +34 -34
- meerk40t/core/cutcode/plotcut.py +335 -335
- meerk40t/core/cutcode/quadcut.py +61 -61
- meerk40t/core/cutcode/rastercut.py +168 -148
- meerk40t/core/cutcode/waitcut.py +34 -34
- meerk40t/core/cutplan.py +1843 -1316
- meerk40t/core/drivers.py +330 -329
- meerk40t/core/elements/align.py +801 -669
- meerk40t/core/elements/branches.py +1858 -1507
- meerk40t/core/elements/clipboard.py +229 -219
- meerk40t/core/elements/element_treeops.py +4595 -2837
- meerk40t/core/elements/element_types.py +125 -105
- meerk40t/core/elements/elements.py +4315 -3617
- meerk40t/core/elements/files.py +117 -64
- meerk40t/core/elements/geometry.py +473 -224
- meerk40t/core/elements/grid.py +467 -316
- meerk40t/core/elements/materials.py +158 -94
- meerk40t/core/elements/notes.py +50 -38
- meerk40t/core/elements/offset_clpr.py +934 -912
- meerk40t/core/elements/offset_mk.py +963 -955
- meerk40t/core/elements/penbox.py +339 -267
- meerk40t/core/elements/placements.py +300 -83
- meerk40t/core/elements/render.py +785 -687
- meerk40t/core/elements/shapes.py +2618 -2092
- meerk40t/core/elements/testcases.py +105 -0
- meerk40t/core/elements/trace.py +651 -563
- meerk40t/core/elements/tree_commands.py +415 -409
- meerk40t/core/elements/undo_redo.py +116 -58
- meerk40t/core/elements/wordlist.py +319 -200
- meerk40t/core/exceptions.py +9 -9
- meerk40t/core/laserjob.py +220 -220
- meerk40t/core/logging.py +63 -63
- meerk40t/core/node/blobnode.py +83 -86
- meerk40t/core/node/bootstrap.py +105 -103
- meerk40t/core/node/branch_elems.py +40 -31
- meerk40t/core/node/branch_ops.py +45 -38
- meerk40t/core/node/branch_regmark.py +48 -41
- meerk40t/core/node/cutnode.py +29 -32
- meerk40t/core/node/effect_hatch.py +375 -257
- meerk40t/core/node/effect_warp.py +398 -0
- meerk40t/core/node/effect_wobble.py +441 -309
- meerk40t/core/node/elem_ellipse.py +404 -309
- meerk40t/core/node/elem_image.py +1082 -801
- meerk40t/core/node/elem_line.py +358 -292
- meerk40t/core/node/elem_path.py +259 -201
- meerk40t/core/node/elem_point.py +129 -102
- meerk40t/core/node/elem_polyline.py +310 -246
- meerk40t/core/node/elem_rect.py +376 -286
- meerk40t/core/node/elem_text.py +445 -418
- meerk40t/core/node/filenode.py +59 -40
- meerk40t/core/node/groupnode.py +138 -74
- meerk40t/core/node/image_processed.py +777 -766
- meerk40t/core/node/image_raster.py +156 -113
- meerk40t/core/node/layernode.py +31 -31
- meerk40t/core/node/mixins.py +135 -107
- meerk40t/core/node/node.py +1427 -1304
- meerk40t/core/node/nutils.py +117 -114
- meerk40t/core/node/op_cut.py +463 -335
- meerk40t/core/node/op_dots.py +296 -251
- meerk40t/core/node/op_engrave.py +414 -311
- meerk40t/core/node/op_image.py +755 -369
- meerk40t/core/node/op_raster.py +787 -522
- meerk40t/core/node/place_current.py +37 -40
- meerk40t/core/node/place_point.py +329 -126
- meerk40t/core/node/refnode.py +58 -47
- meerk40t/core/node/rootnode.py +225 -219
- meerk40t/core/node/util_console.py +48 -48
- meerk40t/core/node/util_goto.py +84 -65
- meerk40t/core/node/util_home.py +61 -61
- meerk40t/core/node/util_input.py +102 -102
- meerk40t/core/node/util_output.py +102 -102
- meerk40t/core/node/util_wait.py +65 -65
- meerk40t/core/parameters.py +709 -707
- meerk40t/core/planner.py +875 -785
- meerk40t/core/plotplanner.py +656 -652
- meerk40t/core/space.py +120 -113
- meerk40t/core/spoolers.py +706 -705
- meerk40t/core/svg_io.py +1836 -1549
- meerk40t/core/treeop.py +534 -445
- meerk40t/core/undos.py +278 -124
- meerk40t/core/units.py +784 -680
- meerk40t/core/view.py +393 -322
- meerk40t/core/webhelp.py +62 -62
- meerk40t/core/wordlist.py +513 -504
- meerk40t/cylinder/cylinder.py +247 -0
- meerk40t/cylinder/gui/cylindersettings.py +41 -0
- meerk40t/cylinder/gui/gui.py +24 -0
- meerk40t/device/__init__.py +1 -1
- meerk40t/device/basedevice.py +322 -123
- meerk40t/device/devicechoices.py +50 -0
- meerk40t/device/dummydevice.py +163 -128
- meerk40t/device/gui/defaultactions.py +618 -602
- meerk40t/device/gui/effectspanel.py +114 -0
- meerk40t/device/gui/formatterpanel.py +253 -290
- meerk40t/device/gui/warningpanel.py +337 -260
- meerk40t/device/mixins.py +13 -13
- meerk40t/dxf/__init__.py +1 -1
- meerk40t/dxf/dxf_io.py +766 -554
- meerk40t/dxf/plugin.py +47 -35
- meerk40t/external_plugins.py +79 -79
- meerk40t/external_plugins_build.py +28 -28
- meerk40t/extra/cag.py +112 -116
- meerk40t/extra/coolant.py +403 -0
- meerk40t/extra/encode_detect.py +204 -0
- meerk40t/extra/ezd.py +1165 -1165
- meerk40t/extra/hershey.py +834 -340
- meerk40t/extra/imageactions.py +322 -316
- meerk40t/extra/inkscape.py +628 -622
- meerk40t/extra/lbrn.py +424 -424
- meerk40t/extra/outerworld.py +283 -0
- meerk40t/extra/param_functions.py +1542 -1556
- meerk40t/extra/potrace.py +257 -253
- meerk40t/extra/serial_exchange.py +118 -0
- meerk40t/extra/updater.py +602 -453
- meerk40t/extra/vectrace.py +147 -146
- meerk40t/extra/winsleep.py +83 -83
- meerk40t/extra/xcs_reader.py +597 -0
- meerk40t/fill/fills.py +781 -335
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +614 -567
- meerk40t/grbl/control.py +87 -87
- meerk40t/grbl/controller.py +990 -903
- meerk40t/grbl/device.py +1084 -768
- meerk40t/grbl/driver.py +989 -771
- meerk40t/grbl/emulator.py +532 -497
- meerk40t/grbl/gcodejob.py +783 -767
- meerk40t/grbl/gui/grblconfiguration.py +373 -298
- meerk40t/grbl/gui/grblcontroller.py +485 -271
- meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
- meerk40t/grbl/gui/grbloperationconfig.py +105 -0
- meerk40t/grbl/gui/gui.py +147 -116
- meerk40t/grbl/interpreter.py +44 -44
- meerk40t/grbl/loader.py +22 -22
- meerk40t/grbl/mock_connection.py +56 -56
- meerk40t/grbl/plugin.py +294 -264
- meerk40t/grbl/serial_connection.py +93 -88
- meerk40t/grbl/tcp_connection.py +81 -79
- meerk40t/grbl/ws_connection.py +112 -0
- meerk40t/gui/__init__.py +1 -1
- meerk40t/gui/about.py +2042 -296
- meerk40t/gui/alignment.py +1644 -1608
- meerk40t/gui/autoexec.py +199 -0
- meerk40t/gui/basicops.py +791 -670
- meerk40t/gui/bufferview.py +77 -71
- meerk40t/gui/busy.py +232 -133
- meerk40t/gui/choicepropertypanel.py +1662 -1469
- meerk40t/gui/consolepanel.py +706 -542
- meerk40t/gui/devicepanel.py +687 -581
- meerk40t/gui/dialogoptions.py +110 -107
- meerk40t/gui/executejob.py +316 -306
- meerk40t/gui/fonts.py +90 -90
- meerk40t/gui/functionwrapper.py +252 -0
- meerk40t/gui/gui_mixins.py +729 -0
- meerk40t/gui/guicolors.py +205 -182
- meerk40t/gui/help_assets/help_assets.py +218 -201
- meerk40t/gui/helper.py +154 -0
- meerk40t/gui/hersheymanager.py +1440 -846
- meerk40t/gui/icons.py +3422 -2747
- meerk40t/gui/imagesplitter.py +555 -508
- meerk40t/gui/keymap.py +354 -344
- meerk40t/gui/laserpanel.py +897 -806
- meerk40t/gui/laserrender.py +1470 -1232
- meerk40t/gui/lasertoolpanel.py +805 -793
- meerk40t/gui/magnetoptions.py +436 -0
- meerk40t/gui/materialmanager.py +2944 -0
- meerk40t/gui/materialtest.py +1722 -1694
- meerk40t/gui/mkdebug.py +646 -359
- meerk40t/gui/mwindow.py +163 -140
- meerk40t/gui/navigationpanels.py +2605 -2467
- meerk40t/gui/notes.py +143 -142
- meerk40t/gui/opassignment.py +414 -410
- meerk40t/gui/operation_info.py +310 -299
- meerk40t/gui/plugin.py +500 -328
- meerk40t/gui/position.py +714 -669
- meerk40t/gui/preferences.py +901 -650
- meerk40t/gui/propertypanels/attributes.py +1461 -1131
- meerk40t/gui/propertypanels/blobproperty.py +117 -114
- meerk40t/gui/propertypanels/consoleproperty.py +83 -80
- meerk40t/gui/propertypanels/gotoproperty.py +77 -0
- meerk40t/gui/propertypanels/groupproperties.py +223 -217
- meerk40t/gui/propertypanels/hatchproperty.py +489 -469
- meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
- meerk40t/gui/propertypanels/inputproperty.py +59 -58
- meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
- meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
- meerk40t/gui/propertypanels/outputproperty.py +59 -58
- meerk40t/gui/propertypanels/pathproperty.py +389 -380
- meerk40t/gui/propertypanels/placementproperty.py +1214 -383
- meerk40t/gui/propertypanels/pointproperty.py +140 -136
- meerk40t/gui/propertypanels/propertywindow.py +313 -181
- meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
- meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
- meerk40t/gui/propertypanels/textproperty.py +770 -755
- meerk40t/gui/propertypanels/waitproperty.py +56 -55
- meerk40t/gui/propertypanels/warpproperty.py +121 -0
- meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
- meerk40t/gui/ribbon.py +2471 -2210
- meerk40t/gui/scene/scene.py +1100 -1051
- meerk40t/gui/scene/sceneconst.py +22 -22
- meerk40t/gui/scene/scenepanel.py +439 -349
- meerk40t/gui/scene/scenespacewidget.py +365 -365
- meerk40t/gui/scene/widget.py +518 -505
- meerk40t/gui/scenewidgets/affinemover.py +215 -215
- meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
- meerk40t/gui/scenewidgets/bedwidget.py +120 -97
- meerk40t/gui/scenewidgets/elementswidget.py +137 -107
- meerk40t/gui/scenewidgets/gridwidget.py +785 -745
- meerk40t/gui/scenewidgets/guidewidget.py +765 -765
- meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
- meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
- meerk40t/gui/scenewidgets/nodeselector.py +28 -28
- meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
- meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
- meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
- meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
- meerk40t/gui/simpleui.py +362 -333
- meerk40t/gui/simulation.py +2451 -2094
- meerk40t/gui/snapoptions.py +208 -203
- meerk40t/gui/spoolerpanel.py +1227 -1180
- meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
- meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
- meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
- meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
- meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
- meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
- meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
- meerk40t/gui/themes.py +200 -78
- meerk40t/gui/tips.py +590 -0
- meerk40t/gui/toolwidgets/circlebrush.py +35 -35
- meerk40t/gui/toolwidgets/toolcircle.py +248 -242
- meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
- meerk40t/gui/toolwidgets/tooldraw.py +97 -90
- meerk40t/gui/toolwidgets/toolellipse.py +219 -212
- meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
- meerk40t/gui/toolwidgets/toolline.py +39 -144
- meerk40t/gui/toolwidgets/toollinetext.py +79 -236
- meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
- meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
- meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
- meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
- meerk40t/gui/toolwidgets/toolparameter.py +754 -668
- meerk40t/gui/toolwidgets/toolplacement.py +108 -108
- meerk40t/gui/toolwidgets/toolpoint.py +68 -59
- meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
- meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
- meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
- meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
- meerk40t/gui/toolwidgets/toolrect.py +211 -207
- meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
- meerk40t/gui/toolwidgets/toolribbon.py +598 -113
- meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
- meerk40t/gui/toolwidgets/tooltext.py +98 -89
- meerk40t/gui/toolwidgets/toolvector.py +213 -204
- meerk40t/gui/toolwidgets/toolwidget.py +39 -39
- meerk40t/gui/usbconnect.py +98 -91
- meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
- meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
- meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
- meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
- meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
- meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
- meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
- meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
- meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
- meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
- meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
- meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
- meerk40t/gui/wordlisteditor.py +985 -931
- meerk40t/gui/wxmeerk40t.py +1447 -1169
- meerk40t/gui/wxmmain.py +5644 -4112
- meerk40t/gui/wxmribbon.py +1591 -1076
- meerk40t/gui/wxmscene.py +1631 -1453
- meerk40t/gui/wxmtree.py +2416 -2089
- meerk40t/gui/wxutils.py +1769 -1099
- meerk40t/gui/zmatrix.py +102 -102
- meerk40t/image/__init__.py +1 -1
- meerk40t/image/dither.py +429 -0
- meerk40t/image/imagetools.py +2793 -2269
- meerk40t/internal_plugins.py +150 -130
- meerk40t/kernel/__init__.py +63 -12
- meerk40t/kernel/channel.py +259 -212
- meerk40t/kernel/context.py +538 -538
- meerk40t/kernel/exceptions.py +41 -41
- meerk40t/kernel/functions.py +463 -414
- meerk40t/kernel/jobs.py +100 -100
- meerk40t/kernel/kernel.py +3828 -3571
- meerk40t/kernel/lifecycles.py +71 -71
- meerk40t/kernel/module.py +49 -49
- meerk40t/kernel/service.py +147 -147
- meerk40t/kernel/settings.py +383 -343
- meerk40t/lihuiyu/controller.py +883 -876
- meerk40t/lihuiyu/device.py +1181 -1069
- meerk40t/lihuiyu/driver.py +1466 -1372
- meerk40t/lihuiyu/gui/gui.py +127 -106
- meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
- meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
- meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
- meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
- meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
- meerk40t/lihuiyu/interpreter.py +53 -53
- meerk40t/lihuiyu/laserspeed.py +450 -450
- meerk40t/lihuiyu/loader.py +90 -90
- meerk40t/lihuiyu/parser.py +404 -404
- meerk40t/lihuiyu/plugin.py +101 -102
- meerk40t/lihuiyu/tcp_connection.py +111 -109
- meerk40t/main.py +231 -165
- meerk40t/moshi/builder.py +788 -781
- meerk40t/moshi/controller.py +505 -499
- meerk40t/moshi/device.py +495 -442
- meerk40t/moshi/driver.py +862 -696
- meerk40t/moshi/gui/gui.py +78 -76
- meerk40t/moshi/gui/moshicontrollergui.py +538 -522
- meerk40t/moshi/gui/moshidrivergui.py +87 -75
- meerk40t/moshi/plugin.py +43 -43
- meerk40t/network/console_server.py +140 -57
- meerk40t/network/kernelserver.py +10 -9
- meerk40t/network/tcp_server.py +142 -140
- meerk40t/network/udp_server.py +103 -77
- meerk40t/network/web_server.py +404 -0
- meerk40t/newly/controller.py +1158 -1144
- meerk40t/newly/device.py +874 -732
- meerk40t/newly/driver.py +540 -412
- meerk40t/newly/gui/gui.py +219 -188
- meerk40t/newly/gui/newlyconfig.py +116 -101
- meerk40t/newly/gui/newlycontroller.py +193 -186
- meerk40t/newly/gui/operationproperties.py +51 -51
- meerk40t/newly/mock_connection.py +82 -82
- meerk40t/newly/newly_params.py +56 -56
- meerk40t/newly/plugin.py +1214 -1246
- meerk40t/newly/usb_connection.py +322 -322
- meerk40t/rotary/gui/gui.py +52 -46
- meerk40t/rotary/gui/rotarysettings.py +240 -232
- meerk40t/rotary/rotary.py +202 -98
- meerk40t/ruida/control.py +291 -91
- meerk40t/ruida/controller.py +138 -1088
- meerk40t/ruida/device.py +676 -231
- meerk40t/ruida/driver.py +534 -472
- meerk40t/ruida/emulator.py +1494 -1491
- meerk40t/ruida/exceptions.py +4 -4
- meerk40t/ruida/gui/gui.py +71 -76
- meerk40t/ruida/gui/ruidaconfig.py +239 -72
- meerk40t/ruida/gui/ruidacontroller.py +187 -184
- meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
- meerk40t/ruida/loader.py +54 -52
- meerk40t/ruida/mock_connection.py +57 -109
- meerk40t/ruida/plugin.py +124 -87
- meerk40t/ruida/rdjob.py +2084 -945
- meerk40t/ruida/serial_connection.py +116 -0
- meerk40t/ruida/tcp_connection.py +146 -0
- meerk40t/ruida/udp_connection.py +73 -0
- meerk40t/svgelements.py +9671 -9669
- meerk40t/tools/driver_to_path.py +584 -579
- meerk40t/tools/geomstr.py +5583 -4680
- meerk40t/tools/jhfparser.py +357 -292
- meerk40t/tools/kerftest.py +904 -890
- meerk40t/tools/livinghinges.py +1168 -1033
- meerk40t/tools/pathtools.py +987 -949
- meerk40t/tools/pmatrix.py +234 -0
- meerk40t/tools/pointfinder.py +942 -942
- meerk40t/tools/polybool.py +941 -940
- meerk40t/tools/rasterplotter.py +1660 -547
- meerk40t/tools/shxparser.py +1047 -901
- meerk40t/tools/ttfparser.py +726 -446
- meerk40t/tools/zinglplotter.py +595 -593
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
- meerk40t-0.9.7020.dist-info/RECORD +446 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
- meerk40t/balormk/elementlightjob.py +0 -159
- meerk40t-0.9.3001.dist-info/RECORD +0 -437
- test/bootstrap.py +0 -63
- test/test_cli.py +0 -12
- test/test_core_cutcode.py +0 -418
- test/test_core_elements.py +0 -144
- test/test_core_plotplanner.py +0 -397
- test/test_core_viewports.py +0 -312
- test/test_drivers_grbl.py +0 -108
- test/test_drivers_lihuiyu.py +0 -443
- test/test_drivers_newly.py +0 -113
- test/test_element_degenerate_points.py +0 -43
- test/test_elements_classify.py +0 -97
- test/test_elements_penbox.py +0 -22
- test/test_file_svg.py +0 -176
- test/test_fill.py +0 -155
- test/test_geomstr.py +0 -1523
- test/test_geomstr_nodes.py +0 -18
- test/test_imagetools_actualize.py +0 -306
- test/test_imagetools_wizard.py +0 -258
- test/test_kernel.py +0 -200
- test/test_laser_speeds.py +0 -3303
- test/test_length.py +0 -57
- test/test_lifecycle.py +0 -66
- test/test_operations.py +0 -251
- test/test_operations_hatch.py +0 -57
- test/test_ruida.py +0 -19
- test/test_spooler.py +0 -22
- test/test_tools_rasterplotter.py +0 -29
- test/test_wobble.py +0 -133
- test/test_zingl.py +0 -124
- {test → meerk40t/cylinder}/__init__.py +0 -0
- /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/tools/shxparser.py
CHANGED
@@ -1,901 +1,1047 @@
|
|
1
|
-
from math import atan2, cos, isinf, sin, tau
|
2
|
-
|
3
|
-
SHXPARSER_VERSION = "0.0.2"
|
4
|
-
|
5
|
-
|
6
|
-
END_OF_SHAPE = 0
|
7
|
-
PEN_DOWN = 1
|
8
|
-
PEN_UP = 2
|
9
|
-
DIVIDE_VECTOR = 3
|
10
|
-
MULTIPLY_VECTOR = 4
|
11
|
-
PUSH_STACK = 5
|
12
|
-
POP_STACK = 6
|
13
|
-
DRAW_SUBSHAPE = 7
|
14
|
-
XY_DISPLACEMENT = 8
|
15
|
-
POLY_XY_DISPLACEMENT = 9 # 0,0 terminated
|
16
|
-
OCTANT_ARC = 0xA
|
17
|
-
FRACTIONAL_ARC = 0xB # 5 bytes, start, end, high radius, radius, ±0SC
|
18
|
-
BULGE_ARC = 0xC # dx, dy, bulge
|
19
|
-
POLY_BULGE_ARC = 0xD # 0,0 terminated BULGE_ARC
|
20
|
-
COND_MODE_2 = 0x0E # PROCESS this command *only if mode=2*
|
21
|
-
|
22
|
-
|
23
|
-
def signed8(b):
|
24
|
-
if b > 127:
|
25
|
-
return -256 + b
|
26
|
-
else:
|
27
|
-
return b
|
28
|
-
|
29
|
-
|
30
|
-
def int_16le(byte):
|
31
|
-
return (byte[0] & 0xFF) + ((byte[1] & 0xFF) << 8)
|
32
|
-
|
33
|
-
|
34
|
-
def int_32le(b):
|
35
|
-
return (
|
36
|
-
(b[0] & 0xFF)
|
37
|
-
+ ((b[1] & 0xFF) << 8)
|
38
|
-
+ ((b[2] & 0xFF) << 16)
|
39
|
-
+ ((b[3] & 0xFF) << 24)
|
40
|
-
)
|
41
|
-
|
42
|
-
|
43
|
-
def read_int_8(stream):
|
44
|
-
byte = bytearray(stream.read(1))
|
45
|
-
if len(byte) == 1:
|
46
|
-
return byte[0]
|
47
|
-
return None
|
48
|
-
|
49
|
-
|
50
|
-
def read_int_16le(stream):
|
51
|
-
byte = bytearray(stream.read(2))
|
52
|
-
if len(byte) == 2:
|
53
|
-
return int_16le(byte)
|
54
|
-
return None
|
55
|
-
|
56
|
-
|
57
|
-
def read_int_32le(stream):
|
58
|
-
b = bytearray(stream.read(4))
|
59
|
-
if len(b) == 4:
|
60
|
-
return int_32le(b)
|
61
|
-
return None
|
62
|
-
|
63
|
-
|
64
|
-
def read_string(stream):
|
65
|
-
|
66
|
-
|
67
|
-
while True:
|
68
|
-
b = stream.read(1)
|
69
|
-
if b == b"":
|
70
|
-
return bb.decode("utf-8")
|
71
|
-
if b == b"\r" or b == b"\n" or b == b"\x00":
|
72
|
-
return bb.decode("utf-8")
|
73
|
-
bb += b
|
74
|
-
except UnicodeDecodeError as e:
|
75
|
-
raise ShxFontParseError(f"Read string did not capture valid text. {bb}") from e
|
76
|
-
|
77
|
-
|
78
|
-
class ShxPath:
|
79
|
-
"""
|
80
|
-
Example path code. Any class with these functions would work as well. When render is called on the ShxFont class
|
81
|
-
the path is given particular useful segments.
|
82
|
-
"""
|
83
|
-
|
84
|
-
def __init__(self):
|
85
|
-
self.path = list()
|
86
|
-
|
87
|
-
def bounds(self):
|
88
|
-
"""
|
89
|
-
Get bounds of paths.
|
90
|
-
:return:
|
91
|
-
"""
|
92
|
-
min_x = float("inf")
|
93
|
-
min_y = float("inf")
|
94
|
-
max_x = -float("inf")
|
95
|
-
max_y = -float("inf")
|
96
|
-
for p in self.path:
|
97
|
-
if p is None:
|
98
|
-
continue
|
99
|
-
min_x = min(p[0], min_x)
|
100
|
-
min_y = min(p[1], min_y)
|
101
|
-
max_x = max(p[0], max_x)
|
102
|
-
max_y = max(p[1], max_y)
|
103
|
-
|
104
|
-
min_x = min(p[-2], min_x)
|
105
|
-
min_y = min(p[-1], min_y)
|
106
|
-
max_x = max(p[-2], max_x)
|
107
|
-
max_y = max(p[-1], max_y)
|
108
|
-
if isinf(min_x):
|
109
|
-
return None
|
110
|
-
return min_x, min_y, max_x, max_y
|
111
|
-
|
112
|
-
def scale(self, scale_x, scale_y):
|
113
|
-
for p in self.path:
|
114
|
-
if p is None:
|
115
|
-
continue
|
116
|
-
if len(p) >= 2:
|
117
|
-
p[0] *= scale_x
|
118
|
-
p[1] *= scale_y
|
119
|
-
if len(p) >= 4:
|
120
|
-
p[2] *= scale_x
|
121
|
-
p[3] *= scale_y
|
122
|
-
if len(p) >= 6:
|
123
|
-
p[4] *= scale_x
|
124
|
-
p[5] *= scale_y
|
125
|
-
|
126
|
-
def translate(self, translate_x, translate_y):
|
127
|
-
for p in self.path:
|
128
|
-
if p is None:
|
129
|
-
continue
|
130
|
-
if len(p) >= 2:
|
131
|
-
p[0] += translate_x
|
132
|
-
p[1] += translate_y
|
133
|
-
if len(p) >= 4:
|
134
|
-
p[2] += translate_x
|
135
|
-
p[3] += translate_y
|
136
|
-
if len(p) >= 6:
|
137
|
-
p[4] += translate_x
|
138
|
-
p[5] += translate_y
|
139
|
-
|
140
|
-
def new_path(self):
|
141
|
-
"""
|
142
|
-
Start of a new path.
|
143
|
-
"""
|
144
|
-
self.path.append(None)
|
145
|
-
|
146
|
-
def move(self, x, y):
|
147
|
-
"""
|
148
|
-
Move current point to the point specified.
|
149
|
-
"""
|
150
|
-
self.path.append([x, y])
|
151
|
-
|
152
|
-
def line(self, x0, y0, x1, y1):
|
153
|
-
"""
|
154
|
-
Draw a line from the current point to the specified point.
|
155
|
-
"""
|
156
|
-
self.path.append([x0, y0, x1, y1])
|
157
|
-
|
158
|
-
def arc(self, x0, y0, cx, cy, x1, y1):
|
159
|
-
"""
|
160
|
-
Draw an arc from the current point to specified point going through the control point.
|
161
|
-
|
162
|
-
3 Points define a circular arc, there is only one arc which travels from start to end going through a given
|
163
|
-
control point. The exceptions are when the arc points are collinear or two arc points are coincident. In some
|
164
|
-
cases the start and end points will be equal and the control point will be located the circle diameter away.
|
165
|
-
"""
|
166
|
-
self.path.append([x0, y0, cx, cy, x1, y1])
|
167
|
-
|
168
|
-
|
169
|
-
class ShxFontParseError(Exception):
|
170
|
-
"""
|
171
|
-
Exception thrown if unable to pop a value from the given codes or other suspected parsing errors.
|
172
|
-
"""
|
173
|
-
|
174
|
-
|
175
|
-
class ShxFont:
|
176
|
-
"""
|
177
|
-
This class performs the parsing of the three major types of .SHX fonts. Composing them into specific glyphs which
|
178
|
-
consist of commands in a vector-shape language. When .render() is called on some text, vector actions are performed
|
179
|
-
on the font which create the vector path.
|
180
|
-
"""
|
181
|
-
|
182
|
-
def __init__(self, filename, debug=False):
|
183
|
-
self.
|
184
|
-
self.
|
185
|
-
self.
|
186
|
-
self.
|
187
|
-
self.
|
188
|
-
self.
|
189
|
-
self.
|
190
|
-
self.
|
191
|
-
self.
|
192
|
-
self.
|
193
|
-
|
194
|
-
|
195
|
-
self.
|
196
|
-
self.
|
197
|
-
self.
|
198
|
-
self.
|
199
|
-
self.
|
200
|
-
self.
|
201
|
-
self.
|
202
|
-
self.
|
203
|
-
self.
|
204
|
-
self.
|
205
|
-
self.
|
206
|
-
self.
|
207
|
-
|
208
|
-
self.
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
def
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
self.
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
self.
|
354
|
-
self.
|
355
|
-
self.
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
self.
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
self.
|
466
|
-
|
467
|
-
self.
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
:
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
)
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
self.
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
:
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
)
|
573
|
-
|
574
|
-
self.
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
def
|
581
|
-
"""
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
if self._debug:
|
604
|
-
print(
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
return
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
:
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
self.
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
self.
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
self.
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
:
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
1
|
+
from math import atan2, cos, isinf, sin, tau
|
2
|
+
|
3
|
+
SHXPARSER_VERSION = "0.0.2"
|
4
|
+
|
5
|
+
|
6
|
+
END_OF_SHAPE = 0
|
7
|
+
PEN_DOWN = 1
|
8
|
+
PEN_UP = 2
|
9
|
+
DIVIDE_VECTOR = 3
|
10
|
+
MULTIPLY_VECTOR = 4
|
11
|
+
PUSH_STACK = 5
|
12
|
+
POP_STACK = 6
|
13
|
+
DRAW_SUBSHAPE = 7
|
14
|
+
XY_DISPLACEMENT = 8
|
15
|
+
POLY_XY_DISPLACEMENT = 9 # 0,0 terminated
|
16
|
+
OCTANT_ARC = 0xA
|
17
|
+
FRACTIONAL_ARC = 0xB # 5 bytes, start, end, high radius, radius, ±0SC
|
18
|
+
BULGE_ARC = 0xC # dx, dy, bulge
|
19
|
+
POLY_BULGE_ARC = 0xD # 0,0 terminated BULGE_ARC
|
20
|
+
COND_MODE_2 = 0x0E # PROCESS this command *only if mode=2*
|
21
|
+
|
22
|
+
|
23
|
+
def signed8(b):
|
24
|
+
if b > 127:
|
25
|
+
return -256 + b
|
26
|
+
else:
|
27
|
+
return b
|
28
|
+
|
29
|
+
|
30
|
+
def int_16le(byte):
|
31
|
+
return (byte[0] & 0xFF) + ((byte[1] & 0xFF) << 8)
|
32
|
+
|
33
|
+
|
34
|
+
def int_32le(b):
|
35
|
+
return (
|
36
|
+
(b[0] & 0xFF)
|
37
|
+
+ ((b[1] & 0xFF) << 8)
|
38
|
+
+ ((b[2] & 0xFF) << 16)
|
39
|
+
+ ((b[3] & 0xFF) << 24)
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
def read_int_8(stream):
|
44
|
+
byte = bytearray(stream.read(1))
|
45
|
+
if len(byte) == 1:
|
46
|
+
return byte[0]
|
47
|
+
return None
|
48
|
+
|
49
|
+
|
50
|
+
def read_int_16le(stream):
|
51
|
+
byte = bytearray(stream.read(2))
|
52
|
+
if len(byte) == 2:
|
53
|
+
return int_16le(byte)
|
54
|
+
return None
|
55
|
+
|
56
|
+
|
57
|
+
def read_int_32le(stream):
|
58
|
+
b = bytearray(stream.read(4))
|
59
|
+
if len(b) == 4:
|
60
|
+
return int_32le(b)
|
61
|
+
return None
|
62
|
+
|
63
|
+
|
64
|
+
def read_string(stream):
|
65
|
+
bb = bytearray()
|
66
|
+
try:
|
67
|
+
while True:
|
68
|
+
b = stream.read(1)
|
69
|
+
if b == b"":
|
70
|
+
return bb.decode("utf-8")
|
71
|
+
if b == b"\r" or b == b"\n" or b == b"\x00":
|
72
|
+
return bb.decode("utf-8")
|
73
|
+
bb += b
|
74
|
+
except UnicodeDecodeError as e:
|
75
|
+
raise ShxFontParseError(f"Read string did not capture valid text. {bb}") from e
|
76
|
+
|
77
|
+
|
78
|
+
class ShxPath:
|
79
|
+
"""
|
80
|
+
Example path code. Any class with these functions would work as well. When render is called on the ShxFont class
|
81
|
+
the path is given particular useful segments.
|
82
|
+
"""
|
83
|
+
|
84
|
+
def __init__(self):
|
85
|
+
self.path = list()
|
86
|
+
|
87
|
+
def bounds(self):
|
88
|
+
"""
|
89
|
+
Get bounds of paths.
|
90
|
+
:return:
|
91
|
+
"""
|
92
|
+
min_x = float("inf")
|
93
|
+
min_y = float("inf")
|
94
|
+
max_x = -float("inf")
|
95
|
+
max_y = -float("inf")
|
96
|
+
for p in self.path:
|
97
|
+
if p is None:
|
98
|
+
continue
|
99
|
+
min_x = min(p[0], min_x)
|
100
|
+
min_y = min(p[1], min_y)
|
101
|
+
max_x = max(p[0], max_x)
|
102
|
+
max_y = max(p[1], max_y)
|
103
|
+
|
104
|
+
min_x = min(p[-2], min_x)
|
105
|
+
min_y = min(p[-1], min_y)
|
106
|
+
max_x = max(p[-2], max_x)
|
107
|
+
max_y = max(p[-1], max_y)
|
108
|
+
if isinf(min_x):
|
109
|
+
return None
|
110
|
+
return min_x, min_y, max_x, max_y
|
111
|
+
|
112
|
+
def scale(self, scale_x, scale_y):
|
113
|
+
for p in self.path:
|
114
|
+
if p is None:
|
115
|
+
continue
|
116
|
+
if len(p) >= 2:
|
117
|
+
p[0] *= scale_x
|
118
|
+
p[1] *= scale_y
|
119
|
+
if len(p) >= 4:
|
120
|
+
p[2] *= scale_x
|
121
|
+
p[3] *= scale_y
|
122
|
+
if len(p) >= 6:
|
123
|
+
p[4] *= scale_x
|
124
|
+
p[5] *= scale_y
|
125
|
+
|
126
|
+
def translate(self, translate_x, translate_y):
|
127
|
+
for p in self.path:
|
128
|
+
if p is None:
|
129
|
+
continue
|
130
|
+
if len(p) >= 2:
|
131
|
+
p[0] += translate_x
|
132
|
+
p[1] += translate_y
|
133
|
+
if len(p) >= 4:
|
134
|
+
p[2] += translate_x
|
135
|
+
p[3] += translate_y
|
136
|
+
if len(p) >= 6:
|
137
|
+
p[4] += translate_x
|
138
|
+
p[5] += translate_y
|
139
|
+
|
140
|
+
def new_path(self):
|
141
|
+
"""
|
142
|
+
Start of a new path.
|
143
|
+
"""
|
144
|
+
self.path.append(None)
|
145
|
+
|
146
|
+
def move(self, x, y):
|
147
|
+
"""
|
148
|
+
Move current point to the point specified.
|
149
|
+
"""
|
150
|
+
self.path.append([x, y])
|
151
|
+
|
152
|
+
def line(self, x0, y0, x1, y1):
|
153
|
+
"""
|
154
|
+
Draw a line from the current point to the specified point.
|
155
|
+
"""
|
156
|
+
self.path.append([x0, y0, x1, y1])
|
157
|
+
|
158
|
+
def arc(self, x0, y0, cx, cy, x1, y1):
|
159
|
+
"""
|
160
|
+
Draw an arc from the current point to specified point going through the control point.
|
161
|
+
|
162
|
+
3 Points define a circular arc, there is only one arc which travels from start to end going through a given
|
163
|
+
control point. The exceptions are when the arc points are collinear or two arc points are coincident. In some
|
164
|
+
cases the start and end points will be equal and the control point will be located the circle diameter away.
|
165
|
+
"""
|
166
|
+
self.path.append([x0, y0, cx, cy, x1, y1])
|
167
|
+
|
168
|
+
|
169
|
+
class ShxFontParseError(Exception):
|
170
|
+
"""
|
171
|
+
Exception thrown if unable to pop a value from the given codes or other suspected parsing errors.
|
172
|
+
"""
|
173
|
+
|
174
|
+
|
175
|
+
class ShxFont:
|
176
|
+
"""
|
177
|
+
This class performs the parsing of the three major types of .SHX fonts. Composing them into specific glyphs which
|
178
|
+
consist of commands in a vector-shape language. When .render() is called on some text, vector actions are performed
|
179
|
+
on the font which create the vector path.
|
180
|
+
"""
|
181
|
+
|
182
|
+
def __init__(self, filename, debug=False):
|
183
|
+
self.STROKE_BASED = True
|
184
|
+
self.format = None # format (usually AutoCAD-86)
|
185
|
+
self.type = None # Font type: shapes, bigfont, unifont
|
186
|
+
self.version = None # Font file version (usually 1.0).
|
187
|
+
self.glyphs = dict() # Glyph dictionary
|
188
|
+
self.font_name = None # Parsed font name.
|
189
|
+
self.above = None # Distance above baseline for capital letters.
|
190
|
+
self.below = None # Distance below baseline for lowercase letters
|
191
|
+
self.modes = None # 0 Horizontal Only, 2 Dual mode (Horizontal or Vertical)
|
192
|
+
self.encoding = False # 0 unicode, 1 packed multibyte, 2 shape file
|
193
|
+
self.embedded = False # 0 font can be embedded, 1 font cannot be embedded, 2 embedding is read-only
|
194
|
+
|
195
|
+
self._debug = debug
|
196
|
+
self._code = None
|
197
|
+
self._path = None
|
198
|
+
self._skip = False
|
199
|
+
self._pen = False
|
200
|
+
self._horizontal = True
|
201
|
+
self._letter = None
|
202
|
+
self._x = 0
|
203
|
+
self._y = 0
|
204
|
+
self._last_x = 0
|
205
|
+
self._last_y = 0
|
206
|
+
self._scale = 1
|
207
|
+
self._stack = []
|
208
|
+
self.active = True
|
209
|
+
self._line_information = []
|
210
|
+
|
211
|
+
self._parse(filename)
|
212
|
+
|
213
|
+
def __str__(self):
|
214
|
+
return f'{self.type}("{self.font_name}", {self.version}, glyphs: {len(self.glyphs)})'
|
215
|
+
|
216
|
+
def line_information(self):
|
217
|
+
return self._line_information
|
218
|
+
|
219
|
+
def _parse(self, filename):
|
220
|
+
with open(filename, "br") as f:
|
221
|
+
self._parse_header(f)
|
222
|
+
if self._debug:
|
223
|
+
print(f"Font header indicates font type is {self.type}")
|
224
|
+
if self.type == "shapes":
|
225
|
+
self._parse_shapes(f)
|
226
|
+
elif self.type == "bigfont":
|
227
|
+
self._parse_bigfont(f)
|
228
|
+
elif self.type == "unifont":
|
229
|
+
self._parse_unifont(f)
|
230
|
+
else:
|
231
|
+
raise ShxFontParseError(f"{self.type} is not a valid shx file type.")
|
232
|
+
self.translate_names_to_codes()
|
233
|
+
|
234
|
+
def translate_names_to_codes(self):
|
235
|
+
# We can't properly deal with long names,
|
236
|
+
# so we translate them a virtual character
|
237
|
+
gdict = list(self.glyphs.items())
|
238
|
+
virtual_char = len(self.glyphs)
|
239
|
+
for key, data in gdict:
|
240
|
+
if isinstance(key, str) and len(key) > 1:
|
241
|
+
while True:
|
242
|
+
newkey = chr(virtual_char)
|
243
|
+
if newkey not in self.glyphs:
|
244
|
+
break
|
245
|
+
virtual_char += 1
|
246
|
+
self.glyphs[newkey] = data
|
247
|
+
del self.glyphs[key]
|
248
|
+
|
249
|
+
def _parse_header(self, f):
|
250
|
+
header = read_string(f)
|
251
|
+
parts = header.split(" ")
|
252
|
+
if len(parts) != 3:
|
253
|
+
raise ShxFontParseError(f"Header information invalid: {header}")
|
254
|
+
self.format = parts[0]
|
255
|
+
self.type = parts[1]
|
256
|
+
self.version = parts[2]
|
257
|
+
f.read(2)
|
258
|
+
|
259
|
+
def _parse_shapes(self, f):
|
260
|
+
start = read_int_16le(f)
|
261
|
+
end = read_int_16le(f)
|
262
|
+
count = read_int_16le(f)
|
263
|
+
if self._debug:
|
264
|
+
print(f"Parsing shape: start={start}, end={end}, count={count}")
|
265
|
+
glyph_ref = list()
|
266
|
+
for i in range(count):
|
267
|
+
index = read_int_16le(f)
|
268
|
+
length = read_int_16le(f)
|
269
|
+
glyph_ref.append((index, length))
|
270
|
+
|
271
|
+
for index, length in glyph_ref:
|
272
|
+
if index == 0:
|
273
|
+
if self.font_name is not None:
|
274
|
+
raise ShxFontParseError("Double-initializing glyph data detected")
|
275
|
+
self.font_name = read_string(f)
|
276
|
+
self.above = read_int_8(f) # vector lengths above baseline
|
277
|
+
self.below = read_int_8(f) # vector lengths below baseline
|
278
|
+
# 0 - Horizontal, 2 - dual. 0x0E command only when mode=2
|
279
|
+
self.modes = read_int_8(f)
|
280
|
+
# end = read_int_16le(f)
|
281
|
+
else:
|
282
|
+
read = f.read(length)
|
283
|
+
data = read
|
284
|
+
if len(data) != length:
|
285
|
+
raise ShxFontParseError("Glyph length did not exist in file.")
|
286
|
+
if data[0] == 0 and data[1] == 0:
|
287
|
+
data = data[2:]
|
288
|
+
elif data[0] == 0:
|
289
|
+
data = data[1:]
|
290
|
+
find = data.find(b"\x00")
|
291
|
+
if find != -1:
|
292
|
+
name = data[:find]
|
293
|
+
|
294
|
+
for c in name:
|
295
|
+
if (
|
296
|
+
ord("A") <= c <= ord("Z")
|
297
|
+
or ord("0") <= c <= ord("9")
|
298
|
+
or c == ord(" ")
|
299
|
+
or c == ord("&")
|
300
|
+
):
|
301
|
+
continue
|
302
|
+
name = None
|
303
|
+
break
|
304
|
+
if name is not None:
|
305
|
+
data = data[find + 1 :]
|
306
|
+
name = name.decode()
|
307
|
+
self.glyphs[name] = data
|
308
|
+
else:
|
309
|
+
if self._debug:
|
310
|
+
print(f"{data} did not contain a name.")
|
311
|
+
self.glyphs[index] = data
|
312
|
+
|
313
|
+
def _parse_bigfont(self, f):
|
314
|
+
count = read_int_16le(f)
|
315
|
+
length = read_int_16le(f)
|
316
|
+
changes = list()
|
317
|
+
change_count = read_int_16le(f)
|
318
|
+
if self._debug:
|
319
|
+
print(
|
320
|
+
f"Parsing bigfont: count={count}, length={length}, change_count={change_count}"
|
321
|
+
)
|
322
|
+
for i in range(change_count):
|
323
|
+
start = read_int_16le(f)
|
324
|
+
end = read_int_16le(f)
|
325
|
+
changes.append((start, end))
|
326
|
+
|
327
|
+
glyph_ref = list()
|
328
|
+
for i in range(count):
|
329
|
+
index = read_int_16le(f)
|
330
|
+
length = read_int_16le(f)
|
331
|
+
offset = read_int_32le(f)
|
332
|
+
glyph_ref.append((index, length, offset))
|
333
|
+
|
334
|
+
for index, length, offset in glyph_ref:
|
335
|
+
f.seek(offset, 0)
|
336
|
+
if index == 0:
|
337
|
+
# self.font_name = read_string(f)
|
338
|
+
self.above = read_int_8(f) # vector lengths above baseline
|
339
|
+
self.below = read_int_8(f) # vector lengths below baseline
|
340
|
+
# 0 - Horizontal, 2 - dual. 0x0E command only when mode=2
|
341
|
+
self.modes = read_int_8(f)
|
342
|
+
|
343
|
+
else:
|
344
|
+
self.glyphs[index] = f.read(length)
|
345
|
+
|
346
|
+
def _parse_unifont(self, f):
|
347
|
+
count = read_int_32le(f)
|
348
|
+
length = read_int_16le(f)
|
349
|
+
f.seek(5)
|
350
|
+
self.font_name = read_string(f)
|
351
|
+
self.above = read_int_8(f)
|
352
|
+
self.below = read_int_8(f)
|
353
|
+
self.mode = read_int_8(f)
|
354
|
+
self.encoding = read_int_8(f)
|
355
|
+
self.embedded = read_int_8(f)
|
356
|
+
ignore = read_int_8(f)
|
357
|
+
if self._debug:
|
358
|
+
print(
|
359
|
+
f"Parsing unifont: name={self.font_name}, count={count}, length={length}"
|
360
|
+
)
|
361
|
+
for i in range(count - 1):
|
362
|
+
index = read_int_16le(f)
|
363
|
+
length = read_int_16le(f)
|
364
|
+
self.glyphs[index] = f.read(length)
|
365
|
+
|
366
|
+
def pop(self):
|
367
|
+
try:
|
368
|
+
return self._code.pop()
|
369
|
+
except IndexError as e:
|
370
|
+
print (f"Error in {self.font_name}: no codes to pop")
|
371
|
+
raise ShxFontParseError("No codes to pop()") from e
|
372
|
+
|
373
|
+
def render(
|
374
|
+
self,
|
375
|
+
path,
|
376
|
+
vtext,
|
377
|
+
horizontal=True,
|
378
|
+
font_size=12.0,
|
379
|
+
h_spacing=1.0,
|
380
|
+
v_spacing=1.1,
|
381
|
+
align="start",
|
382
|
+
):
|
383
|
+
def _do_render(to_render, offsets):
|
384
|
+
if self.above is None:
|
385
|
+
self.above = 1
|
386
|
+
if self.below is None:
|
387
|
+
self.below = 0
|
388
|
+
self._scale = font_size / self.above
|
389
|
+
self._horizontal = horizontal
|
390
|
+
self._path = path
|
391
|
+
replacer = []
|
392
|
+
for tchar in to_render:
|
393
|
+
to_replace = None
|
394
|
+
if tchar == "\n":
|
395
|
+
continue
|
396
|
+
# Yes, I am German :-)
|
397
|
+
if ord(tchar) not in self.glyphs:
|
398
|
+
if tchar == "ä":
|
399
|
+
to_replace = (tchar, "ae")
|
400
|
+
elif tchar == "ö":
|
401
|
+
to_replace = (tchar, "ue")
|
402
|
+
elif tchar == "ü":
|
403
|
+
to_replace = (tchar, "ue")
|
404
|
+
elif tchar == "Ä":
|
405
|
+
to_replace = (tchar, "Ae")
|
406
|
+
elif tchar == "Ö":
|
407
|
+
to_replace = (tchar, "Oe")
|
408
|
+
elif tchar == "Ü":
|
409
|
+
to_replace = (tchar, "Ue")
|
410
|
+
elif tchar == "ß":
|
411
|
+
to_replace = (tchar, "ss")
|
412
|
+
if to_replace is not None and to_replace not in replacer:
|
413
|
+
replacer.append(to_replace)
|
414
|
+
for to_replace in replacer:
|
415
|
+
# print (f"Replace all '{to_replace[0]}' with '{to_replace[1]}'")
|
416
|
+
to_render = to_render.replace(to_replace[0], to_replace[1])
|
417
|
+
lines = to_render.split("\n")
|
418
|
+
offset_y = 0
|
419
|
+
if offsets is None:
|
420
|
+
offsets = [0] * len(lines)
|
421
|
+
self._line_information.clear()
|
422
|
+
for text, offs in zip(lines, offsets):
|
423
|
+
self._y = offset_y * self._scale
|
424
|
+
self._last_y = self._y
|
425
|
+
self._x = offs
|
426
|
+
self._last_x = offs
|
427
|
+
maxx = offs
|
428
|
+
line_start_x = self._x
|
429
|
+
line_start_y = self._y
|
430
|
+
for letter in text:
|
431
|
+
last_letter_x = self._last_x
|
432
|
+
last_letter_y = self._last_y
|
433
|
+
self._letter = letter
|
434
|
+
try:
|
435
|
+
self._code = bytearray(reversed(self.glyphs[ord(letter)]))
|
436
|
+
except KeyError:
|
437
|
+
# Letter is not found.
|
438
|
+
continue
|
439
|
+
self._pen = True
|
440
|
+
while self._code:
|
441
|
+
try:
|
442
|
+
self._parse_code()
|
443
|
+
except IndexError as e:
|
444
|
+
raise ShxFontParseError("Stack Error during render.") from e
|
445
|
+
self._skip = False
|
446
|
+
if h_spacing != 1.0:
|
447
|
+
dx = (h_spacing - 1) * (self._last_x - last_letter_x)
|
448
|
+
self._last_x += dx
|
449
|
+
self._x += dx
|
450
|
+
maxx = max(maxx, self._x)
|
451
|
+
if self.active:
|
452
|
+
path.character_end()
|
453
|
+
# Store start point, nonscaled width plus scaled width and height of line
|
454
|
+
self._line_information.append(
|
455
|
+
(
|
456
|
+
line_start_x,
|
457
|
+
line_start_y,
|
458
|
+
maxx,
|
459
|
+
maxx - line_start_x,
|
460
|
+
self._scale * (self.above + self.below),
|
461
|
+
)
|
462
|
+
)
|
463
|
+
offset_y -= v_spacing * (self.above + self.below)
|
464
|
+
|
465
|
+
if self._debug:
|
466
|
+
print(f"Render Complete.\n\n\n")
|
467
|
+
line_lens = [e[2] for e in self._line_information]
|
468
|
+
return line_lens
|
469
|
+
|
470
|
+
if vtext is None or vtext == "":
|
471
|
+
return
|
472
|
+
self.active = False
|
473
|
+
line_lengths = _do_render(vtext, None)
|
474
|
+
max_len = max(line_lengths)
|
475
|
+
offsets = []
|
476
|
+
for ll in line_lengths:
|
477
|
+
# NB anchor not only defines the alignment of the individual
|
478
|
+
# lines to another but as well of the whole block relative
|
479
|
+
# to the origin
|
480
|
+
if align == "middle":
|
481
|
+
offs = -max_len / 2 + (max_len - ll) / 2
|
482
|
+
elif align == "end":
|
483
|
+
offs = -ll
|
484
|
+
else:
|
485
|
+
offs = 0
|
486
|
+
offsets.append(offs)
|
487
|
+
self.active = True
|
488
|
+
line_lengths = _do_render(vtext, offsets)
|
489
|
+
|
490
|
+
def _parse_code(self):
|
491
|
+
try:
|
492
|
+
b = self.pop()
|
493
|
+
except ShxFontParseError:
|
494
|
+
return
|
495
|
+
direction = b & 0x0F
|
496
|
+
length = (b & 0xF0) >> 4
|
497
|
+
if length == 0:
|
498
|
+
self._parse_code_special(direction)
|
499
|
+
else:
|
500
|
+
self._parse_code_length(direction, length)
|
501
|
+
|
502
|
+
def _parse_code_length(self, direction, length):
|
503
|
+
"""
|
504
|
+
Length direction codes. If length is 0 then direction is special otherwise the
|
505
|
+
command is a move in one of 16 different directions moving in 22.5° increments for
|
506
|
+
a distance of 1 to 15 units lengths.
|
507
|
+
|
508
|
+
:param direction:
|
509
|
+
:param length:
|
510
|
+
:return:
|
511
|
+
"""
|
512
|
+
if self._debug:
|
513
|
+
print(
|
514
|
+
f"MOVE DIRECTION {direction} for {length} {'(Skipped)' if self._skip else ''}"
|
515
|
+
)
|
516
|
+
if self._skip:
|
517
|
+
self._skip = False
|
518
|
+
return
|
519
|
+
if direction in (2, 1, 0, 0xF, 0xE):
|
520
|
+
dx = 1.0
|
521
|
+
elif direction in (3, 0xD):
|
522
|
+
dx = 0.5
|
523
|
+
elif direction in (4, 0xC):
|
524
|
+
dx = 0.0
|
525
|
+
elif direction in (5, 0xB):
|
526
|
+
dx = -0.5
|
527
|
+
else: # (6, 7, 8, 9, 0xa):
|
528
|
+
dx = -1.0
|
529
|
+
if direction in (6, 5, 4, 3, 2):
|
530
|
+
dy = 1.0
|
531
|
+
elif direction in (7, 1):
|
532
|
+
dy = 0.5
|
533
|
+
elif direction in (8, 0):
|
534
|
+
dy = 0.0
|
535
|
+
elif direction in (9, 0xF):
|
536
|
+
dy = -0.5
|
537
|
+
else: # (0xa, 0xb, 0xc, 0xd, 0xe, 0xf):
|
538
|
+
dy = -1.0
|
539
|
+
self._x += dx * length * self._scale
|
540
|
+
self._y += dy * length * self._scale
|
541
|
+
if self.active:
|
542
|
+
if self._pen:
|
543
|
+
self._path.line(self._last_x, self._last_y, self._x, self._y)
|
544
|
+
else:
|
545
|
+
self._path.move(self._x, self._y)
|
546
|
+
self._last_x, self._last_y = self._x, self._y
|
547
|
+
|
548
|
+
def _parse_code_special(self, special):
|
549
|
+
if special == END_OF_SHAPE:
|
550
|
+
self._end_of_shape()
|
551
|
+
elif special == PEN_DOWN:
|
552
|
+
self._pen_down()
|
553
|
+
elif special == PEN_UP:
|
554
|
+
self._pen_up()
|
555
|
+
elif special == DIVIDE_VECTOR:
|
556
|
+
self._divide_vector()
|
557
|
+
elif special == MULTIPLY_VECTOR:
|
558
|
+
self._multiply_vector()
|
559
|
+
elif special == PUSH_STACK:
|
560
|
+
self._push_stack()
|
561
|
+
elif special == POP_STACK:
|
562
|
+
self._pop_stack()
|
563
|
+
elif special == DRAW_SUBSHAPE:
|
564
|
+
self._draw_subshape()
|
565
|
+
elif special == XY_DISPLACEMENT:
|
566
|
+
self._xy_displacement()
|
567
|
+
elif special == POLY_XY_DISPLACEMENT:
|
568
|
+
self._poly_xy_displacement()
|
569
|
+
elif special == OCTANT_ARC:
|
570
|
+
self._octant_arc()
|
571
|
+
elif special == FRACTIONAL_ARC:
|
572
|
+
self._fractional_arc()
|
573
|
+
elif special == BULGE_ARC:
|
574
|
+
self._bulge_arc()
|
575
|
+
elif special == POLY_BULGE_ARC:
|
576
|
+
self._poly_bulge_arc()
|
577
|
+
elif special == COND_MODE_2:
|
578
|
+
self._cond_mode_2()
|
579
|
+
|
580
|
+
def _end_of_shape(self):
|
581
|
+
"""
|
582
|
+
End of shape definition.
|
583
|
+
:return:
|
584
|
+
"""
|
585
|
+
try:
|
586
|
+
while self.pop() != 0:
|
587
|
+
pass
|
588
|
+
except ShxFontParseError:
|
589
|
+
pass
|
590
|
+
if self._debug:
|
591
|
+
print("END_OF_SHAPE")
|
592
|
+
if self._skip:
|
593
|
+
self._skip = False
|
594
|
+
return
|
595
|
+
if self.active:
|
596
|
+
self._path.new_path()
|
597
|
+
|
598
|
+
def _pen_down(self):
|
599
|
+
"""
|
600
|
+
Activates draw mode. Pen is down. Draw is activated for each shape.
|
601
|
+
:return:
|
602
|
+
"""
|
603
|
+
if self._debug:
|
604
|
+
print(f"PEN_DOWN: {self._x}, {self._y} {'(Skipped)' if self._skip else ''}")
|
605
|
+
if self._skip:
|
606
|
+
self._skip = False
|
607
|
+
return
|
608
|
+
self._pen = True
|
609
|
+
if self.active:
|
610
|
+
self._path.move(self._x, self._y)
|
611
|
+
|
612
|
+
def _pen_up(self):
|
613
|
+
"""
|
614
|
+
Deactivates draw mode. Subsequent draws are moves to new locations.
|
615
|
+
:return:
|
616
|
+
"""
|
617
|
+
if self._debug:
|
618
|
+
print(f"PEN_UP {'(Skipped)' if self._skip else ''}")
|
619
|
+
if self._skip:
|
620
|
+
self._skip = False
|
621
|
+
return
|
622
|
+
self._pen = False
|
623
|
+
|
624
|
+
def _divide_vector(self):
|
625
|
+
"""
|
626
|
+
Height is specified with shape command. Initially considered the length of a single vector.
|
627
|
+
Divides the scale factor by the next byte.
|
628
|
+
|
629
|
+
:return:
|
630
|
+
"""
|
631
|
+
try:
|
632
|
+
factor = self.pop()
|
633
|
+
except ShxFontParseError:
|
634
|
+
return
|
635
|
+
if self._debug:
|
636
|
+
print(
|
637
|
+
f"DIVIDE_VECTOR {self._scale}/{factor} {'(Skipped)' if self._skip else ''}"
|
638
|
+
)
|
639
|
+
if factor == 0:
|
640
|
+
raise ShxFontParseError("Divide Vector is not permitted to be 0.")
|
641
|
+
if self._skip:
|
642
|
+
self._skip = False
|
643
|
+
return
|
644
|
+
self._scale /= factor
|
645
|
+
|
646
|
+
def _multiply_vector(self):
|
647
|
+
"""
|
648
|
+
Multiplies the scale factor by the next byte.
|
649
|
+
|
650
|
+
:return:
|
651
|
+
"""
|
652
|
+
factor = self.pop()
|
653
|
+
if self._debug:
|
654
|
+
print(
|
655
|
+
f"MULTIPLY_VECTOR {self._scale}*{factor} {'(Skipped)' if self._skip else ''}"
|
656
|
+
)
|
657
|
+
if factor == 0:
|
658
|
+
raise ShxFontParseError("Multiply Vector is not permitted to be 0.")
|
659
|
+
if self._skip:
|
660
|
+
self._skip = False
|
661
|
+
return
|
662
|
+
self._scale *= factor
|
663
|
+
|
664
|
+
def _push_stack(self):
|
665
|
+
"""
|
666
|
+
Stack is considered four units deep. Everything pushed on the stack must be
|
667
|
+
popped from the stack. Overflows respond with an error message.
|
668
|
+
|
669
|
+
:return:
|
670
|
+
"""
|
671
|
+
if self._debug:
|
672
|
+
print(
|
673
|
+
f"PUSH_STACK {self._x}, {self._y} {'(Skipped)' if self._skip else ''}"
|
674
|
+
)
|
675
|
+
if self._skip:
|
676
|
+
self._skip = False
|
677
|
+
return
|
678
|
+
self._stack.append((self._x, self._y))
|
679
|
+
if len(self._stack) == 4:
|
680
|
+
raise IndexError(f"Position stack overflow in shape {self._letter}")
|
681
|
+
|
682
|
+
def _pop_stack(self):
|
683
|
+
"""
|
684
|
+
Stack is considered four units deep. You may not pop more locations than have
|
685
|
+
been pushed onto the stack. Attempts to do so will respond with an error message.
|
686
|
+
:return:
|
687
|
+
"""
|
688
|
+
if self._debug:
|
689
|
+
print(
|
690
|
+
f"POP_STACK {self._x}, {self._y} {'(Skipped)' if self._skip else ''}"
|
691
|
+
)
|
692
|
+
|
693
|
+
if self._skip:
|
694
|
+
self._skip = False
|
695
|
+
return
|
696
|
+
try:
|
697
|
+
self._x, self._y = self._stack.pop()
|
698
|
+
except IndexError:
|
699
|
+
raise IndexError(f"Position stack underflow in shape {self._letter}")
|
700
|
+
if self.active:
|
701
|
+
self._path.move(self._x, self._y)
|
702
|
+
self._last_x, self._last_y = self._x, self._y
|
703
|
+
|
704
|
+
def _draw_subshape_shapes(self):
|
705
|
+
try:
|
706
|
+
subshape = self.pop()
|
707
|
+
except ShxFontParseError:
|
708
|
+
return
|
709
|
+
if self._debug:
|
710
|
+
print(
|
711
|
+
f"Appending glyph {subshape} (Type={self.type}). {'(Skipped)' if self._skip else ''}"
|
712
|
+
)
|
713
|
+
if self._skip:
|
714
|
+
self._skip = False
|
715
|
+
return
|
716
|
+
try:
|
717
|
+
shape = self.glyphs[subshape]
|
718
|
+
except KeyError as e:
|
719
|
+
raise ShxFontParseError("Referenced subshape does not exist.") from e
|
720
|
+
self._code += bytearray(reversed(shape))
|
721
|
+
|
722
|
+
def _draw_subshape_bigfont(self):
|
723
|
+
try:
|
724
|
+
subshape = self.pop()
|
725
|
+
except ShxFontParseError:
|
726
|
+
return
|
727
|
+
if self._debug:
|
728
|
+
print(
|
729
|
+
f"Appending glyph {subshape} (Type={self.type}). {'(Skipped)' if self._skip else ''}"
|
730
|
+
)
|
731
|
+
if subshape == 0:
|
732
|
+
try:
|
733
|
+
subshape = int_16le([self.pop(), self.pop()])
|
734
|
+
|
735
|
+
origin_x = self.pop() * self._scale
|
736
|
+
origin_y = self.pop() * self._scale
|
737
|
+
width = self.pop() * self._scale
|
738
|
+
height = self.pop() * self._scale
|
739
|
+
if self._debug:
|
740
|
+
print(
|
741
|
+
f"Extended Bigfont Glyph: {subshape}, origin_x = {origin_x}, origin_y = {origin_y}. {width}x{height}"
|
742
|
+
)
|
743
|
+
except ShxFontParseError:
|
744
|
+
return
|
745
|
+
if self._skip:
|
746
|
+
self._skip = False
|
747
|
+
return
|
748
|
+
try:
|
749
|
+
shape = self.glyphs[subshape]
|
750
|
+
except KeyError as e:
|
751
|
+
raise ShxFontParseError("Referenced subshape does not exist.") from e
|
752
|
+
self._code += bytearray(reversed(shape))
|
753
|
+
|
754
|
+
def _draw_subshape_unifont(self):
|
755
|
+
try:
|
756
|
+
subshape = int_16le([self.pop(), self.pop()])
|
757
|
+
except ShxFontParseError:
|
758
|
+
return
|
759
|
+
|
760
|
+
if self._debug:
|
761
|
+
print(
|
762
|
+
f"Appending glyph {subshape} (Type={self.type}). {'(Skipped)' if self._skip else ''}"
|
763
|
+
)
|
764
|
+
if self._skip:
|
765
|
+
self._skip = False
|
766
|
+
return
|
767
|
+
try:
|
768
|
+
shape = self.glyphs[subshape]
|
769
|
+
except KeyError as e:
|
770
|
+
raise ShxFontParseError("Referenced subshape does not exist.") from e
|
771
|
+
self._code += bytearray(reversed(shape))
|
772
|
+
|
773
|
+
def _draw_subshape(self):
|
774
|
+
"""
|
775
|
+
Subshape is given in next byte, for non-unicode fonts one byte is used for
|
776
|
+
unicode fonts two bytes are used and shapes are numbered from 1 to 65535.
|
777
|
+
|
778
|
+
Drawmode is not reset for the subshape. When complete the current shape
|
779
|
+
continues.
|
780
|
+
:return:
|
781
|
+
"""
|
782
|
+
if self.type == "shapes":
|
783
|
+
self._draw_subshape_shapes()
|
784
|
+
elif self.type == "bigfont":
|
785
|
+
self._draw_subshape_bigfont()
|
786
|
+
elif self.type == "unifont":
|
787
|
+
self._draw_subshape_unifont()
|
788
|
+
|
789
|
+
def _xy_displacement(self):
|
790
|
+
"""
|
791
|
+
X,Y displacement given in next two bytes 1 byte-x, 1 byte-y. The displacement
|
792
|
+
ranges from -128 to +127.
|
793
|
+
:return:
|
794
|
+
"""
|
795
|
+
try:
|
796
|
+
dx = signed8(self.pop()) * self._scale
|
797
|
+
dy = signed8(self.pop()) * self._scale
|
798
|
+
except ShxFontParseError:
|
799
|
+
return
|
800
|
+
|
801
|
+
if self._debug:
|
802
|
+
print(f"XY_DISPLACEMENT {dx} {dy} {'(Skipped)' if self._skip else ''}")
|
803
|
+
if self._skip:
|
804
|
+
self._skip = False
|
805
|
+
return
|
806
|
+
self._x += dx
|
807
|
+
self._y += dy
|
808
|
+
if self.active:
|
809
|
+
if self._pen:
|
810
|
+
self._path.line(self._last_x, self._last_y, self._x, self._y)
|
811
|
+
else:
|
812
|
+
self._path.move(self._x, self._y)
|
813
|
+
self._last_x, self._last_y = self._x, self._y
|
814
|
+
|
815
|
+
def _poly_xy_displacement(self):
|
816
|
+
"""
|
817
|
+
XY displacement in a series terminated with (0,0)
|
818
|
+
:return:
|
819
|
+
"""
|
820
|
+
while True:
|
821
|
+
try:
|
822
|
+
dx = signed8(self.pop()) * self._scale
|
823
|
+
dy = signed8(self.pop()) * self._scale
|
824
|
+
except ShxFontParseError:
|
825
|
+
return
|
826
|
+
if self._debug:
|
827
|
+
print(
|
828
|
+
f"POLY_XY_DISPLACEMENT {dx} {dy} {'(Skipped)' if self._skip else ''}"
|
829
|
+
)
|
830
|
+
if dx == 0 and dy == 0:
|
831
|
+
if self._debug:
|
832
|
+
print("POLY_XY_DISPLACEMENT (Terminated)")
|
833
|
+
break
|
834
|
+
if self._skip:
|
835
|
+
continue
|
836
|
+
self._x += dx
|
837
|
+
self._y += dy
|
838
|
+
if self.active:
|
839
|
+
if self._pen:
|
840
|
+
self._path.line(self._last_x, self._last_y, self._x, self._y)
|
841
|
+
else:
|
842
|
+
self._path.move(self._x, self._y)
|
843
|
+
self._last_x, self._last_y = self._x, self._y
|
844
|
+
if self._skip:
|
845
|
+
self._skip = False
|
846
|
+
|
847
|
+
def _octant_arc(self):
|
848
|
+
"""
|
849
|
+
Octant arc spans one or more 45° octants starting and ending at a boundary.
|
850
|
+
Octants are numbered ccw starting from 0° at the 3 o'clock position.
|
851
|
+
|
852
|
+
3 2 1
|
853
|
+
⍀ /
|
854
|
+
4-O-0
|
855
|
+
/ \
|
856
|
+
5 6 7
|
857
|
+
|
858
|
+
First byte specifies the radius as a value from 1 to 255. The second is the
|
859
|
+
direction of the arc. Each nibble of the second byte defines s and c the start
|
860
|
+
and the span.
|
861
|
+
:return:
|
862
|
+
"""
|
863
|
+
try:
|
864
|
+
radius = self.pop() * self._scale
|
865
|
+
sc = signed8(self.pop())
|
866
|
+
except ShxFontParseError:
|
867
|
+
return
|
868
|
+
s = (sc >> 4) & 0x7
|
869
|
+
c = sc & 0x7
|
870
|
+
if self._debug:
|
871
|
+
print(f"OCTANT_ARC, {radius}, {s}, {c} {'(Skipped)' if self._skip else ''}")
|
872
|
+
if self._skip:
|
873
|
+
self._skip = False
|
874
|
+
return
|
875
|
+
octant = tau / 8.0
|
876
|
+
ccw = (sc >> 7) & 1
|
877
|
+
if c == 0:
|
878
|
+
c = 8
|
879
|
+
if ccw:
|
880
|
+
s = -s
|
881
|
+
start_angle = s * octant
|
882
|
+
end_angle = (c + s) * octant
|
883
|
+
mid_angle = (start_angle + end_angle) / 2
|
884
|
+
# negative radius in the direction of start_octent finds center.
|
885
|
+
cx = self._x - radius * cos(start_angle)
|
886
|
+
cy = self._y - radius * sin(start_angle)
|
887
|
+
mx = cx + radius * cos(mid_angle)
|
888
|
+
my = cy + radius * sin(mid_angle)
|
889
|
+
self._x = cx + radius * cos(end_angle)
|
890
|
+
self._y = cy + radius * sin(end_angle)
|
891
|
+
if self.active:
|
892
|
+
if self._pen:
|
893
|
+
self._path.arc(self._last_x, self._last_y, mx, my, self._x, self._y)
|
894
|
+
else:
|
895
|
+
self._path.move(self._x, self._y)
|
896
|
+
self._last_x, self._last_y = self._x, self._y
|
897
|
+
|
898
|
+
def _fractional_arc(self):
|
899
|
+
"""
|
900
|
+
Fractional Arc.
|
901
|
+
Octant Arc plus fractional bits 0-255 parts of 45°
|
902
|
+
55° -> (55 - 45) * (256 / 45) = 56 (octent 1)
|
903
|
+
45° + (56/256 * 45°) = 55°
|
904
|
+
95° -> (95 - 90) * (256 / 45) = 28 (octent 2)
|
905
|
+
90° + (28/256 * 45°) = 95°
|
906
|
+
"""
|
907
|
+
octant = tau / 8.0
|
908
|
+
try:
|
909
|
+
start_offset = octant * self.pop() / 256.0
|
910
|
+
end_offset = octant * self.pop() / 256.0
|
911
|
+
radius = (256 * self.pop() + self.pop()) * self._scale
|
912
|
+
sc = signed8(self.pop())
|
913
|
+
s = (sc >> 4) & 0x7
|
914
|
+
c = sc & 0x7
|
915
|
+
except ShxFontParseError:
|
916
|
+
return
|
917
|
+
if self._debug:
|
918
|
+
print(
|
919
|
+
f"FRACTION_ARC {start_offset}, {end_offset}, {radius}, {s}, {c} {'(Skipped)' if self._skip else ''}"
|
920
|
+
)
|
921
|
+
if self._skip:
|
922
|
+
self._skip = False
|
923
|
+
return
|
924
|
+
ccw = (sc >> 7) & 1
|
925
|
+
|
926
|
+
if c == 0:
|
927
|
+
c = 8
|
928
|
+
if ccw:
|
929
|
+
s = -s
|
930
|
+
start_angle = start_offset + (s * octant)
|
931
|
+
end_angle = (c + s) * octant + end_offset
|
932
|
+
mid_angle = (start_angle + end_angle) / 2
|
933
|
+
cx = self._x - radius * cos(start_angle)
|
934
|
+
cy = self._y - radius * sin(start_angle)
|
935
|
+
mx = cx + radius * cos(mid_angle)
|
936
|
+
my = cy + radius * sin(mid_angle)
|
937
|
+
self._x = cx + radius * cos(end_angle)
|
938
|
+
self._y = cy + radius * sin(end_angle)
|
939
|
+
if self.active:
|
940
|
+
if self._pen:
|
941
|
+
self._path.arc(self._last_x, self._last_y, mx, my, self._x, self._y)
|
942
|
+
else:
|
943
|
+
self._path.move(self._x, self._y)
|
944
|
+
self._last_x, self._last_y = self._x, self._y
|
945
|
+
|
946
|
+
def _bulge_arc(self):
|
947
|
+
"""
|
948
|
+
Arc defined by xy and displacement bulge. 1-byte-X, 1-byte-Y, 1-byte-bulge.
|
949
|
+
|
950
|
+
This gives us X from -127 to +127 and Y from -127 to +127. The bulge height
|
951
|
+
is given as 127 * 2 * H / D If the sign is negative the location is clockwise.
|
952
|
+
|
953
|
+
:return:
|
954
|
+
"""
|
955
|
+
try:
|
956
|
+
dx = signed8(self.pop()) * self._scale
|
957
|
+
dy = signed8(self.pop()) * self._scale
|
958
|
+
h = signed8(self.pop())
|
959
|
+
except ShxFontParseError:
|
960
|
+
return
|
961
|
+
|
962
|
+
if self._debug:
|
963
|
+
print(f"BULGE_ARC {dx}, {dy}, {h} {'(Skipped)' if self._skip else ''}")
|
964
|
+
if self._skip:
|
965
|
+
self._skip = False
|
966
|
+
return
|
967
|
+
r = abs(complex(dx, dy)) / 2
|
968
|
+
bulge = h / 127.0
|
969
|
+
bx = self._x + (dx / 2)
|
970
|
+
by = self._y + (dy / 2)
|
971
|
+
bulge_angle = atan2(dy, dx) - tau / 4
|
972
|
+
mx = bx + r * bulge * cos(bulge_angle)
|
973
|
+
my = by + r * bulge * sin(bulge_angle)
|
974
|
+
self._x += dx
|
975
|
+
self._y += dy
|
976
|
+
if self.active:
|
977
|
+
if self._pen:
|
978
|
+
if bulge == 0:
|
979
|
+
self._path.line(self._last_x, self._last_y, self._x, self._y)
|
980
|
+
else:
|
981
|
+
self._path.arc(self._last_x, self._last_y, mx, my, self._x, self._y)
|
982
|
+
else:
|
983
|
+
self._path.move(self._x, self._y)
|
984
|
+
self._last_x, self._last_y = self._x, self._y
|
985
|
+
|
986
|
+
def _poly_bulge_arc(self):
|
987
|
+
"""
|
988
|
+
Similar to bulge but repeated, until X and Y are (0,0).
|
989
|
+
:return:
|
990
|
+
"""
|
991
|
+
h = 0
|
992
|
+
while True:
|
993
|
+
try:
|
994
|
+
dx = signed8(self.pop()) * self._scale
|
995
|
+
dy = signed8(self.pop()) * self._scale
|
996
|
+
except ShxFontParseError:
|
997
|
+
return
|
998
|
+
|
999
|
+
if self._debug:
|
1000
|
+
print(
|
1001
|
+
f"POLY_BULGE_ARC {dx}, {dy}, {h} {'(Skipped)' if self._skip else ''}"
|
1002
|
+
)
|
1003
|
+
if dx == 0 and dy == 0:
|
1004
|
+
if self._debug:
|
1005
|
+
print(f"POLY_BULGE_ARC (TERMINATED)")
|
1006
|
+
break
|
1007
|
+
try:
|
1008
|
+
h = signed8(self.pop())
|
1009
|
+
except ShxFontParseError:
|
1010
|
+
return
|
1011
|
+
if self._skip:
|
1012
|
+
continue
|
1013
|
+
r = abs(complex(dx, dy)) / 2
|
1014
|
+
bulge = h / 127.0
|
1015
|
+
bx = self._x + (dx / 2)
|
1016
|
+
by = self._y + (dy / 2)
|
1017
|
+
bulge_angle = atan2(dy, dx) - tau / 4
|
1018
|
+
mx = bx + r * bulge * cos(bulge_angle)
|
1019
|
+
my = by + r * bulge * sin(bulge_angle)
|
1020
|
+
self._x += dx
|
1021
|
+
self._y += dy
|
1022
|
+
if self.active:
|
1023
|
+
if self._pen:
|
1024
|
+
if bulge == 0:
|
1025
|
+
self._path.line(self._last_x, self._last_y, self._x, self._y)
|
1026
|
+
else:
|
1027
|
+
self._path.arc(
|
1028
|
+
self._last_x, self._last_y, mx, my, self._x, self._y
|
1029
|
+
)
|
1030
|
+
else:
|
1031
|
+
self._path.move(self._x, self._y)
|
1032
|
+
self._last_x, self._last_y = self._x, self._y
|
1033
|
+
if self._skip:
|
1034
|
+
self._skip = False
|
1035
|
+
|
1036
|
+
def _cond_mode_2(self):
|
1037
|
+
"""
|
1038
|
+
Process the next command only in vertical text.
|
1039
|
+
|
1040
|
+
:return:
|
1041
|
+
"""
|
1042
|
+
if self._debug:
|
1043
|
+
print("COND_MODE_2")
|
1044
|
+
if self.modes == 2 and self._horizontal:
|
1045
|
+
if self._debug:
|
1046
|
+
print("SKIP NEXT")
|
1047
|
+
self._skip = True
|