Silverlight 完全中文解決方案

Silverlight 完全中文解決方案

作者:黃忠成

問題的起源
如你所知,Silverlight 具備相當完善的 2D 向量繪圖及動畫能力,而且擁有了通往 RIA 世界最重要的完整程式架構支援,但是一旦想將其應用在實務的網頁開發上時,你馬上會陷入到 Silverlight 目前無法顯示中文的窘境之中!!事實上,Silverlight 在顯示中文上並沒有問題,只是其 Runtime 並未內建中文字型,導致所有的中文字都會因缺乏字型的緣故而顯示成一個個的小方框,解決這個問題的方案也不是沒有,本文就一一細數目前在 Silverligh 上顯示中文的解決方案。

解法一:下載字型或是 ODTTF(XPS)
雖然 Silverlight Runtime 中未內建中文字型,不過倒是提供了字型下載的功能,Silverlight Runtime 支援 TTF 及 ODTTF(XPS) 等兩種字型,我們可以使用 Downloader 物件來下載包含字型的 ZIP 檔案,然後以 setFontSource 函式將字型指定給 TextBlock,如此便能顯示中文字了,詳細的做法可以參考 Silverlight SDK 的說明文件。但如你所知的,字型本身有著授權的問題,你不能下載未經授權的字型到客戶端,這意味著除非微軟同意,否則我們不能在網站上提供如 Windows 之細明體、標楷體等字型下載,當然!轉成 XPS 後所粹取出的 ODTTF 也是受到同樣限制的。

解法二:Path
微軟的周旺暾先生提出了下載字型外的另一種解法,那就是使用 Path 物件,他提出了兩種使用 Path 物件來顯示中文的方法,一是使用 Blend 將中文直接轉成 Path,這個方法的確可行,但只適用於靜態字之顯示。二是於網站伺服器上安裝 .NET Framework 3.0,利用 WPF API 動態將指定文字轉成 Path 後回傳給客戶端,此法可適用於靜態或是動態之文字顯示,詳細請參考旺暾兄之 Blog:http://blogs.msdn.com/wtchou/

解法三:圖型
圖形解法是我於前幾個月所提出的解法,簡單的說就是在伺服器端將文字輸出成圖形,回傳至客戶端指定給 Image 物件來顯示中文,詳細請參考我的 Blog:http://blog.csdn.net/Code6421/archive/2007/08/09/1733745.aspx

為何不在 Runtime 內嵌中文字型?
老實說,除了下載字型外,使用 Path 或是圖形都只能算暫解燃眉之急的解法,畢竟 TextBlock 控件才是 Silverlight 用來顯示文字的主要控件,以 Path 來說,雖然支援換行上不是問題,但是若要兩行顏色或是字體不同,那就傷腦筋了!圖形也有著同樣的問題,如何算出輸出文字後圖形的大小,頁面上又該如何調配都是問題,即使能夠克服,也必須花上不少時間!總歸一句話,只要 Silverlight Runtime 內嵌中文字型,一切的問題都迎刃而解,但這樣一來,Silverlight Runtime 就不可能低於 2MB,畢竟中文字博大精深,每個字型檔少說都要 8~9MB,就算用 ZIP 壓縮後也要 4~5MB,更不用談要支援繁簡兩種字型了,若再將其它語系算進去的話,那這個 Runtime 可以說是大怪物一隻了,別忘了!Silverlight Runtime 是要能夠執行於所有語系之系統上的!因此,Silverlight Runtime 內嵌中文字型的方式在實務上是不可行的,那又該如何是好呢?我認為應該循視情況下載字型的方向走,當觀看一個繁體中文的 Silverlight 網頁時,若使用者的 Silverlight Runtime 未下載所需要的字型,那麼就提示該使用者並自動由某一網站下載字型,這個某一網站可以是開發者的,也可以是微軟的,不過!這個假設建立在一個前題之下,那就是下載字型的動作必須是合法的。

