Sunday, August 12, 2012

Finding Font Filename from Font Title

Well, all was going well with my code to create custom XPS documents, when I tried to be fancy with fonts. How to point the XPS engine at the correct font file so it could be included within the document as a resource??
I cannot yet find any APIs that help with this; any help would be appreciated.  Instead I managed to look up the registry for the name of the file associated with the font.  This is a bit of a hack, as font collections contained within one file probably will mess this up.
It works, sort of, and I wrote the code that calls this routine to use arial.ttf if all else fails, so there is a back up plan.
Here is the subroutine:

Imports System.IO
Imports Microsoft.Win32
Private Function ReadFontReg(ByVal strFontName As String) As String

        'read from the registry to get the font file name
        Dim regKey As RegistryKey
        Dim strAnswer As String = ""
        Dim strValues() As String

        regKey = Registry.LocalMachine.OpenSubKey("Software\Microsoft\Windows NT\CurrentVersion\Fonts", True)
        If Not regKey Is Nothing Then
            Try
                Dim i As Integer
                strValues = regKey.GetValueNames
                For i = 0 To strValues.Length - 1
                    If strValues(i) = strFontName Or strValues(i) = strFontName & " (TrueType)" Then
                        strAnswer = regKey.GetValue(strValues(i)).ToString
                        If Not File.Exists(strAnswer) Then
                            'add on the file path to the font folder
                            strAnswer = Environment.GetFolderPath(Environment.SpecialFolder.Fonts) & "\" & strAnswer
                        End If
                        Exit For
                    End If
                Next
            Catch ex As Exception

            Finally
                regKey.Close()
            End Try
        End If

        If strAnswer = "" Then
            'take a stab anyway. Might be lucky!
            Dim strSpecialFolderFonts As String
            Dim strFullPath As String

            strSpecialFolderFonts = Environment.GetFolderPath(Environment.SpecialFolder.Fonts) & "\"
            strFullPath = strSpecialFolderFonts & strFontName & ".ttf"
            If File.Exists(strFullPath) Then
                strAnswer = strFullPath
            Else
                strFullPath = strSpecialFolderFonts & strFontName & ".ttc"
                If File.Exists(strFullPath) Then
                    strAnswer = strFullPath
                End If
            End If
        End If
        Return strAnswer
    End Function

Saturday, August 11, 2012

XPS Document Bits and Pieces

This is a bit of a stream of consciousness note; I want to get down important definitions/descriptions of the document specification.

ECMA Document defining an XPS document (a PDF file, of course! *giggle*)
http://www.ecma-international.org/activities/XML%20Paper%20Specification/XPS%20Standard.pdf

Unit of measurement withing an XPS document is ST_GEOne. Otherwise/elsewhere known as an XPS unit.
Finding a definition of this unit was challenging!  Turns out it is 1/96th of an inch.  Yay for non-metric measurement systems!

<SideBar>I live in a non-US country, so I live in metric/ISO standard land. (what is with the US and their inability to get  with the metric program?!).  Do you know an inch is defined as exactly 25.4 mm?  So the stupid old measurement system of inches is actually defined in metric units.</SideBar>

I will typically use in my documents A4 or A5 text, so in various measurement units:
A4297 x 210 mm11.7 x 8.3 in 1122.5 x 793.7 xps units 
A5210 x 148 mm8.3 x 5.8 in 793.7 x 559.4 xps units

When the FixedDocument is defined, you can set the indicated Width and Height of the pages, but these can be overruled in the individual pages of the FixedDocument.  The Width and Height also define whether the page is Portrait or Landscape, so you want to set the printer correctly to avoid clipping.

A FixedPage has the attributes of
Required: Width, Height (physical media size)
Optional: ContentBox (where the content resides) this has 4 values: ContentOriginX, ContentOriginY, ContentWidth, ContentHeight
Optional: BleedBox (BleedOriginX, BleedOriginY, BleedWidth, BleedHeight)
Required: xml:lang eg "en-US", "en-AU"
Optional: Name (used if want to reference the FixedPage elsewhere by name)

Anyway, time to start coding my own XPS creation engine...






Creating Your Own XPS Document

Well, I cobbled together enough sample code to create custom XPS documents, at least in terms of the process to go through.  I haven't explored content creation options, that is my next challenge.

The process to create an XPS document in very basic outline is:

1/            Create an XPSDocument Package
2/            Add a FixedDocumentSequence at the Package root
3/            Add a FixedDocument to the FixedDocumentSequence with an IXpsFixedDocumentWriter
4/            Add page to the FixedDocument using an IXpsFixedPageWriter
5/            Add resources to the page, put them in a Dictionary collection for ease of use
6/            Write the page content using XML to describe the elements
7/            Commit the page/s to the FixedDocument
8             Optionally attach a PrintTicket
9/            Commit the FixedDocument to the FixedDocumentSequence
10/          Close the Package

Resources are embedded fonts and images.
Page contents are text, graphics and brushes (ie text, drawing and pictures)

So, some lines of code to create the elements described above:
Firstly, Imports (really hard to find sample code with correct Imports!)
Imports System.Printing
Imports System.IO
Imports System.Windows.Xps.Packaging
Imports System.IO.Packaging
Imports System.Windows.Xps
Imports System.Xml

1/ Using XPSDocPackage as Package = XPSDocPackage.Open(strDocumentPath)

2/ Dim xpsDoc as XpsDocument = New XpsDcoument(XPSDocPackage)

3/ Dim documentSequenceWriter As IXpsFixedDocumentSequenceWriter =   xpsDoc  .AddFixedDocumentSequence()

4/ Dim fixedPageWriter As IXpsFixedPageWriter = fixedDocumentWriter.AddFixedPage()

5/ Dim resources As Dictionary(Of String, List(Of XpsResource))
        ' Collections of images and fonts used in the current page.
        Dim xpsImages As New List(Of XpsResource)
        Dim xpsFonts As New List(Of XpsResource)

        Dim p_xpsImage As XpsImage
        Dim p_xpsFont As XpsFont

        ' Add, Write, and Commit image1
        p_xpsImage = fixedPageWriter.AddImage(XpsImageType.JpegImageType)
        WriteToStream(p_xpsImage.GetStream(), image1)
        p_xpsImage.Commit()
        xpsImages.Add(p_xpsImage) ' Add image1 as a required resource.

        ' Add, Write, and Commit font 1
        p_xpsFont = fixedPageWriter.AddFont()
        WriteObfuscatedStream(p_xpsFont.Uri.ToString(), p_xpsFont.GetStream(), font1)
        p_xpsFont.Commit()
        xpsFonts.Add(p_xpsFont) ' Add font1 as a required resource.

        ' Return the image and font resources in a combined collection.
        resources.Add("XpsImage", xpsImages)
        resources.Add("XpsFont", xpsFonts)
6/ TO COME!! (I need to flesh out my knowledge here!)

7/  fixedPageWriter.Commit()

8/ TO COME!! (I need to flesh out my knowledge here!)

9/ fixedDocumentWriter.Commit()

10/  xpsDoc.Close()



Tuesday, July 17, 2012

Print an XPS Document in VB.Net (and enumerate printers)

Ok, here is the code to enumerate local and network printers and to print an XPS document to the selected printer.  Seems to work just fine, but obviously no fancy options for ya!  Apologies to the many code sample writers I may have lifted a few things from, as I got my head around things. I kept no records, so sorry and thanks!

No fancy formatting of the code, either! Ok, I will make the code a non-black colour.
The XAML code for the WPF form (as if I even know what XAML stands for!):


<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="440" Width="633">
    <Grid>
        <Button x:Name="PrintSimpleTextButton" Content="Print File" HorizontalAlignment="Left" Height="22" Margin="10,95,0,0" VerticalAlignment="Top" Width="70" RenderTransformOrigin="-4.529,-2.864"/>
        <Button x:Name="btnEnumeratePrinters" Content="Enumerate Printers" HorizontalAlignment="Left" Height="22" Margin="10,10,0,0" VerticalAlignment="Top" Width="135"/>
        <ComboBox x:Name="cboPrinters" HorizontalAlignment="Left" Height="22" Margin="10,37,0,0" VerticalAlignment="Top" Width="218"/>
        <TextBox x:Name="txtFilePath" HorizontalAlignment="Left" Height="26" Margin="10,64,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="359"/>


    </Grid>
</Window>

The code for the form


Imports System.Printing
Imports System.IO


