[
  {
    "path": ".gitignore",
    "content": "*.pdf\nqtbox\npdfinverter\nicon_1024x1024.png\n\n"
  },
  {
    "path": "README.md",
    "content": "### PDFINVERTER - darken (or lighten) a PDF\n\nPDFInverter (GUI and CLI) will create a new PDF at the specified\nlocation from a source PDF. All colors will be inverted (original shown on left):\n\n<img src=\"https://user-images.githubusercontent.com/30498791/166346009-2b635dda-3c79-4557-9a7b-20f5bb64f075.png\" alt=\"example1\"><br>\n<img src=\"https://user-images.githubusercontent.com/30498791/166346010-9d05b846-c924-4012-9693-928eafbc2a83.png\" alt=\"example2\"><br>\n<img src=\"https://user-images.githubusercontent.com/30498791/166346011-c470d255-602c-4379-a8bd-bc0e8a2085ed.png\" alt=\"example3\"><br>\n\n\nUnfortunately page links are not preserved, but this program will darken PDFs making them suitable for night reading.\n\n\nA 2-3 page PDF will invert very quickly. However a 400 page PDF may take 3-4 minutes.\n\n\nThis project should build on any platform with <a href=\"https://github.com/gographics/imagick\">ImageMagick bindings</a> for Golang. <code>export CGO_CFLAGS_ALLOW='-Xpreprocessor'</code> may need to be executed to run/build.\n\n\n\nThe GUI is developed with Golang QT bindings:\n<img src=\"https://user-images.githubusercontent.com/30498791/166346008-b40e110c-9fb9-4ca1-9434-0e1f5a330171.png\" alt=\"example2\">\n\n\n###### Get the project and build:\n<pre>\n  <code>\ngit clone https://github.com/rootVIII/pdfinverter.git\ncd &lt;project root&gt;\ngo build -o bin/pdfinverter\n./bin/pdfinverter \n  </code>\n</pre>\n\n\n###### command-line usage:\n<pre>\n  <code>\n# Required\n-i     input PDF file path\n-o     output PDF file path\n\nNote:  If no command line arguments are provided, the GUI version will open.\n  </code>\n</pre>\n\n<hr>\nThis project was developed on macOS Big Sur 11.0.1\n"
  },
  {
    "path": "bin/.gitignore",
    "content": "*\n*/\n!.gitignore"
  },
  {
    "path": "go.mod",
    "content": "module pdfinverter\n\ngo 1.18\n\nrequire (\n\tgithub.com/gen2brain/go-fitz v1.19.0 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect\n\tgithub.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d // indirect\n\tgopkg.in/gographics/imagick.v3 v3.4.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/gen2brain/go-fitz v1.19.0 h1:tXuT5dpsxPNn7LS8eGv2uQ04EviyGb/o2AdEr1e4W0M=\ngithub.com/gen2brain/go-fitz v1.19.0/go.mod h1:UZAxMETTDK4UPpuh80HaRpPzgkSibUihXVzwj2ip5oQ=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=\ngithub.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=\ngithub.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngopkg.in/gographics/imagick.v3 v3.4.0 h1:kSnbsXOWofo81VJEn/Hw8w3qqoOrfTyWwjAQzSdtPlg=\ngopkg.in/gographics/imagick.v3 v3.4.0/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=\n"
  },
  {
    "path": "inverter/cli.go",
    "content": "package inverter\n\n// rootVIII 2020\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// CLI embeds/inherits App type and controls CLI application startup & processing.\ntype CLI struct {\n\tApp\n}\n\n// RunApp inverts a pdf based on cmd-line arguments.\nfunc (cli *CLI) RunApp() {\n\tcli.extractImage()\n\tfiles, _ := ioutil.ReadDir(cli.TmpDir)\n\tfor _, batch := range chunk(files) {\n\t\tvar wg sync.WaitGroup\n\t\tfor _, fileName := range batch {\n\t\t\tif !strings.Contains(fileName, \"out-\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\twg.Add(1)\n\t\t\tgo cli.imageRoutine(fileName, &wg)\n\t\t\tcli.imgCount++\n\t\t}\n\t\twg.Wait()\n\t}\n\n\tcli.writePDF()\n\n}\n"
  },
  {
    "path": "inverter/gui.go",
    "content": "package inverter\n\n// rootVIII 2020\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/therecipe/qt/core\"\n\t\"github.com/therecipe/qt/gui\"\n\t\"github.com/therecipe/qt/widgets\"\n)\n\n// GUI embeds App Type and controls GUI application startup & processing.\ntype GUI struct {\n\tApp\n\twindow                    *widgets.QMainWindow\n\tinputTextBox              *widgets.QLineEdit\n\toutputTextBox             *widgets.QLineEdit\n\tstatusLabel               *widgets.QLabel\n\tuserInfo                  *user.User\n\tworkingCount, statusCount uint8\n\thaveStatus, runningJob    bool\n\tworkingTitle              string\n}\n\n// openPDFInput opens the PDF that needs to be inverted.\nfunc (g *GUI) openPDFInput() {\n\tif !g.runningJob {\n\t\tg.PDFIn = widgets.QFileDialog_GetOpenFileName(\n\t\t\tg.window, \"Open PDF\", g.userInfo.HomeDir,\n\t\t\t\"(*.pdf)\", \"\", widgets.QFileDialog__DontUseNativeDialog)\n\t\tg.inputTextBox.SetText(g.PDFIn)\n\t} else {\n\t\tg.statusLabel.SetText(\"Job is currently running... please wait\")\n\t}\n}\n\n// openPDFOutput sets the path for the output PDF..\nfunc (g *GUI) openPDFOutput() {\n\tif !g.runningJob {\n\t\tg.PDFOut = widgets.QFileDialog_GetSaveFileName(\n\t\t\tg.window, \"Save\", g.userInfo.HomeDir, \"\", \"\",\n\t\t\twidgets.QFileDialog__DontUseNativeDialog)\n\t\tg.outputTextBox.SetText(g.PDFOut)\n\t} else {\n\t\tg.statusLabel.SetText(\"Job is currently running... please wait\")\n\t}\n}\n\n// invert signals to the background go-routine that a new job is ready to be processed.\nfunc (g *GUI) invert() {\n\tg.PDFIn = g.inputTextBox.Text()\n\tg.PDFOut = g.outputTextBox.Text()\n\terr := g.shouldExecute()\n\tif err != nil {\n\t\tg.statusLabel.SetText(err.Error())\n\t} else {\n\t\tg.runningJob = true\n\t}\n}\n\n// resetGUI sets the GUI fields and attributes back to default if a job is not running.\n// Otherwise the user is warned that a job is currently being processed.\nfunc (g *GUI) resetGUI() {\n\tif !g.runningJob {\n\t\tg.reset()\n\t} else {\n\t\tg.statusLabel.SetText(\"Job is currently running... please wait\")\n\t}\n}\n\n// reset the gui and variables to inital/empty values.\nfunc (g *GUI) reset() {\n\tg.PDFIn, g.PDFOut = \"\", \"\"\n\tg.inputTextBox.SetText(g.PDFIn)\n\tg.outputTextBox.SetText(g.PDFOut)\n\tg.imgCount, g.workingCount = 0, 0\n\tg.runningJob = false\n\tg.window.SetWindowTitle(\"\")\n}\n\nfunc (g GUI) shouldExecute() error {\n\tif g.runningJob {\n\t\treturn fmt.Errorf(\"Job is currently running\")\n\t}\n\tif len(g.PDFIn) < 5 || strings.ToLower(g.PDFIn[len(g.PDFIn)-4:]) != \".pdf\" {\n\t\treturn fmt.Errorf(\"input file must have .pdf extension and MIME type\")\n\t}\n\tif len(g.PDFOut) < 1 {\n\t\treturn fmt.Errorf(\"invalid output file path provided\")\n\t}\n\tfileStat, err := os.Stat(g.PDFIn)\n\tif err != nil || fileStat.IsDir() {\n\t\treturn fmt.Errorf(\"invalid file provided: %v\", err)\n\t}\n\tfileStat, err = os.Stat(g.PDFOut)\n\tif err == nil && fileStat.IsDir() {\n\t\treturn fmt.Errorf(\"output path must be a .pdf file\")\n\t}\n\treturn nil\n}\n\n// clearStatus is a QTimer function that periodically clears any status message after 4 seconds.\nfunc (g *GUI) clearStatus() {\n\tif g.statusCount > 4 {\n\t\tg.statusLabel.SetText(\"\")\n\t\tg.haveStatus = false\n\t\tg.statusCount = 0\n\t}\n\tif len(g.statusLabel.Text()) > 0 {\n\t\tg.haveStatus = true\n\t\tg.statusCount++\n\t}\n}\n\n// displayStatusRunning is a QTimer() method that\n// animates the title bar during processing.\nfunc (g *GUI) displayStatusRunning() {\n\tif g.runningJob {\n\t\tif g.workingCount > 10 {\n\t\t\tg.workingCount = 0\n\t\t} else {\n\t\t\tg.window.SetWindowTitle(g.workingTitle[:g.workingCount])\n\t\t\tg.workingCount++\n\t\t}\n\t}\n}\n\n// removePNGs cleans up old PNGs after converting a PDF from within goroutine.\nfunc (g GUI) removePNGs() {\n\tcontents, _ := ioutil.ReadDir(g.TmpDir)\n\tfor _, png := range contents {\n\t\tif strings.Contains(png.Name(), \"out\") {\n\t\t\terr := os.Remove(fmt.Sprintf(\"%s%s\", g.TmpDir, png.Name()))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// RunApp runs the GUI version of the app. Check for new jobs and process\n// found jobs in a single background goroutine to prevent the hanging GUI\n// and possible spinning beach-ball for larger-sized PDFs.\nfunc (g *GUI) RunApp() {\n\tgo func() {\n\t\tfor {\n\t\t\tif !g.runningJob {\n\t\t\t\ttime.Sleep(time.Millisecond * 500)\n\t\t\t} else {\n\t\t\t\tg.extractImage()\n\t\t\t\tfiles, _ := ioutil.ReadDir(g.TmpDir)\n\t\t\t\tfor _, batch := range chunk(files) {\n\t\t\t\t\tvar wg sync.WaitGroup\n\t\t\t\t\tfor _, fileName := range batch {\n\t\t\t\t\t\tif !strings.Contains(fileName, \"out-\") {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\twg.Add(1)\n\t\t\t\t\t\tgo g.imageRoutine(fileName, &wg)\n\t\t\t\t\t\tg.imgCount++\n\t\t\t\t\t}\n\t\t\t\t\twg.Wait()\n\t\t\t\t}\n\n\t\t\t\tg.writePDF()\n\t\t\t\tg.statusLabel.SetText(fmt.Sprintf(\"%s created\", filepath.Base(g.PDFOut)))\n\t\t\t\tg.reset()\n\t\t\t\tg.removePNGs()\n\t\t\t\tg.runningJob = false\n\t\t\t}\n\t\t}\n\t}()\n\n\tuserInfo, err := user.Current()\n\tif err != nil {\n\t\tpanic(\"Unable to extract username of current user\")\n\t}\n\tg.userInfo = userInfo\n\tg.workingTitle = \"working...\"\n\n\tui := widgets.NewQApplication(len(os.Args), os.Args)\n\n\tg.window = widgets.NewQMainWindow(nil, 0)\n\tg.window.SetMinimumSize2(600, 250)\n\tg.window.SetMaximumSize2(600, 250)\n\tg.window.SetWindowTitle(\"\")\n\n\th1 := widgets.NewQHBoxLayout()\n\th2 := widgets.NewQHBoxLayout()\n\th3 := widgets.NewQHBoxLayout()\n\th4 := widgets.NewQHBoxLayout()\n\th5 := widgets.NewQHBoxLayout()\n\tv := widgets.NewQVBoxLayout()\n\n\ttitle := widgets.NewQGraphicsScene(nil)\n\ttitle.AddText(\"P D F   I N V E R T E R\", gui.NewQFont2(\"Menlo\", 20, 1, false))\n\tview := widgets.NewQGraphicsView(nil)\n\tview.SetScene(title)\n\n\ttimer1 := core.NewQTimer(g.window)\n\ttimer1.ConnectTimeout(func() { g.clearStatus() })\n\ttimer1.Start(1000)\n\ttimer2 := core.NewQTimer(g.window)\n\ttimer2.ConnectTimeout(func() { g.displayStatusRunning() })\n\ttimer2.Start(500)\n\n\tinputLabel := widgets.NewQLabel(nil, 0)\n\tinputLabel.SetText(\"Input PDF:\")\n\tg.inputTextBox = widgets.NewQLineEdit(nil)\n\tg.inputTextBox.SetPlaceholderText(\"None Selected\")\n\tg.inputTextBox.SetFixedWidth(400)\n\tg.inputTextBox.SetStyleSheet(\"color: #00FFFF\")\n\tinputButton := widgets.NewQPushButton2(\"Browse\", nil)\n\tinputButton.ConnectClicked(func(bool) { g.openPDFInput() })\n\toutputLabel := widgets.NewQLabel(nil, 0)\n\toutputLabel.SetText(\"Output PDF:\")\n\tg.outputTextBox = widgets.NewQLineEdit(nil)\n\tg.outputTextBox.SetPlaceholderText(\"None Selected\")\n\tg.outputTextBox.SetFixedWidth(400)\n\tg.outputTextBox.SetStyleSheet(\"color: #00FFFF\")\n\toutputButton := widgets.NewQPushButton2(\"Browse\", nil)\n\toutputButton.ConnectClicked(func(bool) { g.openPDFOutput() })\n\tresetButton := widgets.NewQPushButton2(\"Reset\", nil)\n\tresetButton.ConnectClicked(func(bool) { g.resetGUI() })\n\tinvertButton := widgets.NewQPushButton2(\"Invert\", nil)\n\tinvertButton.ConnectClicked(func(bool) { g.invert() })\n\tg.statusLabel = widgets.NewQLabel(nil, 0)\n\tg.statusLabel.SetText(fmt.Sprintf(\"Greetings %s\", g.userInfo.Username))\n\n\th1.Layout().AddWidget(view)\n\th2.Layout().AddWidget(inputLabel)\n\th2.Layout().AddWidget(g.inputTextBox)\n\th2.Layout().AddWidget(inputButton)\n\th3.Layout().AddWidget(outputLabel)\n\th3.Layout().AddWidget(g.outputTextBox)\n\th3.Layout().AddWidget(outputButton)\n\th4.Layout().AddWidget(resetButton)\n\th4.Layout().AddWidget(invertButton)\n\th5.Layout().AddWidget(g.statusLabel)\n\n\tfor _, layout := range []*widgets.QHBoxLayout{h1, h2, h3, h4, h5} {\n\t\tv.AddLayout(layout, 0)\n\t}\n\n\twidget := widgets.NewQWidget(nil, 0)\n\twidget.SetLayout(v)\n\tg.window.SetCentralWidget(widget)\n\tg.window.Show()\n\tui.Exec()\n}\n"
  },
  {
    "path": "inverter/pdfinverter.go",
    "content": "package inverter\n\n// rootVIII 2020\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\t\"image/color\"\n\t\"strconv\"\n\t\"sync\"\n\n\t\"github.com/gen2brain/go-fitz\"\n\t\"gopkg.in/gographics/imagick.v3/imagick\"\n)\n\n// PDFInverter provides an interface to the CLI and GUI types.\ntype PDFInverter interface {\n\timageRoutine(imgName string, wg *sync.WaitGroup)\n\textractImage()\n\titerImage(imgName string)\n\twritePDF()\n\tRunApp()\n}\n\n// App controls CLI and GUI application startup & processing.\ntype App struct {\n\tTmpDir, PDFIn, PDFOut string\n\timgCount              int\n}\n\n// imageRoutine inverts the image within a goroutine.\nfunc (app *App) imageRoutine(imgName string, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tapp.iterImage(imgName)\n}\n\n// extractImage extracts PNG images from the input PDF using fitz.\nfunc (app App) extractImage() {\n\tdoc, err := fitz.New(app.PDFIn)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer doc.Close()\n\n\tfor pageCount := 0; pageCount < doc.NumPage(); pageCount++ {\n\t\tcurrentImg, err := doc.Image(pageCount)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\twritePNG(fmt.Sprintf(\"%sout-%06d.png\", app.TmpDir, pageCount), currentImg)\n\t}\n}\n\n// iterImage examines each row of pixels in a PNG while creating a new image.\nfunc (app App) iterImage(imgName string) {\n\tpathPNG := fmt.Sprintf(\"%s%s\", app.TmpDir, imgName)\n\tcurrentPNG := readPNG(pathPNG)\n\tperimeter := currentPNG.Bounds()\n\twidth, height := perimeter.Max.X, perimeter.Max.Y\n\trevised := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{width, height}})\n\tvar currentPixel color.Color\n\tfor y := 0; y < height; y++ {\n\t\tfor x := 0; x < width; x++ {\n\t\t\tred, green, blue, alpha := currentPNG.At(x, y).RGBA()\n\t\t\tr, g, b, a := uint8(red), uint8(green), uint8(blue), uint8(alpha)\n\t\t\tif r == 0x7F && g == 0x7F && b == 0x7F {\n\t\t\t\tcurrentPixel = color.RGBA{0x1E, 0x1B, 0x24, a}\n\t\t\t} else {\n\t\t\t\tcurrentPixel = color.RGBA{0xFF - r, 0xFF - g, 0xFF - b, a}\n\t\t\t}\n\t\t\trevised.Set(x, y, currentPixel)\n\t\t}\n\t}\n\twritePNG(pathPNG, revised)\n}\n\n// writePDF uses write images to the PDF file.\nfunc (app *App) writePDF() {\n\n\tconvertCMD := []string{\"convert\"}\n\n\tfor index := 0; index < app.imgCount; index++ {\n\t\tvar indexString string = strconv.Itoa(index)\n\t\tvar leadingZeroes string\n\t\tfor i := 0; i < (6 - len(indexString)); i++ {\n\t\t\tleadingZeroes += \"0\"\n\t\t}\n\t\tinputPath := fmt.Sprintf(\"%sout-%s%s.png\", app.TmpDir, leadingZeroes, indexString)\n\t\tconvertCMD = append(convertCMD, inputPath)\n\t}\n\n\tconvertCMD = append(convertCMD, \"-quality\", \"100\", app.PDFOut)\n\n\timagick.Initialize()\n\tdefer imagick.Terminate()\n\n\t_, err := imagick.ConvertImageCommand(convertCMD)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "inverter/utils.go",
    "content": "package inverter\n\nimport (\n\t\"bytes\"\n\t\"image\"\n\t\"image/png\"\n\t\"io/ioutil\"\n\t\"os\"\n)\n\n// writePNG writes an inverted PNG to disk.\nfunc writePNG(path string, newIMG image.Image) {\n\tbuf := &bytes.Buffer{}\n\terr := png.Encode(buf, newIMG)\n\tif err != nil {\n\t\tpanic(err)\n\t} else {\n\t\terr = ioutil.WriteFile(path, buf.Bytes(), 0600)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n\n// readPNG reads the image to be inverted.\nfunc readPNG(path string) image.Image {\n\timgRaw, err := os.Open(path)\n\tdefer imgRaw.Close()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\timgDecoded, err := png.Decode(imgRaw)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn imgDecoded\n}\n\n// chunk breaks a slice of file names into evenly sized slices. The\n// final slice will contain the remaining filenames.\nfunc chunk(fileNames []os.FileInfo) [][]string {\n\tchunked := [][]string{}\n\tindex, chunkSize := 0, 100\n\n\tfor i := 0; i < len(fileNames)/chunkSize+1; i++ {\n\t\tsection := make([]string, chunkSize)\n\t\tfor j := 0; j < chunkSize && index < len(fileNames); j++ {\n\t\t\tsection[j] = fileNames[index].Name()\n\t\t\tindex++\n\t\t}\n\t\tchunked = append(chunked, section)\n\t}\n\treturn chunked\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/google/uuid\"\n\t\"pdfinverter/inverter\"\n)\n\n// runCLI is the entry point to the cmd-line version.\nfunc runCLI(tmpDir string) {\n\tinputFile := flag.String(\"i\", \"\", \"input file path\")\n\toutputFile := flag.String(\"o\", \"\", \"output file path\")\n\tflag.Parse()\n\tif len(*inputFile) < 1 || len(*outputFile) < 1 {\n\t\tfmt.Println(\"-i <intput PDF path> -o <output PDF path> are required\")\n\t} else if _, err := os.Stat(*inputFile); err != nil {\n\t\tfmt.Println(\"invalid file path provided for -i <input>\")\n\t} else {\n\t\tvar cliInit inverter.PDFInverter\n\t\tcliInit = &inverter.CLI{\n\t\t\tApp: inverter.App{\n\t\t\t\tTmpDir: tmpDir,\n\t\t\t\tPDFIn:  *inputFile,\n\t\t\t\tPDFOut: *outputFile,\n\t\t\t},\n\t\t}\n\t\tcliInit.RunApp()\n\t}\n}\n\n// runGUI runs the program with a QT front-end..\nfunc runGUI(tmpDir string) {\n\tvar guiInit inverter.PDFInverter\n\tguiInit = &inverter.GUI{\n\t\tApp: inverter.App{\n\t\t\tTmpDir: tmpDir,\n\t\t},\n\t}\n\tguiInit.RunApp()\n}\n\nfunc main() {\n\trandPrefix, err := uuid.NewRandom()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttmpdir, err := ioutil.TempDir(\"\", randPrefix.String())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer os.RemoveAll(tmpdir)\n\n\ttmpdir += \"/\"\n\tif len(os.Args) > 1 {\n\t\trunCLI(tmpdir)\n\t} else {\n\t\trunGUI(tmpdir)\n\t}\n}\n"
  }
]