評估使用免費字型的可能性
好了!你現在應該由抱怨微軟不在 Silverlight Runtime 內嵌中文字型慢慢轉成抱怨微軟不在推出 Silverlight Runtime 時開放某些中文字型的授權了,事實上!就我所得到的消息,微軟目前正在研擬修改字型授權,未來各位可能可以將某些特定字型放在 Silverlight 網站上供人下載或是由微軟網站下載而不觸法,但是處理這件事需要一些時間,誰也說不準得花上多久。在這之前,除了使用 Path 或圖形外,我們也可以朝向找尋完全免費授權之免費字型的方向前進,目前筆者能找到的、並確定能使用在 Silverlgiht 的免費中文字型如下:

王漢宗老師:http://briian.com/?p=290
吳老師:ftp://cle.linux.org.tw/pub2/fonts/cwttf/center
文鼎捐贈給自由軟體界的四套字: http://cle.linux.org.tw/fonts/Arphic/
PS:內含兩套簡體字。
雖然王漢宗老師所提供的字型較多且完整,但因為與文鼎有一些法律上的糾紛待處理,所以若你是要用於商業網站上,還是建議使用吳老師或是文鼎所提供的字型,另外!請特別注意一點,Silverlight 的 FontFamily 必須使用英文名稱,以文鼎 PL 中楷來說,FontFamily 必須設定為【AR PL KaitiM Big5】。

當字型、授權不是問題時
OK,現在字型及授權都不是問題了,問題在於你還是得寫下一些 JavaScript 程式碼,使用 Downloader 物件來下載需要的字型,並設定給 TextBlock,這雖然不難,但就是有點繁瑣,基於前次開發 Silverlight DataBindings 技術的經驗,我設計了一組 JavaScript Library 來協助各位更容易的在 Silverlight 上顯示中文。

方便的 JavaScript Library : FontHelper
FontHelper 提供了 Font Bindings 技術,讓你可以直接於 XAM L之 TextBlock 的 Tag 中指定要使用何種字型,如下例:





【DBCS】是 FontHelper 辨識該 TextBlock 是否需要下載字型的關鍵字,【:】號之後的是字型壓縮檔的檔名,此檔案必須放在 Silverlight 網站下,以本例來說是放在網站的根目錄下,【;】後的是修飾字,FontHelper 支援使用者指定一個 default 字型,以本例來說,接下來的 TextBlock 只要以下例方式宣告,就可直接使用 cwkai.zip 的字型。





當呼叫了 FontHelper 物件的 initialize 函式後,其將會搜尋頁面上的 TextBlock 控件,並一一為其下載需要的字型,那麼何時呼叫 initialize 函式呢?在這之前你得先引用 SLFH.js 到你的 Silverlight 網頁中 (此檔可於範例檔中的 SilverlightJSApplication1 或是 SilverlightJSApplication2 等目錄中找到),如下例:

Default.html





SilverlightJSApplication1
















然後在 handleLoad 事件中建立 FontHelper 並呼叫 initialize 函式,如下例:

.xaml.js


handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.button = rootElement.children.getItem(0);
//建立Font Helper 物件
this.fh = new SilverlightHelper.FontHelper(rootElement);
//呼叫initialize函式,掃描Page 上所有的TextBlock控件,並自動下載字型
this.fh.initialize();;
}


這是靜態字型的使用方式,倘若我想在 JavaScript 中直接使用其它字型呢?FontHelper 提供了 setTextWithFont 函式能幫你達到這個需求。

.xaml.js


//呼叫setTextWithFont來設定TextBlock的字型及文字
//參數:
// setTextWithFont(,<要設定的文字>,<下載的字型>,<字型的FontFamily名稱>)
//
//PS: setTextWithFont不會重覆下載已經下載過的字型!!
this.fh.setTextWithFont(sender.findName("DynamicText"),
"我是動態設置的字","cwfs.zip","cwTeXFangSong");


