开发者

Install Type 1 Font in VS 2010 Setup Project

开发者 https://www.devze.com 2023-04-07 14:46 出处:网络
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'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:

  1. Register the font title under 'Type 1 Fonts'
  2. Copy both the PFM and the PFB to the windows fonts directory
  3. 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号