Re: [go-nuts] Printing Documents with Go?

5,841 views
Skip to first unread message

Aaron France

unread,
May 8, 2013, 9:33:22 AM5/8/13
to Kul, golang-nuts
Hi,

All of the solutions to this are very platform-centric? Can I assume you are probably running Windows and direct you to check out any of the Win32 bindings.

Regards,
Aaron


On Wed, May 8, 2013 at 1:19 PM, Kul <kulsh...@gmail.com> wrote:
I'm currently evaluating Go to build a few tools and utilities for internal use in my company. So far, it is aces in my book with the exception of printing.
 
Despite having searched for the past week or so, I have been unable to find any example illustrating how a document can be sent to a printer (network/local). The tools that I'll be using will need the ability to print reports/vouchers/etc which could be in any format (raw/pdf/text/etc).
 
In the worst case, I would have to use another utility to send an intermediate file (generated by the utilities built with Go) to the printer but I'm hoping that there's something in Go that can help me with this.
 
I'd greatly appreciate any pointers or a nudge in the right direction.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Andy Balholm

unread,
May 8, 2013, 10:19:57 AM5/8/13
to golan...@googlegroups.com
If you're not running Windows, most Unix variants have a command called lp or lpr that sends data to the printer. Pipe your output to lp, and it gets printed.

Plain text should always work. If your lp command comes from CUPS (as it does on Mac OS and many Linux distros), it will also accept PostScript or PDF input. You can generate PDFs with bitbucket.org/zombiezen/gopdf. It works well, but currently it does not support custom fonts or non-ASCII text.

Kul

unread,
May 8, 2013, 10:45:03 AM5/8/13
to golan...@googlegroups.com, Kul
Yes, I intend to use these utilities on the Windows platform (as of now)
 
I have written the equivalent program using Win32 bindings for the printer but I'm not sure how to approach this using Go. If I can do the same thing using Go, I'll be able to internalize this issue in the utility and avoid any dependencies on external utilities.
 
I guess I'll start looking for examples of Go which make use of Win32 calls.
 
Thanks for the pointer :)

Kul

unread,
May 8, 2013, 10:50:13 AM5/8/13
to golan...@googlegroups.com
I spent half a day playing around with gopdf and I have to admit it's a great package (admittedly with a few shortcomings as you pointed out). I'm currently trying to get it to work with custom fonts. Hopefully, I should be able to get it working sometime soon. As requirements grow, I'm sure I'll have to incorporate the ability to create PDFs in the utilities.
 
I'm wondering whether document processing is ever going to be a part of Go. But that's a discussion for another thread :)

Mateusz Czapliński

unread,
May 8, 2013, 1:17:20 PM5/8/13
to golan...@googlegroups.com, Kul
On Wednesday, May 8, 2013 4:45:03 PM UTC+2, Kul wrote:
I have written the equivalent program using Win32 bindings for the printer but I'm not sure how to approach this using Go.
I guess I'll start looking for examples of Go which make use of Win32 calls.

In a windows build of Go, have a look first at:
  godoc syscall LazyDLL
  godoc syscall LazyProc
and how they're used in the Go codebase.

Also note the https://github.com/mattn/go-ole package which may be useful should you need to interact with OLE/COM.

/M.

Ross Light

unread,
May 8, 2013, 1:27:20 PM5/8/13
to Kul, golan...@googlegroups.com
If you get custom fonts working in gopdf, please send a pull request.  Many people have requested that feature.

Ross Light | Software Engineer | li...@google.com | 661-375-7677


--

brainman

unread,
May 8, 2013, 8:30:53 PM5/8/13
to golan...@googlegroups.com, Kul
On Thursday, 9 May 2013 00:45:03 UTC+10, Kul wrote:

>  I have written the equivalent program using Win32 bindings for the printer but I'm not sure how to approach this using Go. ...

