Skip to content

Commit f2ec08e

Browse files
committed
feat: windows image printing for label printer
1 parent c5bec20 commit f2ec08e

File tree

2 files changed

+163
-1
lines changed

2 files changed

+163
-1
lines changed

cmd/app/window-direct.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ import (
1212
// available printing types. This will only be build
1313
// and executed on Windows systems.
1414
func init() {
15-
serverOptions = append(serverOptions, server.WithPrinter(&windows.Direct{}))
15+
serverOptions = append(serverOptions, server.WithPrinter(&windows.Direct{}), server.WithPrinter(&windows.Image{}))
1616
}

printing/windows/image.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Package windows provides printing for Sales & Dungeons
2+
// via direct printing on windows. The printer commands will
3+
// be written directly to the printer as raw document.
4+
package windows
5+
6+
import (
7+
"fmt"
8+
"image"
9+
"image/color"
10+
"image/draw"
11+
"image/png"
12+
"io/ioutil"
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
17+
"github.com/alexbrainman/printer"
18+
)
19+
20+
type Image struct{}
21+
22+
func (dp *Image) Name() string {
23+
return "Windows Image Printing"
24+
}
25+
26+
func (dp *Image) Description() string {
27+
return "Print as image to a attached printer. Use the Name of the printer as endpoint. Can be used for label printing. Make sure to setup the right paper size in the printer driver."
28+
}
29+
30+
func (dp *Image) AvailableEndpoints() (map[string]string, error) {
31+
names, err := printer.ReadNames()
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
available := map[string]string{}
37+
for i := range names {
38+
available[names[i]] = names[i]
39+
}
40+
41+
return available, nil
42+
}
43+
44+
func printPNGWithPowerShell(imagePath, printerName string) error {
45+
imgAbs, err := filepath.Abs(imagePath)
46+
if err != nil {
47+
return fmt.Errorf("abs image path: %w", err)
48+
}
49+
50+
psScript := `
51+
param(
52+
[Parameter(Mandatory = $true)][string]$ImagePath,
53+
[Parameter(Mandatory = $true)][string]$PrinterName
54+
)
55+
56+
Add-Type -AssemblyName System.Drawing
57+
58+
$img = [System.Drawing.Image]::FromFile($ImagePath)
59+
$pd = New-Object System.Drawing.Printing.PrintDocument
60+
$pd.PrinterSettings.PrinterName = $PrinterName
61+
$pd.OriginAtMargins = $false
62+
$pd.DefaultPageSettings.Margins = New-Object System.Drawing.Printing.Margins(0,0,0,0)
63+
$pd.PrintController = New-Object System.Drawing.Printing.StandardPrintController
64+
65+
$pd.add_PrintPage({
66+
param($sender, $e)
67+
68+
$page = $e.PageBounds
69+
70+
# Image size
71+
$iw = [double]$img.Width
72+
$ih = [double]$img.Height
73+
74+
# Page size (device units)
75+
$pw = [double]$page.Width
76+
$ph = [double]$page.Height
77+
78+
$scale = $pw / $iw
79+
$dw = [int]$pw
80+
$dh = [int]([Math]::Round($ih * $scale))
81+
82+
$dx = $page.X
83+
$dy = $page.Y
84+
85+
$e.Graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
86+
$e.Graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
87+
$e.Graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
88+
$e.Graphics.CompositingQuality= [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
89+
90+
$e.Graphics.DrawImage($img, $dx, $dy, $dw, $dh)
91+
})
92+
93+
try {
94+
$pd.Print()
95+
}
96+
finally {
97+
if ($img) { $img.Dispose() }
98+
if ($pd) { $pd.Dispose() }
99+
}
100+
`
101+
execPath, err := os.Executable()
102+
if err != nil {
103+
return fmt.Errorf("get executable path: %w", err)
104+
}
105+
106+
scriptPath := filepath.Join(filepath.Dir(execPath), "print_image.ps1")
107+
if err := os.WriteFile(scriptPath, []byte(psScript), 0644); err != nil {
108+
return fmt.Errorf("create script file: %w", err)
109+
}
110+
111+
cmd := exec.Command(
112+
"powershell.exe",
113+
"-NoProfile",
114+
"-NonInteractive",
115+
"-ExecutionPolicy", "Bypass",
116+
"-File", scriptPath,
117+
imgAbs,
118+
printerName,
119+
)
120+
121+
out, err := cmd.CombinedOutput()
122+
if err != nil {
123+
return fmt.Errorf("powershell failed: %v\nOutput:\n%s", err, string(out))
124+
}
125+
126+
return nil
127+
}
128+
129+
func (dp *Image) Print(printerEndpoint string, img image.Image, data []byte) error {
130+
file, err := ioutil.TempFile("", "print_*.png")
131+
if err != nil {
132+
return err
133+
}
134+
135+
padding := 30
136+
bounds := img.Bounds()
137+
paddedImg := image.NewRGBA(image.Rect(0, 0, bounds.Dx()+padding*2, bounds.Dy()+padding*2))
138+
139+
white := color.RGBA{255, 255, 255, 255}
140+
for y := 0; y < paddedImg.Bounds().Dy(); y++ {
141+
for x := 0; x < paddedImg.Bounds().Dx(); x++ {
142+
paddedImg.Set(x, y, white)
143+
}
144+
}
145+
146+
draw.Draw(paddedImg, image.Rect(padding, padding, bounds.Dx()+padding, bounds.Dy()+padding), img, bounds.Min, draw.Src)
147+
148+
defer func() {
149+
_ = os.Remove(file.Name())
150+
}()
151+
defer file.Close()
152+
153+
if err := png.Encode(file, paddedImg); err != nil {
154+
return fmt.Errorf("failed to encode image: %w", err)
155+
}
156+
157+
if err := file.Close(); err != nil {
158+
return err
159+
}
160+
161+
return printPNGWithPowerShell(file.Name(), printerEndpoint)
162+
}

0 commit comments

Comments
 (0)