Skip to content

Instantly share code, notes, and snippets.

@rionmonster
Created March 24, 2015 22:12
Show Gist options
  • Save rionmonster/1072469d6b50e447d022 to your computer and use it in GitHub Desktop.
Save rionmonster/1072469d6b50e447d022 to your computer and use it in GitHub Desktop.
Continued Attempt at Dynamic Glyphs
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.CSS.Editor.Completion;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using TheArtOfDev.HtmlRenderer.WinForms;
namespace Glyphfriend.GlyphCompletionProviders
{
[Export(typeof(ICssCompletionEntryGlyphProvider))]
class FontAwesomeCompletionEntryGlyphProvider : ICssCompletionEntryGlyphProvider
{
// A flag to determine if the dynamic renderer has rendered the fonts for this library
private static bool _fontsInitialized = false;
// Store each of the byte[] data for each font for future calls (so that the fonts only have to be generated once)
private static Dictionary<string, byte[]> _fontMappings;
//private static BitmapFrame _icon = BitmapFrame.Create(new Uri("pack://application:,,,/WebEssentials2015;component/Resources/Images/foundation.png", UriKind.RelativeOrAbsolute));
private static Regex _regex = new Regex(@"^font(-?)awesome(-.*)?(\.min)?\.css$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public ImageSource GetCompletionGlyph(string entryName, Uri sourceUri, CssNameType nameType)
{
// If the source Uri for the image is null, ignore it
if (sourceUri == null) { return null; }
// Get the file path of the source being used
string filename = Path.GetFileName(sourceUri.ToString()).Trim();
// Determine if this matches our filename
if (_regex.IsMatch(filename))
{
// It's a valid Font Awesome file, so generate the Glyphs
if (!_fontsInitialized)
{
// Get the path for the current Project being targeted
var projectDirectoryPath = GetExecutingProjectDirectory(sourceUri.AbsolutePath);
// Initialize the Font Awesome icons for the renderer
InitializeFontAwesomeFonts(projectDirectoryPath);
}
// Check and initialize the glyph mappings from the CSS
if (_fontMappings == null)
{
// Generate the mappings for each font
_fontMappings = ParseAndGenerateMappings(sourceUri.AbsolutePath);
}
// At this point, find the font that cooresponds to the existing Glyph and serve it
if (_fontMappings.ContainsKey(entryName))
{
return BitmapFrame.Create(new MemoryStream(_fontMappings[entryName]));
}
else
{
// Otherwise serve the appropriate placeholder icon for this image
// return _icon;
}
}
return null;
}
private void InitializeFontAwesomeFonts(string projectDirectoryPath)
{
// Instantiate all Font Awesome True Type fonts that are available (for the renderer)
// Build a collection of fonts
var fonts = new PrivateFontCollection();
// Get the appropriate fonts that are present within this Project
foreach (var fontFamily in Directory.GetFiles(projectDirectoryPath, "*.ttf"))
{
fonts.AddFontFile(fontFamily);
}
// Add each of the true type fonts to this renderer
foreach (var trueTypeFont in fonts.Families)
{
HtmlRender.AddFontFamily(trueTypeFont);
}
_fontsInitialized = true;
}
private Dictionary<string, byte[]> ParseAndGenerateMappings(string path)
{
// Read in the CSS from the file
var css = System.IO.File.ReadAllText(path);
// Read only the CSS Classes (only those with :before and beginning with .fa-)
var classes = Regex.Matches(css, @"\.[-]?([_a-zA-Z][_a-zA-Z0-9-]*):before|[^\0-\177]*\\[0-9a-f]{1,6}(\r\n[ \n\r\t\f])?|\\[^\n\r\f0-9a-f]*")
.Cast<Match>()
.Where(m => m.Value.StartsWith(".fa-"))
.Distinct();
// Create a dictionary that maps class names to their respective unicode values
Dictionary<string, string> mappings = new Dictionary<string, string>();
// Using those classes, grab the content value that appears within each of them, which will map
// the unicode values to the appropriate class
foreach (var cssClass in classes)
{
// Find the cooresponding code for each class
var unicode = Regex.Match(css, cssClass + "[^\"]*\"(.*)\"[^}]*}");
if (unicode != null && unicode.Groups.Count > 1)
{
// Grab the match and store it
mappings.Add(cssClass.Value, String.Format("&#x{0};", unicode.Groups[1].Value.TrimStart('\\')));
}
}
// At this point we have the unicode values that maps to each Glyph, so generate the byte[]
// for the images and store them
Dictionary<string, byte[]> glyphMappings = new Dictionary<string, byte[]>();
// Loop through the available mappings and generate a Glyph for each
foreach (var mapping in mappings)
{
// Generate an HTML Snippet to properly render the Glyph
var html = String.Format("<span style='font-family: FontAwesome; display:block;'>{0}</span>", mapping.Value);
glyphMappings.Add(mapping.Key, ImageToBytes(HtmlRender.RenderToImageGdiPlus(html, new Size(16, 16))));
}
// Return all of these mappings
return glyphMappings;
}
private byte[] ImageToBytes(Image img)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
// Resolve the appropriate path for this project
private string GetExecutingProjectDirectory(string filename)
{
var dte2 = (DTE2)Package.GetGlobalService(typeof(DTE2));
if (dte2 != null)
{
var projItem = dte2.Solution.FindProjectItem(filename);
if (projItem != null)
{
// If you have the project, figure out how to get it's root directory here
//return projItem.ContainingProject.GetThePathSomehow;
}
}
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment