之前幫小木頭寫的英文單字測驗,有個小問題。

原本網頁介面上,每個單字只有圖片加英文,配合單字的圖片都是去網路上找的,很難做到100%精準搭配,有時甚至找不到明確符合的(例如: 星期三要用什麼圖片來象徵?),不小心就會變成聯想力考驗。例如,當初女王挑了一張可愛的老鼠卡通圖片(下圖左)當成Rats的插圖,結果發現小木頭竟把這個學校還沒教的單字誤解成"溜滑板",暈倒~~~

決定出個改良版,為每張圖片加註附含注音的中文翻譯,小木頭讀注音國字已不是問題,這樣子看到生字才不用看圖亂猜一通。

為了小木頭的功課,我有了新功課 ==> 研發網頁注音字型產生模組!

注音字型的來源,我選擇使用中原大學數學系王漢宗教授所研發的GPL授權中文字型(Microsoft Word也有產生注音字的實力,不過直接用字型比較單純),理論上中文字只要選用注音字型就會有注音標示,但我採行的策略是為預先產生中文翻譯並存成圖檔,網頁執行時直接顯示圖檔。如此,瀏覽器端不需要安裝任何字型都能正確顯示,才算做到Windows、iPad或Android通吃。(以前寫網頁要跨瀏覽器就夠煩了,現在起還要考慮跨平台,這行飯愈來愈不容易吃囉!)

在IIS Server的機器上安裝好王漢宗注音字型,我寫了以下的ChWordImage.ashx,可接受不同文字內容,利用Graphics.DrawString使用注音字型繪製指定文字內容並轉為圖檔。程式還接收寬度、高度、前景/背景色、字型大小等參數。另外,由於中文字有破音字(同一字有多種讀音),故實際上注音字型共有四組,平時用第一組注音字型,遇破音字時需切換其他組,所以我再多接收一個破音字選用參數,文字內容的每個字元可用0,1,2,3指定不同的破音字型。而DrawString也因為如此必須改寫成以字元為繪製單位,才能做到每個字元獨立切換不同字型。

<%@ WebHandler Language="C#" Class="ChWordImage" %>
 
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Drawing.Text;
 
public class ChWordImage : IHttpHandler {
 
    static string[] PhonFonts = new string[] 
    {
      "王漢宗中楷體注音", "王漢宗中楷體破音一",
      "王漢宗中楷體破音二", "王漢宗中楷體破音三"
    };
    