As others suggested, you could call functions inside of windows DLL. Here http://play.golang.org/p/KjypOYjzTD is I played with windows memory just the other day. Or http://play.golang.org/p/bFA02H1cwE determining windows workstation domain status. It is bit dangerous, because, if you make mistake defining your syscall parameters, windows will write to wrong address, and you will end up with corrupted memory. But once you got things right it will work just fine. So, if you could write it in C, you should be able to translate it into Go easily enough.

Alternatively, as Andy suggested, you could use "os/exec" package to run any external windows program of your choice to do your job under covers. You could communicate with that program by passing command line parameters, setting environment variables and passing data via disk files or stdin/stdout/stderr. You could gather numeric exit code when the program exits. As to files, you could create them in temp directory and remove them once external program finished.

Just ask for help here, if you get stuck.

Alex

Kul

unread,
May 9, 2013, 3:08:38 AM5/9/13
to golan...@googlegroups.com, Kul
Ross,
 
I'll surely do that if I can implement the usage of custom fonts with gopdf.
 
Alex,
 
Thanks a lot for the sample code. These have definitely helped me get started.
 
I'm going to be using the GetDefaultPrinterW, OpenPrinterW, ClosePrinter, StartDocPrinterW, EndDocPrinter, StartPagePrinter, EndPagePrinter and WritePrinter calls to implement the printing tasks.
 
So far I have been able to implement GetDefaultPrinterW successfully using the following code:
 
var(
 dll = syscall.MustLoadDLL("winspool.drv")
 getDefaultPrinter = dll.MustFindProc("GetDefaultPrinterW")
)
 
func getDefaultPrinterName() (string, []uint16){
 var pn[256] uint16
 plen := len(pn)
 getDefaultPrinter.Call(uintptr(unsafe.Pointer(&pn)), uintptr(unsafe.Pointer(&plen)))
 printerName := syscall.UTF16ToString(pn[:])
 return printerName, syscall.StringToUTF16(printerName)
}
 
I'll add error handling later but this function is now fetching the default printer name correctly.
 
However, I am now stuck at OpenPrinterW for several hours now. I just keep getting the message "The printer name is invalid". I'm using the printer name as it is being returned from the above function. I have testing both types - string & []uint16 but the result is the same. Following is my implementation of this function:
 
openPrinter = dll.MustFindProc("OpenPrinterW")
 
func openPrinterFunc(printerName string, printerName16 []uint16){
 var printerHandle uintptr
 r,e1,e2 := openPrinter.Call(uintptr(unsafe.Pointer(&printerName)), uintptr(unsafe.Pointer(&printerHandle)), 0)
 fmt.Println(r)
 fmt.Println(e1)
 fmt.Println(e2)
}
 
The function returns the error "The printer name is invalid."
 
If I replace uintptr(unsafe.Pointer(&printerHandle)) in the above function call with printerHandle, the function returns the error "The parameter is incorrect" along with code 87
 
I'll keep working on this and post the solution as and when I find it!
 
-Kul

brainman

unread,
May 9, 2013, 3:30:29 AM5/9/13
to golan...@googlegroups.com, Kul
On Thursday, 9 May 2013 17:08:38 UTC+10, Kul wrote:
>    
>    openPrinter = dll.MustFindProc("OpenPrinterW")
>    
>    func openPrinterFunc(printerName string, printerName16 []uint16){
>
>         var printerHandle uintptr
>         r,e1,e2 := openPrinter.Call(uintptr(unsafe.Pointer(&printerName)), uintptr(unsafe.Pointer(&printerHandle)), 0)
>         fmt.Println(r)
>         fmt.Println(e1)
>         fmt.Println(e2)
>
>    }
>    
>    The function returns the error "The printer name is invalid."

