iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 25
1

Office使用的內建圖型定義在ECMA-376的presetShapeDefinitions.xml這份文件中。(下載規格書Part1的zip檔解開後,會看OfficeOpenXML-DrawingMLGeometries.zip這個zip檔,解開就可以看到這個文件)

以向下的箭頭為例

這個圖型名稱是downArrow,在presetShapeDefinitions.xml中的定義長這樣:

<downArrow>
    <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
        <gd name="adj1" fmla="val 50000" />
        <gd name="adj2" fmla="val 50000" />
    </avLst>
    <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
        <gd name="maxAdj2" fmla="*/ 100000 h ss" />
        <gd name="a1" fmla="pin 0 adj1 100000" />
        <gd name="a2" fmla="pin 0 adj2 maxAdj2" />
        <gd name="dy1" fmla="*/ ss a2 100000" />
        <gd name="y1" fmla="+- b 0 dy1" />
        <gd name="dx1" fmla="*/ w a1 200000" />
        <gd name="x1" fmla="+- hc 0 dx1" />
        <gd name="x2" fmla="+- hc dx1 0" />
        <gd name="dy2" fmla="*/ x1 dy1 wd2" />
        <gd name="y2" fmla="+- y1 dy2 0" />
    </gdLst>
    <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
        <ahXY gdRefX="adj1" minX="0" maxX="100000">
            <pos x="x1" y="t" />
        </ahXY>
        <ahXY gdRefY="adj2" minY="0" maxY="maxAdj2">
            <pos x="l" y="y1" />
        </ahXY>
    </ahLst>
    <cxnLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
        <cxn ang="3cd4">
            <pos x="hc" y="t" />
        </cxn>
        <cxn ang="cd2">
            <pos x="l" y="y1" />
        </cxn>
        <cxn ang="cd4">
            <pos x="hc" y="b" />
        </cxn>
        <cxn ang="0">
            <pos x="r" y="y1" />
        </cxn>
    </cxnLst>
    <rect l="x1" t="t" r="x2" b="y2" xmlns="http://schemas.openxmlformats.org/drawingml/2006/main" />
    <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
        <path>
            <moveTo>
                <pt x="l" y="y1" />
            </moveTo>
            <lnTo>
                <pt x="x1" y="y1" />
            </lnTo>
            <lnTo>
                <pt x="x1" y="t" />
            </lnTo>
            <lnTo>
                <pt x="x2" y="t" />
            </lnTo>
            <lnTo>
                <pt x="x2" y="y1" />
            </lnTo>
            <lnTo>
                <pt x="r" y="y1" />
            </lnTo>
            <lnTo>
                <pt x="hc" y="b" />
            </lnTo>
            <close />
        </path>
    </pathLst>
</downArrow>

繪製的方法在pathLst中,透過pptx文件指定輸入的參數。從圖型綁定的長方形可以取得對應的參數,另外有Adj1以及Adj2可以透過使用者在PowerPoint編輯時調整。規格書中指定的是預設值。

除了路徑的繪製,另一個重要的問題是,路徑中指定的座標如何計算出來。計算的部份,定義在gdLst裡面的gd。gd的fmla屬性(fomula簡寫),會用前序式指定計算方式,透過一個一個gd,就可以求出所要繪製的座標。

例如在投影片中碰到繪製圖型的需求:

<p:sp>
    <p:nvSpPr>
        <p:cNvPr id="14358" name="AutoShape 22" />
        <p:cNvSpPr>
            <a:spLocks noChangeArrowheads="1" />
        </p:cNvSpPr>
        <p:nvPr />
    </p:nvSpPr>
    <p:spPr bwMode="auto">
        <a:xfrm>
            <a:off x="1608138" y="2813050" />
            <a:ext cx="204787" cy="393700" />
        </a:xfrm>
        <a:prstGeom prst="downArrow">
            <a:avLst>
                <a:gd name="adj1" fmla="val 50000" />
                <a:gd name="adj2" fmla="val 48062" />
            </a:avLst>
        </a:prstGeom>
        <a:gradFill rotWithShape="1">
            <a:gsLst>
                <a:gs pos="0">
                    <a:schemeClr val="accent1" />
                </a:gs>
                <a:gs pos="100000">
                    <a:schemeClr val="accent1">
                        <a:gamma />
                        <a:shade val="46275" />
                        <a:invGamma />
                    </a:schemeClr>
                </a:gs>
            </a:gsLst>
            <a:lin ang="5400000" scaled="1" />
        </a:gradFill>
        <a:ln w="9525">
            <a:solidFill>
                <a:schemeClr val="tx1" />
            </a:solidFill>
            <a:miter lim="800000" />
            <a:headEnd />
            <a:tailEnd />
        </a:ln>
        <a:effectLst />
        <a:extLst>
            <a:ext uri="{AF507438-7753-43e0-B8FC-AC1667EBCBE1}">
                <a14:hiddenEffects xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main">
                    <a:effectLst>
                        <a:outerShdw blurRad="63500" dist="38099" dir="2700000" algn="ctr" rotWithShape="0">
                            <a:schemeClr val="bg2">
                                <a:alpha val="74998" />
                            </a:schemeClr>
                        </a:outerShdw>
                    </a:effectLst>
                </a14:hiddenEffects>
            </a:ext>
        </a:extLst>
    </p:spPr>
    <p:txBody>
        <a:bodyPr vert="eaVert" wrap="none" anchor="ctr" />
        <a:lstStyle />
        <a:p>
            <a:endParaRPr lang="zh-TW" altLang="en-US" />
        </a:p>
    </p:txBody>
</p:sp>

