none
Using CreateFontPackage in FontSub.dll

    Question

  • Hi,

    I found samples of how to use CreateFontPackage in the C# forums however after my poor attempt at converting the C# code over to VB code (using VS 2010) I'm ending up with a PInvoke unbalanced stack error when the function is called.  I've searched for hours looking for VB code to call this function and it just looks like it has not been done under VB.NET or for some reason the API cannot be called in VB.NET.

    Would like to be able to get this working is it will help to reduce the output file size of the XPS files I am creating.

    This is the code I ended up with...

     

    Imports System
    Imports System.IO
    Imports System.Runtime.InteropServices
    
    Public Class SubsetFont
    
     <UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
     Public Delegate Function ALLOCPROC(ByVal Sze As Int32) As IntPtr
     <UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
     Public Delegate Function REALLOCPROC(ByVal MemBlock As IntPtr, ByVal Sze As IntPtr) As IntPtr
     <UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
     Public Delegate Sub FREEPROC(ByVal MemBlock As IntPtr)
    
     Declare Auto Function CreateFontPackage Lib "FontSub.DLL" Alias "CreateFontPackage" (ByVal puchSrcBuffer() As Byte, _
       ByVal ulSrcBufferSize As UInteger, _
       ByRef puchFontPackageBuffer() As Byte, _
       ByRef pulFontPackageBufferSize As UInteger, _
       ByRef pulBytesWritten As UInteger, _
       ByVal usFlags As UShort, _
       ByVal usTTCIndex As UShort, _
       ByVal usSubsetFormat As UShort, _
       ByVal usSubsetLanguage As UShort, _
       ByVal usSubsetPlatform As UShort, _
       ByVal usSubsetEncoding As UShort, _
       ByVal pusSubsetKeepList() As UShort, _
       ByVal usSubsetKeepListCount As UShort, _
       ByVal lpfnAllocate As ALLOCPROC, _
       ByVal lpfnReAllocate As REALLOCPROC, _
       ByVal lpfnFree As FREEPROC, _
       ByVal lpvReserved As IntPtr) As UInteger
    
     Public Shared Function CreateSubsetFont(ByVal FontFile As String, ByVal characters() As UShort) As Byte()
      Dim result() As Byte = File.ReadAllBytes(FontFile)
    
      Dim allocproc As ALLOCPROC = New ALLOCPROC(AddressOf Marshal.AllocHGlobal)
      Dim reallocproc As REALLOCPROC = New REALLOCPROC(AddressOf Marshal.ReAllocHGlobal)
      Dim freeproc As FREEPROC = New FREEPROC(AddressOf Marshal.FreeHGlobal)
      Dim subset() As UShort = characters
      Dim buffer() As Byte
      Dim buffer_size, bytes_written As UInteger
      Dim rc As UInteger = CreateFontPackage(result, CUInt(result.Length), buffer, buffer_size, bytes_written, 0, 0, 0, 0, 0, 1, subset, CUShort(subset.Length), allocproc, reallocproc, freeproc, Nothing)
    
      Return buffer
     End Function
    End Class

     

    Thanks!

    Thursday, December 30, 2010 2:42 AM

