Install Type 1 Font in VS 2010 Setup Project
I'm trying use a Visual Studio 2010 setup project to package a set of Type 1 fonts into a MSI file for easy installation.
I've configured my setup project to place all the PFM and PFB files in the Fonts folder, set all the PFM files to vsdrfFont
and fixed the naming issue mentioned here:
http://cjwdev.wordpress.com/2011/03/14/installing-non-truetype-fonts-with-visual-studio-installer/
However, this isn't working for Type 1 fonts.
The Type 1 font files are installed but the font is still not recognized and doesn't appear in the Fonts
window.
If installed manually, Type 1 fonts are registered under HKLM\SOFTW开发者_如何学运维ARE\Microsoft\Windows NT\CurrentVersion\Type 1 Installer\Type 1 Fonts
and work fine.
How can the same result be achieved with a setup project?
To install Type 1 fonts you need to do the following:
- Register the font title under 'Type 1 Fonts'
- Copy both the PFM and the PFB to the windows fonts directory
- Call the AddFontResource method
Register the font title under 'Type 1 Fonts'
SOFTWARE\Microsoft\Windows NT\CurrentVersion\Type 1 Installer\Type 1 Fonts
The Font Title is required, rather than providing this to the installer, the following code snippet will allow you to read the font title from the PFM. It is based on information gathered from the following source:
http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf
private static string GetType1FontName(string filename)
{
StringBuilder postscriptName = new StringBuilder();
FileInfo fontFile = new FileInfo(filename);
using (FileStream fs = fontFile.OpenRead())
{
using (StreamReader sr = new StreamReader(fs))
{
using (BinaryReader inStream = new BinaryReader(fs))
{
// PFM Header is 117 bytes
inStream.ReadBytes(117); // skip 117
short size = inStream.ReadInt16();
int extMetricsOffset = inStream.ReadInt32();
int extentTableOffset = inStream.ReadInt32();
inStream.ReadBytes(4); // skip 4
int kernPairOffset = inStream.ReadInt32();
int kernTrackOffset = inStream.ReadInt32();
int driverInfoOffset = inStream.ReadInt32();
fs.Position = driverInfoOffset;
while (inStream.PeekChar() != 0)
{
postscriptName.Append(inStream.ReadChar());
}
}
}
}
return postscriptName.ToString();
}
Copy both the PFM and the PFB to the windows fonts directory
According to this blog http://www.atalasoft.com/cs/blogs/stevehawley/archive/2008/08/25/getting-the-fonts-folder.aspx the right way to get the windows fonts folder is as follows:
[DllImport("shell32.dll")]
private static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken,
uint dwFlags, [Out] StringBuilder pszPath);
public static string GetFontFolderPath()
{
StringBuilder sb = new StringBuilder();
SHGetFolderPath(IntPtr.Zero, 0x0014, IntPtr.Zero, 0x0000, sb);
return sb.ToString();
}
Call the AddFontResource method
Finally, the method AddFontResource should be called, the parameter lpFilename should be made up of the pfm and pfb files separated by the pipe character '|'. In my case I put the full path to the windows fonts folder which seemed to work. After calling AddFontResource you need to call PostMessage with a parameter of WM.FONTCHANGE (0x001D) to inform other windows of the change.
[DllImport("gdi32.dll")]
static extern int AddFontResource(string lpFilename);
// build the name for the api "<pfm>|<pfb>"
string apiValue = string.Format("{0}|{1}", PfmFileDestination, PfbFileDestination);
// Call the api to register the font
int retVal = AddFontResource(apiValue);
// Inform other windows of change
PostMessage(HWND_BROADCAST, WM.FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
Here is a solution that involves an MSI custom action. I have written in using C#, but any other language capable of calling a DLL can be user. Here is a tutorial link for C#: Walkthrough: Creating a Custom Action
using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;
using System.Runtime.InteropServices;
namespace InstallType1Font
{
[RunInstaller(true)]
public partial class Installer1 : Installer
{
public Installer1()
{
InitializeComponent();
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
// here, you'll have to determine the proper path
string path = @"c:\Windows\Fonts\MyFont.pfm";
if (File.Exists(path))
{
InstallFontFile(IntPtr.Zero, path, 0);
}
}
[DllImport("fontext.dll", CharSet = CharSet.Auto)]
private static extern void InstallFontFile(IntPtr hwnd, string filePath, int flags);
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Rollback(IDictionary savedState)
{
base.Rollback(savedState);
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Uninstall(IDictionary savedState)
{
base.Uninstall(savedState);
}
}
}
As far as I know, InstallFontFile
is undocumented, but allows to install the font permanently. Use this at your own risk.
Note: you still need to modify the .MSI to ensure the Fonts file have a FontTitle as described in the link you gave.
精彩评论