圖型的座標會透過a:off定義,所以lt已知。圖型的長寬則透過a:ext定義,cx指定寬度、cy指定高度,透過x, y, cx, cy就可以求出rb。另外,在avLst中也會指定Adj1及Adj2兩個參數值,這樣就可以做計算了。

先來看第一個動作,移動到(l, y1)這個座標,l是已知的,y1則要去參考gdLst。從gdLst可以看出

  1. y1:+- b 0 dy1,dy1是另一個變數,可以透過
  2. dy1:*/ ss a2 100000求得,但是這裡碰到另一個變數a2
  3. a2:pin 0 adj2 maxAdj2,這個公式中adj是已知參數,maxAdj2則是另一個變數
  4. maxAdj2:*/ 100000 h ss,這裡用了兩個系統內建的gd,h代表圖型高度,可以透過cy取得;ss是圖型的短邊,可以透過min w h公式取得(w就是cx)

其他使用到的變數,可以直接用圖型的大小及座標算出來,就不需要用公式來換算。

把這幾個公式結合起來,就可以算出(emu2pixel會根據目前canvas大小把emu轉成pixel):

function drawDownArrowPath(ctx, x, y, cx, cy, adj1, adj2, pcw, ccw) {
    var l = x;
    var maxAdj2 = 100000 * cy / (cx>cy)?cy:cx;
    var a2 = adj2<0?0:adj2>maxAdj2?maxAdj2:adj2;
    var ss = (cx>cy)?cy:cx;//ss是「較短的邊」
    var dy1 = ss * a2 / 100000;
    var y1 = y + cy + 0 - dy1;
    ctx.moveTo(emu2pixel(l, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.beginPath();
    var x1 = l;
    ctx.lineTo(emu2pixel(x1, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.lineTo(emu2pixel(x1, pcw, ccw), emu2pixel(y, pcw, ccw));
    var x2 = x + cx;
    ctx.lineTo(emu2pixel(x2, pcw, ccw), emu2pixel(y, pcw, ccw));
    ctx.lineTo(emu2pixel(x2, pcw, ccw), emu2pixel(y1, pcw, ccw));
    var r = x2;
    ctx.lineTo(emu2pixel(r, pcw, ccw), emu2pixel(y1, pcw, ccw));
    var b = y + cy;
    var hc = x + cx / 2;//hc是水平的中點
    ctx.lineTo(emu2pixel(hc, pcw, ccw), emu2pixel(b, pcw, ccw));
    ctx.closePath();
    var gradient = ctx.createLinearGradient(emu2pixel(x, pcw, ccw), emu2pixel(y, pcw,ccw), emu2pixel(x, pcw, ccw), emu2pixel(y+cy, pcw, ccw));
    gradient.addColorStop(0, '#89AFD5');
    gradient.addColorStop(1, '#45586B');
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.strokeStyle = '#5F5F5F';
    ctx.stroke();
}

繪製出來的箭頭圖型

畫出來的箭頭長這樣(example013.html):

99-001.png

...看到以後有點無言,跟PowerPoint裡面用的不太一樣阿XD

我原本預期他會長這樣:

99-002.png

好吧,微軟,算你狠...


上一篇
?? - 目前的簡單成果
下一篇
?? - 雖然沒完賽,還是發一下心得
系列文
30天實作線上簡報播放機制31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
fillano
iT邦超人 1 級 ‧ 2017-01-13 16:56:23

回頭看規格書,發現規格書上示意的圖形是正確的...所以回頭檢查了一下程式,發現有錯誤XD

改過的程式:

function drawDownArrowPath(ctx, l, t, w, h, adj1, adj2, pcw, ccw) {
    var ss = (w>h)?h:w;
    var maxAdj2 = 100000 * h / ss;
    var a2 = adj2<0?0:adj2>maxAdj2?maxAdj2:adj2;
    var dy1 = ss * a2 / 100000;
    var b = t + h;
    var y1 = b + 0 - dy1;
    var a1 = adj1<0?0:adj1>100000?100000:adj1;
    var dx1 = w * a1 / 200000;
    var hc = l + w / 2;
    var x1 = hc + 0 - dx1;
    var x2 = hc + dx1 - 0;
    var r = l + w;
    ctx.moveTo(emu2pixel(l, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.beginPath();
    ctx.lineTo(emu2pixel(x1, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.lineTo(emu2pixel(x1, pcw, ccw), emu2pixel(t, pcw, ccw));
    ctx.lineTo(emu2pixel(x2, pcw, ccw), emu2pixel(t, pcw, ccw));
    ctx.lineTo(emu2pixel(x2, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.lineTo(emu2pixel(r, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.lineTo(emu2pixel(hc, pcw, ccw), emu2pixel(b, pcw, ccw));
    ctx.lineTo(emu2pixel(l, pcw, ccw), emu2pixel(y1, pcw, ccw));
    ctx.closePath();
    var gradient = ctx.createLinearGradient(emu2pixel(l, pcw, ccw), emu2pixel(t, pcw,ccw), emu2pixel(l, pcw, ccw), emu2pixel(b, pcw, ccw));
    gradient.addColorStop(0, '#89AFD5');
    gradient.addColorStop(1, '#45586B');
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.strokeStyle = '#5F5F5F';
    ctx.stroke();
}

現在畫出來的箭頭是對的了:
99-003.png

0
fillano
iT邦超人 1 級 ‧ 2017-01-13 17:22:53

出錯的原因是因為圖型定義中有一行:

<rect l="x1" t="t" r="x2" b="y2" xmlns="http://schemas.openxmlformats.org/drawingml/2006/main" />

看著看著就把座標綁定的長方形跟他搞混。我猜這個應該是圖型內部的長方形,也許可以用來放文字...

另外,定義中沒特別寫,但是有需要額外畫一條線回到起點,然後才closePath()...這個也忘記了XD

我要留言

立即登入留言