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定義,所以l
跟t
已知。圖型的長寬則透過a:ext定義,cx指定寬度、cy指定高度,透過x, y, cx, cy就可以求出r
及b
。另外,在avLst
中也會指定Adj1及Adj2兩個參數值,這樣就可以做計算了。
先來看第一個動作,移動到(l, y1)
這個座標,l是已知的,y1則要去參考gdLst。從gdLst可以看出
+- b 0 dy1
,dy1是另一個變數,可以透過*/ ss a2 100000
求得,但是這裡碰到另一個變數a2pin 0 adj2 maxAdj2
,這個公式中adj是已知參數,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):
...看到以後有點無言,跟PowerPoint裡面用的不太一樣阿XD
我原本預期他會長這樣:
好吧,微軟,算你狠...
回頭看規格書,發現規格書上示意的圖形是正確的...所以回頭檢查了一下程式,發現有錯誤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();
}
現在畫出來的箭頭是對的了:
出錯的原因是因為圖型定義中有一行:
<rect l="x1" t="t" r="x2" b="y2" xmlns="http://schemas.openxmlformats.org/drawingml/2006/main" />
看著看著就把座標綁定的長方形跟他搞混。我猜這個應該是圖型內部的長方形,也許可以用來放文字...
另外,定義中沒特別寫,但是有需要額外畫一條線回到起點,然後才closePath()...這個也忘記了XD