setTextWithFont 會幫你下載指定的字型到客戶端,並設定指定的文字及 FontFamily,更好的是她會顯示 目前字型的下載進度於畫面上。好了,了解 FontHelper 的大概使用方法後,下面是個實際的例子,你可以在範例壓縮檔中的 SilverlightJSApplication1 目錄中找到這些檔案。

Default.html





SilverlightJSApplication1














Default.html.js


function createSilverlight()
{
var scene = new SilverlightJSApplication1.Scene();
Silverlight.createObjectEx({
source: 'Scene.sxaml',
parentElement: document.getElementById('SilverlightPlugInHost'),
id: 'SilverlightPlugIn',
properties: {
width: '400',
height: '400',
background:'#ffffffff',
isWindowless: 'false',
version: '0.8'
},
events: {
onError: null,
onLoad: Silverlight.createDelegate(scene, scene.handleLoad)
},
context: null
});
}

if (!window.Silverlight)
window.Silverlight = {};

Silverlight.createDelegate = function(instance, method) {
return function() {
return method.apply(instance, arguments);
}
}


Scene.xaml





Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />




Width="120" Height="44" Stroke="#46000000">







Canvas.Left="17" Canvas.Top="12" Foreground="#FFEFEFEF" Text="我是中文"
FontFamily="cwTeXKai" FontSize="24" Tag="DBCS:cwkai.zip;default" />
FontSize="26" Text="格式化中文測試" Tag="DBCS:default">


FontHelper 完全支援 TextBlock的Run指令



斜體目前仍無法處理



粗體字也無法處理



Canvas.Top="280"
FontFamily="cwTeXKai"
FontSize="42"
Tag="DBCS:default">
漸層中文字















Fill="#00FFFFFF" Name="highlightEllipse" Canvas.Left="3" Canvas.Top="3"/>




Scene.xml.js


if (!window.SilverlightJSApplication1)
window.SilverlightJSApplication1 = {};


SilverlightJSApplication1.Scene = function()
{
}

SilverlightJSApplication1.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.button = rootElement.children.getItem(0);

this.button.addEventListener("MouseEnter",
Silverlight.createDelegate(this, this.handleMouseEnter));
this.button.addEventListener("MouseLeftButtonDown",
Silverlight.createDelegate(this, this.handleMouseDown));
this.button.addEventListener("MouseLeftButtonUp",
Silverlight.createDelegate(this, this.handleMouseUp));
this.button.addEventListener("MouseLeave",
Silverlight.createDelegate(this, this.handleMouseLeave));

//建立Font Helper 物件
this.fh = new SilverlightHelper.FontHelper(rootElement);
//呼叫initialize函式,掃描Page 上所有的TextBlock控件,並自動下載字型
this.fh.initialize();;
},

// Sample event handlers
handleMouseEnter: function(sender, eventArgs)
{
// The following code shows how to find an element by name and call a method on it.
var mouseEnterAnimation = sender.findName("mouseEnter");
mouseEnterAnimation.begin();
},

handleMouseDown: function(sender, eventArgs)
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin();
},

handleMouseUp: function(sender, eventArgs)
{
var mouseUpAnimation = sender.findName("mouseUp");
mouseUpAnimation.begin();

// Put clicked logic here
alert("clicked");
//呼叫setTextWithFont來設定TextBlock的字型及文字
//參數:
// setTextWithFont(,<要設定的文字>,<下載的字型>,<字型的FontFamily名稱>)
//
//PS: setTextWithFont不會重覆下載已經下載過的字型!!
this.fh.setTextWithFont(sender.findName("DynamicText"),
"我是動態設置的字","cwfs.zip","cwTeXFangSong");
},

handleMouseLeave: function(sender, eventArgs)
{
var mouseLeaveAnimation = sender.findName("mouseLeave");
mouseLeaveAnimation.begin();
}
}


