SQL Server Reporting Services Tips links, questions, complaints, and other thoughts

26Mar/1116

Free Reporting Services Barcodes

Recently I had to integrate Code 39 and Code 128 barcodes into Reporting Services. Commercial solutions range from $500 to $1000 so I looked for free fonts that could do the work for me. It seemed simple but I came across a bunch of problems and thought it might help to explain the troubles I went through. By the end of the post you'll have a complete and free solution that you can cut and paste and know worked and didn't work.

You can download the completed demo project (for SSRS 2008) here: Reporting Services Free Barcodes Demo.

In The Beginning...

I originally came across John T Barton's site where he provides Code 39 and Code 128 fonts along with C# code to format input strings into something the barcode fonts can display. The source code is in here under BarcodeDLL\BarcodeConverter.cs.

However he himself downloaded the fonts from the french site that created it, Grand Zebu, and they have since released newer versions of the font, which require some minor changes to the code. The reference links are here (Code 39) and here (Code 128) respectively.

I ran Barton's code through a free online C# to VB.NET converter at developer Fusion, and modified it as best I could understand Grand Zebu's notes above, it came out with the hideously ugly but functional code below. Disclaimer: I didn't try to tidy it up...:

    Public Shared Function StringToBarcode128String(ByVal value As String) As String
        ' Parameters : a string
        ' Return     : a string which give the bar code when it is dispayed with CODE128.TTF font
        ' 			 : an empty string if the supplied parameter is no good
        Dim charPos As Integer, minCharPos As Integer
        Dim currentChar As Integer, checksum As Integer
        Dim isTableB As Boolean = True, isValid As Boolean = True
        Dim returnValue As String = String.Empty
 
        If value.Length > 0 Then
 
            ' Check for valid characters
            For charCount As Integer = 0 To value.Length - 1
                'currentChar = char.GetNumericValue(value, charPos);
                currentChar = AscW(Char.Parse(value.Substring(charCount, 1)))
                If Not (currentChar >= 32 AndAlso currentChar <= 126) Then
                    isValid = False
                    Exit For
                End If
            Next
 
            ' Barcode is full of ascii characters, we can now process it
            If isValid Then
                charPos = 0
                While charPos < value.Length
                    If isTableB Then
                        ' See if interesting to switch to table C
                        ' yes for 4 digits at start or end, else if 6 digits
                        If charPos = 0 OrElse charPos + 4 = value.Length Then
                            minCharPos = 4
                        Else
                            minCharPos = 6
                        End If
 
 
                        minCharPos = IsNumber(value, charPos, minCharPos)
 
                        If minCharPos < 0 Then
                            ' Choice table C
                            If charPos = 0 Then
                                ' Starting with table C
                                ' char.ConvertFromUtf32(210);
                                returnValue = (ChrW(210)).ToString()
                            Else
                                ' Switch to table C
                                returnValue = returnValue & (ChrW(204)).ToString()
                            End If
                            isTableB = False
                        Else
                            If charPos = 0 Then
                                ' Starting with table B
                                ' char.ConvertFromUtf32(209);
                                returnValue = (ChrW(209)).ToString()
 
                            End If
                        End If
                    End If
 
                    If Not isTableB Then
                        ' We are on table C, try to process 2 digits
                        minCharPos = 2
                        minCharPos = IsNumber(value, charPos, minCharPos)
                        If minCharPos < 0 Then
                            ' OK for 2 digits, process it
                            currentChar = Integer.Parse(value.Substring(charPos, 2))
                            currentChar = IIf(currentChar < 95, currentChar + 32, currentChar + 105) ''
                            returnValue = returnValue & (ChrW(currentChar)).ToString()
                            charPos += 2
                        Else
                            ' We haven't 2 digits, switch to table B
                            returnValue = returnValue & (ChrW(205)).ToString()
                            isTableB = True
                        End If
                    End If
                    If isTableB Then
                        ' Process 1 digit with table B
                        returnValue = returnValue & value.Substring(charPos, 1)
                        charPos += 1
                    End If
                End While
 
                ' Calculation of the checksum
                checksum = 0
                For [loop] As Integer = 0 To returnValue.Length - 1
                    currentChar = AscW(Char.Parse(returnValue.Substring([loop], 1)))
                    currentChar = IIf(currentChar < 127, currentChar - 32, currentChar - 105)
                    If [loop] = 0 Then
                        checksum = currentChar
                    Else
                        checksum = (checksum + ([loop] * currentChar)) Mod 103
                    End If
                Next
 
                ' Calculation of the checksum ASCII code
                checksum = IIf(checksum < 95, checksum + 32, checksum + 105)
                ' Add the checksum and the STOP
                returnValue = returnValue & (ChrW(checksum)).ToString() & (ChrW(211)).ToString()
            End If
        End If
 
        Return returnValue
    End Function
 
 
    Private Shared Function IsNumber(ByVal InputValue As String, ByVal CharPos As Integer, ByVal MinCharPos As Integer) As Integer
        ' if the MinCharPos characters from CharPos are numeric, then MinCharPos = -1
        MinCharPos -= 1
        If CharPos + MinCharPos < InputValue.Length Then
            While MinCharPos >= 0
                If AscW(Char.Parse(InputValue.Substring(CharPos + MinCharPos, 1))) < 48 OrElse AscW(Char.Parse(InputValue.Substring(CharPos + MinCharPos, 1))) > 57 Then
                    Exit While
                End If
                MinCharPos -= 1
            End While
        End If
        Return MinCharPos
    End Function
 
    Public Shared Function StringToBarcode39String(ByVal value As String, Optional ByVal addChecksum As Boolean = False) As String
        ' Parameters : a string
        ' Return     : a string which give the bar code when it is dispayed with CODE128.TTF font
        ' 			 : an empty string if the supplied parameter is no good
        Dim isValid As Boolean = True
        Dim currentChar As Char
        Dim returnValue As String = String.Empty
        Dim checksum As Integer = 0
        If value.Length > 0 Then
 
            'Check for valid characters
            For CharPos As Integer = 0 To value.Length - 1
                currentChar = Char.Parse(value.Substring(CharPos, 1))
                If Not ((currentChar >= "0"c AndAlso currentChar <= "9"c) OrElse (currentChar >= "A"c AndAlso currentChar <= "Z"c) OrElse currentChar = " "c OrElse currentChar = "-"c OrElse currentChar = "."c OrElse currentChar = "$"c OrElse currentChar = "/"c OrElse currentChar = "+"c OrElse currentChar = "%"c) Then
                    isValid = False
                    Exit For
                End If
            Next
            If isValid Then
                ' Add start char
                returnValue = "*"
                ' Add other chars, and calc checksum
                For CharPos As Integer = 0 To value.Length - 1
                    currentChar = Char.Parse(value.Substring(CharPos, 1))
                    returnValue += currentChar.ToString()
                    If currentChar >= "0"c AndAlso currentChar <= "9"c Then
                        checksum = checksum + AscW(currentChar) - 48
                    ElseIf currentChar >= "A"c AndAlso currentChar <= "Z"c Then
                        checksum = checksum + AscW(currentChar) - 55
                    Else
                        Select Case currentChar
                            Case "-"c
                                checksum = checksum + AscW(currentChar) - 9
                                Exit Select
                            Case "."c
                                checksum = checksum + AscW(currentChar) - 9
                                Exit Select
                            Case "$"c
                                checksum = checksum + AscW(currentChar) + 3
                                Exit Select
                            Case "/"c
                                checksum = checksum + AscW(currentChar) - 7
                                Exit Select
                            Case "+"c
                                checksum = checksum + AscW(currentChar) - 2
                                Exit Select
                            Case "%"c
                                checksum = checksum + AscW(currentChar) + 5
                                Exit Select
                            Case " "c
                                checksum = checksum + AscW(currentChar) + 6
                                Exit Select
                        End Select
                    End If
                Next
                ' Calculation of the checksum ASCII code
                If addChecksum Then
                    checksum = checksum Mod 43
                    If checksum >= 0 AndAlso checksum <= 9 Then
                        returnValue += (ChrW(checksum + 48)).ToString()
                    ElseIf checksum >= 10 AndAlso checksum <= 35 Then
                        returnValue += (ChrW(checksum + 55)).ToString()
                    Else
                        Select Case checksum
                            Case 36
                                returnValue += "-"
                                Exit Select
                            Case 37
                                returnValue += "."
                                Exit Select
                            Case 38
                                returnValue += " "
                                Exit Select
                            Case 39
                                returnValue += "$"
                                Exit Select
                            Case 40
                                returnValue += "/"
                                Exit Select
                            Case 41
                                returnValue += "+"
                                Exit Select
                            Case 42
                                returnValue += "%"
                                Exit Select
                        End Select
                    End If
                End If
                ' Add stop char
                returnValue += "*"
            End If
        End If
        Return returnValue
    End Function

