前幾天看到小閃光帶回只考30分的英文聽寫測驗卷,大驚! 才發現這小妮子從來不記單字,就算英文字有聽懂,卻只靠發音猜字母,有些胡亂湊出來的組合讓人好氣又好笑。再一次,這又讓做爹的無法再坐視不管,決定採取行動強行介入。

初步構想是寫一個網頁,配合字彙庫,動態出題(用克漏字或語音提示),讓小閃光在平板電腦用觸控筆默寫單字,跟學校的測驗方式差不多,其中有個需求想產生描線版的英文字,當成練習模式下的提示。

網路上可以找到小朋友學寫字母用的描線版英文字型(例如這個),其餘原理跟上回產生注音文字差不多。利用GDI+動態繪製並輸出圖檔。不過,上次想把注音文字產生程式放上租用的網路空間卻失敗了,理由是Shared Web Hosting環境是多名客戶共用一台主機,客戶只能透過上傳各自網站目錄下的檔案進行部署,無法要求在系統另外安裝軟體或其他系統檔案(包含字型),而缺少注音字型檔,程式就沒戲唱了。

這回突發奇想,又查了資料才發現原來.NET有提供不用在Windows安裝字型檔的解決方案 – PrivateFontCollection,允許應用程式自行載入要用的字型檔,只在該應用程式執行期間有效,程式結束後就消失。這種做法需要額外佔用一些記憶體空間存放字型檔,但本次使用的英文描字字型檔案大小只有23KB,不致搆成問題,因此算可行的做法。

產生描字圖檔的做法與注音文字圖檔很類似,但加了兩道額外程序: 首先,我利用static靜態變數保存PrivateFontCollection(整個Process共享一份以節省資源),並在靜態建構式時載入放在App_Data下的TRACE.TTF檔案,要使用時透過GetTraceFont(fontSize)傳入字型大小取得Font物件。另外,我想由文字呈現的大小決定Bitmap物件尺寸,所以另外建了一個虛的Bitmap取得Graphics,預先以MeasureString()測量字串顯示的尺寸,再建立適當大小的Bitmap物件。

<%@ WebHandler Language="C#" Class="Word" %>
 
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Drawing.Text;
using System.Web.Hosting;
using System.Drawing.Drawing2D;
 
public class Word : IHttpHandler
{
    //為避免在Web Server上要加裝字型檔的部署需求,自行載入字型
    static PrivateFontCollection myOwnFonts = 
        new PrivateFontCollection();
    //在靜態建構式中載入
    static Word()
    {
        //截入預先準備好的字型檔案
        myOwnFonts.AddFontFile(
            HostingEnvironment.MapPath("~/App_Data/TRACE.TTF"));
    }
    //傳入字型大小,取得Font物件    
    private static Font GetTraceFont(float fontSz)
    {
        return new Font(myOwnFonts.Families[0], fontSz);
    }
    //建立一個虛的Graphic物件,以便MeasureString預測字型尺寸
    static Bitmap bmpPreview = new Bitmap(10, 10);
    static Graphics gPreview = Graphics.FromImage(bmpPreview);
    private static Size MeasureSize(string text, float fontSz)
    {
        lock (gPreview) //For Thread-Safe
        {
            return gPreview.MeasureString(text, 
                GetTraceFont(fontSz)).ToSize();
        }
    }
 
    public void ProcessRequest(HttpContext context)
    {
        float fs; //Font Size 字型大小
        if (!float.TryParse(context.Request["fs"], out fs)) fs = 96;
        string txt = context.Request["t"] ?? "Darkthread";
        int pd; //Padding 上下留白
        if (!int.TryParse(context.Request["pd"], out pd)) pd = 15;
        
        //預測文字尺寸
        Size estSize = MeasureSize(txt, fs);
        //由文字尺寸決定寬、高
        int w = estSize.Width + pd * 2;
        int.TryParse(context.Request["w"] ?? w.ToString(), out w);
        int h = estSize.Height + pd * 2;
 
        Color bc = ColorTranslator.FromHtml("0x" + //背景色
            (context.Request["bc"] ?? "005f00"));
        Color fc = ColorTranslator.FromHtml("0x" + //前景色
            (context.Request["fc"] ?? "ffffff"));
        Color lc = ColorTranslator.FromHtml("0x" + //輔助線顏色
            (context.Request["lc"] ?? "ffff00"));
        //建立畫布
        Bitmap bmp = new Bitmap(w, h);
        Graphics g = Graphics.FromImage(bmp);
        //塗上背景色
        Brush p = new SolidBrush(bc);
        g.FillRectangle(p, 0, 0, bmp.Width, bmp.Height);
        //設定文字反鋸齒
        g.TextRenderingHint = TextRenderingHint.AntiAlias;
        //劃輔助線
        Pen pen = new Pen(new SolidBrush(lc));
        foreach (int y in 
            new int[] { pd, pd + estSize.Height * 2 /3, pd + estSize.Height })
            g.DrawLine(pen, 0, y, bmp.Width, y);
        pen.DashStyle = DashStyle.Dot;
        float dy = pd + estSize.Height * 0.25f;
        g.DrawLine(pen, 0, dy, bmp.Width, dy);
        //以前景色印上描圖文字
        g.DrawString(txt, GetTraceFont(fs), new SolidBrush(fc), new PointF(pd, pd));
        //將結果以PNG格式傳回
        context.Response.ContentType = "image/png";
        bmp.Save(context.Response.OutputStream, ImageFormat.Png);
    }
    public bool IsReusable { get { return false; } }
}

以下是執行範例:


Comments

# by jain

好爸爸,筆記下來以後用~~

# by 貓咪圓滾滾

其實小閃光靠發音猜字母是很棒的英文學習法哪 黑大應該不要把她的這種天賦抹殺掉 我知道黑大這個軟體是想要針對台灣的考試文化 但其實以英文為母語的小孩子就真的是以靠發音猜字母的方式學母語 所以小閃光在考試之餘 黑大可能可以讓她多重覆閱讀些與這些單字相關的文章 最好也可以配合聽 這樣的方式就可以很接近英國小孩學習母語的方法

# by 蓋瑞

請問黑大~ 有遇到使用PrivateFontCollection將字型載入後.但最後印出的字卻變成系統預設字體的狀況嗎? 謝謝~

# by Jeffrey

to 蓋瑞, 我的使用經驗不多,沒遇過。建議先讓測試單純化,寫成 Console Application、換換其他字型看結果是否不同,再從中尋找關鍵。

# by 蓋瑞

謝謝黑大建議! 已經找出問題.是第三方元件所引起的. 第三方元件的字型Families.只抓系統有安裝的. 改自己去Render文字內容不用元件的方法就正常了.

Post a comment