    public void ProcessRequest (HttpContext context) {
        //忽略參數檢查
        int w = int.Parse(context.Request["w"] ?? "256");
        int h = int.Parse(context.Request["h"] ?? "64");
        float fs = float.Parse(context.Request["fs"] ?? "20");
        Color bc = ColorTranslator.FromHtml("0x" + 
            (context.Request["bc"] ?? "dddddd"));
        Color fc = ColorTranslator.FromHtml("0x" + 
            (context.Request["fc"] ?? "000000"));
        string txt = context.Request["t"] ?? "黑暗執行緒";
        //允許不同的字指定破音字,如四個字第三個字要用破音字一 af=0010
        string af = context.Request["af"] ?? new string('0', txt.Length);
        //建立畫布
        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);
        //使用"王漢宗中楷體"
        Font fnt = new Font(PhonFonts[0], fs);
        var sz = g.MeasureString(txt, fnt);
        //設定文字反鋸齒
        g.TextRenderingHint = TextRenderingHint.AntiAlias;
        //取得每個字的寬度
        float widthPerChar = sz.Width / txt.Length;   
        //計算置中用的位移     
        float offsetX = (bmp.Width - sz.Width) / 2;
        float offsetY = (bmp.Height - sz.Height) / 2;
        //考量破音字要換字型,每個字元可用不同字型
        //用迴圈一次畫一個字元
        for (int i = 0; i < txt.Length; i++)
        {
            //查第i個字元的破音字指定
            int fntIdx = (byte)af[i] - 0x30;
            //以前景色寫上文件
            g.DrawString(
                txt[i].ToString(), 
                new Font(PhonFonts[fntIdx], fs),
                new SolidBrush(fc),
                new PointF(
                    offsetX + widthPerChar * i, 
                    offsetY));
        }
        if (context.Request["m"] == "save")
        {
            //將結果存為檔案
            bmp.Save(context.Server.MapPath(
                string.Format("{0}", context.Request["f"])));
        }
        else
        {
            //將結果以PNG格式傳回
            context.Response.ContentType = "image/png";
            bmp.Save(context.Response.OutputStream, ImageFormat.Png);
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
 
}

另外,我寫了一個測試執行效果的網頁:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>注音文字測試</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.js" 
            type="text/javascript"></script>
<script type="text/javascript">
    $(function () {
        $("#t").change(function () {
            //取得文字長度, select數要相等
            var len = $(this).val().length;
            //移除多餘的select
            var $altBlock = $("#spnAlt");
            $altBlock.find("select:gt(" + (len-1) + ")").remove();
            //取現有select數
            var curLen = $altBlock.find("select").length;
            //補足字元數個select
            for (var i = curLen; i < len; i++)
                $altBlock.append(
                    "<select><option value='0'>0</option>" +
                    "<option value='1'>1</option>" +
                    "<option value='2'>2</option>" +
                    "<option value='3'>3</option></select>");
        }).change();
        $("#bc,#fc").change(function () {
            $(this).parent().find(".c").css("background-color", "#" + this.value);
        }).change();
 
        //任何欄位變動後就重新顯示
        $("input,select").live("change", function () {
            //組成URL
            var url = "ChWordImage.ashx?t=" + escape($("#t").val()) +
                      "&w=" + $("#w").val() + "&h=" + $("#h").val() +
                      "&bc=" + $("#bc").val() + "&fc=" + $("#fc").val() +
                      "&af=" + getAltFont() + "&fs=" + $("#fs").val();
            //設定圖檔產生參數
            $("#preview").attr("src", url);
            //取得破音字型切換
            function getAltFont() {
                var v = [];
                $("#spnAlt").find("option:selected").each(function () {
                    v.push(this.value);
                });
                return v.join('');
            }
        });
        
        //觸發初始change
        $("input:first").change();
 
    });
</script>
<style type="text/css">
    body { font-size: 11pt; background-color: #444444; color: yellow; }
    #bc,#fc { width: 50px; }
    #w,#h,#fs { width: 50px; }
    input.c  { border: 1px dotted black; width: 40px; }
    span { margin: 3px; }
    div { padding: 4px; }
</style>
</head>
<body>
<div>
<div><span>寬 度:</span><input type="text" id="w" value="256"/></div>
<div><span>高 度:</span><input type="text" id="h" value="50"/></div>
<div><span>文 字:</span><input type="text" id="t" value="黑暗執行緒" /></div>
<div><span>破音字:</span><span id="spnAlt"></span></div>
<div><span>大 小:</span><input type="text" id="fs" value="20" /></div>
<div><span>顏 色:</span><input type="text" id="fc" value="ffffff" />
<input class='c' readonly /></div>
<div><span>底 色:</span><input type="text" id="bc" value="af2f00" />
<input class='c' readonly /></div>
<div style="padding: 5px;"><img id="preview" /></div>
</div>
</body>
</html>

執行範例:

破音字示範:

如上圖,"差"字有很多讀音,切換0/1/2/3就可以得到不同的注音標示。
PS: 若要更方便指定破音字,還可以定義特殊的標註語法,例如[$差1$]代表差字的破音字組1,[$差2$]代表差字的破音字組2,但為求範例單純,這裡僅用額外參數的做法。

From Monday to Friday, coding for the company.
At weekends, coding for the family.
嗯! 驗證本人為程式魔人無誤~~


Comments

# by 毛豆

讚,為家人付出最帥

# by 米斯特‧載卡多

魔人是不需要休息的 XD

# by KKBruce

突然覺得,好像可以拿來當驗證程式碼使用。

# by 資訊一哥

如果搭配線上出題考試 就太帥了!!!

Post a comment