下圖是執行畫面。



點選按鈕後的畫面。



PS:目前 Silverlight Runtime 1.0 似乎有著一個 Bug,那就是在設定 setFontSource 後, 如粗體、斜體等修飾樣式都將無法使用,即使強制再設定也一樣,此問題不僅發生在中文字型,英文字也一樣。

當字型大小成為問題時
有了 FontHelper 及免費字型的幫忙,相信在你的 Silverlight 網站上使用中文已經不是大問題了,現在的問題是變成是字型檔大小,先讓我們看一下前例於 FireFox 上執行時的流量。



很可怕是吧,這樣一個網頁要價 6.98MB,其實光是字型檔就得要 6.95MB 了,對多數用戶來說,這是無法承受的沉重負擔!

使用 ODTTF
如果字型大小對你或是客戶而言是一個困擾的話 (大部份是),那麼 ODTTF 檔案可能會是解決方案之一,當你的電腦上安裝了 .NET Framework 3.0 時,其順便也為你安裝了 XPS Document Writer 工具,此工具以虛擬印表機的方式呈現,她可以讓你將 Word 檔案輸出成 XPS 檔案,一旦有了這個 XPS 檔案後,我們便可以由其中粹取 ODTTF 檔,這個 ODTTF 檔中包含了該 Word 檔案中所使用的字型,與 TTF 不同,ODTTF 中僅包含用到的字型,所以檔案自然小上許多。以將前例來說,首先我們必須將需要的字型安裝到 Windows 系統中,請以檔案總管開啟 Windows\Fonts 資料夾,由選單之檔案|安裝新字型來安裝所需要的字型:





接著準備一個 Word 檔案,內容如下,請注意!字體要選擇所需要的。





完成後選擇列印。



點選確定後選擇存檔位置。



待輸出完成後,請該檔案之副檔名由 .xps 改成 .zip,解壓後可於其中找到 .ODTTF 的檔案。



將這三個檔案再壓縮成一個 zip 檔案,將檔名取為 partialFonts.zip,放到 SilverlightJSApplication1 的目錄下,然後修改 Sence.xaml 及 Sence.xaml.js 如下。

Sence.xaml





Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />




Height="44" Stroke="#46000000">







Canvas.Top="12" Foreground="#FFEFEFEF" Text="我是中文"
FontFamily="cwTeXKai" FontSize="24" Tag="DBCS:partialFonts.zip;default" />
FontSize="26" Text="格式化中文測試" Tag="DBCS:default">


FontHelper 完全支援 TextBlock的Run指令



斜體目前仍無法處理



粗體字也無法處理



Canvas.Top="280"
FontFamily="cwTeXKai"
FontSize="42"
Tag="DBCS:default">
漸層中文字















Fill="#00FFFFFF" Name="highlightEllipse" Canvas.Left="3" Canvas.Top="3"/>





Sence.xaml.js


if (!window.SilverlightJSApplication1)
window.SilverlightJSApplication1 = {};


SilverlightJSApplication1.Scene = function()
{
}

SilverlightJSApplication1.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.button = rootElement.children.getItem(0);

this.button.addEventListener("MouseEnter",
Silverlight.createDelegate(this, this.handleMouseEnter));
this.button.addEventListener("MouseLeftButtonDown",
Silverlight.createDelegate(this, this.handleMouseDown));
this.button.addEventListener("MouseLeftButtonUp",
Silverlight.createDelegate(this, this.handleMouseUp));
this.button.addEventListener("MouseLeave",
Silverlight.createDelegate(this, this.handleMouseLeave));

//建立Font Helper 物件
this.fh = new SilverlightHelper.FontHelper(rootElement);
//呼叫initialize函式,掃描Page 上所有的TextBlock控件,並自動下載字型
this.fh.initialize();;
},

