From 755efa31c408480231ec90d701e0ad6c2129a60f Mon Sep 17 00:00:00 2001 From: Simon Waldherr Date: Sun, 16 Jun 2024 15:35:26 +0200 Subject: [PATCH] refactor and gh-actions --- .github/workflows/go.yml | 28 ++++++ cmd/zplgfa/main.go | 204 +++++++++++++++++++-------------------- example_test.go | 2 +- go.mod | 8 ++ go.sum | 35 +++++++ tests/tests.json | 22 ++--- zplgfa.go | 144 +++++++++++---------------- zplgfa_test.go | 109 +++++++++++---------- 8 files changed, 294 insertions(+), 258 deletions(-) create mode 100644 .github/workflows/go.yml create mode 100644 go.mod create mode 100644 go.sum diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..0cca3b8 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Build + run: go build -v ./cmd/zplgfa + + - name: Test + run: go test -v ./... diff --git a/cmd/zplgfa/main.go b/cmd/zplgfa/main.go index d174b85..cdc8e05 100644 --- a/cmd/zplgfa/main.go +++ b/cmd/zplgfa/main.go @@ -19,154 +19,146 @@ import ( "simonwaldherr.de/go/zplgfa" ) -func specialCmds(zebraCmdFlag, networkIpFlag, networkPortFlag string) bool { - var cmdSent bool - if networkIpFlag == "" { - return cmdSent - } - if strings.Contains(zebraCmdFlag, "cancel") { - if err := sendCancelCmdToZebra(networkIpFlag, networkPortFlag); err == nil { - cmdSent = true - } - } - if strings.Contains(zebraCmdFlag, "calib") { - if err := sendCalibCmdToZebra(networkIpFlag, networkPortFlag); err == nil { - cmdSent = true - } +func handleZebraCommands(cmd, ip, port string) bool { + if ip == "" { + return false } - if strings.Contains(zebraCmdFlag, "feed") { - if err := sendFeedCmdToZebra(networkIpFlag, networkPortFlag); err == nil { - cmdSent = true - } + + // Define a map of command strings to functions that return error only + cmdActionsErr := map[string]func(string, string) error{ + "cancel": sendCancelCmdToZebra, + "calib": sendCalibCmdToZebra, + "feed": sendFeedCmdToZebra, } - if strings.Contains(zebraCmdFlag, "info") { - info, err := getInfoFromZebra(networkIpFlag, networkPortFlag) - if err == nil { - fmt.Println(info) - cmdSent = true - } + + // Define a map of command strings to functions that return (string, error) + cmdActionsStr := map[string]func(string, string) (string, error){ + "info": getInfoFromZebra, + "config": getConfigFromZebra, + "diag": getDiagFromZebra, } - if strings.Contains(zebraCmdFlag, "config") { - info, err := getConfigFromZebra(networkIpFlag, networkPortFlag) - if err == nil { - fmt.Println(info) - cmdSent = true + + for key, action := range cmdActionsErr { + if strings.Contains(cmd, key) { + if err := action(ip, port); err == nil { + return true + } } } - if strings.Contains(zebraCmdFlag, "diag") { - info, err := getDiagFromZebra(networkIpFlag, networkPortFlag) - if err == nil { - fmt.Println(info) - cmdSent = true + + for key, action := range cmdActionsStr { + if strings.Contains(cmd, key) { + result, err := action(ip, port) + if err == nil { + fmt.Println(result) + return true + } } } - return cmdSent + + return false } -func main() { - var filenameFlag string - var zebraCmdFlag string - var graphicTypeFlag string - var imageEditFlag string - var networkIpFlag string - var networkPortFlag string - var imageResizeFlag float64 - var graphicType zplgfa.GraphicType - - flag.StringVar(&filenameFlag, "file", "", "filename to convert to zpl") - flag.StringVar(&zebraCmdFlag, "cmd", "", "send special command to printer [cancel,calib,feed,info,config,diag]") - flag.StringVar(&graphicTypeFlag, "type", "CompressedASCII", "type of graphic field encoding") - flag.StringVar(&imageEditFlag, "edit", "", "manipulate the image [invert,monochrome]") - flag.StringVar(&networkIpFlag, "ip", "", "send zpl to printer") - flag.StringVar(&networkPortFlag, "port", "9100", "network port of printer") - flag.Float64Var(&imageResizeFlag, "resize", 1.0, "zoom/resize the image") - - // load flag input arguments - flag.Parse() +func parseFlags() (string, string, string, string, string, string, float64) { + var filename, zebraCmd, graphicType, imageEdit, ip, port string + var resizeFactor float64 - // send special commands to printer - cmdSent := specialCmds(zebraCmdFlag, networkIpFlag, networkPortFlag) + flag.StringVar(&filename, "file", "", "filename to convert to zpl") + flag.StringVar(&zebraCmd, "cmd", "", "send special command to printer [cancel,calib,feed,info,config,diag]") + flag.StringVar(&graphicType, "type", "CompressedASCII", "type of graphic field encoding") + flag.StringVar(&imageEdit, "edit", "", "manipulate the image [invert,monochrome]") + flag.StringVar(&ip, "ip", "", "send zpl to printer") + flag.StringVar(&port, "port", "9100", "network port of printer") + flag.Float64Var(&resizeFactor, "resize", 1.0, "zoom/resize the image") - // check input parameter - if filenameFlag == "" { - if cmdSent { - return - } - log.Printf("Warning: no input file specified\n") - return - } + flag.Parse() + return filename, zebraCmd, graphicType, imageEdit, ip, port, resizeFactor +} - // open file - file, err := os.Open(filenameFlag) +func openImageFile(filename string) (image.Image, image.Config, error) { + file, err := os.Open(filename) if err != nil { - log.Printf("Warning: could not open the file \"%s\": %s\n", filenameFlag, err) - return + return nil, image.Config{}, fmt.Errorf("could not open the file \"%s\": %s", filename, err) } - - // close file when complete defer file.Close() - // load image head information config, format, err := image.DecodeConfig(file) if err != nil { - log.Printf("Warning: image not compatible, format: %s, config: %v, error: %s\n", format, config, err) + return nil, config, fmt.Errorf("image not compatible, format: %s, config: %v, error: %s", format, config, err) } - // reset file pointer to the beginning of the file file.Seek(0, 0) - - // load and decode image img, _, err := image.Decode(file) if err != nil { - log.Printf("Warning: could not decode the file, %s\n", err) - return + return nil, config, fmt.Errorf("could not decode the file, %s", err) } - // select graphic field type - switch strings.ToUpper(graphicTypeFlag) { - case "ASCII": - graphicType = zplgfa.ASCII - case "BINARY": - graphicType = zplgfa.Binary - case "COMPRESSEDASCII": - graphicType = zplgfa.CompressedASCII - default: - graphicType = zplgfa.CompressedASCII - } + return img, config, nil +} - // apply image manipulation functions - if strings.Contains(imageEditFlag, "monochrome") { +func processImage(img image.Image, editFlag string, resizeFactor float64, config image.Config) image.Image { + if strings.Contains(editFlag, "monochrome") { img = editImageMonochrome(img) } - if strings.Contains(imageEditFlag, "blur") { + if strings.Contains(editFlag, "blur") { img = blur.Gaussian(img, float64(config.Width)/300) } - if strings.Contains(imageEditFlag, "edge") { + if strings.Contains(editFlag, "edge") { img = effect.Sobel(img) } - if strings.Contains(imageEditFlag, "segment") { + if strings.Contains(editFlag, "segment") { img = segment.Threshold(img, 128) } - if strings.Contains(imageEditFlag, "invert") { + if strings.Contains(editFlag, "invert") { img = editImageInvert(img) } - // resize image - if imageResizeFlag != 1.0 { - img = resize.Resize(uint(float64(config.Width)*imageResizeFlag), uint(float64(config.Height)*imageResizeFlag), img, resize.MitchellNetravali) + if resizeFactor != 1.0 { + img = resize.Resize(uint(float64(config.Width)*resizeFactor), uint(float64(config.Height)*resizeFactor), img, resize.MitchellNetravali) } - // flatten image - flat := zplgfa.FlattenImage(img) + return img +} + +func getGraphicType(typeFlag string) zplgfa.GraphicType { + switch strings.ToUpper(typeFlag) { + case "ASCII": + return zplgfa.ASCII + case "BINARY": + return zplgfa.Binary + case "COMPRESSEDASCII": + return zplgfa.CompressedASCII + default: + return zplgfa.CompressedASCII + } +} + +func main() { + filename, zebraCmd, graphicTypeFlag, imageEdit, ip, port, resizeFactor := parseFlags() - // convert image to zpl compatible type - gfimg := zplgfa.ConvertToZPL(flat, graphicType) + if handleZebraCommands(zebraCmd, ip, port) && filename == "" { + return + } + + if filename == "" { + log.Printf("Warning: no input file specified\n") + return + } + + img, config, err := openImageFile(filename) + if err != nil { + log.Printf("Warning: %s\n", err) + return + } + + img = processImage(img, imageEdit, resizeFactor, config) + + flat := zplgfa.FlattenImage(img) + gfimg := zplgfa.ConvertToZPL(flat, getGraphicType(graphicTypeFlag)) - if networkIpFlag != "" { - // send zpl to printer - sendDataToZebra(networkIpFlag, networkPortFlag, gfimg) + if ip != "" { + sendDataToZebra(ip, port, gfimg) } else { - // output zpl with graphic field data to stdout fmt.Println(gfimg) } } diff --git a/example_test.go b/example_test.go index b5044c4..3f0704f 100644 --- a/example_test.go +++ b/example_test.go @@ -52,5 +52,5 @@ func ExampleConvertToZPL() { fmt.Println(zplstr) - // Output: ^XA,^FS^FO0,0^GFA,52,51,3,FFFF00::FE3F00::FFFF00FFE300::FFFF00E22300::FFFF00::^FS,^XZ + // Output: ^XA,^FS^FO0,0^GFA,52,51,3,FFFF80::FE3F80::FFFF80FFE380::FFFF80E22380::FFFF80::^FS,^XZ } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5e2387a --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module simonwaldherr.de/go/zplgfa + +go 1.22 + +require ( + github.com/anthonynsimon/bild v0.13.0 + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c771e92 --- /dev/null +++ b/go.sum @@ -0,0 +1,35 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/anthonynsimon/bild v0.13.0 h1:mN3tMaNds1wBWi1BrJq0ipDBhpkooYfu7ZFSMhXt1C8= +github.com/anthonynsimon/bild v0.13.0/go.mod h1:tpzzp0aYkAsMi1zmfhimaDyX1xjn2OUc1AJZK/TF0AE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/tests/tests.json b/tests/tests.json index 609001d..a7666a6 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -1,17 +1,17 @@ [ { "filename": "./tests/test.png", - "zplstring": "^XA,^FS^FO0,0^GFA,32,51,3,,::01C000::,001C00::,1DDC00::,::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,36,51,3,L0::01C000::L0001C00::L01DDC00::L0::^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test2.png", - "zplstring": "^XA,^FS^FO0,0^GFA,389,630,63,,038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038000::1C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C7100::E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00:lJF00^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,391,630,63,lL0038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038038000::1C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C71C0::E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00E00:lJFC0^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test3.png", - "zplstring": "^XA,^FS^FO0,0^GFA,4911,45000,75,LFCmI0LFEmI0MFmI0MF8mH0MFCmH0MFEmH0NFmH0NF8mG0NFCmG0NFEmG0OFmG0OF8m0OFCm0OFEm0PFm0PF8lY0PFClY0PFElY0QFlY0QF8lX0QFClX0QFElX0RFlX0RF8lW0RFClW0RFElW0SFlW0SF8lV0SFClV0SFElV07SFlV03SF8lU01SFClU00SFElU007SFlU003SF8lT001SFClT000SFElT0007SFlT0003SF8lS0001SFClS0000SFElS00007SFlS00003SF8lR00001SFClR00000SFElR000007SFlR000003SF8lQ000001SFClQ0K0SFElQ0K07SFlQ0K03SF8lP0K01SFClP0L0SFElP0L07SFlP0L03SF8lO0L01SFClO0M0SFElO0M07SFlO0M03SF8lN0M01SFClN0N0SFElN0N07SFlN0N03SF8lM0N01SFClM0O0SFElM0O07SFlM0O03SF8lL0O01SFClL0P0SFElL0P07SFlL0P03SF8lK0P01SFClK0Q0SFElK0Q07SFlK0Q03SF8lJ0Q01SFClJ0R0SFElJ0R07SFlJ0R03SF8lI0R01SFClI0S0SFElI0S07SFlI0S03SF8lH0S01SFClH0T0SFElH0T07SFlH0T03SF8lG0T01SFClG0U0SFElG0U07SFlG0U03SF8l0U01SFCl0V0SFEl0V07SFl0V03SF8kY0V01SFCkY0W0SFEkY0W07SFkY0W03SF8kX0W01SFCkX0X0SFEkX0X07SFkX0X03SF8kW0X01SFCkW0Y0SFEkW0Y07SFkW0Y03SF8kV0Y01SFCkV0g0SFEkV0g07SFkV0g03SF8kU0g01SFCkU0gG0SFEkU0gG07SFkU0gG03SF8kT0gG01SFCkT0gH0SFEkT0gH07SFkT0gH03SF8kS0gH01SFCkS0gI0SFEkS0gI07SFkS0gI03SF8kR0gI01SFCkR0gJ0SFEkR0gJ07SFkR0gJ03SF8kQ0gJ01SFCkQ0gK0SFEkQ0gK07SFkQ0gK03SF8kP0gK01SFCkP0gL0SFEkP0gL07SFkP0gL03SF8kO0gL01SFCkO0gM0SFEkO0gM07SFkO0gM03SF8kN0gM01SFCkN0gN0SFEkN0gN07SFkN0gN03SF8kM0gN01SFCkM0gO0SFEkM0gO07SFkM0gO03SF8kL0gO01SFCkL0gP0SFEkL0gP07SFkL0gP03SF8kK0gP01SFCkK0gQ0SFEkK0gQ07SFkK0gQ03SF8kJ0gQ01SFCkJ0gR0SFEkJ0gR07SFkJ0gR03SF8kI0gR01SFCkI0gS0SFEkI0gS07SFkI0gS03SF8kH0gS01SFCkH0gT0SFEkH0gT07SFkH0gT03SF8kG0gT01SFCkG0gU0SFEkG0gU07SFkG0gU03SF8k0gU01SFCk0gV0SFEk0gV07SFk0gV03SF8jY0gV01SFCjY0gW0SFEjY0gW07SFjY0gW03SF8jX0gW01SFCjX0gX0SFEjX0gX07SFjX0gX03SF8jW0gX01SFCjW0gY0SFEjW0gY07SFjW0gY03SF8jV0gY01SFCjV0h0SFEjV0h07SFjV0h03SF8jU0h01SFCjU0hG0SFEjU0hG07SFjU0hG03SF8jT0hG01SFCjT0hH0SFEjT0hH07SFjT0hH03SF8jS0hH01SFCjS0hI0SFEjS0hI07SFjS0hI03SF8jR0hI01SFCjR0hJ0SFEjR0hJ07SFjR0hJ03SF8jQ0hJ01SFCjQ0hK0SFEjQ0hK07SFjQ0hK03SF8jP0hK01SFCjP0hL0SFEjP0hL07SFjP0hL03SF8jO0hL01SFCjO0hM0SFEjO0hM07SFjO0hM03SF8jN0hM01SFCjN0hN0SFEjN0hN07SFjN0hN03SF8jM0hN01SFCjM0hO0SFEjM0hO07SFjM0hO03SF8jL0hO01SFCjL0hP0SFEjL0hP07SFjL0hP03SF8jK0hP01SFCjK0hQ0SFEjK0hQ07SFjK0hQ03SF8jJ0hQ01SFCjJ0hR0SFEjJ0hR07SFjJ0hR03SF8jI0hR01SFCjI0hS0SFEjI0hS07SFjI0hS03SF8jH0hS01SFCjH0hT0SFEjH0hT07SFjH0hT03SF8jG0hT01SFCjG0hU0SFEjG0hU07SFjG0hU03SF8j0hU01SFCj0hV0SFEj0hV07SFj0hV03SFj0hV01SFj0hW0SFj0hW07RFj0hW03RFj0hW01RFj0hX0RFj0hX07QFj0hX03QFj0hX01QFj0hY0QFj0hY07PFj0hY03PFj0hY01PFj0i0PFj0i07OFj0i03OFj0i01OFj0iG0OFj0iG07NFj0iG03NFj0iG01NFj0iH0NFj0iH07MFj0iH03MFj0iH01MFj0,::::::::::::::::::::::::::::::::::::::iY01LFCiI0iY01LFEiI0iY01MFiI0iY01MF8iH0iY01MFCiH0iY01MFEiH0iY01NFiH0iY01NF8iG0iY01NFCiG0iY01NFEiG0iY01OFiG0iY01OF8i0iY01OFCi0iY01OFEi0iY01PFi0iY01PF8hY0iY01PFChY0iY01PFEhY0iY01QFhY0iY01QF8hX0iY01QFChX0iY01QFEhX0iY01RFhX0iY01RF8hW0iY01RFChW0iY01RFEhW0iY01SFhW0iY01SF8hV0iY01SFChV0j0SFEhV0j07SFhV0j03SF8hU0j01SFChU0jG0SFEhU0jG07SFhU0jG03SF8hT0jG01SFChT0jH0SFEhT0jH07SFhT0jH03SF8hS0jH01SFChS0jI0SFEhS0jI07SFhS0jI03SF8hR0jI01SFChR0jJ0SFEhR0jJ07SFhR0jJ03SF8hQ0jJ01SFChQ0jK0SFEhQ0jK07SFhQ0jK03SF8hP0jK01SFChP0jL0SFEhP0jL07SFhP0jL03SF8hO0jL01SFChO0jM0SFEhO0jM07SFhO0jM03SF8hN0jM01SFChN0jN0SFEhN0jN07SFhN0jN03SF8hM0jN01SFChM0jO0SFEhM0jO07SFhM0jO03SF8hL0jO01SFChL0jP0SFEhL0jP07SFhL0jP03SF8hK0jP01SFChK0jQ0SFEhK0jQ07SFhK0jQ03SF8hJ0jQ01SFChJ0jR0SFEhJ0jR07SFhJ0jR03SF8hI0jR01SFChI0jS0SFEhI0jS07SFhI0jS03SF8hH0jS01SFChH0jT0SFEhH0jT07SFhH0jT03SF8hG0jT01SFChG0jU0SFEhG0jU07SFhG0jU03SF8h0jU01SFCh0jV0SFEh0jV07SFh0jV03SF8gY0jV01SFCgY0jW0SFEgY0jW07SFgY0jW03SF8gX0jW01SFCgX0jX0SFEgX0jX07SFgX0jX03SF8gW0jX01SFCgW0jY0SFEgW0jY07SFgW0jY03SF8gV0jY01SFCgV0k0SFEgV0k07SFgV0k03SF8gU0k01SFCgU0kG0SFEgU0kG07SFgU0kG03SF8gT0kG01SFCgT0kH0SFEgT0kH07SFgT0kH03SF8gS0kH01SFCgS0kI0SFEgS0kI07SFgS0kI03SF8gR0kI01SFCgR0kJ0SFEgR0kJ07SFgR0kJ03SF8gQ0kJ01SFCgQ0kK0SFEgQ0kK07SFgQ0kK03SF8gP0kK01SFCgP0kL0SFEgP0kL07SFgP0kL03SF8gO0kL01SFCgO0kM0SFEgO0kM07SFgO0kM03SF8gN0kM01SFCgN0kN0SFEgN0kN07SFgN0kN03SF8gM0kN01SFCgM0kO0SFEgM0kO07SFgM0kO03SF8gL0kO01SFCgL0kP0SFEgL0kP07SFgL0kP03SF8gK0kP01SFCgK0kQ0SFEgK0kQ07SFgK0kQ03SF8gJ0kQ01SFCgJ0kR0SFEgJ0kR07SFgJ0kR03SF8gI0kR01SFCgI0kS0SFEgI0kS07SFgI0kS03SF8gH0kS01SFCgH0kT0SFEgH0kT07SFgH0kT03SF8gG0kT01SFCgG0kU0SFEgG0kU07SFgG0kU03SF8g0kU01SFCg0kV0SFEg0kV07SFg0kV03SF8Y0kV01SFCY0kW0SFEY0kW07SFY0kW03SF8X0kW01SFCX0kX0SFEX0kX07SFX0kX03SF8W0kX01SFCW0kY0SFEW0kY07SFW0kY03SF8V0kY01SFCV0l0SFEV0l07SFV0l03SF8U0l01SFCU0lG0SFEU0lG07SFU0lG03SF8T0lG01SFCT0lH0SFET0lH07SFT0lH03SF8S0lH01SFCS0lI0SFES0lI07SFS0lI03SF8R0lI01SFCR0lJ0SFER0lJ07SFR0lJ03SF8Q0lJ01SFCQ0lK0SFEQ0lK07SFQ0lK03SF8P0lK01SFCP0lL0SFEP0lL07SFP0lL03SF8O0lL01SFCO0lM0SFEO0lM07SFO0lM03SF8N0lM01SFCN0lN0SFEN0lN07SFN0lN03SF8M0lN01SFCM0lO0SFEM0lO07SFM0lO03SF8L0lO01SFCL0lP0SFEL0lP07SFL0lP03SF8K0lP01SFCK0lQ0SFEK0lQ07SFK0lQ03SF80000lQ01SFC0000lR0SFE0000lR07SF0000lR03SF8000lR01SFC000lS0SFE000lS07SF000lS03SF800lS01SFC00lT0SFE00lT07SF00lT03SF80lT01SFC0lU0SFE0lU07SF0lU03SF8lU01SFClV0SFElV07SFlV03SFlV01SFlW0SFlW07RFlW03RFlW01RFlX0RFlX07QFlX03QFlX01QFlY0QFlY07PFlY03PFlY01PFm0PFm07OFm03OFm01OFmG0OFmG07NFmG03NFmG01NFmH0NFmH07MFmH03MF^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,4913,45000,75,LFCmI0LFEmI0MFmI0MF8mH0MFCmH0MFEmH0NFmH0NF8mG0NFCmG0NFEmG0OFmG0OF8m0OFCm0OFEm0PFm0PF8lY0PFClY0PFElY0QFlY0QF8lX0QFClX0QFElX0RFlX0RF8lW0RFClW0RFElW0SFlW0SF8lV0SFClV0SFElV07SFlV03SF8lU01SFClU00SFElU007SFlU003SF8lT001SFClT000SFElT0007SFlT0003SF8lS0001SFClS0000SFElS00007SFlS00003SF8lR00001SFClR00000SFElR000007SFlR000003SF8lQ000001SFClQ0K0SFElQ0K07SFlQ0K03SF8lP0K01SFClP0L0SFElP0L07SFlP0L03SF8lO0L01SFClO0M0SFElO0M07SFlO0M03SF8lN0M01SFClN0N0SFElN0N07SFlN0N03SF8lM0N01SFClM0O0SFElM0O07SFlM0O03SF8lL0O01SFClL0P0SFElL0P07SFlL0P03SF8lK0P01SFClK0Q0SFElK0Q07SFlK0Q03SF8lJ0Q01SFClJ0R0SFElJ0R07SFlJ0R03SF8lI0R01SFClI0S0SFElI0S07SFlI0S03SF8lH0S01SFClH0T0SFElH0T07SFlH0T03SF8lG0T01SFClG0U0SFElG0U07SFlG0U03SF8l0U01SFCl0V0SFEl0V07SFl0V03SF8kY0V01SFCkY0W0SFEkY0W07SFkY0W03SF8kX0W01SFCkX0X0SFEkX0X07SFkX0X03SF8kW0X01SFCkW0Y0SFEkW0Y07SFkW0Y03SF8kV0Y01SFCkV0g0SFEkV0g07SFkV0g03SF8kU0g01SFCkU0gG0SFEkU0gG07SFkU0gG03SF8kT0gG01SFCkT0gH0SFEkT0gH07SFkT0gH03SF8kS0gH01SFCkS0gI0SFEkS0gI07SFkS0gI03SF8kR0gI01SFCkR0gJ0SFEkR0gJ07SFkR0gJ03SF8kQ0gJ01SFCkQ0gK0SFEkQ0gK07SFkQ0gK03SF8kP0gK01SFCkP0gL0SFEkP0gL07SFkP0gL03SF8kO0gL01SFCkO0gM0SFEkO0gM07SFkO0gM03SF8kN0gM01SFCkN0gN0SFEkN0gN07SFkN0gN03SF8kM0gN01SFCkM0gO0SFEkM0gO07SFkM0gO03SF8kL0gO01SFCkL0gP0SFEkL0gP07SFkL0gP03SF8kK0gP01SFCkK0gQ0SFEkK0gQ07SFkK0gQ03SF8kJ0gQ01SFCkJ0gR0SFEkJ0gR07SFkJ0gR03SF8kI0gR01SFCkI0gS0SFEkI0gS07SFkI0gS03SF8kH0gS01SFCkH0gT0SFEkH0gT07SFkH0gT03SF8kG0gT01SFCkG0gU0SFEkG0gU07SFkG0gU03SF8k0gU01SFCk0gV0SFEk0gV07SFk0gV03SF8jY0gV01SFCjY0gW0SFEjY0gW07SFjY0gW03SF8jX0gW01SFCjX0gX0SFEjX0gX07SFjX0gX03SF8jW0gX01SFCjW0gY0SFEjW0gY07SFjW0gY03SF8jV0gY01SFCjV0h0SFEjV0h07SFjV0h03SF8jU0h01SFCjU0hG0SFEjU0hG07SFjU0hG03SF8jT0hG01SFCjT0hH0SFEjT0hH07SFjT0hH03SF8jS0hH01SFCjS0hI0SFEjS0hI07SFjS0hI03SF8jR0hI01SFCjR0hJ0SFEjR0hJ07SFjR0hJ03SF8jQ0hJ01SFCjQ0hK0SFEjQ0hK07SFjQ0hK03SF8jP0hK01SFCjP0hL0SFEjP0hL07SFjP0hL03SF8jO0hL01SFCjO0hM0SFEjO0hM07SFjO0hM03SF8jN0hM01SFCjN0hN0SFEjN0hN07SFjN0hN03SF8jM0hN01SFCjM0hO0SFEjM0hO07SFjM0hO03SF8jL0hO01SFCjL0hP0SFEjL0hP07SFjL0hP03SF8jK0hP01SFCjK0hQ0SFEjK0hQ07SFjK0hQ03SF8jJ0hQ01SFCjJ0hR0SFEjJ0hR07SFjJ0hR03SF8jI0hR01SFCjI0hS0SFEjI0hS07SFjI0hS03SF8jH0hS01SFCjH0hT0SFEjH0hT07SFjH0hT03SF8jG0hT01SFCjG0hU0SFEjG0hU07SFjG0hU03SF8j0hU01SFCj0hV0SFEj0hV07SFj0hV03SFj0hV01SFj0hW0SFj0hW07RFj0hW03RFj0hW01RFj0hX0RFj0hX07QFj0hX03QFj0hX01QFj0hY0QFj0hY07PFj0hY03PFj0hY01PFj0i0PFj0i07OFj0i03OFj0i01OFj0iG0OFj0iG07NFj0iG03NFj0iG01NFj0iH0NFj0iH07MFj0iH03MFj0iH01MFj0mP0::::::::::::::::::::::::::::::::::::::iY01LFCiI0iY01LFEiI0iY01MFiI0iY01MF8iH0iY01MFCiH0iY01MFEiH0iY01NFiH0iY01NF8iG0iY01NFCiG0iY01NFEiG0iY01OFiG0iY01OF8i0iY01OFCi0iY01OFEi0iY01PFi0iY01PF8hY0iY01PFChY0iY01PFEhY0iY01QFhY0iY01QF8hX0iY01QFChX0iY01QFEhX0iY01RFhX0iY01RF8hW0iY01RFChW0iY01RFEhW0iY01SFhW0iY01SF8hV0iY01SFChV0j0SFEhV0j07SFhV0j03SF8hU0j01SFChU0jG0SFEhU0jG07SFhU0jG03SF8hT0jG01SFChT0jH0SFEhT0jH07SFhT0jH03SF8hS0jH01SFChS0jI0SFEhS0jI07SFhS0jI03SF8hR0jI01SFChR0jJ0SFEhR0jJ07SFhR0jJ03SF8hQ0jJ01SFChQ0jK0SFEhQ0jK07SFhQ0jK03SF8hP0jK01SFChP0jL0SFEhP0jL07SFhP0jL03SF8hO0jL01SFChO0jM0SFEhO0jM07SFhO0jM03SF8hN0jM01SFChN0jN0SFEhN0jN07SFhN0jN03SF8hM0jN01SFChM0jO0SFEhM0jO07SFhM0jO03SF8hL0jO01SFChL0jP0SFEhL0jP07SFhL0jP03SF8hK0jP01SFChK0jQ0SFEhK0jQ07SFhK0jQ03SF8hJ0jQ01SFChJ0jR0SFEhJ0jR07SFhJ0jR03SF8hI0jR01SFChI0jS0SFEhI0jS07SFhI0jS03SF8hH0jS01SFChH0jT0SFEhH0jT07SFhH0jT03SF8hG0jT01SFChG0jU0SFEhG0jU07SFhG0jU03SF8h0jU01SFCh0jV0SFEh0jV07SFh0jV03SF8gY0jV01SFCgY0jW0SFEgY0jW07SFgY0jW03SF8gX0jW01SFCgX0jX0SFEgX0jX07SFgX0jX03SF8gW0jX01SFCgW0jY0SFEgW0jY07SFgW0jY03SF8gV0jY01SFCgV0k0SFEgV0k07SFgV0k03SF8gU0k01SFCgU0kG0SFEgU0kG07SFgU0kG03SF8gT0kG01SFCgT0kH0SFEgT0kH07SFgT0kH03SF8gS0kH01SFCgS0kI0SFEgS0kI07SFgS0kI03SF8gR0kI01SFCgR0kJ0SFEgR0kJ07SFgR0kJ03SF8gQ0kJ01SFCgQ0kK0SFEgQ0kK07SFgQ0kK03SF8gP0kK01SFCgP0kL0SFEgP0kL07SFgP0kL03SF8gO0kL01SFCgO0kM0SFEgO0kM07SFgO0kM03SF8gN0kM01SFCgN0kN0SFEgN0kN07SFgN0kN03SF8gM0kN01SFCgM0kO0SFEgM0kO07SFgM0kO03SF8gL0kO01SFCgL0kP0SFEgL0kP07SFgL0kP03SF8gK0kP01SFCgK0kQ0SFEgK0kQ07SFgK0kQ03SF8gJ0kQ01SFCgJ0kR0SFEgJ0kR07SFgJ0kR03SF8gI0kR01SFCgI0kS0SFEgI0kS07SFgI0kS03SF8gH0kS01SFCgH0kT0SFEgH0kT07SFgH0kT03SF8gG0kT01SFCgG0kU0SFEgG0kU07SFgG0kU03SF8g0kU01SFCg0kV0SFEg0kV07SFg0kV03SF8Y0kV01SFCY0kW0SFEY0kW07SFY0kW03SF8X0kW01SFCX0kX0SFEX0kX07SFX0kX03SF8W0kX01SFCW0kY0SFEW0kY07SFW0kY03SF8V0kY01SFCV0l0SFEV0l07SFV0l03SF8U0l01SFCU0lG0SFEU0lG07SFU0lG03SF8T0lG01SFCT0lH0SFET0lH07SFT0lH03SF8S0lH01SFCS0lI0SFES0lI07SFS0lI03SF8R0lI01SFCR0lJ0SFER0lJ07SFR0lJ03SF8Q0lJ01SFCQ0lK0SFEQ0lK07SFQ0lK03SF8P0lK01SFCP0lL0SFEP0lL07SFP0lL03SF8O0lL01SFCO0lM0SFEO0lM07SFO0lM03SF8N0lM01SFCN0lN0SFEN0lN07SFN0lN03SF8M0lN01SFCM0lO0SFEM0lO07SFM0lO03SF8L0lO01SFCL0lP0SFEL0lP07SFL0lP03SF8K0lP01SFCK0lQ0SFEK0lQ07SFK0lQ03SF80000lQ01SFC0000lR0SFE0000lR07SF0000lR03SF8000lR01SFC000lS0SFE000lS07SF000lS03SF800lS01SFC00lT0SFE00lT07SF00lT03SF80lT01SFC0lU0SFE0lU07SF0lU03SF8lU01SFClV0SFElV07SFlV03SFlV01SFlW0SFlW07RFlW03RFlW01RFlX0RFlX07QFlX03QFlX01QFlY0QFlY07PFlY03PFlY01PFm0PFm07OFm03OFm01OFmG0OFmG07NFmG03NFmG01NFmH0NFmH07MFmH03MF^FS,^XZ", "graphictype": "CompressedASCII" }, { @@ -21,42 +21,42 @@ }, { "filename": "./tests/test4.jpg", - "zplstring": "^XA,^FS^FO0,0^GFA,4910,45000,75,,::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::iQ07LFEiQ0iN01RF8iN0iL01VF8iL0iK07XFEiK0iJ0gHFiJ0iI0gJFiI0iG01gLF8iG0iG0gNFiG0i0gPFi0hY07gPFEhY0hX03gRFChX0hW03gTFChW0hW0gVFhW0hV07gVFEhV0hU03gXFChU0hU0hFhU0hT07hFEhT0hS01hHF8hS0hS07hHFEhS0hR03hJFChR0hR0hLFhR0hQ03hLFChQ0hQ0hNFhQ0hP03hNFChP0hP0hPFhP0hO03hPFChO0hO0hRFhO0hN01hRF8hN0hN07hRFEhN0hM01hTF8hM0hM03gIFCL03gIFChM0hM0gHF8P01gHFhM0hL03gFCT03gFChL0hL07YFX0YFEhL0hK01XFEg07XF8hK0hK03WFEgH07WFChK0hK0WFEgJ07WFhK0hJ01WFgL0WF8hJ0hJ07VF8gL01VFEhJ0hJ0VFCgN03VFhJ0hI03UFEgP07UFChI0hI07UFgR0UFEhI0hI0UF8gR01UFhI0hH03TFEgT07TFChH0hH07TFgV0TFEhH0hH0TFCgV03TFhH0hG03TFgX0TFChG0hG07SF8gX01SFEhG0hG0SFEh07SFhG0h01SF8h01SF8h0h03SFhH0SFCh0h0SFChH03SFh0gY01SFhJ0SF8gY0gY03RFChJ03RFCgY0gY07RFhL0RFEgY0gY0RFChL03RFgY0gX01RF8hL01RF8gX0gX03QFEhN07QFCgX0gX07QF8hN01QFEgX0gX0RFhP0RFgX0gW01QFChP03QF8gW0gW07QF8hP01QFEgW0gW07PFEhR07PFEgW0gV01QFChR03QF8gV0gV03QF8hR01QFCgV0gV03PFEhT07PFCgV0gV07PFChT03PFEgV0gV0QFhV0QFgV0gU01PFEhV07PF8gU0gU03PFChV03PFCgU0gU07PF8hV01PFEgU0gU0PFEhX07PFgU0gT01PFChX03PF8gT0gT03PF8hX01PFCgT0gT07PFi0PFEgT0gT0PFEi07PFgT0gT0PF8i01PFgT0gS01PFiH0PF8gS0gS03OFEiH07OFCgS0gS07OFCiH03OFEgS0gS0PF8iH01PFgS0gR01PFiJ0PF8gR0gR01OFEiJ07OF8gR0gR03OFCiJ03OFCgR0gR07OF8iJ01OFEgR0gR0PFiL0PFgR0gR0OFEiL07OFgR0gQ01OFCiL03OF8gQ0gQ03OF8iL01OFCgQ0gQ07OFiN0OFEgQ0gQ07NFEiN07NFEgQ0gQ0OFCiN03OFgQ0gP01OF8iN03OF8gP0gP01OF8iN01OF8gP0gP03OFiP0OFCgP0gP07NFEiP07NFEgP0gP07NFCiP03NFEgP0gP0OF8iP01OFgP0gO01OFiR0OF8gO0:gO03NFEiR07NFCgO0gO07NFCiR03NFEgO0gO07NF8iR01NFEgO0gO0OFiT0OFgO0:gN01NFEiT07NF8gN0gN03NFCiT03NFCgN0:gN07NF8iT01NFEgN0gN07NFiV0NFEgN0gN0NFEiV07NFgN0gM01NFEiV07NF8gM0gM01NFCiV03NF8gM0gM03NF8iV01NFCgM0:gM07NFiX0NFEgM0gM07MFEiX07MFEgM0gM0NFEiX07NFgM0gM0NFCiX03NFgM0gL01NFCiX03NF8gL0gL01NF8iX01NF8gL0gL03NFj0NFCgL0:gL07MFEj07MFEgL0:gL0NFCj03NFgL0:gL0NF8j01NFgL0gK01NF8jG0NF8gK0gK01NFjH0NF8gK0gK03NFjH0NFCgK0gK03MFEjH07MFCgK0gK07MFCjH03MFEgK0:gK07MF8jH01MFEgK0gK0NF8jH01NFgK0gK0NFjJ0NFgK0gJ01NFjJ0NF8gJ0:gJ01MFEjJ07MF8gJ0gJ03MFEjJ07MFCgJ0gJ03MFCjJ03MFCgJ0:gJ07MF8jJ01MFEgJ0:gJ0NF8jJ01NFgJ0gJ0NFjL0NFgJ0:gI01MFEjL07MF8gI0::gI01MFCjL03MF8gI0gI03MFCjL03MFCgI0:gI03MF8jL01MFCgI0gI07MF8jL01MFEgI0:gI07MFjN0MFEgI0:gI0MFEjN07MFgI0::gH01MFEjN07MF8gH0gH01MFCjN03MF8gH0:::gH03MF8jN01MFCgH0::gH03MFjP0MFCgH0gH07MFjP0MFEgH0::gH07LFEjP07LFEgH0:gH0MFEjP07MFgH0::gH0MFCjP03MFgH0::gG01MFCjP03MF8gG0::gG01MF8jP01MF8gG0::::gG03MF8jP01MFCgG0:gG03MFjR0MFCgG0:::::::gG07MFjR0MFEgG0gG07LFEjR07LFEgG0:::::::::::::::::::::::::::gG07MFjR0MFEgG0gG03MFjR0MFCgG0:::::::gG03MF8jP01MFCgG0:gG01MF8jP01MF8gG0::::gG01MFCjP03MF8gG0::gH0MFCjP03MFgH0::gH0MFEjP07MFgH0::gH07LFEjP07LFEgH0:gH07MFjP0MFEgH0::gH03MFjP0MFCgH0gH03MF8jN01MFCgH0::gH01MF8jN01MF8gH0gH01MFCjN03MF8gH0::gH01MFEjN07MF8gH0gI0MFEjN07MFgI0:gI0NFjN0NFgI0gI07MFjN0MFEgI0:gI07MF8jM0MFEgI0gI07MF8jL01MFEgI0gI03MF8jL01MFCgI0gI03MFCjL03MFCgI0:gI01MFCjL03MF8gI0gI01MFEjL07MF8gI0::gJ0NFjL0NFgJ0:gJ0NF8jJ01NFgJ0gJ07MF8jJ01MFEgJ0:gJ03MFCjJ03MFCgJ0:gJ03MFEjJ07MFCgJ0gJ01MFEjJ07MF8gJ0gJ01NFjJ0NF8gJ0:gK0NF8jH01NFgK0:gK07MF8jH01MFEgK0gK07MFCjH03MFEgK0:gK03MFEjH07MFCgK0gK03NFjH0NFCgK0gK01NFjH0NF8gK0:gL0NF8j01NFgL0gL0NFCj03NFgL0:gL07MFEj07MFEgL0:gL03NFj0NFCgL0:gL01NF8iX01NF8gL0gL01NFCiX03NF8gL0gM0NFCiX03NFgM0gM0NFEiX07NFgM0gM07MFEiX07MFEgM0gM07NFiX0NFEgM0gM03NF8iV01NFCgM0:gM01NFCiV03NF8gM0gM01NFEiV07NF8gM0gN0NFEiV07NFgN0gN07NFiV0NFEgN0gN07NF8iT01NFEgN0gN03NFCiT03NFCgN0:gN01NFEiT07NF8gN0gO0OFiT0OFgO0:gO07NF8iR01NFEgO0gO07NFCiR03NFEgO0gO03NFEiR07NFCgO0gO01OFiR0OF8gO0:gP0OF8iP01OFgP0gP07NFCiP03NFEgP0gP07NFEiP07NFEgP0gP03OFiP0OFCgP0gP01OF8iN01OF8gP0:gQ0OFCiN03OFgQ0gQ07NFEiN07NFEgQ0gQ07OFiN0OFEgQ0gQ03OF8iL01OFCgQ0gQ01OFCiL03OF8gQ0gR0OFEiL07OFgR0gR0PFiL0PFgR0gR07OF8iJ01OFEgR0gR03OFCiJ03OFCgR0gR01OFEiJ07OF8gR0gR01PFiJ0PF8gR0gS0PF8iH01PFgS0gS07OFCiH03OFEgS0gS03OFEiH07OFCgS0gS01PFiH0PF8gS0gT0PF8i01PFgT0gT0PFEi07PFgT0gT07PFi0PFEgT0gT03PF8hX01PFCgT0gT01PFChX03PF8gT0gU0PFEhX07PFgU0gU07PF8hV01PFEgU0gU03PFChV03PFCgU0gU01PFEhV07PF8gU0gV0QFhV0QFgV0gV07PFChT03PFEgV0gV03PFEhT07PFCgV0gV03QF8hR01QFCgV0gV01QFChR03QF8gV0gW0QFEhR07QFgW0gW07QF8hP01QFEgW0gW03QFChP03QFCgW0gW01RFhP0RF8gW0gX07QF8hN01QFEgX0gX03QFEhN07QFCgX0gX01RF8hL01RF8gX0gY0RFChL03RFgY0gY07RFhL0RFEgY0gY03RFChJ03RFCgY0gY01SFhJ0SF8gY0h0SFChH03SFh0h03SFhH0SFCh0h01SF8h01SF8h0hG0SFEh07SFhG0hG07SF8gX01SFEhG0hG03TFgX0TFChG0hH0TFCgV03TFhH0hH07TFgV0TFEhH0hH03TFEgT07TFChH0hI0UF8gR01UFhI0hI07UFgR0UFEhI0hI03UFEgP07UFChI0hJ0VFCgN03VFhJ0hJ07VF8gL01VFEhJ0hJ01WFgL0WF8hJ0hK0WFEgJ07WFhK0hK03WFEgH07WFChK0hK01XFEg07XF8hK0hL07YFX0YFEhL0hL03gFCT03gFChL0hM0gHF8P01gHFhM0hM03gIFCL03gIFChM0hM01hTF8hM0hN07hRFEhN0hN01hRF8hN0hO0hRFhO0hO03hPFChO0hP0hPFhP0hP03hNFChP0hQ0hNFhQ0hQ03hLFChQ0hR0hLFhR0hR03hJFChR0hS07hHFEhS0hS01hHF8hS0hT07hFEhT0hU0hFhU0hU03gXFChU0hV07gVFEhV0hW0gVFhW0hW03gTFChW0hX07gRFEhX0hY07gPFEhY0i0gPFi0iG0gNFiG0iG01gLF8iG0iI0gJFiI0iJ0gHFiJ0iK07XFEiK0iL01VF8iL0iN01RF8iN0iQ07LFEiQ0,::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,4914,45000,75,mP0::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::iQ07LFEiQ0iN01RF8iN0iL01VF8iL0iK07XFEiK0iJ0gHFiJ0iI0gJFiI0iG01gLF8iG0iG0gNFiG0i0gPFi0hY07gPFEhY0hX03gRFChX0hW03gTFChW0hW0gVFhW0hV07gVFEhV0hU03gXFChU0hU0hFhU0hT07hFEhT0hS01hHF8hS0hS07hHFEhS0hR03hJFChR0hR0hLFhR0hQ03hLFChQ0hQ0hNFhQ0hP03hNFChP0hP0hPFhP0hO03hPFChO0hO0hRFhO0hN01hRF8hN0hN07hRFEhN0hM01hTF8hM0hM03gIFCL03gIFChM0hM0gHF8P01gHFhM0hL03gFCT03gFChL0hL07YFX0YFEhL0hK01XFEg07XF8hK0hK03WFEgH07WFChK0hK0WFEgJ07WFhK0hJ01WFgL0WF8hJ0hJ07VF8gL01VFEhJ0hJ0VFCgN03VFhJ0hI03UFEgP07UFChI0hI07UFgR0UFEhI0hI0UF8gR01UFhI0hH03TFEgT07TFChH0hH07TFgV0TFEhH0hH0TFCgV03TFhH0hG03TFgX0TFChG0hG07SF8gX01SFEhG0hG0SFEh07SFhG0h01SF8h01SF8h0h03SFhH0SFCh0h0SFChH03SFh0gY01SFhJ0SF8gY0gY03RFChJ03RFCgY0gY07RFhL0RFEgY0gY0RFChL03RFgY0gX01RF8hL01RF8gX0gX03QFEhN07QFCgX0gX07QF8hN01QFEgX0gX0RFhP0RFgX0gW01QFChP03QF8gW0gW07QF8hP01QFEgW0gW07PFEhR07PFEgW0gV01QFChR03QF8gV0gV03QF8hR01QFCgV0gV03PFEhT07PFCgV0gV07PFChT03PFEgV0gV0QFhV0QFgV0gU01PFEhV07PF8gU0gU03PFChV03PFCgU0gU07PF8hV01PFEgU0gU0PFEhX07PFgU0gT01PFChX03PF8gT0gT03PF8hX01PFCgT0gT07PFi0PFEgT0gT0PFEi07PFgT0gT0PF8i01PFgT0gS01PFiH0PF8gS0gS03OFEiH07OFCgS0gS07OFCiH03OFEgS0gS0PF8iH01PFgS0gR01PFiJ0PF8gR0gR01OFEiJ07OF8gR0gR03OFCiJ03OFCgR0gR07OF8iJ01OFEgR0gR0PFiL0PFgR0gR0OFEiL07OFgR0gQ01OFCiL03OF8gQ0gQ03OF8iL01OFCgQ0gQ07OFiN0OFEgQ0gQ07NFEiN07NFEgQ0gQ0OFCiN03OFgQ0gP01OF8iN03OF8gP0gP01OF8iN01OF8gP0gP03OFiP0OFCgP0gP07NFEiP07NFEgP0gP07NFCiP03NFEgP0gP0OF8iP01OFgP0gO01OFiR0OF8gO0:gO03NFEiR07NFCgO0gO07NFCiR03NFEgO0gO07NF8iR01NFEgO0gO0OFiT0OFgO0:gN01NFEiT07NF8gN0gN03NFCiT03NFCgN0:gN07NF8iT01NFEgN0gN07NFiV0NFEgN0gN0NFEiV07NFgN0gM01NFEiV07NF8gM0gM01NFCiV03NF8gM0gM03NF8iV01NFCgM0:gM07NFiX0NFEgM0gM07MFEiX07MFEgM0gM0NFEiX07NFgM0gM0NFCiX03NFgM0gL01NFCiX03NF8gL0gL01NF8iX01NF8gL0gL03NFj0NFCgL0:gL07MFEj07MFEgL0:gL0NFCj03NFgL0:gL0NF8j01NFgL0gK01NF8jG0NF8gK0gK01NFjH0NF8gK0gK03NFjH0NFCgK0gK03MFEjH07MFCgK0gK07MFCjH03MFEgK0:gK07MF8jH01MFEgK0gK0NF8jH01NFgK0gK0NFjJ0NFgK0gJ01NFjJ0NF8gJ0:gJ01MFEjJ07MF8gJ0gJ03MFEjJ07MFCgJ0gJ03MFCjJ03MFCgJ0:gJ07MF8jJ01MFEgJ0:gJ0NF8jJ01NFgJ0gJ0NFjL0NFgJ0:gI01MFEjL07MF8gI0::gI01MFCjL03MF8gI0gI03MFCjL03MFCgI0:gI03MF8jL01MFCgI0gI07MF8jL01MFEgI0:gI07MFjN0MFEgI0:gI0MFEjN07MFgI0::gH01MFEjN07MF8gH0gH01MFCjN03MF8gH0:::gH03MF8jN01MFCgH0::gH03MFjP0MFCgH0gH07MFjP0MFEgH0::gH07LFEjP07LFEgH0:gH0MFEjP07MFgH0::gH0MFCjP03MFgH0::gG01MFCjP03MF8gG0::gG01MF8jP01MF8gG0::::gG03MF8jP01MFCgG0:gG03MFjR0MFCgG0:::::::gG07MFjR0MFEgG0gG07LFEjR07LFEgG0:::::::::::::::::::::::::::gG07MFjR0MFEgG0gG03MFjR0MFCgG0:::::::gG03MF8jP01MFCgG0:gG01MF8jP01MF8gG0::::gG01MFCjP03MF8gG0::gH0MFCjP03MFgH0::gH0MFEjP07MFgH0::gH07LFEjP07LFEgH0:gH07MFjP0MFEgH0::gH03MFjP0MFCgH0gH03MF8jN01MFCgH0::gH01MF8jN01MF8gH0gH01MFCjN03MF8gH0::gH01MFEjN07MF8gH0gI0MFEjN07MFgI0:gI0NFjN0NFgI0gI07MFjN0MFEgI0:gI07MF8jM0MFEgI0gI07MF8jL01MFEgI0gI03MF8jL01MFCgI0gI03MFCjL03MFCgI0:gI01MFCjL03MF8gI0gI01MFEjL07MF8gI0::gJ0NFjL0NFgJ0:gJ0NF8jJ01NFgJ0gJ07MF8jJ01MFEgJ0:gJ03MFCjJ03MFCgJ0:gJ03MFEjJ07MFCgJ0gJ01MFEjJ07MF8gJ0gJ01NFjJ0NF8gJ0:gK0NF8jH01NFgK0:gK07MF8jH01MFEgK0gK07MFCjH03MFEgK0:gK03MFEjH07MFCgK0gK03NFjH0NFCgK0gK01NFjH0NF8gK0:gL0NF8j01NFgL0gL0NFCj03NFgL0:gL07MFEj07MFEgL0:gL03NFj0NFCgL0:gL01NF8iX01NF8gL0gL01NFCiX03NF8gL0gM0NFCiX03NFgM0gM0NFEiX07NFgM0gM07MFEiX07MFEgM0gM07NFiX0NFEgM0gM03NF8iV01NFCgM0:gM01NFCiV03NF8gM0gM01NFEiV07NF8gM0gN0NFEiV07NFgN0gN07NFiV0NFEgN0gN07NF8iT01NFEgN0gN03NFCiT03NFCgN0:gN01NFEiT07NF8gN0gO0OFiT0OFgO0:gO07NF8iR01NFEgO0gO07NFCiR03NFEgO0gO03NFEiR07NFCgO0gO01OFiR0OF8gO0:gP0OF8iP01OFgP0gP07NFCiP03NFEgP0gP07NFEiP07NFEgP0gP03OFiP0OFCgP0gP01OF8iN01OF8gP0:gQ0OFCiN03OFgQ0gQ07NFEiN07NFEgQ0gQ07OFiN0OFEgQ0gQ03OF8iL01OFCgQ0gQ01OFCiL03OF8gQ0gR0OFEiL07OFgR0gR0PFiL0PFgR0gR07OF8iJ01OFEgR0gR03OFCiJ03OFCgR0gR01OFEiJ07OF8gR0gR01PFiJ0PF8gR0gS0PF8iH01PFgS0gS07OFCiH03OFEgS0gS03OFEiH07OFCgS0gS01PFiH0PF8gS0gT0PF8i01PFgT0gT0PFEi07PFgT0gT07PFi0PFEgT0gT03PF8hX01PFCgT0gT01PFChX03PF8gT0gU0PFEhX07PFgU0gU07PF8hV01PFEgU0gU03PFChV03PFCgU0gU01PFEhV07PF8gU0gV0QFhV0QFgV0gV07PFChT03PFEgV0gV03PFEhT07PFCgV0gV03QF8hR01QFCgV0gV01QFChR03QF8gV0gW0QFEhR07QFgW0gW07QF8hP01QFEgW0gW03QFChP03QFCgW0gW01RFhP0RF8gW0gX07QF8hN01QFEgX0gX03QFEhN07QFCgX0gX01RF8hL01RF8gX0gY0RFChL03RFgY0gY07RFhL0RFEgY0gY03RFChJ03RFCgY0gY01SFhJ0SF8gY0h0SFChH03SFh0h03SFhH0SFCh0h01SF8h01SF8h0hG0SFEh07SFhG0hG07SF8gX01SFEhG0hG03TFgX0TFChG0hH0TFCgV03TFhH0hH07TFgV0TFEhH0hH03TFEgT07TFChH0hI0UF8gR01UFhI0hI07UFgR0UFEhI0hI03UFEgP07UFChI0hJ0VFCgN03VFhJ0hJ07VF8gL01VFEhJ0hJ01WFgL0WF8hJ0hK0WFEgJ07WFhK0hK03WFEgH07WFChK0hK01XFEg07XF8hK0hL07YFX0YFEhL0hL03gFCT03gFChL0hM0gHF8P01gHFhM0hM03gIFCL03gIFChM0hM01hTF8hM0hN07hRFEhN0hN01hRF8hN0hO0hRFhO0hO03hPFChO0hP0hPFhP0hP03hNFChP0hQ0hNFhQ0hQ03hLFChQ0hR0hLFhR0hR03hJFChR0hS07hHFEhS0hS01hHF8hS0hT07hFEhT0hU0hFhU0hU03gXFChU0hV07gVFEhV0hW0gVFhW0hW03gTFChW0hX07gRFEhX0hY07gPFEhY0i0gPFi0iG0gNFiG0iG01gLF8iG0iI0gJFiI0iJ0gHFiJ0iK07XFEiK0iL01VF8iL0iN01RF8iN0iQ07LFEiQ0mP0::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test5.png", - "zplstring": "^XA,^FS^FO0,0^GFA,5602,125000,125,E38E38E38E38E38E38E38E38qL0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::,::ErO0::hV07F800001FE0FFgN0FFhJ07F8P0FEjR0R078hH07F800001FE0FFgN0FFhJ07F8O07FEjR0R0FChH07F800001FE0FFgN0FFP07CW0F8S07F8O0FFEjR0EP01FEhH07F800001FE0FFgN0FFP0F8V01FT07F8N01FFEjR0:EP01FEhH07F800001FE0FFgN0FFP0F8V01FT07F8N03FFEjR0Q01FEhH07F800001FE0FFgN0FFP0F8V01FT07F8N03FCjS0R0FChH07F800001FE0FFgN0FFO01FW03ET07F8N07F8jS0R078hH07F800001FE0FFgN0FFO01FW03ET07F8N07F8jS0EhU07F800001FE0FFgN0FFO01FW03ET07F8N07F8jS0EhU07F800001FE0FFgN0FFO03EW07CT07F8N07F8jS0EL01FC01FE07F0FC01F80000FE0007F0FC01FE01F80FF00FF0007F8003F1FE0FF0F800003F000FE0E1FC1C00001F8FF0003F00003E000FC7F0003F800007C3FFFFC1FC1F8007F8007E3F83FFFC03FCjN0M0FFF81FE07F3FF07FC0007FFC007F3FF01FE03F80FF0FFFE007F800FFDFE0FF3FE0001FFE00FE3E1FC7C00007FEFF001FFE0003E003FE7F001FFF00007C3FFFFC1FCFFE007F801FF3F83FFFC3FFF8jM0L01FFF01FE07F7FFCFFE000FFFE007F7FF80FE03F80FE1FFFF807F803FFFFE0FF7FF0003FFF00FE7E1FCFC0001KF003FFF0007C00FFF7F003FFF8000F83FFFFC1FDFFF007F807FFBF83FFFC7FFFEjM0L03FFF01FE07FFFFDFFF003FFFF807FFFFC0FE03FC0FE1FFFFC07F807FFFFE0KF8007FFF80FEFE1FDFC0003KF007FFF8007C01KF00FFFFE000F83FFFFC1KF807F80KF83FFFC7FFFFjM0EK07FFF01FE07NF803FFFF807FFFFE0FF03FC0FE0FFFFC07F807FFFFE0KFC00FFFFC0FFFE1FFFC0003KF00FFFFC007C01KF00FFFFE000F83FFFF81KFC07F80KF83FFFC3FFFFjM0EK07F0701FE07NF807FC7FC07FFFFE07F07FC1FC0F03FE07F80KFE0KFC01FE1FC0FFFE1FFFC0007KF01FE1FC007C03KF01FF1FF000F83FFFF81KFC07F81KF83FFFC3C0FF8jL0EK0FF0001FE07FC1FF87F80FF83FE07FC1FE07F07FC1FC0C01FE07F81FF07FE0FF83FC01F80FE0FFFE1FFFC000FF83FF01F80FE00F807FC1FF03FE0FF801F0000FF01FF83FE07F83FE0FF807F803007F8jL0L0FF0001FE07FC1FF83FC0FF01FE07FC1FF07F07FE1FC0000FE07F81FE03FE0FF83FE03F80FE0FFE01FFC0000FF01FF03F80FE00F807F81FF03FC07F801F0001FE01FF01FE07F83FC0FF807F800003F8jL0L0FF8001FE07F80FF03FC0FF01FE07F80FF07F0FBE1F80000FF07F81FE01FE0FF01FE03F007F0FF801FFK0FF00FF03F007F00F807F80FF03FC07F801F0003FC01FE01FE07F83FC07F807F800003FCjL0L0FFC001FE07F80FF03FC1FE00FF07F80FF03F8FBE3F8001FFF07F83FC01FE0FF01FE07F007F0FF801FF00001FE00FF07F007F01F007F00FF07F803FC03E0007FC01FE00FF07F83F807F807F80007FFCjL0EK07FF001FE07F80FF03FC1FE00FF07F80FF03F8FBE3F801FFFF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF01F00FF00FF07F803FC03E0007F801FE00FF07F87F807F807F8007FFFCjL0EK03FFC01FE07F80FF03FC1FE00FF07F80FF03F8FBE3F007FFFF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF01F00FF00FF07F803FC03E000FF001FE00FF07F87F807F807F801FFFFCjL0EK01FFF01FE07F80FF03FC1FE00FF07F80FF01F8FBF3F00KF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF03F00FF00FF07F803FC07E001FF001FE00FF07F87F807F807F803FFFFCjL0M0FFF81FE07F80FF03FC1FE00FF07F80FF01F9F3F3F01KF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF03E00FF00FF07F803FC07C003FE001FE00FF07F87F807F807F807FFFFCjL0M03FF81FE07F80FF03FC1FE00FF07F80FF01F9F1F3F03FE0FF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF03E00FF00FF07F803FC07C003FC001FE00FF07F87F807F807F80FF83FCjL0N0FFC1FE07F80FF03FC1FE00FE07F80FF00FDF1F3E03FC0FF07F83FC01FE0FF01FE07FK0FF001FE00001FE00FF07FK03E00FF00FF07F803F807C007F8001FE00FE07F87F807F807F80FF03FCjL0EM03FC1FE07F80FF03FC0FF01FE07F80FF00FDF1F7E07F80FF07F81FE01FE0FF01FE03FK0FF001FE00F80FF00FF03FK07C007F80FF03FC07F80F800FF8001FE01FE07F83FC07F807F81FE03FCjL0EM03FC1FE07F80FF03FC0FF01FE07F80FF00FDE1FFE07F80FF07F81FE03FE0FF01FE03F80000FF001FE01FC0FF01FF03F800007C007F81FF03FC07F80F800FF0001FF01FE07F83FC0FF807F81FE03FCjL0EK0403FC1FE07F80FF03FC0FF83FC07F80FF00FFE0FFC07F81FF07F81FF07FE0FF01FE03FE0040FF001FE03FE0FF83FF03FE00407C007FC3FF03FE0FF00F801FE0001FF83FE07F83FE1FF807F81FE07FCjL0L0703F81FE07F80FF03FC07FFFFC07F80FF007FE0FFC07FC3FF07F80KFE0FF01FE01FFFFC0FF001FE03FE07KF01FFFFC0F8003KF01KF01F003FFFFC1KFC07F81KF807F81FF0FFCjL0L0FFFF81FE07F80FF03FC03FFFF807F80FF007FE0FFC03KF07F80KFE0FF01FE01FFFFC0FF001FE03FE07KF01FFFFC0F8003KF00FFFFE01F007FFFFC1KF807F81KF807F80KFCjL0L0FFFF01FE07F80FF03FC01FFFF007F80FF007FC0FF803KF07F807FFEFE0FF01FE00FFFFC0FF001FE03FE03FFF7F00FFFFC0F8001KF007FFFC01F007FFFFC1KF807F80KF807F80KFCjL0EK0FFFE01FE07F80FF03FC00FFFE007F80FF003FC07F801FFF7F07F803FFEFE0FF01FE007FFFC0FF001FE01FC01FFF7F007FFFC0F8000FFEFF003FFF801F007FFFFC1KF007F807FF7F807F807FFDFCjL0EK0FFFC01FE07F80FF03FC007FFC007F80FF003FC07F800FFE7F07F801FF8FE0FF01FE001FFFE0FF001FE01FC00FFC7F001FFFE1F00007FCFF001FFF003E007FFFFC1FEFFC007F803FE7F807F803FF9FCjL0EL0FE001FE07F80FF03FC000FE0007F80FF003F807F8001F07F07F8007E0FE0FF01FE0003FE00FF001FE0070003F07F0003FE01FK0F0FF0003F8003E007FFFFC1FE3F0007F800787F807F8007C1FCjL0kL01FM0FEN03EN01FER07FjX0kL03EL01FEN07CN01FER0FFjX0kT01FEX01FER0FFjX0kR0C03FEX01FEO0601FFjX0kQ01FFFFCX01FEO0FFFFEjX0kQ01FFFF8X01FEO0FFFFCjX0:kQ03FFFFY01FEN01FFFF8jX0kQ01FFFCY01FEO0FFFEjY0kR03FEg01FEO01FFk0,:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::rJF8K0::::::::::::::::::,:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::79E79E79E79EqX0:::79E78001E79EqX079E79E79E79EqX0:::,^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,5628,125000,125,E38E38E38E38E38E38E38E38qL0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::rP0::ErO0::hV07F800001FE0FFgN0FFhJ07F8P0FEjR0R078hH07F800001FE0FFgN0FFhJ07F8O07FEjR0R0FChH07F800001FE0FFgN0FFP07CW0F8S07F8O0FFEjR0EP01FEhH07F800001FE0FFgN0FFP0F8V01FT07F8N01FFEjR0:EP01FEhH07F800001FE0FFgN0FFP0F8V01FT07F8N03FFEjR0Q01FEhH07F800001FE0FFgN0FFP0F8V01FT07F8N03FCjS0R0FChH07F800001FE0FFgN0FFO01FW03ET07F8N07F8jS0R078hH07F800001FE0FFgN0FFO01FW03ET07F8N07F8jS0EhU07F800001FE0FFgN0FFO01FW03ET07F8N07F8jS0EhU07F800001FE0FFgN0FFO03EW07CT07F8N07F8jS0EL01FC01FE07F0FC01F80000FE0007F0FC01FE01F80FF00FF0007F8003F1FE0FF0F800003F000FE0E1FC1C00001F8FF0003F00003E000FC7F0003F800007C3FFFFC1FC1F8007F8007E3F83FFFC03FCjN0M0FFF81FE07F3FF07FC0007FFC007F3FF01FE03F80FF0FFFE007F800FFDFE0FF3FE0001FFE00FE3E1FC7C00007FEFF001FFE0003E003FE7F001FFF00007C3FFFFC1FCFFE007F801FF3F83FFFC3FFF8jM0L01FFF01FE07F7FFCFFE000FFFE007F7FF80FE03F80FE1FFFF807F803FFFFE0FF7FF0003FFF00FE7E1FCFC0001KF003FFF0007C00FFF7F003FFF8000F83FFFFC1FDFFF007F807FFBF83FFFC7FFFEjM0L03FFF01FE07FFFFDFFF003FFFF807FFFFC0FE03FC0FE1FFFFC07F807FFFFE0KF8007FFF80FEFE1FDFC0003KF007FFF8007C01KF00FFFFE000F83FFFFC1KF807F80KF83FFFC7FFFFjM0EK07FFF01FE07NF803FFFF807FFFFE0FF03FC0FE0FFFFC07F807FFFFE0KFC00FFFFC0FFFE1FFFC0003KF00FFFFC007C01KF00FFFFE000F83FFFF81KFC07F80KF83FFFC3FFFFjM0EK07F0701FE07NF807FC7FC07FFFFE07F07FC1FC0F03FE07F80KFE0KFC01FE1FC0FFFE1FFFC0007KF01FE1FC007C03KF01FF1FF000F83FFFF81KFC07F81KF83FFFC3C0FF8jL0EK0FF0001FE07FC1FF87F80FF83FE07FC1FE07F07FC1FC0C01FE07F81FF07FE0FF83FC01F80FE0FFFE1FFFC000FF83FF01F80FE00F807FC1FF03FE0FF801F0000FF01FF83FE07F83FE0FF807F803007F8jL0L0FF0001FE07FC1FF83FC0FF01FE07FC1FF07F07FE1FC0000FE07F81FE03FE0FF83FE03F80FE0FFE01FFC0000FF01FF03F80FE00F807F81FF03FC07F801F0001FE01FF01FE07F83FC0FF807F800003F8jL0L0FF8001FE07F80FF03FC0FF01FE07F80FF07F0FBE1F80000FF07F81FE01FE0FF01FE03F007F0FF801FFK0FF00FF03F007F00F807F80FF03FC07F801F0003FC01FE01FE07F83FC07F807F800003FCjL0L0FFC001FE07F80FF03FC1FE00FF07F80FF03F8FBE3F8001FFF07F83FC01FE0FF01FE07F007F0FF801FF00001FE00FF07F007F01F007F00FF07F803FC03E0007FC01FE00FF07F83F807F807F80007FFCjL0EK07FF001FE07F80FF03FC1FE00FF07F80FF03F8FBE3F801FFFF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF01F00FF00FF07F803FC03E0007F801FE00FF07F87F807F807F8007FFFCjL0EK03FFC01FE07F80FF03FC1FE00FF07F80FF03F8FBE3F007FFFF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF01F00FF00FF07F803FC03E000FF001FE00FF07F87F807F807F801FFFFCjL0EK01FFF01FE07F80FF03FC1FE00FF07F80FF01F8FBF3F00KF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF03F00FF00FF07F803FC07E001FF001FE00FF07F87F807F807F803FFFFCjL0M0FFF81FE07F80FF03FC1FE00FF07F80FF01F9F3F3F01KF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF03E00FF00FF07F803FC07C003FE001FE00FF07F87F807F807F807FFFFCjL0M03FF81FE07F80FF03FC1FE00FF07F80FF01F9F1F3F03FE0FF07F83FC01FE0FF01FE07KF0FF001FE00001FE00FF07KF03E00FF00FF07F803FC07C003FC001FE00FF07F87F807F807F80FF83FCjL0N0FFC1FE07F80FF03FC1FE00FE07F80FF00FDF1F3E03FC0FF07F83FC01FE0FF01FE07FK0FF001FE00001FE00FF07FK03E00FF00FF07F803F807C007F8001FE00FE07F87F807F807F80FF03FCjL0EM03FC1FE07F80FF03FC0FF01FE07F80FF00FDF1F7E07F80FF07F81FE01FE0FF01FE03FK0FF001FE00F80FF00FF03FK07C007F80FF03FC07F80F800FF8001FE01FE07F83FC07F807F81FE03FCjL0EM03FC1FE07F80FF03FC0FF01FE07F80FF00FDE1FFE07F80FF07F81FE03FE0FF01FE03F80000FF001FE01FC0FF01FF03F800007C007F81FF03FC07F80F800FF0001FF01FE07F83FC0FF807F81FE03FCjL0EK0403FC1FE07F80FF03FC0FF83FC07F80FF00FFE0FFC07F81FF07F81FF07FE0FF01FE03FE0040FF001FE03FE0FF83FF03FE00407C007FC3FF03FE0FF00F801FE0001FF83FE07F83FE1FF807F81FE07FCjL0L0703F81FE07F80FF03FC07FFFFC07F80FF007FE0FFC07FC3FF07F80KFE0FF01FE01FFFFC0FF001FE03FE07KF01FFFFC0F8003KF01KF01F003FFFFC1KFC07F81KF807F81FF0FFCjL0L0FFFF81FE07F80FF03FC03FFFF807F80FF007FE0FFC03KF07F80KFE0FF01FE01FFFFC0FF001FE03FE07KF01FFFFC0F8003KF00FFFFE01F007FFFFC1KF807F81KF807F80KFCjL0L0FFFF01FE07F80FF03FC01FFFF007F80FF007FC0FF803KF07F807FFEFE0FF01FE00FFFFC0FF001FE03FE03FFF7F00FFFFC0F8001KF007FFFC01F007FFFFC1KF807F80KF807F80KFCjL0EK0FFFE01FE07F80FF03FC00FFFE007F80FF003FC07F801FFF7F07F803FFEFE0FF01FE007FFFC0FF001FE01FC01FFF7F007FFFC0F8000FFEFF003FFF801F007FFFFC1KF007F807FF7F807F807FFDFCjL0EK0FFFC01FE07F80FF03FC007FFC007F80FF003FC07F800FFE7F07F801FF8FE0FF01FE001FFFE0FF001FE01FC00FFC7F001FFFE1F00007FCFF001FFF003E007FFFFC1FEFFC007F803FE7F807F803FF9FCjL0EL0FE001FE07F80FF03FC000FE0007F80FF003F807F8001F07F07F8007E0FE0FF01FE0003FE00FF001FE0070003F07F0003FE01FK0F0FF0003F8003E007FFFFC1FE3F0007F800787F807F8007C1FCjL0kL01FM0FEN03EN01FER07FjX0kL03EL01FEN07CN01FER0FFjX0kT01FEX01FER0FFjX0kR0C03FEX01FEO0601FFjX0kQ01FFFFCX01FEO0FFFFEjX0kQ01FFFF8X01FEO0FFFFCjX0:kQ03FFFFY01FEN01FFFF8jX0kQ01FFFCY01FEO0FFFEjY0kR03FEg01FEO01FFk0rP0:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::rJF8K0::::::::::::::::::rP0:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::79E79E79E79EqX0:::79E78001E79EqX079E79E79E79EqX0:::rP0^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test6.png", - "zplstring": "^XA,^FS^FO0,0^GFA,52,51,3,FFFF00::FE3F00::FFFF00FFE300::FFFF00E22300::FFFF00::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,52,51,3,FFFF80::FE3F80::FFFF80FFE380::FFFF80E22380::FFFF80::^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test7.png", - "zplstring": "^XA,^FS^FO0,0^GFA,71,128,4,!::FE3KF::!FFE3FFFF::!E223FFFF::!:::FFFFC7FF::!FFFFC7C7::!FFFFC47F::!::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,78,128,4,NF::FE3KF::NFFFE3FFFF::NFE223FFFF::NF:::FFFFC7FF::NFFFFFC7C7::NFFFFFC47F::NF::^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test6.png", - "zplstring": "XlhBLF5GUwpeRk8wLDAKXkdGQiw1MSw1MSwzLAr//wD//wD//wD+PwD+PwD+PwD//wD/4wD/4wD/4wD//wDiIwDiIwDiIwD//wD//wD//wBeRlMsXlhaCg==", + "zplstring": "XlhBLF5GUwpeRk8wLDAKXkdGQiw1MSw1MSwzLAr//4D//4D//4D+P4D+P4D+P4D//4D/44D/44D/44D//4DiI4DiI4DiI4D//4D//4D//4BeRlMsXlhaCg==", "graphictype": "Binary" }, { "filename": "./tests/test9.jpg", - "zplstring": "^XA,^FS^FO0,0^GFA,100,7600,76,,:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,102,7500,75,mP0:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test10.gif", - "zplstring": "^XA,^FS^FO0,0^GFA,122,7600,76,mN03800::,::mN03800:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::mPF00::mN03800::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,116,7500,75,mN038::mP0::mN038:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::mPF::mN038::^FS,^XZ", "graphictype": "CompressedASCII" }, { "filename": "./tests/test11.gif", - "zplstring": "^XA,^FS^FO0,0^GFA,611,7200,12,1CgH0:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::gIFC::1CgH0::^FS,^XZ", + "zplstring": "^XA,^FS^FO0,0^GFA,613,7800,13,1CgJ0:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::gIFC70::1CgJ0::^FS,^XZ", "graphictype": "CompressedASCII" }, { diff --git a/zplgfa.go b/zplgfa.go index 173536a..1b61d48 100644 --- a/zplgfa.go +++ b/zplgfa.go @@ -21,19 +21,15 @@ const ( CompressedASCII ) -// ConvertToZPL is just a wrapper for ConvertToGraphicField which also includes the ZPL -// starting code ^XA and ending code ^XZ, as well as a Field Separator and Field Origin. +// ConvertToZPL is a wrapper for ConvertToGraphicField, adding ZPL start and end codes. func ConvertToZPL(img image.Image, graphicType GraphicType) string { - //Check before entering the ConvertToGraphicField function - //if the width is 0, the function can exit early - check := img.Bounds().Size().X / 8 - if check == 0 { + if img.Bounds().Size().X/8 == 0 { return "" } return fmt.Sprintf("^XA,^FS\n^FO0,0\n%s^FS,^XZ\n", ConvertToGraphicField(img, graphicType)) } -// FlattenImage optimizes an image for the converting process +// FlattenImage optimizes an image for the converting process. func FlattenImage(source image.Image) *image.NRGBA { size := source.Bounds().Size() background := color.White @@ -41,37 +37,36 @@ func FlattenImage(source image.Image) *image.NRGBA { for y := 0; y < size.Y; y++ { for x := 0; x < size.X; x++ { p := source.At(x, y) - flat := flatten(p, background) - target.Set(x, y, flat) + target.Set(x, y, flatten(p, background)) } } return target } -func flatten(input color.Color, background color.Color) color.Color { +func flatten(input, background color.Color) color.Color { source := color.NRGBA64Model.Convert(input).(color.NRGBA64) r, g, b, a := source.RGBA() bgR, bgG, bgB, _ := background.RGBA() alpha := float32(a) / 0xffff - conv := func(c uint32, bg uint32) uint8 { - val := 0xffff - uint32((float32(bg) * alpha)) - val = val | uint32(float32(c)*alpha) + + conv := func(c, bg uint32) uint8 { + val := 0xffff - uint32(float32(bg)*alpha) + val |= uint32(float32(c) * alpha) return uint8(val >> 8) } - c := color.NRGBA{ - conv(r, bgR), - conv(g, bgG), - conv(b, bgB), - uint8(0xff), + + return color.NRGBA{ + R: conv(r, bgR), + G: conv(g, bgG), + B: conv(b, bgB), + A: 0xff, } - return c } func getRepeatCode(repeatCount int, char string) string { repeatStr := "" if repeatCount > 419 { - repeatCount -= 419 - repeatStr += getRepeatCode(repeatCount, char) + repeatStr += getRepeatCode(repeatCount-419, char) repeatCount = 419 } @@ -88,118 +83,89 @@ func getRepeatCode(repeatCount int, char string) string { repeatStr += string(lowString[low]) } - repeatStr += char - - return repeatStr + return repeatStr + char } -// CompressASCII compresses the ASCII data of a ZPL Graphic Field using RLE -func CompressASCII(in string) string { - var curChar string - var lastChar string - var lastCharSince int - var output string - var repCode string - - for i := 0; i < len(in)+1; i++ { - if i == len(in) { - curChar = "" - if lastCharSince == 0 { - switch lastChar { - case "0": - output = "," - return output - case "F": - output = "!" - return output - } - } - } else { - curChar = string(in[i]) +// CompressASCII compresses the ASCII data of a ZPL Graphic Field using RLE. +func CompressASCII(input string) string { + var output, lastChar, repCode string + lastCharSince := 0 + + for i := 0; i < len(input)+1; i++ { + curChar := "" + if i < len(input) { + curChar = string(input[i]) } + if lastChar != curChar { if i-lastCharSince > 4 { repCode = getRepeatCode(i-lastCharSince, lastChar) output += repCode } else { - for j := 0; j < i-lastCharSince; j++ { - output += lastChar - } + output += strings.Repeat(lastChar, i-lastCharSince) } - lastChar = curChar lastCharSince = i } + + if curChar == "" && lastCharSince == 0 { + switch lastChar { + case "0": + return "," + case "F": + return "!" + } + } } if output == "" { - output += getRepeatCode(len(in), lastChar) + output += getRepeatCode(len(input), lastChar) } return output } -// ConvertToGraphicField converts an image.Image picture to a ZPL compatible Graphic Field. -// The ZPL ^GF (Graphic Field) supports various data formats, this package supports the -// normal ASCII encoded, as well as a RLE compressed ASCII format. It also supports the -// Binary Graphic Field format. The encoding can be chosen by the second argument. +// ConvertToGraphicField converts an image.Image to a ZPL compatible Graphic Field. func ConvertToGraphicField(source image.Image, graphicType GraphicType) string { - var gfType string - var lastLine string + var gfType, lastLine, graphicFieldData string size := source.Bounds().Size() - width := size.X / 8 + width := (size.X + 7) / 8 // round up division height := size.Y - if size.Y%8 != 0 { - width = width + 1 - } - - var GraphicFieldData string for y := 0; y < size.Y; y++ { line := make([]uint8, width) - lineIndex := 0 - index := uint8(0) - currentByte := line[lineIndex] for x := 0; x < size.X; x++ { - index = index + 1 - p := source.At(x, y) - lum := color.Gray16Model.Convert(p).(color.Gray16) - if lum.Y < math.MaxUint16/2 { - currentByte = currentByte | (1 << (8 - index)) + if x%8 == 0 { + line[x/8] = 0 } - if index >= 8 { - line[lineIndex] = currentByte - lineIndex++ - if lineIndex < len(line) { - currentByte = line[lineIndex] - } - index = 0 + if lum := color.Gray16Model.Convert(source.At(x, y)).(color.Gray16).Y; lum < math.MaxUint16/2 { + line[x/8] |= 1 << (7 - uint(x)%8) } } - hexstr := strings.ToUpper(hex.EncodeToString(line)) - + hexStr := strings.ToUpper(hex.EncodeToString(line)) switch graphicType { case ASCII: - GraphicFieldData += fmt.Sprintln(hexstr) + graphicFieldData += fmt.Sprintln(hexStr) case CompressedASCII: - curLine := CompressASCII(hexstr) + curLine := CompressASCII(hexStr) if lastLine == curLine { - GraphicFieldData += ":" + graphicFieldData += ":" } else { - GraphicFieldData += curLine + graphicFieldData += curLine } lastLine = curLine case Binary: - GraphicFieldData += fmt.Sprintf("%s", line) + graphicFieldData += string(line) } } - if graphicType == ASCII || graphicType == CompressedASCII { + switch graphicType { + case ASCII, CompressedASCII: gfType = "A" - } else if graphicType == Binary { + case Binary: gfType = "B" } - return fmt.Sprintf("^GF%s,%d,%d,%d,\n%s", gfType, len(GraphicFieldData), width*height, width, GraphicFieldData) + return fmt.Sprintf("^GF%s,%d,%d,%d,\n%s", gfType, len(graphicFieldData), width*height, width, graphicFieldData) } diff --git a/zplgfa_test.go b/zplgfa_test.go index 59396c7..4493060 100644 --- a/zplgfa_test.go +++ b/zplgfa_test.go @@ -23,73 +23,80 @@ type zplTest struct { var zplTests []zplTest func init() { - jsonstr, _ := ioutil.ReadFile("./tests/tests.json") - json.Unmarshal(jsonstr, &zplTests) + jsonstr, err := ioutil.ReadFile("./tests/tests.json") + if err != nil { + log.Fatalf("Failed to read test cases: %s", err) + } + if err := json.Unmarshal(jsonstr, &zplTests); err != nil { + log.Fatalf("Failed to unmarshal test cases: %s", err) + } } func Test_CompressASCII(t *testing.T) { if str := CompressASCII("FFFFFFFF000000"); str != "NFL0" { - t.Fatalf("CompressASCII failed") + t.Fatalf("CompressASCII failed: got %s, want NFL0", str) } } func Test_ConvertToZPL(t *testing.T) { - var graphicType GraphicType for i, testcase := range zplTests { - filename, zplstring, graphictype := testcase.Filename, testcase.Zplstring, testcase.Graphictype - // open file - file, err := os.Open(filename) - if err != nil { - log.Printf("Warning: could not open the file \"%s\": %s\n", filename, err) - return - } - - defer file.Close() + t.Run(testcase.Filename, func(t *testing.T) { + testConvertToZPL(t, testcase, i) + }) + } +} - // load image head information - config, format, err := image.DecodeConfig(file) - if err != nil { - log.Printf("Warning: image not compatible, format: %s, config: %v, error: %s\n", format, config, err) - } +func testConvertToZPL(t *testing.T, testcase zplTest, index int) { + file, err := os.Open(testcase.Filename) + if err != nil { + t.Fatalf("Failed to open file %s: %s", testcase.Filename, err) + } + defer file.Close() - // reset file pointer to the beginning of the file - file.Seek(0, 0) + _, _, err = image.DecodeConfig(file) + if err != nil { + t.Fatalf("Failed to decode config for file %s: %s", testcase.Filename, err) + } - // load and decode image - img, _, err := image.Decode(file) - if err != nil { - log.Printf("Warning: could not decode the file, %s\n", err) - return - } + if _, err := file.Seek(0, 0); err != nil { + t.Fatalf("Failed to reset file pointer for %s: %s", testcase.Filename, err) + } - // flatten image - flat := FlattenImage(img) + img, _, err := image.Decode(file) + if err != nil { + t.Fatalf("Failed to decode image for file %s: %s", testcase.Filename, err) + } - // convert image to zpl compatible type - switch graphictype { - case "ASCII": - graphicType = ASCII - case "Binary": - graphicType = Binary - case "CompressedASCII": - graphicType = CompressedASCII - default: - graphicType = CompressedASCII - } + flat := FlattenImage(img) + graphicType := parseGraphicType(testcase.Graphictype) + gfimg := ConvertToZPL(flat, graphicType) - gfimg := ConvertToZPL(flat, graphicType) + if graphicType == Binary { + gfimg = base64.StdEncoding.EncodeToString([]byte(gfimg)) + } else { + gfimg = cleanZPLString(gfimg) + } - if graphictype == "Binary" { - gfimg = base64.StdEncoding.EncodeToString([]byte(gfimg)) - } else { - // remove whitespace - only for the test - gfimg = strings.Replace(gfimg, " ", "", -1) - gfimg = strings.Replace(gfimg, "\n", "", -1) - } + if gfimg != testcase.Zplstring { + t.Fatalf("Testcase %d ConvertToZPL failed for file %s: \nExpected: \n%s\nGot: \n%s\n", index, testcase.Filename, testcase.Zplstring, gfimg) + } +} - if gfimg != zplstring { - log.Printf("ConvertToZPL Test for file \"%s\" failed, wanted: \n%s\ngot: \n%s\n", filename, zplstring, gfimg) - t.Fatalf("Testcase %d ConvertToZPL failed", i) - } +func parseGraphicType(graphicTypeStr string) GraphicType { + switch graphicTypeStr { + case "ASCII": + return ASCII + case "Binary": + return Binary + case "CompressedASCII": + return CompressedASCII + default: + return CompressedASCII } } + +func cleanZPLString(zpl string) string { + zpl = strings.ReplaceAll(zpl, " ", "") + zpl = strings.ReplaceAll(zpl, "\n", "") + return zpl +}