I suspect your OpenPrinter first parameter is wrong. The doco says "A pointer to a null-terminated string that specifies the name of the printer or print server". Since you use W version of OpenPrinter (OpenPrinterW), you should give it array of uint16s with 0 at the end. So, your code is no good, because you are passing address of Go string, which , I suspect, contains internal Go structure and not string data itself. But even string data is no good, because it would be utf8 encoded (probably ascii in your case). Try using syscall.UTF16PtrFromString instead.

Show us your code. That way we could just run it here and adjust appropriately.

Alex

Kul

unread,
May 9, 2013, 3:39:55 AM5/9/13
to golan...@googlegroups.com, Kul


I suspect your OpenPrinter first parameter is wrong. The doco says "A pointer to a null-terminated string that specifies the name of the printer or print server". Since you use W version of OpenPrinter (OpenPrinterW), you should give it array of uint16s with 0 at the end. So, your code is no good, because you are passing address of Go string, which , I suspect, contains internal Go structure and not string data itself. But even string data is no good, because it would be utf8 encoded (probably ascii in your case). Try using syscall.UTF16PtrFromString instead.

Show us your code. That way we could just run it here and adjust appropriately.

Alex
 That's what I suspected too and tried using &printerName16 instead of &printerName but the results were the same. (It is to test this that the function openPrinterFunc takes two parameters.
 
Here are the contents of these two variables:
 
string: Canon MP250 series Printer
uint16[]: [67 97 110 111 110 32 77 80 50 53 48 32 115 101 114 105 101 115 32 80 114 105 110 116 101 114 0]
 
My complete code is just like in my earlier post but with the test statements removed. Nevertheless, here's the entire code:
 
package main
 
import(
 "fmt"
 "syscall"
 "unsafe"
)
 
var(
 dll = syscall.MustLoadDLL("winspool.drv")
 getDefaultPrinter = dll.MustFindProc("GetDefaultPrinterW")
 openPrinter = dll.MustFindProc("OpenPrinterW")
)
 
func openPrinterFunc(printerName string, printerName16 []uint16){
 var printerHandle uintptr
 r,e1,e2 := openPrinter.Call(uintptr(unsafe.Pointer(&printerName)), uintptr(unsafe.Pointer(&printerHandle)), 0)
 fmt.Println(r)
 fmt.Println(e1)
 fmt.Println(e2)
}
 
func getDefaultPrinterName() (string, []uint16){
 var pn[256] uint16
 plen := len(pn)
 r, e1, e2 := getDefaultPrinter.Call(uintptr(unsafe.Pointer(&pn)), uintptr(unsafe.Pointer(&plen)))
 r=r
 e1=e1
 e2=e2
 printerName := syscall.UTF16ToString(pn[:])
 //fmt.Println(printerName)
 //fmt.Println(syscall.StringToUTF16(printerName))
 return printerName, syscall.StringToUTF16(printerName)
}
 
func main(){
 printerName, printerName16 := getDefaultPrinterName();
 //fmt.Println(printerName)
 //fmt.Println(printerName16)
 openPrinterFunc(printerName, printerName16)
}

brainman

unread,
May 9, 2013, 8:37:35 AM5/9/13
to golan...@googlegroups.com, Kul
This http://play.golang.org/p/593HvJQcqW version works for me. You should really check errors everywhere.

Alex

Kul

unread,
May 9, 2013, 8:43:49 AM5/9/13
to golan...@googlegroups.com, Kul
Yup, that works. I have been trying to use &printerName16 when I should have been using printerName16[0]. Thank you so much :)
 
I'll post the entire working code here once it is done.

brainman

unread,
May 9, 2013, 8:47:49 AM5/9/13
to golan...@googlegroups.com, Kul
0 at the end is important. printerName16 does have 0 at the end. If you use syscall.UTF16PtrFromString, it puts 0 at the end too, as documented.

Alex

Kul

unread,
May 9, 2013, 10:16:29 AM5/9/13
to golan...@googlegroups.com, Kul
Here's the complete POC to send a file to the printer (on Windows) :
 
