| File: Serialization\XpsFontSubsetter.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\ReachFramework\ReachFramework.csproj (ReachFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. /*++ Abstract: This file implements the XpsFontSubsetter used by the Xps Serialization APIs for serializing fonts to a Xps package. --*/ using System.IO; using System.Windows.Media; using System.Windows.Xps.Packaging; using MS.Internal.Utility; using MS.Internal.IO.Packaging; namespace System.Windows.Xps.Serialization { /// <summary> /// Flags indicating which parts are to be exluded /// from a digital signature /// May not be or'ed together /// </summary> [FlagsAttribute] public enum FontSubsetterCommitPolicies { /// <summary> /// No font subsettint, the entire font is copied /// fonts are assumed shared accross all documents /// </summary> None = 0x00000000, /// <summary> /// Font parts will be generted for the fonts /// used in a single page /// </summary> CommitPerPage = 0x00000001, /// <summary> /// Font parts will be generted for the fonts /// used in a single document /// </summary> CommitPerDocument = 0x00000002, /// <summary> /// Font parts will be generted for all fonts /// used in accros the entire sequence /// </summary> CommitEntireSequence = 0x00000003, }; /// <summary> /// Actions to be taken to embed font /// based on type flag /// </summary> [FlagsAttribute] internal enum FontEmbeddingAction { /// <summary> /// No action /// </summary> None = 0x00000000, /// <summary> /// Obfuscate and subset the font /// </summary> ObfuscateSubsetFont = 0x00000001, /// <summary> /// Obfuscate but copy the data /// directly from the font stream /// </summary> ObfuscateOnlyFont = 0x00000002, /// <summary> /// Generate Images of glyps using Image Brush /// directly from the font stream /// </summary> ImageOnlyFont = 0x00000004, } /// <summary> /// Implements the functionality to generate font subsets /// based on glyph runs obtained. This class uses the /// serialization manager to write data to the Xps /// package. /// </summary> internal class XpsFontSubsetter { #region Constructors /// <summary> /// Constructs a XpsFontSubsetter instance. /// </summary> /// <param name="packagingPolicy"> /// The serialization manager to serialize to. /// </param> public XpsFontSubsetter( BasePackagingPolicy packagingPolicy ) { ArgumentNullException.ThrowIfNull(packagingPolicy); _packagingPolicy = packagingPolicy; _fontEmbeddingManagerCache = new Dictionary<Uri, FEMCacheItem>(3, MS.Internal.UriComparer.Default); } #endregion Constructors #region Public methods /// <summary> /// Adds a specified Glyph run to an existing or new /// cache item and returns the part Uri where font /// will be committed. /// </summary> /// <param name="glyphRun"> /// The GlyphRun to add. /// </param> /// <returns> /// A Uri to locate the font within the Xps package. /// </returns> public Uri ComputeFontSubset( GlyphRun glyphRun ) { ArgumentNullException.ThrowIfNull(glyphRun); FontEmbeddingRight embeddingRights = glyphRun.GlyphTypeface.EmbeddingRights; Uri fontUri = null; if (DetermineEmbeddingAction(embeddingRights) == FontEmbeddingAction.ImageOnlyFont) { // // Aquire a bit map stream to embedd the Glyph Run image // fontUri = _packagingPolicy.AcquireResourceStreamForXpsImage(XpsS0Markup.ImageUriPlaceHolder).Uri; } else { FEMCacheItem cacheItem = AcquireCacheItem( glyphRun.GlyphTypeface ); if (cacheItem != null) { cacheItem.CurrentPageReferences = true; if( _commitPolicy == FontSubsetterCommitPolicies.None && !cacheItem.IsStreamWritten ) { fontUri = cacheItem.CopyFontStream(); } else { fontUri = cacheItem.AddGlyphRunUsage(glyphRun); } } } return fontUri; } /// <summary> /// Commits all fonts that are currently in the cache /// and clears the cache out. /// </summary> /// <param name="signal"> /// Signal indicating the font should be subsetted /// according to which policy. /// </param> /// <return> /// true if the signal completed a subset. /// </return> public bool CommitFontSubsetsSignal( FontSubsetterCommitPolicies signal ) { // // flag indicating if this signal completed a subset // bool completedSubset = false; if( signal == FontSubsetterCommitPolicies.CommitPerPage ) { foreach (FEMCacheItem item in _fontEmbeddingManagerCache.Values) { if (item != null) { item.AddRelationship(); item.CurrentPageReferences = false; } } } else if( signal == FontSubsetterCommitPolicies.CommitPerDocument && _commitPolicy == FontSubsetterCommitPolicies.CommitEntireSequence ) { foreach (FEMCacheItem item in _fontEmbeddingManagerCache.Values) { item?.AddRestrictedRelationship(); } } // // If we recieve a signal to commit a superset of what we are commiting on // i.e. Document Signal with commit Policy is per page // we must commit // if( signal >= _commitPolicy ) { if (signal == _commitPolicy) { _currentCommitCnt++; } if( signal > _commitPolicy || _currentCommitCnt%_commitCountPolicy == 0 ) { foreach (FEMCacheItem item in _fontEmbeddingManagerCache.Values) { if (item != null && _commitPolicy != FontSubsetterCommitPolicies.None) { item.Commit(); // // We will be removing this FEMCacheItem from the cache // after this loop so we must add its document relationship // if restricted. If the policy is CommitPerDocumentSequence // we do not need to add since it will have been added at the document signal // if (signal != FontSubsetterCommitPolicies.CommitEntireSequence) { item.AddRestrictedRelationship(); } } } _fontEmbeddingManagerCache.Clear(); _currentCommitCnt = 0; completedSubset = true; } } return completedSubset; } /// <summery> /// Determines Embedding action based /// on flags in the fsType field /// </summery> public static FontEmbeddingAction DetermineEmbeddingAction(GlyphTypeface glyphTypeface) { return DetermineEmbeddingAction(glyphTypeface.EmbeddingRights); } /// <summary> /// Determines Embedding action based /// on flags in the fsType field ///</summary> public static FontEmbeddingAction DetermineEmbeddingAction( FontEmbeddingRight fsType ) { FontEmbeddingAction action = FontEmbeddingAction.ObfuscateSubsetFont; switch( fsType ) { case FontEmbeddingRight.RestrictedLicense: case FontEmbeddingRight.PreviewAndPrintButWithBitmapsOnly: case FontEmbeddingRight.PreviewAndPrintButNoSubsettingAndWithBitmapsOnly: case FontEmbeddingRight.EditableButWithBitmapsOnly: case FontEmbeddingRight.EditableButNoSubsettingAndWithBitmapsOnly: case FontEmbeddingRight.InstallableButWithBitmapsOnly: case FontEmbeddingRight.InstallableButNoSubsettingAndWithBitmapsOnly: action = FontEmbeddingAction.ImageOnlyFont; break; case FontEmbeddingRight.PreviewAndPrint: case FontEmbeddingRight.Editable: case FontEmbeddingRight.Installable: action = FontEmbeddingAction.ObfuscateSubsetFont; break; case FontEmbeddingRight.EditableButNoSubsetting: case FontEmbeddingRight.PreviewAndPrintButNoSubsetting: case FontEmbeddingRight.InstallableButNoSubsetting: action = FontEmbeddingAction.ObfuscateOnlyFont; break; } return action; } /// <summery> /// Determines Embedding action based /// on flags in the fsType field /// </summery> public static bool IsRestrictedFont(GlyphTypeface glyphTypeface) { return IsRestrictedFont(glyphTypeface.EmbeddingRights); } /// <summary> /// Determines Embedding action based /// on flags in the fsType field ///</summary> public static bool IsRestrictedFont( FontEmbeddingRight fsType ) { bool isRestrictedFont = false; switch( fsType ) { case FontEmbeddingRight.PreviewAndPrintButNoSubsetting: case FontEmbeddingRight.PreviewAndPrint: isRestrictedFont = true; break; } return isRestrictedFont; } /// <summary> /// Determines on what signal subsets will be commited ///</summary> public void SetSubsetCommitPolicy( FontSubsetterCommitPolicies policy ) { if( policy == FontSubsetterCommitPolicies.CommitEntireSequence && _commitCountPolicy != 1 ) { throw new ArgumentOutOfRangeException(SR.ReachPackaging_SequenceCntMustBe1); } _commitPolicy = policy; } /// <summary> /// Determines the number of signals subsets will be commited on ///</summary> public void SetSubsetCommitCountPolicy( int commitCount ) { if( _commitPolicy == FontSubsetterCommitPolicies.CommitEntireSequence && commitCount != 1 ) { throw new ArgumentOutOfRangeException(SR.ReachPackaging_SequenceCntMustBe1); } else if( commitCount < 1 ) { throw new ArgumentOutOfRangeException(SR.ReachPackaging_CommitCountPolicyLessThan1); } _commitCountPolicy = commitCount; } #endregion Public methods #region Private data private IDictionary<Uri, FEMCacheItem> _fontEmbeddingManagerCache; private BasePackagingPolicy _packagingPolicy; private FontSubsetterCommitPolicies _commitPolicy = FontSubsetterCommitPolicies.CommitEntireSequence; private int _commitCountPolicy = 1; private int _currentCommitCnt = 0; #endregion Private data #region Private methods /// <summary> /// Acquires a cache item for the specified font. If one /// does not exist a new one is created. /// </summary> /// <param name="glyphTypeface"> /// glyphTypeface of font to acquire. /// </param> /// <returns> /// A reference to a FEMCacheItem. /// </returns> private FEMCacheItem AcquireCacheItem( GlyphTypeface glyphTypeface ) { FEMCacheItem manager = null; Uri fontUri = glyphTypeface.FontUri; if (!_fontEmbeddingManagerCache.TryGetValue(fontUri, out manager)) { manager = new FEMCacheItem(glyphTypeface, _packagingPolicy); _fontEmbeddingManagerCache.Add(fontUri, manager); } return manager; } #endregion Private methods } /// <summary> /// This class represents a single cache item for /// a FontEmbeddingManager. Each manager represents /// one font. /// </summary> internal class FEMCacheItem { #region Constructors /// <summary> /// Constructs a new FEMCacheItem. /// </summary> /// <param name="glyphTypeface"> /// The glyphTypeface of the font for this cache item. /// </param> /// <param name="packagingPolicy"> /// The BasePackagingPolicy to write to. /// </param> public FEMCacheItem( GlyphTypeface glyphTypeface, BasePackagingPolicy packagingPolicy ) { ArgumentNullException.ThrowIfNull(packagingPolicy); ArgumentNullException.ThrowIfNull(glyphTypeface); _packagingPolicy = packagingPolicy; _streamWritten = false; // // Acquire the stream that will be used to save the font. // if the font is Image only we do not need a font stream // since each glyph run will be saved in its own image stream // _fontEmbeddingManager = new FontEmbeddingManager(); _glyphTypeface = glyphTypeface; _fontUri = glyphTypeface.FontUri; Uri fontUri = new Uri(_fontUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.SafeUnescaped), UriKind.RelativeOrAbsolute); string fontUriAsString = fontUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); _fontResourceStream = packagingPolicy.AcquireResourceStreamForXpsFont(fontUriAsString); // // Aquiring the Resource stream will instantiate the font // and add the require resource relationship // _curPageRelAdded = true; } #endregion Constructors #region Public methods /// <summary> /// Adds a specified glyph run to the ongoing subset. /// </summary> /// <param name="glyphRun"> /// The GlyphRun to add to subset. /// </param> /// <returns> /// A reference to a Uri where font is stored /// within the package. /// </returns> public Uri AddGlyphRunUsage( GlyphRun glyphRun ) { Uri fontUri = null; FontEmbeddingAction action = XpsFontSubsetter.DetermineEmbeddingAction( _glyphTypeface ); switch( action ) { // // Provide an empty image stream. The Glyphs serializer // will check content type and render adn serialize an // image into this stream based on content type // case FontEmbeddingAction.ImageOnlyFont: break; case FontEmbeddingAction.ObfuscateOnlyFont: fontUri = _fontResourceStream.Uri; // nothing to do here since the entire font will be added break; case FontEmbeddingAction.ObfuscateSubsetFont: _fontEmbeddingManager.RecordUsage(glyphRun); fontUri = _fontResourceStream.Uri; break; } return fontUri; } /// <summary> /// Computes the font subset of all GlyphRuns seen /// and commits the font to the Xps package. /// This ends the lifetime of this cache item. /// </summary> public void Commit( ) { FontEmbeddingAction action = XpsFontSubsetter.DetermineEmbeddingAction( _glyphTypeface ); switch( action ) { case FontEmbeddingAction.ImageOnlyFont: // nothing to do here since the glyph runs have already been converted break; case FontEmbeddingAction.ObfuscateOnlyFont: CopyFontStream(); break; case FontEmbeddingAction.ObfuscateSubsetFont: SubSetFont( _fontEmbeddingManager.GetUsedGlyphs(_fontUri), _fontResourceStream.Stream ); break; } } public void AddRestrictedRelationship( ) { FontEmbeddingAction action = XpsFontSubsetter.DetermineEmbeddingAction( _glyphTypeface ); if( action != FontEmbeddingAction.ImageOnlyFont && XpsFontSubsetter.IsRestrictedFont(_glyphTypeface) ) { _packagingPolicy. RelateRestrictedFontToCurrentDocument( _fontResourceStream.Uri ); } } public void AddRelationship( ) { if( !_curPageRelAdded && CurrentPageReferences) { FontEmbeddingAction action = XpsFontSubsetter.DetermineEmbeddingAction( _glyphTypeface ); switch( action ) { case FontEmbeddingAction.ImageOnlyFont: break; case FontEmbeddingAction.ObfuscateOnlyFont: case FontEmbeddingAction.ObfuscateSubsetFont: _packagingPolicy. RelateResourceToCurrentPage( _fontResourceStream.Uri, XpsS0Markup.ResourceRelationshipName); break; } } // Now we are finished with the current page // and need flag the need for relationship on the next _curPageRelAdded = false; } #endregion Public methods #region Private static methods private void SubSetFont( ICollection<ushort> glyphs, Stream stream ) { byte[] fontData = _glyphTypeface.ComputeSubset(glyphs); Guid guid = ParseGuidFromUri(_fontResourceStream.Uri); ObfuscateData(fontData, guid); stream.Write(fontData, 0, fontData.Length); Uri fontUri = new Uri(_fontUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.SafeUnescaped), UriKind.RelativeOrAbsolute); string fontUriAsString = fontUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); _packagingPolicy.ReleaseResourceStreamForXpsFont(fontUriAsString); _streamWritten = true; } internal Uri CopyFontStream() { Uri sourceUri = _fontUri; Uri destUri = _fontResourceStream.Uri; Stream destStream = _fontResourceStream.Stream; Stream sourceStream = null; byte [] memoryFont; GlyphTypeface glyphTypeface = new GlyphTypeface(sourceUri); sourceStream = glyphTypeface.GetFontStream(); memoryFont = new byte[_readBlockSize]; Guid guid = ParseGuidFromUri(destUri); int bytesRead = PackagingUtilities.ReliableRead(sourceStream, memoryFont,0,_readBlockSize); if (bytesRead > 0) { // Obfuscate the first block ObfuscateData(memoryFont, guid ); } while( bytesRead > 0 ) { destStream.Write(memoryFont, 0, bytesRead); bytesRead = PackagingUtilities.ReliableRead( sourceStream, memoryFont, 0, _readBlockSize); } Uri fontUri = new Uri(_fontUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.SafeUnescaped), UriKind.RelativeOrAbsolute); string fontUriAsString = fontUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); _packagingPolicy.ReleaseResourceStreamForXpsFont(fontUriAsString); _streamWritten = true; return destUri; } /// <summary> /// Parses a Guid from the file name /// in accordence with 6.2.7.3 Embedded Font Obfuscation /// of the metro spec /// </summary> /// <param name="uri"> /// Uri to parse /// </param> /// <returns> /// Guid parsed out of Uri /// </returns> private static Guid ParseGuidFromUri( Uri uri ) { string fileName = System.IO.Path.GetFileNameWithoutExtension( BindUriHelper.UriToString(uri) ); return new Guid( fileName ); } /// <summary> /// Obfuscate font data /// in accordence with 6.2.7.3 Embedded Font Obfuscation /// of the metro spec /// </summary> /// <param name="fontData"> /// Data to obfuscate /// </param> /// <param name="guid"> /// Guid to be used in XORing the header /// </param> public static void ObfuscateData( byte[] fontData, Guid guid ) { // Convert the GUID into string in 32 digits format (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) Span<byte> guidByteArray = stackalloc byte[16]; string guidString = guid.ToString("N"); for (int i = 0; i < guidByteArray.Length; i++) { guidByteArray[i] = Convert.ToByte(guidString.Substring(i * 2, 2), 16); } for( int j = 0; j < 2; j++ ) { for( int i = 0; i < 16; i ++ ) { fontData[i+j*16] ^= guidByteArray[15-i]; } } } #endregion Private static methods #region Public Properties public bool IsStreamWritten { get { return _streamWritten; } } public bool CurrentPageReferences { get { return _currentPageReferences; } set { _currentPageReferences = value; } } #endregion Public Properties #region Private data private bool _currentPageReferences; private bool _curPageRelAdded; private FontEmbeddingManager _fontEmbeddingManager; private BasePackagingPolicy _packagingPolicy; private XpsResourceStream _fontResourceStream; private GlyphTypeface _glyphTypeface; private bool _streamWritten; private Uri _fontUri; private static readonly int _readBlockSize = 1048576; //1MB #endregion Private data } }