First Attempt

For my first attempt, I installed the two fonts (and later rebooted when the fonts didn't work and I realised this is still necessary as recent as Windows 7), and set to work on a demonstration in BIDS to see if I could use the fonts simply with the code provided to show and print barcodes.

It consists simply of:

  • The custom code from above.
  • A dummy data set (use anything) and a table with the detail grouping removed.
  • Two expressions, one in a table cell each.
    =Code.StringToBarcode39String("12345")
    =Code.StringToBarcode128String("12345")
  • Setting the font properties for each table cell to Code 3 de 9 and Code 128 respectively, 36pt (or any other arbitrary size) and resize the cell a little bigger to match.

It should look like this:

And after you change the font settings:

And then when you preview the report:

And then when you export the report to PDF:

Wait a minute, that doesn't look right!

What Went Wrong

This is where it gets complicated; the free Code 128 barcode font doesn't work (as of RS2008 R2) when exporting to PDF. It's supposed to work as it's a TrueType font with embed rights and after checking the PDF properties it has embedded the correct font; it just doesn't display properly in Adobe Reader (as of 10.0.1).

Final Attempt

The way I got around this is to render the text using that font as an image then display it in an image control, which accepts a byte array representing the file from some more custom code. As it turns out other people have done this before, but it hasn't been well explained:

    Public Shared Function Code39(ByVal stringText As String) As Byte()
        Dim result As Byte() = Nothing
 
        Try
            result = GenerateImage("Code 3 de 9", StringToBarcode39String(stringText))
        Catch ex As Exception
        End Try
 
        Return result
    End Function
 
    Public Shared Function Code128(ByVal stringText As String) As Byte()
        Dim result As Byte() = Nothing
 
        Try
            result = GenerateImage("Code 128", StringToBarcode128String(stringText))
        Catch ex As Exception
        End Try
 
        Return result
    End Function
 
    Public Shared Function GenerateImage(ByVal fontName As String, ByVal stringText As String) As Byte()
        Dim oGraphics As System.Drawing.Graphics
        Dim barcodeSize As System.Drawing.SizeF
        Dim ms As System.IO.MemoryStream
 
        Using font As New System.Drawing.Font(New System.Drawing.FontFamily(fontName), 36)
            Using tmpBitmap As New System.Drawing.Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
                oGraphics = System.Drawing.Graphics.FromImage(tmpBitmap)
                oGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixel
                barcodeSize = oGraphics.MeasureString(stringText, font)
                oGraphics.Dispose()
            End Using
 
            Using newBitmap As New System.Drawing.Bitmap(barcodeSize.Width, barcodeSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
                oGraphics = System.Drawing.Graphics.FromImage(newBitmap)
                oGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixel
 
                Using oSolidBrushWhite As New System.Drawing.SolidBrush(System.Drawing.Color.White)
                    Using oSolidBrushBlack As New System.Drawing.SolidBrush(System.Drawing.Color.Black)
                        oGraphics.FillRectangle(oSolidBrushWhite, New System.Drawing.Rectangle(0, 0, barcodeSize.Width, barcodeSize.Height))
                        oGraphics.DrawString(stringText, font, oSolidBrushBlack, 0, 0)
                    End Using
 
                End Using
 
                ms = New System.IO.MemoryStream()
                newBitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png)
            End Using
        End Using
 
        Return ms.ToArray()
    End Function

I've now used two wrapper functions Code39 and Code128 which will do all the processing with the right font and return it as an image in a byte array. The steps to get this working, continuing on from before, are:

  • Adding the above custom code to the report.
  • Going into Report Properties -> References and adding the System.Drawing assembly to the report.
  • Replacing the two textboxes with two image controls instead, with their settings as per below:
  • It will also look far better to set the image sizing controls to Clip instead of Fit or FitProportional. For this reason you will probably want to experiment with the font sizes above and the longest barcode you're going to reproduce so that it looks right without cutting anything off.

The final report looks like this:

And surprise surprise, the screen renders similar to before and the PDF shows correctly too:

Conclusion

It took a lot of ugly code and work but I had the solution I was looking for, and now hopefully others can use it easily. I should make a few notes though:

  • I wrote the part that generates the image, but I'm aware that there might be problems because it uses GDI+ which can leak non-managed Windows resources if they aren't used properly. I wrapped everything in Using blocks which do an automatic .Dispose(), but I'm not a professional programmer and could have missed something.
  • Using the plain fonts method showed much clearer barcodes than when drawning on an image. I also couldn't work out why this is a case; I tried different types of font hints, and even changing the GDI+ code for the modern .NET DrawString function, and it only made things even worse.

If anyone ever works out a better plug-and-play way (not using custom assemblies or paid fonts), let me know.

Until next time,
Cody

Update

After using this in the real world, I've found that the Code 39 generally scans okay as provided, but the Code 128 works better generated at 96pt, and then downscaled into the image box with "Fit", so that it's actually longer and thinner than it should be. Sounds crazy, but it works ...

Be sure to do your own testing.

Comments (16) Trackbacks (1)
  1. Hi Cody,
    Seems that the link http://www.ssrstips.com/wp-content/uploads/2011/03/Reporting-Services-Free-Barcodes-Demo.zip (source code sample for Reporting services) above is not working. Can you update? Do you still have the code? I’ve tried to generate barcode using SSRS for a few days, but to no avail. I’ve searched the internet for samples, ways to do it, samples, but none works, and most of them don’t provide the sample for reporting services, only winforms.
    Mind to share your sample?

    Thanks.

  2. Hi,

    I’ve tried your demo, and it gives the following output

    The First Attempt – gives an output of *12345* O,BI5VO
    Final Version – gives a blank output.

    No barcodes display.

    Please help, what am i doing wrong.
    I need to get this working quite urgertly.

    Thanks
    Rebekah

  3. Dear author,

    You are AWE-wait for it-SOME!
    I looked EVERY-FREAKING-WHERE for this solution!!!

    Some problems with SSRS 2008 R2 to display custom True Type Font properly…
    Spend more then a week on this issue and thanks to your solution, it’s solved!

    Not even MS could answer this issue, so you’re a pro :p
    Wished I encountered this page earlier!

    I’m making this a dll to pass a custom font, and optionally FontStyle, Size, Weight, Color, BackColor…
    Barcode data is rendered when storing it in a table, so I didn’t need the convertion functions.

    If you mail me I can send you the result ;)

    Thanks again!

    Lawrence

    • I’m glad it worked for you. I really don’t know why SSRS still has so many problems dealing with embedding unicode TT fonts in PDFs and elsewhere, even with Microsoft’s insistence that it all works perfectly.

  4. I tried this but it seemed like it’s not working with more characters/numbers. I tried 10 characters but the scanner cannot read it anymore.

  5. Hi,
    I tried this in SSRS 2005 but no luck.

    • Hi,

      Sorry to hear that, though I’m not surprised because 2005->2008 is so different. I haven’t used 2005 in a long time so I can’t give any advice.

      Cheers

  6. I tried installing the fonts and printing they work great. But when I try to scan Code 128 barcode it does not scan it. What may be the issue?
    I am using a image to create barcode.
    What specs. should the image barcode have? Thanks.

    • Are you scanning with your phone or with a proper barcode scanner?

      I noticed that on my phone (using its camera) it’s easy to pick up very small barcodes but on the hand-held laser scanners, they have to be much bigger.

      For example a 12 letter barcode in Code 128 has to be at least 3.3cm x 0.75cm to scan. It seems the width is more important than the height.


Leave a comment

(required)