// Sample event handlers
handleMouseEnter: function(sender, eventArgs)
{
// The following code shows how to find an element by name and call a method on it.
var mouseEnterAnimation = sender.findName("mouseEnter");
mouseEnterAnimation.begin();
},

handleMouseDown: function(sender, eventArgs)
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin();
},

handleMouseUp: function(sender, eventArgs)
{
var mouseUpAnimation = sender.findName("mouseUp");
mouseUpAnimation.begin();

// Put clicked logic here
alert("clicked");
//呼叫setTextWithFont來設定TextBlock的字型及文字
//參數:
// setTextWithFont(,<要設定的文字>,<下載的字型>,<字型的FontFamily名稱>)
//
//PS: setTextWithFont不會重覆下載已經下載過的字型!!
//this.fh.setTextWithFont(sender.findName("DynamicText"),
//"我是動態設置的字","cwfs.zip","cwTeXFangSong");
this.fh.setTextWithFont(sender.findName("DynamicText"),
"我是動態設置的字","partialFonts.zip","cwTeXFangSong");
},

handleMouseLeave: function(sender, eventArgs)
{
var mouseLeaveAnimation = sender.findName("mouseLeave");
mouseLeaveAnimation.begin();
}
}



完成後執行,使用 FireFox 的流量分析如下所示。



只需 67KB,很戲劇化的結果是吧。