All replies

  • No replies yet, huh?  Why didn't you post the link to the C# code so someone could translate it for you?  This is what Tergiver came up with.


    Imports System.Runtime.InteropServices
    Imports System.IO


    Module Module1

        <UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
       Friend Delegate Function ALLOCPROC(ByVal Size As Integer) As IntPtr

        <UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
        Friend Delegate Sub FREEPROC(ByVal MemBlock As IntPtr)

        <UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet:=CharSet.Unicode)> _
        Friend Delegate Function REALLOCPROC(ByVal MemBlock As IntPtr, ByVal Size As IntPtr) As IntPtr


        Sub Main()
            Test_CreateFontPackage()
            Console.ReadLine()
        End Sub

        Friend Sub Test_CreateFontPackage()
            Dim buffer As Byte()
            Dim buffer_size As UInt32
            Dim bytes_written As UInt32
            Dim result As Byte() = File.ReadAllBytes("C:\Windows\Fonts\Arial.ttf")
            Dim allocproc As ALLOCPROC = New ALLOCPROC(AddressOf Marshal.AllocHGlobal)
            Dim reallocproc As REALLOCPROC = New REALLOCPROC(AddressOf Marshal.ReAllocHGlobal)
            Dim freeproc As FREEPROC = New FREEPROC(AddressOf Marshal.FreeHGlobal)
            Dim subset As UInt16() = New UInt16(0 - 1) {}

            Dim rc As UInt32 = CreateFontPackage( _
                result, _
                CUInt(result.Length), _
                buffer, _
                buffer_size, _
                bytes_written, _
                0, 0, 0, 0, 0, 1, _
                subset, _
                CUShort(subset.Length), _
                allocproc, _
                reallocproc, _
                freeproc, _
                IntPtr.Zero)

            If (rc <> 0) Then
                Console.WriteLine("Failed with code {0}", rc)
            Else
                Console.WriteLine("Success.")
            End If
        End Sub

        <DllImport("FontSub.dll", CharSet:=CharSet.Unicode, ExactSpelling:=True)> _
        Friend Function CreateFontPackage( _
            <[In](), MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> _
            ByVal puchSrcBuffer As Byte(), _
            ByVal ulSrcBufferSize As UInt32, _
            <Out(), MarshalAs(UnmanagedType.LPArray)> _
            ByRef puchFontPackageBuffer As Byte(), _
            <Out()> ByRef pulFontPackageBufferSize As UInt32, _
            <Out()> ByRef pulBytesWritten As UInt32, _
            ByVal usFlags As UInt16, _
            ByVal usTTCIndex As UInt16, _
            ByVal usSubsetFormat As UInt16, _
            ByVal usSubsetLanguage As UInt16, _
            ByVal usSubsetPlatform As UInt16, _
            ByVal usSubsetEncoding As UInt16, _
            <[In](), MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=11)> _
            ByVal pusSubsetKeepList As UInt16(), _
            ByVal usSubsetKeepListCount As UInt16, _
            ByVal lpfnAllocate As ALLOCPROC, _
            ByVal lpfnReAllocate As REALLOCPROC, _
            ByVal lpfnFree As FREEPROC, _
            ByVal lpvReserved As IntPtr) As UInt32
        End Function

    End Module

    ' web links
    '
    ' http://social.msdn.microsoft.com/Forums/en-IE/csharplanguage/thread/1652a9fb-87ab-4725-8a73-7f1015519a71
    ' http://msdn.microsoft.com/en-us/magazine/cc163606.aspx#S1
    ' http://msdn.microsoft.com/en-us/library/dd183502.aspx 


     Hope this helps.

    Rudy    =8^D

    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Friday, December 31, 2010 1:52 PM
  • Thanks for the copy of Tergiver's code.

    I just tried this code as is and I am still getting -

    "A call to PInvoke function 'XPSPrint!XPSPrint.Module1::CreateFontPackage' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature."

    Will have a look at the web links you supplied to see if this has come up when using VB/VS 2010/Windows7x64.  Possibly the dll function has been modified slightly in Win7?

    Thanks,

    John

     

    Monday, January 03, 2011 12:22 AM
  • What did you do?  I tested the code before I postd it and got the "Success" message running 64-bit Windows 7.  What changes did you make?


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 12:28 AM
  • Do you know what that message means?   Stack Imbalance?

    The link describes it, but I will summarize it here.  When a method call is made, some parameters may be placed onto the stack by the caller.  It is the callee's responsibility to clean up the stack prior to exiting.  The callee must leave the stack size unchanged.  Leave it the same size prior to the method call.

    A Stack Imbalance means the stack was not left the same size after the method call.  The most common cause of these imbalances is the caller and the callee are not on the same page with their data types.  This can easily occur between 16-bit code and 32-bit code when passing default integers. 

    For example, your 32-bit code passes a default integer, which is four bytes.  The callee is 16-bit code.  When it reads a default integer in it only pulls 2 bytes off the stack, leaving 2 behind.  When the method finishes, the two extra bytes are still on the stack.  The stack size has changed, leaving it "Imbalanced".

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 12:37 AM
  • Sub Main
    
    Dim result As UInt32 = CreateSubsetFont("C:\windows\fonts\arial.ttf", "C:\test\arialss.ttf")
    
    End Sub
    
    Function CreateSubsetFont(ByVal originalTTF, ByVal subsetTTF) As UInt32
    
    
        Dim buffer As Byte()
        Dim buffer_size As UInt32
        Dim bytes_written As UInt32
        Dim result As Byte() = File.ReadAllBytes(originalTTF)
        Dim allocproc As ALLOCPROC = New ALLOCPROC(AddressOf Marshal.AllocHGlobal)
        Dim reallocproc As REALLOCPROC = New REALLOCPROC(AddressOf Marshal.ReAllocHGlobal)
        Dim freeproc As FREEPROC = New FREEPROC(AddressOf Marshal.FreeHGlobal)
        Dim subset As UInt16() = New UInt16(5) {50, 51, 52, 53, 54, 55}
    
        Dim rc As UInt32 = CreateFontPackage( _
          result, _
          CUInt(result.Length), _
          buffer, _
          buffer_size, _
          bytes_written, _
          0, 0, 0, 0, 0, 1, _
          subset, _
          CUShort(subset.Length), _
          allocproc, _
          reallocproc, _
          freeproc, _
          IntPtr.Zero)
    
        Return rc
    
    End Function

    The rest of the code is identical to what you posted.  In this case I set the subset to 5 specific character index's.  I did try with the same subset setting you posted (no sub setcharacters) however this also had the stack imbalance error as well.  Also, I have noticed that it warns about buffer not being assigned a value before being referenced.  

    If I go into Debug - Exceptions and turn off the PInvokeStackImbalance the code runs with a returned RC of 0 so it appears to work, however buffer, buffer_size and bytes_written all come up empty.  Do you actually see a byte array returned with the TTF file bytes in buffer?

    Monday, January 03, 2011 12:45 AM
  • I did not check the contents of the buffer, but I can.  I get the same warning about the buffer.  I ignored it for the time being.  The size is being passed as parameter, so the Interop has the info it needs to intialize it dynamically.  Size is not known until runtime.  C# allows you to turn off warnings for sections of code that you designate with compiler directives.

    http://msdn.microsoft.com/en-us/library/ed8yd1ha(VS.71).aspx  note the one labeled #warning

    VB does not permit that yet.   http://msdn.microsoft.com/en-us/library/7ah135z7.aspx

    But, did the test program give you the "success" message?  The imbalance message says that you are not calling it with the proper data types.  I did not get that message with the test program.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 12:59 AM
  • OK, I created a new console app and placed your code as is and I no longer get the stack imbalance. However I still do not get any bytes returned in the buffer, regardless of the subset being set to your initial value or if I set it as - Dim subset As UInt16() = New UInt16(5) {50, 51, 52, 53, 54, 55} what puzzles me is that you set the dll function as -

    CreateFontPackage(<[In](), MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> _

    ByVal puchSrcBuffer As Byte(), _

    ByVal ulSrcBufferSize As UInt32, ...

    but when you call the function you use -

    Dim rc As UInt32 = CreateFontPackage( _

    result, _

    CUInt(result.Length), _

    buffer,...

    What is <[In](), MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> and how would this match "result" which is a byte array?

    How would puchSrcBuffer as byte array match with CUInt(result.Length).

    Finding it tricky to see how the definition of the parameters all align with the call you make? Either way something is not right as I am not getting back anything in buffer.

    Regards, John

    Monday, January 03, 2011 1:05 AM
  • Take a look at the C signature.

    unsigned long CreateFontPackage(
      __in   const unsigned char *puchSrcBuffer,
      __in   const unsigned long ulSrcBufferSize,
      __out  unsigned char **puchFontPackageBuffer,
      __out  const unsigned long *pulFontPackageBufferSize,
      __out  unsigned long *pulBytesWritten,
      __in   const unsigned short usFlags,
      __in   const unsigned short usTTCIndex,
      __in   const unsigned short usSubsetFormat,
      __in   const unsigned short usSubsetLanguage,
      __in   const unsigned short usSubsetPlatform,
      __in   const unsigned short usSubsetEncoding,
      __in   const unsigned short *pusSubsetKeepList,
      __in   const unsigned short usSubsetKeepListCount,
      __in   CFP_ALLOCPROC lpfnAllocate,
      __in   CFP_REALLOCPROC lpfnReAllocate,
      __in   CFP_FREEPROC lpfnFree,
      __in   LPVOID lpvReserved
    );

    Notice the asterisks associated with some parameters.  Those parameters are being not being passed by value.  They are being passed by reference.  The asterisks designates the variable as a pointer.  Pointers are of fixed size.  The attribute helps the marshaller provide the correct pointer to unmanaged code.  The double asterisks indicate pointers to a pointer, which I called "indirect pointers" when I first learned about them.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 1:24 AM
  • Also, the new console app with your code which does execute the function is using .net 2.0 whereas the project I am working on is .net 4.0.  Would .net 4.0 cause the stack imbalance?  If I change the new console app over to use .net 4.0 then the pInvoke error is back.  Bit of an issue as my main project is based on .net 4.0.

    Still would like to nut out why the buffer of bytes which is supposed to make up the subset TTF file bytes is coming back empty.  If I can get that working and work out what the .net 4.0 issue is then we are onto a winner.

    Monday, January 03, 2011 1:24 AM
  • I am running VS2010 Ultimate.  No stack imbalance error for me.  The only change that I am aware of is that the warning has been turned on by default in 2010, whereas in previous versions it was off by default. 

    One of my links describe the MDAs, Managed Debugging Assistants, available to Visual Studio.  The article is about VS2005 and suggests that anyone doing P/Invoke should turn it on.  Well, it is now on by default since most developers were not aware of it.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 1:31 AM
  • Can you confirm the following on your end -

    VS 2010 VB x86 console app using .NET 2.0 (default for new console apps) - code executes without error?

    buffer bytes comes back with nothing?

    Same app changed to use .NET 4.0 - code executes with PInvoke Stack Imbalance error?

     

     

    Monday, January 03, 2011 1:39 AM
  • I take that back.  I translated and tested that on my older 32 bit machine.  My Win7x64 machine throws the stack imbalance error.  The pointer sizes are different for 32 and 64 bit machines, but I have my build platform set for x86 and still get the error.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 1:42 AM
  • Based on your finding I then changed the .NET 4.0 console app to anyCPU rather than x86 and the function does execute without error.  I  assume anyCPU exe's can run on either x64 or x86 platforms so this may be the workaround.  I do still need you to confirm that the buffer is returned empty on your end as this is the whole reason I need this function.  Bit pointless using the function if it is not going to give me the bytes of the subset TTF file.
    Monday, January 03, 2011 1:50 AM
  • I am not familiar with that method.  I would assume that the parameters being passed as zeroes may have something to do with it.  Also, the font must be a true type font.
    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, January 03, 2011 2:25 AM
  • Try asking that question here.  Providing a link to this thread would be a good thing.

    http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/threads

     


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Tuesday, January 04, 2011 1:17 AM
  • I am also trying to figure this out. The code itself seems to work fine, but as soon as I set the "usFlags" to 1 (as stated in the fontSub.h) I get an out of memory error. when I run the exact same code in C++ it seems to work fine. I rember reading something about arrays and unmanaged code in the MSDN, saying that it wasnt possible and would cause this error. you guys figured anything out?
    Monday, February 21, 2011 6:55 PM
  • The stack imbalance error probably means that your data types don't match.  The stack size is supposed to remain unchanged after a method call. 

    If you place a C# "int" on the stack for a C++ DLL looking for a C++ "int", then your code will seem to run properly most of the time.  But, the DLL is only removing the first 2 bytes of the 4 bytes passed to it.  When the method returns, these extra bytes are left on the stack.  This is known as Stack Imbalance.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Monday, February 21, 2011 8:29 PM
  • hmmm... unfortuantely I am not exactly a wiz when it gomes to transfering data types, which is why I used the exact same codethat you had posted on december 31 with the exception of one single flag:

     Dim rc As UInt32 = CreateFontPackage( _
          result, _
          CUInt(result.Length), _
          buffer, _
          buffer_size, _
          bytes_written, _
          1, 0, 0, 0, 0, 1, _
          subset, _
          CUShort(subset.Length), _
          allocproc, _
          reallocproc, _
          freeproc, _
          IntPtr.Zero)
    
    without the usFlag = 1 the function returns 0, but the buffer, buffer_size and bytes_written are all nothing/0. This is the same for the c++ version, if the usFlags = 0 the buffer is also nothing and both buffer_size and bytes_written are 0.

    Tuesday, February 22, 2011 10:28 AM
  • Based on your finding I then changed the .NET 4.0 console app to anyCPU rather than x86 and the function does execute without error.  I  assume anyCPU exe's can run on either x64 or x86 platforms so this may be the workaround.  .


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Wednesday, February 23, 2011 11:42 PM
  • Unfortunately I have to use the .NET framework 2.0 for this program. and regardless if the app is set to x86 or AnyCPU I still recieve an OutOfMemoryException, "Insufficient memory to continue the execution of the program.":

    "   at System.Runtime.InteropServices.Marshal.ReAllocHGlobal(IntPtr pv, IntPtr cb)    at FontSub_VB_console.Module1.CreateFontPackage(Byte[] puchSrcBuffer, UInt32 ulSrcBufferSize, Byte[]& puchFontPackageBuffer, UInt32& pulFontPackageBufferSize, UInt32& pulBytesWritten, UInt16 usFlags, UInt16 usTTCIndex, UInt16 usSubsetFormat, UInt16 usSubsetLanguage, UInt16 usSubsetPlatform, UInt16 usSubsetEncoding, UInt16[] pusSubsetKeepList, UInt16 usSubsetKeepListCount, ALLOCPROC lpfnAllocate, REALLOCPROC lpfnReAllocate, FREEPROC lpfnFree, IntPtr lpvReserved)    at FontSub_VB_console.Module1.Test_CreateFontPackage() in C:\Users\MG.CAXPERTS01\Documents\Visual Studio 2008\Projects\FontSub_VB_console\FontSub_VB_console\Module1.vb:line 25    at FontSub_VB_console.Module1.Main() in C:\Users\MG.CAXPERTS01\Documents\Visual Studio 2008\Projects\FontSub_VB_console\FontSub_VB_console\Module1.vb:line 72    at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)    at System.Threading.ThreadHelper.ThreadStart()"

    Thursday, February 24, 2011 11:36 AM
  • Hi - The above code worked without error. but the buffer is null and the buffer size is zero. The buffer should contain the new subset font which could be written to a ttf file.
    Thursday, March 03, 2011 4:19 PM
  • yes,  which is why I changed one line of the above code to:

    1, 0, 0, 0, 0, 1, _

    since the C++ code works with the usFlags = 0x0001

    but as I stated above this causes the error
    Friday, March 04, 2011 1:59 PM
  • Ok, since starting this thread it seems we are able to get the function to call without error however it still seems that we cannot get the TTF subset bytes back.

    Only just today I was looking into .NET 4 WPF and the GlyphTypeface.  Seems that it actually does what we are after -

    http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k(SYSTEM.WINDOWS.MEDIA.GLYPHTYPEFACE.COMPUTESUBSET);k(TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV4.0%22);k(DevLang-VB)&rd=true

    GlyphTypeface.ComputeSubset Method

    Returns the binary image of the font subset based on a specified collection of glyphs.

    Namespace:  System.Windows.Media

    Assembly:  PresentationCore (in PresentationCore.dll)

    VB Syntax

    <CLSCompliantAttribute(False)> Public Function ComputeSubset (glyphs As ICollection(Of UShort)) As Byte()

    Parameters
    glyphs
    Type: System.Collections.Generic.ICollection(Of UInt16)
    The collection of glyph indices to be included in the subset.

    Return Value
    Type: System.Byte()
    A Byte array that represents the binary image of the font subset.

    I'm assuming at this early stage that the bytes returned are the actual TTF subset font.

    Here's hoping this is the way forward.

     

     

    Friday, March 04, 2011 11:09 PM
  • Ok, this works with VB 2010 .NET 4.0 

     

     

            Dim glyphTypeface as Media.GlyphTypeface = New Media.GlyphTypeface(New Uri("C:\Windows\Fonts\Arial.TTF")) 'can be any font installed or not

            Dim Gindex As System.Collections.Generic.ICollection(Of UShort)

            Gindex = New Generic.List(Of UShort)

            Dim myBytes As Byte() = System.Text.Encoding.Unicode.GetBytes(SourceText)

            Dim myChars As Char() = System.Text.Encoding.Unicode.GetChars(myBytes)

            For Charpos = 0 to Ubound(myChars)

                  Dim CharVal As Integer = AscW(myChars(Charpos))

                  Dim glyphIndex As Integer = glyphTypeface.CharacterToGlyphMap(CharVal)

                  Gindex.Add(glyphIndex)

            Next       

            Dim filebytes() As Byte = glyphTypeface.ComputeSubset(Gindex)

            Dim oFileStream As System.IO.FileStream

            oFileStream = New System.IO.FileStream("D:\NewFonts\ArialSS.TTF", System.IO.FileMode.Create)

            oFileStream.Write(filebytes, 0, filebytes.Length)

            oFileStream.Close()

     

     

     

     

     

     

     

     

    Friday, March 04, 2011 11:32 PM
  • Unfortunately this sucks as a solution to anyone not in a position where they can use .NET 4.  

    Looking at the other new calls available in .NET 4 I'm really starting to warm to it.  Just have to convince your customers that the extra .NET 4 Client Profile installer is worth the effort.  If only it wasn't 42MB!

    Friday, March 04, 2011 11:44 PM
  • As even we have not switched to VS 2010 (even though it will be done soon), our costermers are still using windows XP so this really is not an option.
    Monday, March 07, 2011 11:41 AM
  • As even we have not switched to VS 2010 (even though it will be done soon), our costermers are still using windows XP so this really is not an option.

    Tested my app under XP SP3 x86 and it works fine.  Only additional installs were .NET 4 and XPS Essentials Pack (to be able to view XPS files).  .NET 4 is not bound specifically to Vista/Win7/2008/R2. Minimum is XP SP3.  Also tested under 2003 server and it also works fine.

    The jump from VS 2008 to 2010 was a no-brainer.  Even VS 2005 projects / solutions converted across seamlessly.  My old VB6 code was a bit tricker however massive chunks of code was eventually replaced by functions that were introduced in .NET 3 and up.  They even re-introduced the good old Sring(char,length) function which went missing for a while there (among others).  Even C# is friendly enough to learn from a VB programmer's perspective (reading through C++ code still does my head in).

     

     

    Monday, March 07, 2011 9:07 PM
  • Sorry, I've been busy with a couple of deadlines on some projects of late.  I have been tinkering with this trying to see why this function seems to work in C++ , but not in managed code. VB/C#.  I found some curious behavior using the VB code I posted earlier, the code that comes back "Success".  But yet the 'buffer' variable is returned not just empty, it is completely corrupted and has a value of Nothing/null.

    Oh yeah, while I am thinking about it and forget to post it again.  Here's the link to Tergiver's thread .

    Okay.  The VB code that I had posted above was a translation of what Tergiver had posted in C#.  He noted that he was uncertain as to what methods to use for the function pointers that had to be passed to the method.  He used the Marshal class.  Since my 'buffer' was coming back corrupted, I began to wonder about the Marshal class.

    So I created some methods of my own, which called the Marshal class, so I could perform some diagnostics and trouble shooting. 


            'Dim allocproc As ALLOCPROC = New ALLOCPROC(AddressOf Marshal.AllocHGlobal)
            Dim allocproc As ALLOCPROC = New ALLOCPROC(AddressOf alloc)

            'Dim reallocproc As REALLOCPROC = New REALLOCPROC(AddressOf Marshal.ReAllocHGlobal)
            Dim reallocproc As REALLOCPROC = New REALLOCPROC(AddressOf realloc)

            'Dim freeproc As FREEPROC = New FREEPROC(AddressOf Marshal.FreeHGlobal)
            Dim freeProc As FREEPROC = New FREEPROC(AddressOf free)

            Dim subset As UInt16() = New UInt16(4) {48, 49, 50, 51, 71}

            Dim name As String = "name"

    I got some interesting results.  Nothing.  Here are the methods that are now used by the delegates.

     Friend Sub free(ByVal MemBlock As IntPtr)
            Marshal.FreeHGlobal(MemBlock)
            Console.WriteLine("Free MemBlock")
            Return
        End Sub

        Friend Function realloc(ByVal MemBlock As IntPtr, ByVal Size As IntPtr) As IntPtr
            Dim bufferSize As Integer = IntPtr.Size
            Return Marshal.ReAllocHGlobal(MemBlock, Size)
            Console.WriteLine("Realloc MemBlock")
        End Function

        Friend Function alloc(ByVal Size As Integer) As IntPtr
            Return Marshal.AllocHGlobal(Size)
            Console.WriteLine("Alloc MemBlock")
        End Function

    I got nothing.  I never got any messages on the Console.  The methods were apparently never even being called.  Yet, I still got the Success message at the end.  And the 'buffer' variable was still somehow corrupted and set to Nothing/null without the delegates being invoking my methods.

    I don't know what you think, but those result don't help.  The C++ code calls functions with the same name as my new VB methods above, which are defined within stdio.h, if that means anything to anyone.  I am tempted to write a C++ wrapper DLL around the code in the link with the C++ code so that managed code gain utilize those same methods.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Edited by Rudedog2MVP Tuesday, March 08, 2011 2:19 PM
    Tuesday, March 08, 2011 1:47 PM
  • F.Y.I.

     

    Module TTF

        '  for usSubsetFormat 
        Public Const CFP_SUBSET As Int32 = 0
        Public Const CFP_SUBSET1 As Int32 = 1
        Public Const CFP_DELTA As Int32 = 2

        '/* for usSubsetPlatform ID values */
        Public Const CFP_UNICODE_PLATFORMID As Int32 = 0
        Public Const CFP_APPLE_PLATFORMID As Int32 = 1
        Public Const CFP_ISO_PLATFORMID As Int32 = 2
        Public Const CFP_MS_PLATFORMID As Int32 = 3

        ' for usSubsetEncoding values
        Public Const CFP_STD_MAC_CHAR_SET As Int32 = 0 ' goes with TTFSUB_APPLE_PLATFORMID
        Public Const CFP_SYMBOL_CHAR_SET As Int32 = 0 ' goes with TTFSUB_MS_PLATFORMID
        Public Const CFP_UNICODE_CHAR_SET As Int32 = 1 '  goes with TTFSUB_MS_PLATFORMID
        Public Const CFP_DONT_CARE As Int32 = &HFFFF

        ' for usSubsetLanguage values
        Public Const CFP_LANG_KEEP_ALL As Int32 = 0 '

        ' for usFlags values
        Public Const CFP_FLAGS_NONE As Int32 = 0 '
        Public Const CFP_FLAGS_SUBSET As Int32 = 1 ' if bit off, don't subset
        Public Const CFP_FLAGS_COMPRESS As Int32 = 2 ' if bit off, don't compress
        Public Const CFP_FLAGS_TTF As Int32 = 4 ' if bit off, its a TTF
        Public Const CFP_FLAGS_GLYPHLIST As Int32 = 8 ' if bit off, list is characters

        ' for usModes
        Public Const MFP_SUBSET As Int32 = 0 ' Copy a Straight Subset Font package to Dest buffer
        Public Const MFP_SUBSET1 As Int32 = 1 ' Expand a format 1 font into a format 3 font
        Public Const MFP_DELTA As Int32 = 2 ' Merge a format 2 with a format 3 font

    End Module

    ' links
    '
    ' http://social.msdn.microsoft.com/Forums/en-US/windowsxps/thread/355c4ee9-c9be-4b00-86d7-a5200156936b/
    '



    '/* for usSubsetFormat */
    '#define TTFCFP_SUBSET 0      /* Straight Subset Font - Backward compatibility */
    '#define TTFCFP_SUBSET1 1      /* Subset font with full TTO and Kern tables. For later merge */
    '#define TTFCFP_DELTA 2      /* Delta font, for merge with a subset1 font */

    '/* for usSubsetPlatform ID values */
    '#define TTFCFP_UNICODE_PLATFORMID 0
    '#define TTFCFP_APPLE_PLATFORMID   1
    '#define TTFCFP_ISO_PLATFORMID     2
    '#define TTFCFP_MS_PLATFORMID      3

    '/* for usSubsetEncoding values */
    '#define TTFCFP_STD_MAC_CHAR_SET  0    /* goes with TTFSUB_APPLE_PLATFORMID */
    '#define TTFCFP_SYMBOL_CHAR_SET  0    /* goes with TTFSUB_MS_PLATFORMID */
    '#define TTFCFP_UNICODE_CHAR_SET  1    /* goes with TTFSUB_MS_PLATFORMID */
    '#define TTFCFP_DONT_CARE  0xFFFF

    '/* for usSubsetLanguage values */
    '#define TTFCFP_LANG_KEEP_ALL 0

    '/* for usFlags values */
    '#define TTFCFP_FLAGS_SUBSET 0x0001    /* if bit off, don't subset */
    '#define TTFCFP_FLAGS_COMPRESS 0x0002  /* if bit off, don't compress */
    '#define TTFCFP_FLAGS_TTC 0x0004  /* if bit off, its a TTF */
    '#define TTFCFP_FLAGS_GLYPHLIST 0x0008 /* if bit off, list is characters */


    '/* for usModes */
    '#define TTFMFP_SUBSET 0   /* copy a Straight Subset Font package to Dest buffer */
    '#define TTFMFP_SUBSET1 1  /* Expand a format 1 font into a format 3 font */
    '#define TTFMFP_DELTA 2       /* Merge a format 2 with a format 3 font */

     

    The commented lines of code are from the file fontsub.h, which is found in the Windows SDK.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Tuesday, March 08, 2011 2:17 PM
  • Thank JGrouse. Your program is perctely working.

    Do you have any idea about converting ttf file to svg file. If yes, please share your thoughts..thanks

     

    Thursday, March 17, 2011 6:02 PM
  • Hi JGrouse

    Your .net 4.0 code creating ttf sub set file but it seems it includes empty placeholders for every font character that's been removed. Can we remove plcaeholders also for characters that we have not included in the subset file.

    Monday, March 21, 2011 9:51 PM
  • I have some not so good news when it comes to finding a resolution to the original problem in the MSDN forums.

    http://social.msdn.microsoft.com/Forums/en-US/clr/thread/52587c2d-52e4-4991-af06-2416d3e9d4b6

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    Tuesday, March 22, 2011 8:02 PM
  • Unfortunatly, I have returned to this issue, though we have upgraded to VS 2010 we are still using .NET 2.0 so the 4.0 solution is still not accapable for us... anyone have anynew Ideas?

    Wednesday, August 31, 2011 12:46 PM
  • Most likely this answer comes way too late, but I struggled with the API call too, so I'm posting my code anyway

    A .NET 2.0 solution that works for me is:

    using System;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace FontSubLibTest
    {
    	public class FontSubLib
    	{
    		[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    		private delegate IntPtr AllocProc(Int32 size);
    
    		[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    		private delegate IntPtr ReallocProc(IntPtr memBlock, IntPtr size);
    
    		[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    		private delegate void FreeProc(IntPtr memBlock);
    
    		[DllImport("FontSub.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
    		private static extern uint CreateFontPackage(
    			[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    			byte[] puchSrcBuffer, // 0
    			uint ulSrcBufferSize, // 1
    			out IntPtr puchFontPackageBufferPtr, // 2
    			out uint pulFontPackageBufferSize, // 3
    			out uint pulBytesWritten, // 4
    			ushort usFlags, // 5
    			ushort usTtcIndex, // 6
    			ushort usSubsetFormat, // 7
    			ushort usSubsetLanguage, // 8
    			ushort usSubsetPlatform, // 9
    			ushort usSubsetEncoding, // 10
    			[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 12)]
    			byte[] pusSubsetKeepList, // 11
    			ushort usSubsetKeepListCount, // 12
    			AllocProc lpfnAllocate, // 13
    			ReallocProc lpfnReAllocate, // 14
    			FreeProc lpfnFree, // 15
    			IntPtr lpvReserved // 16
    			);
    
    		public static byte[] GenerateSubset(byte[] sourceFontData, string keepChars)
    		{
    			AllocProc allocProc = Marshal.AllocHGlobal;
    
    			ReallocProc reallocProc = (p, c) =>
    				p == IntPtr.Zero
    				? Marshal.AllocHGlobal(c)
    				: Marshal.ReAllocHGlobal(p, c);
    
    			FreeProc freeProc = Marshal.FreeHGlobal;
    
    			IntPtr bufferPtr;
    			uint bufferSize;
    			uint bytesWritten;
    
    			byte[] unicodeChars = Encoding.Unicode.GetBytes(keepChars);
    
    			uint rc = CreateFontPackage(
    				sourceFontData, (uint)sourceFontData.Length,
    				out bufferPtr,
    				out bufferSize,
    				out bytesWritten,
    				1, /* flags */
    				0, /* TTC index is used only if  */
    				0, /* TTFCFP_SUBSET */
    				0, /* Subset langugage */
    				3, /* Subset platform: TTFCFP_MS_PLATFORMID */
    				1, /* Subset encoding: TTFCFP_UNICODE_CHAR_SET */
    				unicodeChars, (ushort)(unicodeChars.Length/2),
    				allocProc,
    				reallocProc,
    				freeProc,
    				IntPtr.Zero);
    
    			if (rc != 0 || bufferPtr == IntPtr.Zero)
    				return null;
    
    			var buffer = new byte[bytesWritten];
    			Marshal.Copy(bufferPtr, buffer, 0, buffer.Length);
    			return buffer;
    		}
    	}
    }
    

    For .NET 4, it is a bit simpler:

    using System;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace FontSubLibTest
    {
    	public class FontSubLib
    	{
    		[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    		private delegate IntPtr AllocProc(Int32 size);
    
    		[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    		private delegate IntPtr ReallocProc(IntPtr memBlock, IntPtr size);
    
    		[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    		private delegate void FreeProc(IntPtr memBlock);
    
    		[DllImport("FontSub.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
    		private static extern uint CreateFontPackage(
    			[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    			byte[] puchSrcBuffer, // 0
    			uint ulSrcBufferSize, // 1
    			[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]
    			out byte[] puchFontPackageBufferPtr, // 2
    			out uint pulFontPackageBufferSize, // 3
    			out uint pulBytesWritten, // 4
    			ushort usFlags, // 5
    			ushort usTtcIndex, // 6
    			ushort usSubsetFormat, // 7
    			ushort usSubsetLanguage, // 8
    			ushort usSubsetPlatform, // 9
    			ushort usSubsetEncoding, // 10
    			[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 12)]
    			byte[] pusSubsetKeepList, // 11
    			ushort usSubsetKeepListCount, // 12
    			AllocProc lpfnAllocate, // 13
    			ReallocProc lpfnReAllocate, // 14
    			FreeProc lpfnFree, // 15
    			IntPtr lpvReserved // 16
    			);
    
    		public static byte[] GenerateSubset(byte[] sourceFontData, string keepChars)
    		{
    			AllocProc allocProc = Marshal.AllocHGlobal;
    
    			ReallocProc reallocProc = (p, c) =>
    				p == IntPtr.Zero
    				? Marshal.AllocHGlobal(c)
    				: Marshal.ReAllocHGlobal(p, c);
    
    			FreeProc freeProc = Marshal.FreeHGlobal;
    
    			byte[] buffer;
    			uint bufferSize;
    			uint bytesWritten;
    
    			byte[] unicodeChars = Encoding.Unicode.GetBytes(keepChars);
    
    			uint rc = CreateFontPackage(
    				sourceFontData, (uint)sourceFontData.Length,
    				out buffer,
    				out bufferSize,
    				out bytesWritten,
    				1, /* flags */
    				0, /* TTC index is used only if  */
    				0, /* TTFCFP_SUBSET */
    				0, /* Subset langugage */
    				3, /* Subset platform: TTFCFP_MS_PLATFORMID */
    				1, /* Subset encoding: TTFCFP_UNICODE_CHAR_SET */
    				unicodeChars, (ushort)(unicodeChars.Length/2),
    				allocProc,
    				reallocProc,
    				freeProc,
    				IntPtr.Zero);
    
    			if (rc != 0 || buffer == null)
    				return null;
    
    			return buffer;
    		}
    	}
    }
    

    To test the code, I used:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    
    namespace FontSubLibTest
    {
    	class Program
    	{
    		static void Main(string[] args)
    		{
    			const string sourceFontPath = @"c:\windows\fonts\arial.ttf";
    			var sourceFontData = File.ReadAllBytes(sourceFontPath);
    			Console.WriteLine("Original font size = {0}", sourceFontData.Length);
    			var subsetFontData = FontSubLib.GenerateSubset(sourceFontData, "0123456789");
    			Console.WriteLine("Subset font size = {0}", subsetFontData.Length);
    			File.WriteAllBytes("subset.ttf", subsetFontData);
    		}
    	}
    }
    

    Good luck,

    Peter

    Thursday, June 27, 2013 4:49 PM