Notes:
  • Replace FILENAME with the relative/absolute path of the file you want to print.
  • I haven't yet taken an actual print as I don't have access to a printer right now but I can see the print count increasing in the print queue.
  • For some reason, I have been unable to set the document name (which appears in the print queue). As that doesn't really impact the functionality that I required, I haven't spent too much time on it.
 
package main
 
import(
 "syscall"
 "unsafe"
 "io/ioutil"
)
 
type DOC_INFO_1 struct{
 pDocName []byte
 pOutputFile []byte
 pDatatype []byte
}
 
var(
 dll = syscall.MustLoadDLL("winspool.drv")
 getDefaultPrinter = dll.MustFindProc("GetDefaultPrinterW")
 openPrinter = dll.MustFindProc("OpenPrinterW")
 startDocPrinter = dll.MustFindProc("StartDocPrinterW")
 startPagePrinter = dll.MustFindProc("StartPagePrinter")
 writePrinter = dll.MustFindProc("WritePrinter")
 endPagePrinter = dll.MustFindProc("EndPagePrinter")
 endDocPrinter = dll.MustFindProc("EndDocPrinter")
 closePrinter= dll.MustFindProc("ClosePrinter")
)
 
func main(){
 printerName, printerName16 := getDefaultPrinterName();
 printerHandle := openPrinterFunc(printerName, printerName16)
 startPrinter(printerHandle)
 startPagePrinter.Call(printerHandle)
 writePrinterFunc(printerHandle)
 endPagePrinter.Call(printerHandle)
 endDocPrinter.Call(printerHandle)
 closePrinter.Call(printerHandle)
}
 
func writePrinterFunc(printerHandle uintptr){
 fileContents,_ := ioutil.ReadFile(FILENAME)
 var contentLen uintptr = uintptr(len(fileContents))
 var writtenLen int
 writePrinter.Call(printerHandle, uintptr(unsafe.Pointer(&fileContents[0])),  contentLen, uintptr(unsafe.Pointer(&writtenLen)))
}
 
func startPrinter(printerHandle uintptr){
 di := DOC_INFO_1{[]byte(""), nil, []byte("RAW")}
 startDocPrinter.Call(printerHandle, 1, uintptr(unsafe.Pointer(&di)))
}
 
func openPrinterFunc(printerName string, printerName16 []uint16) uintptr{
 var printerHandle uintptr
 openPrinter.Call(uintptr(unsafe.Pointer(&printerName16[0])), uintptr(unsafe.Pointer(&printerHandle)), 0)
 return(printerHandle)
}
 
func getDefaultPrinterName() (string, []uint16){
 var pn[256] uint16
 plen := len(pn)
 getDefaultPrinter.Call(uintptr(unsafe.Pointer(&pn)), uintptr(unsafe.Pointer(&plen)))
 printerName := syscall.UTF16ToString(pn[:])
 return printerName, syscall.StringToUTF16(printerName)
}
 
A big thanks to everyone for your help.
 
Alex,
 
Thank you for taking the time to go through and correct my code,
 
-Kul

brainman

unread,
May 9, 2013, 10:36:01 PM5/9/13
to golan...@googlegroups.com, Kul
On Friday, 10 May 2013 00:16:29 UTC+10, Kul wrote:

>    
>    type DOC_INFO_1 struct{
>
>         pDocName []byte
>         pOutputFile []byte
>         pDatatype []byte
>
>    }

This is wrong. pDocName is not a slice, it is a pointer. Windows will get confused reading this.

I created little package http://godoc.org/code.google.com/p/brainman/printer to help you along. See printer_test.go on how to use it.

Alex

Argyris Prosilis

unread,
Mar 31, 2022, 3:14:31 PM3/31/22
to golang-nuts
Hello,

I'm using the package https://github.com/alexbrainman/printer but I have an issue with printing Greek characters.

Does anyone have any suggestion?

Thank you,
Argyris
Reply all
Reply to author
Forward
0 new messages