字型部份下載:XPS Font Service
使用 ODTTF 的優點是字型檔較小,缺點則是必須仰賴 Word 及 XPS Document Writer 來製作 ODTTF 檔案,在需要由資料庫或是與使用者互動來動態顯示中文字的應用上,這種解法就完全不適用了!當然,你可以事先準備較多的中文字,但這畢竟不是 100% 完整的解法!!那麼到頭來還是得下載所有字型了嗎?不見得!我們能用WPF將字轉成 Path,當然也能如法泡製將字轉成 XPS,然後粹取出 ODTTF 後壓縮回傳到客戶端,當然!前題得先在網站伺服器上安裝 .NET Framework 3.0,下面列出 Font Services 及使用她的 GetFontsXPS.aspx、及 ZIP 壓縮解壓縮部份 (此處使用 SharpZipLib 這個免費的 Zip 物件庫,可以在 http://www.icsharpcode.net/OpenSource/SharpZipLib/ 取得) 的原始碼,你可以在範例壓縮檔的 SilverlightJSApplication2 中找到她們。

FontServces.cs


using System;
using System.ComponentModel;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Windows;
using System.Globalization;
using System.Windows.Media;
using System.IO.Packaging;
using System.IO;
using System.Windows.Xps;
using System.Printing;
using System.Windows.Xps.Packaging;
using System.Text;
using System.Reflection;
using System.Security.Permissions;

public class FontService
{
private static Dictionary _fontMapping = new Dictionary();

static FontService()
{
//王老師的字型(由於與文鼎有些爭議未決,建議多使用吳老師的)
_fontMapping.Add("HanWangMingLight", "王漢宗細明體繁");
_fontMapping.Add("HanWangMingMedium", "王漢宗中明體繁");
_fontMapping.Add("HanWang WeiBeiMedium-Gb5", "王漢宗中魏碑簡");
_fontMapping.Add("HanWang FangSongMedium-Gb5", "王漢宗中仿宋簡");
//吳老師的字型
_fontMapping.Add("cwTeXHeiBold", "cwTeXHeiBold");
_fontMapping.Add("cwTeXFangSong", "cwTeXFangSong");
_fontMapping.Add("cwTeXKai", "cwTeXKai");
_fontMapping.Add("cwTeXYen", "cwTeXYen");
_fontMapping.Add("cwTeXMing", "cwTeXMing");
}

//create xps.
private DrawingVisual CreateDrawingVisualText(string formattedText,
string fontName, CultureInfo ci)
{
// Create an instance of a DrawingVisual.
DrawingVisual drawingVisual = new DrawingVisual();

// Retrieve the DrawingContext from the DrawingVisual.
DrawingContext drawingContext = drawingVisual.RenderOpen();

// Draw a formatted text string into the DrawingContext.
drawingContext.DrawText(
new FormattedText(formattedText,
ci,
FlowDirection.LeftToRight,
new Typeface(fontName),
12, Brushes.Black),
new Point(200, 116));

// Close the DrawingContext to persist changes to the DrawingVisual.
drawingContext.Close();
return drawingVisual;
}


private void VisualToXps(string fileName, string formattedText,
string fontName, CultureInfo ci)
{

Package package = Package.Open(fileName, FileMode.Create);
{
XpsDocument doc = new XpsDocument(package, CompressionOption.SuperFast);
{

XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(CreateDrawingVisualText(formattedText, fontName, ci));
}
doc.Close();
}
package.Close();
}


private FileStream BuildFontFont(string formattedText, string fontName, CultureInfo ci)
{
string key = Guid.NewGuid().ToString();
if (!Directory.Exists(HttpRuntime.AppDomainAppPath + "App_Data\\TempFonts"))
Directory.CreateDirectory(HttpRuntime.AppDomainAppPath + "App_Data\\TempFonts");
string basePath = HttpRuntime.AppDomainAppPath + "App_Data\\TempFonts\\";
VisualToXps(basePath + "\\" + key + ".xps", formattedText, fontName, ci);
Compression.UnZipClass unZip = new Compression.UnZipClass();
string fileName = unZip.UnZip(basePath + "\\" + key + ".xps", basePath);
File.Delete(basePath + "\\" + key + ".xps");
Compression.ZipClass Zip = new Compression.ZipClass();
Zip.ZipFile(fileName, HttpRuntime.AppDomainAppPath + "CachedFonts\\" +
key + ".zip", 8, 8192);
File.Delete(fileName);
return new FileStream(HttpRuntime.AppDomainAppPath + "CachedFonts\\" +
key + ".zip", FileMode.Open, FileAccess.Read);
}

private void DeleteOutDateFonts()
{
foreach (string item in Directory.GetFiles(
HttpRuntime.AppDomainAppPath + "CachedFonts\\"))
{
DateTime dt = File.GetCreationTime(item);
TimeSpan ts = DateTime.Now - dt;
if (ts.Hours > 2)
File.Delete(item);
}
}

public FontService(Page page)
{
page.Load += new EventHandler(page_Load);
}

void page_Load(object sender, EventArgs e)
{
Page page = (Page)sender;
if (!String.IsNullOrEmpty(page.Request.QueryString["FontName"]))
{
DeleteOutDateFonts();
string fontName = page.Request.QueryString["FontName"];
string texts = page.Server.UrlDecode(Encoding.Default.GetString(
Convert.FromBase64String(page.Request.QueryString["Data"])));
if (_fontMapping.ContainsKey(fontName))
fontName = _fontMapping[fontName];
FileStream fs = BuildFontFont(texts, fontName,
CultureInfo.GetCultureInfo("zh-TW"));
fs.Close();
page.Response.Clear();
page.Response.Write("CachedFonts/" + Path.GetFileName(fs.Name));
page.Response.End();
}
}
}



DeCompress.cs


using System;
using System.Text;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;

using ICSharpCode.SharpZipLib.BZip2;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Checksums;


namespace Compression
{
public class UnZipClass
{
public string UnZip(string fileName,string basePath)
{
string name = string.Empty;
using (FileStream inputStream = new FileStream(fileName,
FileMode.Open, FileAccess.Read))
{
ZipInputStream s = new ZipInputStream(inputStream);

ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
string directoryName = Path.GetDirectoryName(theEntry.Name);
if (directoryName == "Resources")
{
if (!Directory.Exists(
HttpRuntime.AppDomainAppPath + "App_Data\\TempFonts"))
Directory.CreateDirectory(
HttpRuntime.AppDomainAppPath + "App_Data\\TempFonts");
FileStream fs = File.Create(basePath+"\\"+
Path.GetFileName(theEntry.Name));
name = fs.Name;
int size = 2048;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0)
fs.Write(data, 0, size);
else
break;
}
fs.Close();
break;
}
}
s.Close();
}
return name;
}
}