Class MainWindow


    Private Sub PrintSimpleTextButton_Click(sender As Object, e As RoutedEventArgs) Handles PrintSimpleTextButton.Click
        If cboPrinters.HasItems Then
            If cboPrinters.SelectedIndex > -1 Then
                Dim strPrinter As String
                strPrinter = cboPrinters.Items(cboPrinters.SelectedIndex).ToString
                If strPrinter.Length > 0 Then
                    Dim nextFile As String = txtFilePath.Text.Trim
                    If File.Exists(nextFile) Then
                        Try
                            Dim printque As PrintQueue
                            Dim printServer As New LocalPrintServer()
                            printque = printServer.GetPrintQueue(strPrinter)
                            ' Print the Xps file while providing XPS validation and progress notifications.
                            Dim xpsPrintJob As PrintSystemJobInfo = printque.AddJob(Path.GetFileName(nextFile), nextFile, False)
                        Catch f As PrintJobException
                            Console.WriteLine(vbLf & vbTab & "{0} could not be added to the print queue.", Path.GetFileName(nextFile))
                            If f.InnerException.Message = "File contains corrupted data." Then
                                Console.WriteLine(vbTab & "It is not a valid XPS file. Use the isXPS Conformance Tool to debug it.")
                            End If
                            Console.WriteLine(vbTab & "Continuing with next XPS file." & vbLf)
                        End Try
                    End If
                End If
            End If
        End If


    End Sub
   
    Private Function fwEnumeratePrinters() As PrintQueueCollection


        ' Specify that the list will contain only the print queues that are installed as local and are shared
        Dim enumerationFlags() As EnumeratedPrintQueueTypes = {EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections}


        Dim printServer As New LocalPrintServer()


        'Use the enumerationFlags to filter out unwanted print queues
        Dim printQueuesOnLocalServer As PrintQueueCollection = printServer.GetPrintQueues(enumerationFlags)


        Return printQueuesOnLocalServer
    End Function ' end:GetPrintTicketFromPrinter()


    Private Sub btnEnumeratePrinters_Click(sender As Object, e As RoutedEventArgs) Handles btnEnumeratePrinters.Click
        Dim pqPrinters As PrintQueueCollection
        cboPrinters.Items.Clear()
        pqPrinters = fwEnumeratePrinters()


        For Each printer As PrintQueue In pqPrinters
            cboPrinters.Items.Add(printer.Name)
        Next


    End Sub


   
    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        txtFilePath.Text = "C:\Users\LIZ\Desktop\First Second Third.xps"
    End Sub
End Class

Wednesday, July 11, 2012

I just want to learn VB.Net. No Biggie.

 Well.  I have programmed in VB from version 3 to version 6, not commercially except for a practice management program I use daily, and a staff rostering program was (is??) used around Australia by a big optical company.  I mainly do front ends to databases (Access, yes, I know, I know; could never get around to or afford, say, SQL Server).  A man needs a hobby, amirite?  A few years ago I reckon I could have done it for a living, now I have been well and truly left behind!

So, I need to get into a new development environment.  I am tempted to go with C#, but I find it difficult to cut myself off from Visual Basic.  I have a simple project that I want written, so I plan to use VB.Net with Visual Studio 2012 to create it.  So, jumping 4 versions of VB in one hit.  OMG!


I want to create the following program:
A Metro-style program in which I can choose already created documents, and send them to a printer.  In addition, I want to select some options (text and images), generate a document, and send a newly generated document to a printer.
Sounds easy.


I initially thought I would just send a PDF to Acrobat with a command to print.  Pfft.  Turns out that is really tricky if you don't want Acrobat to have a UI, and if you don't want to use the default printer.  PDF libraries are really quite expensive, so I shelved that idea after a couple of days of research.


Then I thought, "Wait a minute!  Hasn't Microsoft developed a PDF killer!  I'll do it in XPS format!!!".  LOLZ.


In the last day, I have come to realise that the internetz is a black hole for information about creating XPS documents in VB.NET/WPF.  I can now do my first part, which is load an XPS document and send it to the printer of my choosing, but the second part is really hard to find VB.Net info on.  There is a bit of C# stuff around, but I'm already struggling to learn VB.Net; to convert from C# to VB.Net is one step too far for this VB6 casual programmer!  I really get the feeling that Microsoft has given up on XPS...


So I thought I would record my progress so that others may find a few short cuts.  Of course, there is every possibility that I will fail/give up/not post, but my intentions are good.


If you come across this Blog, and have better solutions, PLEASE add them in comments.  Thanks!


Rene