public class ZipClass
{
public void ZipFile(string FileToZip, string ZipedFile,
int CompressionLevel, int BlockSize)
{
FastZip fz = new FastZip();
fz.CreateZip(ZipedFile, Path.GetDirectoryName(FileToZip), false,
Path.GetFileName(FileToZip));
}
}
}



GetFontsXPS.aspx.cs


using System;
using System.ComponentModel;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Windows;
using System.Globalization;
using System.Windows.Media;
using System.IO.Packaging;
using System.IO;
using System.Windows.Xps;
using System.Printing;
using System.Windows.Xps.Packaging;
using System.Text;
using System.Reflection;
using System.Security.Permissions;



public partial class GetFontsXPS : System.Web.UI.Page
{
private FontService _fontServices = null;

public GetFontsXPS()
{
_fontServices = new FontService(this);
}
}



Font Services 的設計是希望透過 ASP.NET 接收客戶端所傳上來的 FontName 及 Data 參數來動態產生 XPS,然後粹取出 ODTTF 後再壓縮後回傳給客戶端,基於必須由 JavaScript 將中文字送上來的緣故,Data 參數必須在 JavaScript 端轉成 Base64 後才能送上來解碼,FontHelper 提供了這些功能。

FontHelper 於 XPS Font Service
FontHelper 支援使用 XPS Font Service,SilverlightJSApplication2 就是使用 XPS Font Service 的例子。

Scene.xaml





Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />


Storyboard.TargetName="highlightEllipse"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />




Width="120" Height="44" Stroke="#46000000">







Canvas.Top="12" Foreground="#FFEFEFEF" Text="我是中文"
FontFamily="cwTeXKai" FontSize="24" Tag="XPS:GetFontsXPS.aspx?FontName=cwTeXKai" />
Text="格式化中文測試" Tag="XPS:GetFontsXPS.aspx?FontName=cwTeXKai">

FontHelper 完全支援 TextBlock的Run指令

斜體目前仍無法處理

粗體字也無法處理


Canvas.Top="280"
FontFamily="cwTeXKai"
FontSize="42"
Tag="XPS:GetFontsXPS.aspx?FontName=cwTeXKai">
漸層中文字





















Secne.xaml.js


if (!window.SilverlightJSApplication1)
window.SilverlightJSApplication1 = {};


SilverlightJSApplication1.Scene = function()
{
}

SilverlightJSApplication1.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement)
{
this.plugIn = plugIn;
this.rootElement = rootElement;
this.button = rootElement.children.getItem(0);

this.button.addEventListener("MouseEnter",
Silverlight.createDelegate(this, this.handleMouseEnter));
this.button.addEventListener("MouseLeftButtonDown",
Silverlight.createDelegate(this, this.handleMouseDown));
this.button.addEventListener("MouseLeftButtonUp",
Silverlight.createDelegate(this, this.handleMouseUp));
this.button.addEventListener("MouseLeave",
Silverlight.createDelegate(this, this.handleMouseLeave));
var fh = new SilverlightHelper.FontHelper(rootElement);
fh.initialize();
},

// Sample event handlers
handleMouseEnter: function(sender, eventArgs)
{
// The following code shows how to find an element by name and call a method on it.
var mouseEnterAnimation = sender.findName("mouseEnter");
mouseEnterAnimation.begin();
},

handleMouseDown: function(sender, eventArgs)
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin();
var fh = new SilverlightHelper.FontHelper(this.rootElement);
var input = document.getElementById("DyanmicInput");
if(input && input.value && input.value != '')
fh.setTextWithXPS(sender.findName("DynamicText"),input.value,
"GetFontsXPS.aspx?FontName=cwTeXFangSong","cwTeXFangSong");
else
fh.setTextWithXPS(sender.findName("DynamicText"),
"我是動態由JavaScript設定的","GetFontsXPS.aspx?FontName=cwTeXFangSong","cwTeXFangSong");
},

handleMouseUp: function(sender, eventArgs)
{
var mouseUpAnimation = sender.findName("mouseUp");
mouseUpAnimation.begin();

// Put clicked logic here
alert("clicked");
},

handleMouseLeave: function(sender, eventArgs)
{
var mouseLeaveAnimation = sender.findName("mouseLeave");
mouseLeaveAnimation.begin();
}
}



除了靜態文字外,FontHelper 提供了相對於 setTextWithFont 的 setTextWithXPS 函式可供指定動態文字用,如下所示。


handleMouseDown: function(sender, eventArgs)
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin();
var fh = new SilverlightHelper.FontHelper(this.rootElement);
var input = document.getElementById("DyanmicInput");
if(input && input.value && input.value != '')
fh.setTextWithXPS(sender.findName("DynamicText"),input.value,
"GetFontsXPS.aspx?FontName=cwTeXFangSong","cwTeXFangSong");
else
fh.setTextWithXPS(sender.findName("DynamicText"),
"我是動態由JavaScript設定的",
"GetFontsXPS.aspx?FontName=cwTeXFangSong","cwTeXFangSong");
}



下圖是執行範例。



下圖是輸入文字後,再點選 (我是中文) 按鈕後的執行畫面。



PS:請特別注意,你必須將需要的中文字型安裝到 Windows 中方能使用 XPS Font Services。

就這樣!中文不再是 Silverlight 的致命傷!
就這樣,困擾我們多時的 Silverlight 顯示中文問題,在 XPS Font Services 及免費中文字型及 Font Helper Library 的協助下得以完全解決,在此特別感謝吳老師所提供的免費字型!!未來希望微軟也能開放字型授權,屆時我們會有更多的字型可選擇。

Developer Notes
當你使用 Font Binding Helper 時,於下載字型時發生 Download/Network Error,請檢查 TextBlock 之 TAG 設定值,所要求的檔案可能不在目錄中,亦或是要求的字型未安裝至 Windows 系統中 (Use XPS)。
範例檔中的 Font Binding Helper 及 Font Services 最新版本已支援大量字處理,當要顯示的字大於 100 個時,Font Binding Helper 及 Font Servcies 會自動分批下載並整合 (Downloader 在處理長度過長的 URL 會出現錯誤)。
受限於 Cross-Domain 的設計,Font Services (GetFontsXPS.aspx、FontServcies.cs) 必須與 Silverlight 網頁放在同一網站中。
Font Servcies 會將字型檔案產生於 App_Data\TempFonts 及 CachedFonts 目錄中,因此若你想將 Silverlight 網站安裝到 IIS 中,請預先建立這幾個目錄,並設定目錄存取權限 (以 Windows 2003 來說,必須新增 IIS_WPG 為完全授權使用者)。
吳老師的兩個字型 (楷書與仿宋) 位於範例檔中的 SilverlightJSApplication1 目錄中,以 .zip 方式壓縮,若你想使用 XPS Font Service,請自行解壓後安裝到 Windows 字型中。
本文列出之範例程式碼僅是參考,完整請參照範例檔,若有更新,文章將不另行更新。
因 Visual Studio 2005 之 Silverlight XAML Designer 目前與中文有不相容的問題,若你開啟專案後發現 Scene.xaml 中的中文變成亂碼,那麼請由目錄下我所準備的備份檔 backup_xaml.txt 複製回來。
範例檔下載:http://code6421.myweb.hinet.net /SLFH_Samples.zip (19MB)