1 /*
2    drvPPTX.cpp : This file is part of pstoedit
3    Backend for Office Open XML files
4    Contributed by: Scott Pakin <scott+ps2ed_AT_pakin.org>
5 
6    Copyright (C) 1993 - 2014 Wolfgang Glunz, wglunz35_AT_pstoedit.net
7 
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with this program; if not, write to the Free Software
20     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 
22 */
23 #if 1
24 
25 #ifdef _MSC_VER
26 // avoid this warning
27 // this macro needs to be define before all the includes
28 // warning C4996: 'sscanf': This function or variable may be unsafe. Consider using sscanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
29 #define _CRT_SECURE_NO_WARNINGS 1
30 #endif
31 
32 #include "drvpptx.h"
33 #include I_fstream
34 #include I_stdio
35 #include I_stdlib
36 #include I_iomanip
37 #include <cfloat>
38 #include <time.h>
39 
40 #include <errno.h>
41 
42 #ifdef _MSC_VER
43 // MS VC++ Windows
44 // _USE_MATH_DEFINES needed on Windows to enable the define M_PI
45 #define _USE_MATH_DEFINES
46 #define srandom srand
47 #define random rand
48 #include <process.h>
49 
50 // work-around - missing on WIndows.
lroundf(float f)51 long lroundf(float f) {
52         return (long)(floor(f +0.5f));
53 }
54 
55 #else
56 #include <unistd.h>
57 #endif
58 
59 #include <math.h>
60 
61 // when linking against static library - otherwise it means declspec(dllextern)
62 #define ZIP_EXTERN extern
63 
64 #include <zip.h>
65 
66 #ifdef _MSC_VER
67 // MS VC++ Windows
68 // handle this warning:
69 // 'xxxx': The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _xxxx. See online help for details.
70 #define getpid _getpid
71 #define strdup _strdup
72 #define unlink _unlink
73 
74 #endif
75 
76 // #include "version.h"
77 
78 /*
79   The following are some things to know about the Office Open XML
80   (OOXML) DrawingML format (a.k.a. PowerPoint or pptx) that should
81   help understand the code in this file:
82 
83     - A pptx file is really a zip archive that contains a bunch of XML
84       files (plus embedded-image files).
85 
86     - These XML files are cross-linked to each other via
87       "relationships" (indirections through ID-to-filename mappings).
88 
89     - Describing graphics -- or anything else -- requires lots of XML
90       verbosity and a substantial amount of boilerplate text.
91 
92     - The DrawingML coordinate system places the origin in the
93       upper-left corner, in contrast to PostScript, which puts it in
94       the lower-left corner.
95 
96     - The main unit of measurement is the English Metric Unit (EMU),
97       of which there are exactly 914,400 per inch, 360,000 per
98       centimeter, and 12,700 per PostScript point.  Note that all of
99       those are integers; DrawingML works exclusively with integers.
100       As an example, a distance of 10cm (approximately 3.9" or 283
101       pt.) is written as "3600000" in OOXML.
102 
103     - Angles are specified in 60,000ths of a degree.  Positive numbers
104       represent clockwise rotations, and negative numbers represent
105       counterclockwise rotations.  For example a 30-degree clockwise
106       rotation is written as "1800000" in OOXML.
107 
108     - Percentages are expressed in 1000ths and with no trailing "%"
109       character.  For example, 75% is written as "75000" in OOXML.
110 
111     - Positions are specified as a distance from the upper-left corner
112       of the object's bounding box.
113 
114     - Coordinates within a shape are specified with the origin in the
115       upper-left corner of their bounding box.
116 
117     - Scaling is performed relative to the upper-left corner of the
118       object's bounding box.  That is, the upper-left corner is
119       invariant with respect to scaling.
120 
121     - Rotation is performed relative to the center of the object's
122       bounding box.
123 
124     - The order of object transformations is (1) translate the
125       upper-left corner, (2) scale from the upper-left corner, (3)
126       flip horizontally/vertically around the image's center, (4)
127       rotate around the image's center.
128 
129     - Although DrawingML supports horizontal and vertical flipping for
130       text, PowerPoint flips only the shape in which the text is
131       embedded.  (This box is always an invisible rectangle in the
132       case of drvpptx-generated DrawingML.)  The text itself is in
133       fact *rotated*, not flipped.  Specifically, horizontally flipped
134       text negates the sign of the rotation angle, and vertically
135       flipped text subtracts the rotation angle from 180 degrees.  For
136       example, if a piece of text is rotated clockwise by 30 degrees
137       (angle="1800000"), flipping it horizontally (flipH="1") is
138       equivalent to rotating it instead by -30 degrees; flipping it
139       vertically (flipV="1") is equivalent to rotating it instead by
140       150 degrees.
141 
142     - Coordinates are always specified in an unrotated coordinate
143       space.
144 
145     - DrawingML supports a fairly large subset of the PostScript that
146       pstoedit understands.  Omissions include winding-number fills
147       (only even-odd fills are supported), dash patterns with nonzero
148       offsets, arbitrarily transformed (e.g., skewed) text and images,
149       and vertical text kerning produced by PostScript's awidthshow.
150       I believe that vertical kerning can probably be faked using sub-
151       and superscripts, but that's probably not worth the effort.
152 
153   Office Open XML has been ratified as an Ecma International standard
154   (ECMA-376).  See
155   http://www.ecma-international.org/publications/standards/Ecma-376.htm
156   for the complete specification.  Note that Microsoft PowerPoint is
157   not always consistent with the ECMA-376 specification and that not
158   all of the information listed above appears in the
159   specification.
160 */
161 
162 const char * const drvPPTX::xml_rels =
163   "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
164   "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
165   "  <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"ppt/presentation.xml\"/>\n"
166   "</Relationships>\n";
167 
168 const char * const  drvPPTX::xml_slideLayout1_xml =
169   "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
170   "<p:sldLayout preserve=\"1\" type=\"blank\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n"
171   "  <p:cSld name=\"Blank Slide\">\n"
172   "    <p:spTree>\n"
173   "      <p:nvGrpSpPr>\n"
174   "        <p:cNvPr id=\"1\" name=\"\"/>\n"
175   "        <p:cNvGrpSpPr/>\n"
176   "        <p:nvPr/>\n"
177   "      </p:nvGrpSpPr>\n"
178   "      <p:grpSpPr>\n"
179   "        <a:xfrm>\n"
180   "          <a:off x=\"0\" y=\"0\"/>\n"
181   "          <a:ext cx=\"0\" cy=\"0\"/>\n"
182   "          <a:chOff x=\"0\" y=\"0\"/>\n"
183   "          <a:chExt cx=\"0\" cy=\"0\"/>\n"
184   "        </a:xfrm>\n"
185   "      </p:grpSpPr>\n"
186   "    </p:spTree>\n"
187   "  </p:cSld>\n"
188   "</p:sldLayout>\n";
189 
190 const char * const drvPPTX::xml_slideLayout1_xml_rels =
191   "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
192   "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
193   "  <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster\" Target=\"../slideMasters/slideMaster1.xml\"/>\n"
194   "</Relationships>\n";
195 
196 const char * const drvPPTX::xml_slideMaster1_xml =
197   "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
198   "<p:sldMaster xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n"
199   "  <p:cSld>\n"
200   "    <p:spTree>\n"
201   "      <p:nvGrpSpPr>\n"
202   "        <p:cNvPr id=\"1\" name=\"\"/>\n"
203   "        <p:cNvGrpSpPr/>\n"
204   "        <p:nvPr/>\n"
205   "      </p:nvGrpSpPr>\n"
206   "      <p:grpSpPr>\n"
207   "        <a:xfrm>\n"
208   "          <a:off x=\"0\" y=\"0\"/>\n"
209   "          <a:ext cx=\"0\" cy=\"0\"/>\n"
210   "          <a:chOff x=\"0\" y=\"0\"/>\n"
211   "          <a:chExt cx=\"0\" cy=\"0\"/>\n"
212   "        </a:xfrm>\n"
213   "      </p:grpSpPr>\n"
214   "    </p:spTree>\n"
215   "  </p:cSld>\n"
216   "  <p:clrMap accent1=\"accent1\" accent2=\"accent2\" accent3=\"accent3\" accent4=\"accent4\" accent5=\"accent5\" accent6=\"accent6\" bg1=\"lt1\" bg2=\"lt2\" folHlink=\"folHlink\" hlink=\"hlink\" tx1=\"dk1\" tx2=\"dk2\"/>\n"
217   "  <p:sldLayoutIdLst>\n"
218   "    <p:sldLayoutId id=\"2147483649\" r:id=\"rId2\"/>\n"
219   "  </p:sldLayoutIdLst>\n"
220   "</p:sldMaster>\n";
221 
222 const char * const drvPPTX::xml_slideMaster1_xml_rels =
223   "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
224   "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
225   "  <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme\" Target=\"../theme/theme1.xml\"/>\n"
226   "  <Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"../slideLayouts/slideLayout1.xml\"/>\n"
227   "</Relationships>\n";
228 
229 const char * const drvPPTX::xml_theme1_xml =
230   "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
231   "<a:theme name=\"Office Theme\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">\n"
232   "  <a:themeElements>\n"
233   "    <a:clrScheme name=\"Office\">\n"
234   "      <a:dk1>\n"
235   "        <a:sysClr val=\"windowText\" lastClr=\"000000\"/>\n"
236   "      </a:dk1>\n"
237   "      <a:lt1>\n"
238   "        <a:sysClr val=\"window\" lastClr=\"FFFFFF\"/>\n"
239   "      </a:lt1>\n"
240   "      <a:dk2>\n"
241   "        <a:srgbClr val=\"1F497D\"/>\n"
242   "      </a:dk2>\n"
243   "      <a:lt2>\n"
244   "        <a:srgbClr val=\"EEECE1\"/>\n"
245   "      </a:lt2>\n"
246   "      <a:accent1>\n"
247   "        <a:srgbClr val=\"4F81BD\"/>\n"
248   "      </a:accent1>\n"
249   "      <a:accent2>\n"
250   "        <a:srgbClr val=\"C0504D\"/>\n"
251   "      </a:accent2>\n"
252   "      <a:accent3>\n"
253   "        <a:srgbClr val=\"9BBB59\"/>\n"
254   "      </a:accent3>\n"
255   "      <a:accent4>\n"
256   "        <a:srgbClr val=\"8064A2\"/>\n"
257   "      </a:accent4>\n"
258   "      <a:accent5>\n"
259   "        <a:srgbClr val=\"4BACC6\"/>\n"
260   "      </a:accent5>\n"
261   "      <a:accent6>\n"
262   "        <a:srgbClr val=\"F79646\"/>\n"
263   "      </a:accent6>\n"
264   "      <a:hlink>\n"
265   "        <a:srgbClr val=\"0000FF\"/>\n"
266   "      </a:hlink>\n"
267   "      <a:folHlink>\n"
268   "        <a:srgbClr val=\"800080\"/>\n"
269   "      </a:folHlink>\n"
270   "    </a:clrScheme>\n"
271   "    <a:fontScheme name=\"Office\">\n"
272   "      <a:majorFont>\n"
273   "        <a:latin typeface=\"Arial\"/>\n"
274   "        <a:ea typeface=\"DejaVu Sans\"/>\n"
275   "        <a:cs typeface=\"DejaVu Sans\"/>\n"
276   "      </a:majorFont>\n"
277   "      <a:minorFont>\n"
278   "        <a:latin typeface=\"Arial\"/>\n"
279   "        <a:ea typeface=\"DejaVu Sans\"/>\n"
280   "        <a:cs typeface=\"DejaVu Sans\"/>\n"
281   "      </a:minorFont>\n"
282   "    </a:fontScheme>\n"
283   "    <a:fmtScheme name=\"Office\">\n"
284   "      <a:fillStyleLst>\n"
285   "        <a:solidFill>\n"
286   "          <a:schemeClr val=\"phClr\"/>\n"
287   "        </a:solidFill>\n"
288   "        <a:gradFill rotWithShape=\"1\">\n"
289   "          <a:gsLst>\n"
290   "            <a:gs pos=\"0\">\n"
291   "              <a:schemeClr val=\"phClr\">\n"
292   "                <a:tint val=\"50000\"/>\n"
293   "                <a:satMod val=\"300000\"/>\n"
294   "              </a:schemeClr>\n"
295   "            </a:gs>\n"
296   "            <a:gs pos=\"35000\">\n"
297   "              <a:schemeClr val=\"phClr\">\n"
298   "                <a:tint val=\"37000\"/>\n"
299   "                <a:satMod val=\"300000\"/>\n"
300   "              </a:schemeClr>\n"
301   "            </a:gs>\n"
302   "            <a:gs pos=\"100000\">\n"
303   "              <a:schemeClr val=\"phClr\">\n"
304   "                <a:tint val=\"15000\"/>\n"
305   "                <a:satMod val=\"350000\"/>\n"
306   "              </a:schemeClr>\n"
307   "            </a:gs>\n"
308   "          </a:gsLst>\n"
309   "          <a:lin ang=\"16200000\" scaled=\"1\"/>\n"
310   "        </a:gradFill>\n"
311   "        <a:gradFill rotWithShape=\"1\">\n"
312   "          <a:gsLst>\n"
313   "            <a:gs pos=\"0\">\n"
314   "              <a:schemeClr val=\"phClr\">\n"
315   "                <a:shade val=\"51000\"/>\n"
316   "                <a:satMod val=\"130000\"/>\n"
317   "              </a:schemeClr>\n"
318   "            </a:gs>\n"
319   "            <a:gs pos=\"80000\">\n"
320   "              <a:schemeClr val=\"phClr\">\n"
321   "                <a:shade val=\"93000\"/>\n"
322   "                <a:satMod val=\"130000\"/>\n"
323   "              </a:schemeClr>\n"
324   "            </a:gs>\n"
325   "            <a:gs pos=\"100000\">\n"
326   "              <a:schemeClr val=\"phClr\">\n"
327   "                <a:shade val=\"94000\"/>\n"
328   "                <a:satMod val=\"135000\"/>\n"
329   "              </a:schemeClr>\n"
330   "            </a:gs>\n"
331   "          </a:gsLst>\n"
332   "          <a:lin ang=\"16200000\" scaled=\"0\"/>\n"
333   "        </a:gradFill>\n"
334   "      </a:fillStyleLst>\n"
335   "      <a:lnStyleLst>\n"
336   "        <a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\">\n"
337   "          <a:solidFill>\n"
338   "            <a:schemeClr val=\"phClr\">\n"
339   "              <a:shade val=\"95000\"/>\n"
340   "              <a:satMod val=\"105000\"/>\n"
341   "            </a:schemeClr>\n"
342   "          </a:solidFill>\n"
343   "          <a:prstDash val=\"solid\"/>\n"
344   "        </a:ln>\n"
345   "        <a:ln w=\"25400\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\">\n"
346   "          <a:solidFill>\n"
347   "            <a:schemeClr val=\"phClr\"/>\n"
348   "          </a:solidFill>\n"
349   "          <a:prstDash val=\"solid\"/>\n"
350   "        </a:ln>\n"
351   "        <a:ln w=\"38100\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\">\n"
352   "          <a:solidFill>\n"
353   "            <a:schemeClr val=\"phClr\"/>\n"
354   "          </a:solidFill>\n"
355   "          <a:prstDash val=\"solid\"/>\n"
356   "        </a:ln>\n"
357   "      </a:lnStyleLst>\n"
358   "      <a:effectStyleLst>\n"
359   "        <a:effectStyle>\n"
360   "          <a:effectLst>\n"
361   "            <a:outerShdw blurRad=\"40000\" dist=\"20000\" dir=\"5400000\" rotWithShape=\"0\">\n"
362   "              <a:srgbClr val=\"000000\">\n"
363   "                <a:alpha val=\"38000\"/>\n"
364   "              </a:srgbClr>\n"
365   "            </a:outerShdw>\n"
366   "          </a:effectLst>\n"
367   "        </a:effectStyle>\n"
368   "        <a:effectStyle>\n"
369   "          <a:effectLst>\n"
370   "            <a:outerShdw blurRad=\"40000\" dist=\"23000\" dir=\"5400000\" rotWithShape=\"0\">\n"
371   "              <a:srgbClr val=\"000000\">\n"
372   "                <a:alpha val=\"35000\"/>\n"
373   "              </a:srgbClr>\n"
374   "            </a:outerShdw>\n"
375   "          </a:effectLst>\n"
376   "        </a:effectStyle>\n"
377   "        <a:effectStyle>\n"
378   "          <a:effectLst>\n"
379   "            <a:outerShdw blurRad=\"40000\" dist=\"23000\" dir=\"5400000\" rotWithShape=\"0\">\n"
380   "              <a:srgbClr val=\"000000\">\n"
381   "                <a:alpha val=\"35000\"/>\n"
382   "              </a:srgbClr>\n"
383   "            </a:outerShdw>\n"
384   "          </a:effectLst>\n"
385   "          <a:scene3d>\n"
386   "            <a:camera prst=\"orthographicFront\">\n"
387   "              <a:rot lat=\"0\" lon=\"0\" rev=\"0\"/>\n"
388   "            </a:camera>\n"
389   "            <a:lightRig rig=\"threePt\" dir=\"t\">\n"
390   "              <a:rot lat=\"0\" lon=\"0\" rev=\"1200000\"/>\n"
391   "            </a:lightRig>\n"
392   "          </a:scene3d>\n"
393   "          <a:sp3d>\n"
394   "            <a:bevelT w=\"63500\" h=\"25400\"/>\n"
395   "          </a:sp3d>\n"
396   "        </a:effectStyle>\n"
397   "      </a:effectStyleLst>\n"
398   "      <a:bgFillStyleLst>\n"
399   "        <a:solidFill>\n"
400   "          <a:schemeClr val=\"phClr\"/>\n"
401   "        </a:solidFill>\n"
402   "        <a:gradFill rotWithShape=\"1\">\n"
403   "          <a:gsLst>\n"
404   "            <a:gs pos=\"0\">\n"
405   "              <a:schemeClr val=\"phClr\">\n"
406   "                <a:tint val=\"40000\"/>\n"
407   "                <a:satMod val=\"350000\"/>\n"
408   "              </a:schemeClr>\n"
409   "            </a:gs>\n"
410   "            <a:gs pos=\"40000\">\n"
411   "              <a:schemeClr val=\"phClr\">\n"
412   "                <a:tint val=\"45000\"/>\n"
413   "                <a:shade val=\"99000\"/>\n"
414   "                <a:satMod val=\"350000\"/>\n"
415   "              </a:schemeClr>\n"
416   "            </a:gs>\n"
417   "            <a:gs pos=\"100000\">\n"
418   "              <a:schemeClr val=\"phClr\">\n"
419   "                <a:shade val=\"20000\"/>\n"
420   "                <a:satMod val=\"255000\"/>\n"
421   "              </a:schemeClr>\n"
422   "            </a:gs>\n"
423   "          </a:gsLst>\n"
424   "          <a:path path=\"circle\">\n"
425   "            <a:fillToRect l=\"50000\" t=\"-80000\" r=\"50000\" b=\"180000\"/>\n"
426   "          </a:path>\n"
427   "        </a:gradFill>\n"
428   "        <a:gradFill rotWithShape=\"1\">\n"
429   "          <a:gsLst>\n"
430   "            <a:gs pos=\"0\">\n"
431   "              <a:schemeClr val=\"phClr\">\n"
432   "                <a:tint val=\"80000\"/>\n"
433   "                <a:satMod val=\"300000\"/>\n"
434   "              </a:schemeClr>\n"
435   "            </a:gs>\n"
436   "            <a:gs pos=\"100000\">\n"
437   "              <a:schemeClr val=\"phClr\">\n"
438   "                <a:shade val=\"30000\"/>\n"
439   "                <a:satMod val=\"200000\"/>\n"
440   "              </a:schemeClr>\n"
441   "            </a:gs>\n"
442   "          </a:gsLst>\n"
443   "          <a:path path=\"circle\">\n"
444   "            <a:fillToRect l=\"50000\" t=\"50000\" r=\"50000\" b=\"50000\"/>\n"
445   "          </a:path>\n"
446   "        </a:gradFill>\n"
447   "      </a:bgFillStyleLst>\n"
448   "    </a:fmtScheme>\n"
449   "  </a:themeElements>\n"
450   "</a:theme>\n";
451 
452 
453 
derivedConstructor(drvPPTX)454 drvPPTX::derivedConstructor(drvPPTX):
455 constructBase,
456 outzip(NIL)
457 {
458   // Parse our command-line options.
459   if (options->colortype == "original")
460     color_type = C_ORIGINAL;
461   else if (options->colortype == "theme")
462     color_type = C_THEME;
463   else if (options->colortype == "theme-pure")
464     color_type = C_THEME_PURE;
465   else {
466     errorMessage("ERROR: -colors must be either \"original\", \"theme\", or \"theme-pure\"");
467     abort();
468   }
469   if (options->fonttype == "windows")
470     font_type = F_WINDOWS;
471   else if (options->fonttype == "native")
472     font_type = F_NATIVE;
473   else if (options->fonttype == "theme")
474     font_type = F_THEME;
475   else {
476     errorMessage("ERROR: -fonts must be one of \"windows\", \"native\", or \"theme\"");
477     abort();
478   }
479   if (options->embeddedfonts != "") {
480     // Loop over each EOT filename.
481     stringstream embed_stream(RSString(options->embeddedfonts).c_str());
482     string efile;
483     while (getline(embed_stream, efile, ',')) {
484       // Ensure the file exists, then add it to the list.
485       const char * const efile_cstr = efile.c_str();
486       if (!fileExists(efile_cstr)) {
487 		RSString errmess("ERROR: Cannot open file ");
488 		errmess += efile_cstr;
489         errorMessage(errmess.c_str());
490         abort();
491       }
492       eotlist.insert(efile);
493     }
494   }
495 
496   // Map PostScript core font names to their Windows + PANOSE replacements.
497   if (font_type == F_WINDOWS) {
498     ps2win.insert("Courier",                 "Courier New,Courier New,02070309020205020404");
499     ps2win.insert("Courier-Bold",            "Courier New Bold,Courier New,02070609020205020404");
500     ps2win.insert("Courier-BoldOblique",     "Courier New Bold Italic,Courier New,02070609020205090404");
501     ps2win.insert("Courier-Oblique",         "Courier New Italic,Courier New,02070409020205090404");
502     ps2win.insert("Helvetica",               "Arial,Arial,020b0604020202020204");
503     ps2win.insert("Helvetica-Bold",          "Arial Bold,Arial,020b0704020202020204");
504     ps2win.insert("Helvetica-BoldOblique",   "Arial Bold Italic,Arial,020b0704020202090204");
505     ps2win.insert("Helvetica-Oblique",       "Arial Italic,Arial,020b0604020202090204");
506     ps2win.insert("Times-Bold",              "Times New Roman Bold,Times New Roman,02020803070505020304");
507     ps2win.insert("Times-BoldItalic",        "Times New Roman Bold Italic,Times New Roman,02020703060505090304");
508     ps2win.insert("Times-Italic",            "Times New Roman Italic,Times New Roman,02020503050405090304");
509     ps2win.insert("Times-Roman",             "Times New Roman,Times New Roman,02020603050405020304");
510   }
511 
512   // Output all floating-point numbers as integers.
513   slidef << fixed << setprecision(0);
514 
515   // Seed the random-number generator.
516   srandom((unsigned int) time(NULL)*getpid());
517 
518   // Create a zip archive for holding PresentationML data.
519   create_pptx();
520 
521   // Specify the slide dimensions (must match ppt/presentation.xml's
522   // <p:sldSz> tag).
523   slideBBox.ll = Point(0, 0);
524   slideBBox.ur = Point(10080625/12700.0, 7559675/12700.0);
525 
526   // Number IDs from 1 and images from 0.
527   next_id = 1;
528   total_images = 0;
529   page_images = 0;
530 }
531 
~drvPPTX()532 drvPPTX::~drvPPTX()
533 {
534   // Embed fonts in the PPTX file if asked to do so.
535   if (!eotlist.empty()) {
536     unsigned int fontNum = 1;
537     for (set<string>::iterator iter = eotlist.begin();
538          iter != eotlist.end();
539          ++iter) {
540       const char * const eotFileName = iter->c_str();
541       struct zip_source *font_file = zip_source_file(outzip, eotFileName, 0, -1);
542       if (font_file == NULL) {
543 	    RSString errmessage("ERROR: Failed to embed font file ");
544 	    errmessage += eotFileName ;
545 	    errmessage += " (" ;
546 	    errmessage += zip_strerror(outzip) ;
547 	    errmessage += ")" ;
548 
549         errorMessage(errmessage.c_str());
550         abort();
551       }
552       ostringstream full_eot_filename;
553       full_eot_filename << "ppt/fonts/font" << fontNum << ".fntdata";
554       if (zip_add(outzip, full_eot_filename.str().c_str(), font_file) == -1) {
555 		RSString errmessage("ERROR: Failed to embed font file ");
556 		errmessage += eotFileName ;
557 		errmessage += " as " ;
558 		errmessage += full_eot_filename.str().c_str() ;
559 		errmessage += " (" ;
560 		errmessage += zip_strerror(outzip) ;
561 		errmessage += ")" ;
562 
563         errorMessage(errmessage.c_str());
564         abort();
565       }
566       fontNum++;
567     }
568   }
569 
570   // Create the presentation file.
571   ostringstream xml_presentation_xml;
572   xml_presentation_xml <<
573     "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
574     "<p:presentation xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\""
575                        << (eotlist.empty() ? ">\n" : " embedTrueTypeFonts=\"1\">\n");
576   xml_presentation_xml <<
577     "  <p:sldMasterIdLst>\n"
578     "    <p:sldMasterId id=\"2147483648\" r:id=\"rId2\"/>\n"
579     "  </p:sldMasterIdLst>\n"
580     "  <p:sldIdLst>\n";
581   for (unsigned int p = 0; p < totalNumberOfPages(); p++)
582     xml_presentation_xml << "    <p:sldId id=\"" << p+256
583                          << "\" r:id=\"rId" << p+3 << "\"/>\n";
584   xml_presentation_xml << "  </p:sldIdLst>\n"
585     "  <p:sldSz cx=\"10080625\" cy=\"7559675\"/>\n"
586     "  <p:notesSz cx=\"7772400\" cy=\"10058400\"/>\n";
587   if (!eotlist.empty()) {
588     unsigned int rId = totalNumberOfPages() + 3;
589     xml_presentation_xml << "  <p:embeddedFontLst>\n";
590     for (set<string>::iterator iter = eotlist.begin();
591          iter != eotlist.end();
592          ++iter) {
593       // Get information about the font.
594 
595       TextInfo textinfo;
596       eot2texinfo(*iter,textinfo);
597       RSString typeface;
598       RSString panose;
599       bool isBold;
600       bool isItalic;
601       unsigned char pitchFamily;
602       get_font_props(textinfo, &typeface, &panose, &isBold, &isItalic, &pitchFamily);
603 
604       // Describe the font to be embedded.
605       xml_presentation_xml << "    <p:embeddedFont>\n"
606                            << "      <p:font typeface=\"" << typeface
607                            << "\" pitchFamily=\"" << (unsigned int)pitchFamily
608                            << "\" charset=\"0\"/>\n";
609       switch (int(isBold)*2 + int(isItalic)) {
610       case 0:
611         xml_presentation_xml << "      <p:regular r:id=\"rId" << rId << "\"/>\n";
612         break;
613 
614       case 1:
615         xml_presentation_xml << "      <p:italic r:id=\"rId" << rId << "\"/>\n";
616         break;
617 
618       case 2:
619         xml_presentation_xml << "      <p:bold r:id=\"rId" << rId << "\"/>\n";
620         break;
621 
622       case 3:
623         xml_presentation_xml << "      <p:boldItalic r:id=\"rId" << rId << "\"/>\n";
624         break;
625 
626       default:
627         errf << "\t\tERROR: unexpected case in drvpptx " << endl;
628         abort();
629         break;
630       }
631       xml_presentation_xml << "    </p:embeddedFont>\n";
632       rId++;
633     }
634     xml_presentation_xml << "  </p:embeddedFontLst>\n";
635   }
636   xml_presentation_xml << "</p:presentation>\n";
637   create_pptx_file("ppt/presentation.xml", xml_presentation_xml.str().c_str());
638 
639   // Create the presentation relationships file.
640   ostringstream xml_presentation_xml_rels;
641   xml_presentation_xml_rels <<
642     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
643     "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
644     "  <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme\" Target=\"theme/theme1.xml\"/>\n"
645     "  <Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster\" Target=\"slideMasters/slideMaster1.xml\"/>\n";
646   for (unsigned int p = 0; p < totalNumberOfPages(); p++)
647     xml_presentation_xml_rels << "  <Relationship Id=\"rId" << p+3
648                               << "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide\" Target=\"slides/slide" << p+1
649                               << ".xml\"/>\n";
650   if (!eotlist.empty()) {
651     unsigned int fontNum = 1;
652     unsigned int rId = totalNumberOfPages() + 3;
653     for (set<string>::iterator iter = eotlist.begin();
654          iter != eotlist.end();
655          ++iter) {
656       xml_presentation_xml_rels << "  <Relationship Id=\"rId" << rId
657                                 << "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/font\""
658                                 << " Target=\"fonts/font" << fontNum << ".fntdata\"/>\n";
659       fontNum++;
660       rId++;
661     }
662   }
663   xml_presentation_xml_rels << "</Relationships>\n";
664   create_pptx_file("ppt/_rels/presentation.xml.rels", xml_presentation_xml_rels.str().c_str());
665 
666   // Create the content-types file.
667   ostringstream xml_content_types;
668   xml_content_types <<
669     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
670     "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n"
671     "  <Override PartName=\"/_rels/.rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n"
672     "  <Override PartName=\"/ppt/_rels/presentation.xml.rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n"
673     "  <Override PartName=\"/ppt/slideLayouts/_rels/slideLayout1.xml.rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n"
674     "  <Override PartName=\"/ppt/slideLayouts/slideLayout1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml\"/>\n"
675     "  <Override PartName=\"/ppt/theme/theme1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.theme+xml\"/>\n"
676     "  <Override PartName=\"/ppt/slideMasters/_rels/slideMaster1.xml.rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n"
677     "  <Override PartName=\"/ppt/slideMasters/slideMaster1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml\"/>\n"
678     "  <Override PartName=\"/ppt/presentation.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml\"/>\n";
679   for (unsigned int p = 0; p < totalNumberOfPages(); p++)
680     xml_content_types << "  <Override PartName=\"/ppt/slides/_rels/slide" << p+1
681                       << ".xml.rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n"
682                       << "  <Override PartName=\"/ppt/slides/slide" << p+1
683                       << ".xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>\n";
684   if (total_images > 0)
685     xml_content_types << "  <Default Extension=\"png\" ContentType=\"image/png\"/>\n";
686   if (!eotlist.empty())
687     xml_content_types << "  <Default Extension=\"fntdata\" ContentType=\"application/x-fontdata\"/>\n";
688   xml_content_types << "</Types>\n";
689   create_pptx_file("[Content_Types].xml", xml_content_types.str().c_str());
690 
691   // Write the PPTX file to disk.
692   if (zip_close(outzip) == -1) {
693     RSString errmessage("ERROR: Failed to generate ");
694     errmessage += outFileName ;
695     errmessage += " (" ;
696     errmessage += zip_strerror(outzip) ;
697     errmessage += ")" ;
698 
699     errorMessage(errmessage.c_str());
700     abort();
701   }
702 }
703 
704 // Create a file in a PPTX package from given contents.
create_pptx_file(const char * relname,const char * contents)705 void drvPPTX::create_pptx_file(const char * relname, const char * contents)
706 {
707   // Convert the file contents into a data source.
708   struct zip_source * file_source = zip_source_buffer(outzip, strdup(contents), strlen(contents), 1);
709   if (file_source == NULL) {
710     RSString errmessage("ERROR: Failed to create data for ");
711     errmessage += relname ;
712     errmessage += " (" ;
713     errmessage += zip_strerror(outzip) ;
714     errmessage += ")" ;
715     errorMessage(errmessage.c_str());
716     abort();
717   }
718 
719   // Add the data source to the PPTX file.
720   if (zip_add(outzip, relname, file_source) == -1) {
721     RSString errmessage("ERROR: Failed to insert ");
722     errmessage += relname ;
723     errmessage += " into " ;
724     errmessage += outFileName ;
725     errmessage += " (" ;
726     errmessage += zip_strerror(outzip) ;
727     errmessage += ")" ;
728     errorMessage( errmessage.c_str());
729     abort();
730   }
731 }
732 
733 // Create a PPTX file from scratch and add some of the boilerplate.
create_pptx()734 void drvPPTX::create_pptx()
735 {
736   // Create a PPTX file for writing.
737   unlink(outFileName.c_str());
738   int ziperr;
739   outzip = zip_open(outFileName.c_str(), ZIP_CREATE, &ziperr);
740   if (outzip == NULL) {
741     char reason[101];
742     zip_error_to_str(reason, 100, ziperr, errno);
743 	RSString errmessage("ERROR: Failed to create ");
744 	errmessage += outFileName ;
745 	errmessage += " (" ;
746 	errmessage += reason ;
747 	errmessage += ")" ;
748 
749     errorMessage(errmessage.c_str());
750     abort();
751   }
752   RSString comment("Created by pstoedit's pptx driver from PostScript input ");
753   comment += inFileName;
754   zip_set_archive_comment(outzip, comment.c_str(), (zip_uint16_t)comment.length());
755 
756   // Insert boilerplate files into the PPTX archive.
757   create_pptx_file("_rels/.rels", xml_rels);
758   create_pptx_file("ppt/slideLayouts/slideLayout1.xml", xml_slideLayout1_xml);
759   create_pptx_file("ppt/slideLayouts/_rels/slideLayout1.xml.rels", xml_slideLayout1_xml_rels);
760   create_pptx_file("ppt/slideMasters/slideMaster1.xml", xml_slideMaster1_xml);
761   create_pptx_file("ppt/slideMasters/_rels/slideMaster1.xml.rels", xml_slideMaster1_xml_rels);
762   create_pptx_file("ppt/theme/theme1.xml", xml_theme1_xml);
763 }
764 
print_coords(const BBox & pathBBox)765 void drvPPTX::print_coords(const BBox & pathBBox)
766 {
767   // Output a list of coordinates in the shape's coordinate system.
768   long int xshift_emu = -xtrans(pathBBox.ll.x_);
769   long int yshift_emu = -ytrans(pathBBox.ur.y_);
770   for (unsigned int n = 0; n < numberOfElementsInPath(); n++) {
771     const basedrawingelement & elem = pathElement(n);
772     switch (elem.getType()) {
773     case moveto:
774       {
775         const Point & p = elem.getPoint(0);
776         slidef << "                <a:moveTo>\n"
777                << "                  <a:pt "
778                << pt2emu(p.x_, p.y_, xshift_emu, yshift_emu) << "/>\n"
779                << "                </a:moveTo>\n";
780       }
781       break;
782     case lineto:
783       {
784         const Point & p = elem.getPoint(0);
785         slidef << "                <a:lnTo>\n"
786                << "                  <a:pt "
787                << pt2emu(p.x_, p.y_, xshift_emu, yshift_emu) << "/>\n"
788                << "                </a:lnTo>\n";
789       }
790       break;
791     case curveto:
792       {
793         slidef << "                <a:cubicBezTo>\n";
794         for (unsigned int cp = 0; cp < 3; cp++) {
795           const Point & p = elem.getPoint(cp);
796           slidef << "                  <a:pt "
797                  << pt2emu(p.x_, p.y_, xshift_emu, yshift_emu) << "/>\n";
798         }
799         slidef << "                </a:cubicBezTo>\n";
800       }
801       break;
802     case closepath:
803       slidef << "                <a:close/>\n";
804       break;
805     default:
806       errf << "\t\tERROR: unexpected case in drvpptx " << endl;
807       abort();
808       break;
809     }
810   }
811 }
812 
open_page()813 void drvPPTX::open_page()
814 {
815   // Determine how much to offset the current page to center its
816   // graphics within the slide.
817   BBox pageBBox = getCurrentBBox();
818   center_offset.x_ = (slideBBox.ur.x_ - slideBBox.ll.x_ - (pageBBox.ur.x_ - pageBBox.ll.x_)) / 2.0f;
819   center_offset.y_ = (slideBBox.ur.y_ - slideBBox.ll.y_ - (pageBBox.ur.y_ - pageBBox.ll.y_)) / 2.0f;
820 
821   // Output OOXML header boilerplate.
822   slidef << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
823          << "<p:sld xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"\n"
824          << "       xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"\n"
825          << "       xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n"
826          << "  <p:cSld>\n"
827          << "    <p:spTree>\n"
828          << "      <p:nvGrpSpPr>\n"
829          << "        <p:cNvPr id=\"1\" name=\"\"/>\n"
830          << "        <p:cNvGrpSpPr/>\n"
831          << "        <p:nvPr/>\n"
832          << "      </p:nvGrpSpPr>\n"
833          << "      <p:grpSpPr>\n"
834          << "        <a:xfrm>\n"
835          << "          <a:off x=\"0\" y=\"0\"/>\n"
836          << "          <a:ext cx=\"0\" cy=\"0\"/>\n"
837          << "          <a:chOff x=\"0\" y=\"0\"/>\n"
838          << "          <a:chExt cx=\"0\" cy=\"0\"/>\n"
839          << "        </a:xfrm>\n"
840          << "      </p:grpSpPr>\n";
841 
842   // Reset the image count.
843   page_images = 0;
844 }
845 
close_page()846 void drvPPTX::close_page()
847 {
848   // Output OOXML trailer boilerplate.
849   slidef << "    </p:spTree>\n"
850          << "  </p:cSld>\n"
851          << "</p:sld>\n";
852 
853   // Add the current slide to the PPTX file.
854   const char * const slideContents_c = strdup(slidef.str().c_str());
855   struct zip_source * slideContents = zip_source_buffer(outzip, slideContents_c, strlen(slideContents_c), 1);
856   ostringstream slideFileName;
857   slideFileName << "ppt/slides/slide" << currentPageNumber << ".xml";
858   const char * const slideFileName_c = strdup(slideFileName.str().c_str());  // libzip seems to store a pointer to this.
859   if (zip_add(outzip, slideFileName_c, slideContents) == -1) {
860     RSString errmessage("ERROR: Failed to store ");
861     errmessage += slideFileName_c ;
862     errmessage += " in " ;
863     errmessage += outFileName ;
864     errmessage += " (" ;
865     errmessage += zip_strerror(outzip) ;
866     errmessage += ")" ;
867     errorMessage( errmessage.c_str());
868     abort();
869   }
870 
871   // Clear the slide contents in preparation for the next page.
872   slidef.str("");
873   slidef.clear();
874 
875   // Create a relationships file for the current slide.
876   ostringstream slideRelName;
877   slideRelName << "ppt/slides/_rels/slide" << currentPageNumber << ".xml.rels";
878   ostringstream slideRelContents;
879   slideRelContents <<
880     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
881     "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
882     "  <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"../slideLayouts/slideLayout1.xml\"/>\n";
883   for (unsigned int i = 0; i < page_images; i++)
884     slideRelContents << "  <Relationship Id=\"rId" << i + 2
885                      << "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\" Target=\"../media/image"
886                      << total_images - page_images + i + 1 << ".png\"/>\n";
887   slideRelContents  << "</Relationships>\n";
888   create_pptx_file(slideRelName.str().c_str(), slideRelContents.str().c_str());
889 }
890 
panose2pitch(const unsigned int * panose_vals)891 unsigned char drvPPTX::panose2pitch  (const unsigned int * panose_vals)
892 {
893   // Convert a PANOSE characterization to an OOXML pitch family.
894   unsigned char pitchFamily = 0;
895   switch (panose_vals[0]) {
896   case 3:
897     // "Latin script"
898     pitchFamily = 0x40;  // "Script"
899     break;
900   case 4:
901     // "Latin decorative"
902     pitchFamily = 0x50;  // "Decorative"
903     break;
904   default:
905     // "Latin text" or other
906     pitchFamily = (panose_vals[1] >= 11 && panose_vals[1] <= 13 ? 0x20 : 0x10);  // "Swiss" or "Roman"
907     break;
908   }
909   pitchFamily |= (panose_vals[3] == 9 ? 0x01 : 0x02);  // "Fixed" or "Variable"
910   return pitchFamily;
911 }
912 
get_font_props(const TextInfo & textinfo,RSString * typeface,RSString * panose,bool * isBold,bool * isItalic,unsigned char * pitchFamily)913 void drvPPTX::get_font_props(const TextInfo & textinfo,
914                              RSString * typeface, RSString * panose,
915                              bool * isBold, bool * isItalic,
916                              unsigned char * pitchFamily)
917 {
918   // Replace PostScript core fonts with Windows fonts.
919   RSString currentFontName(textinfo.currentFontName);
920   if (font_type == F_WINDOWS) {
921     const RSString * winFont = ps2win.getValue(currentFontName);
922     if (winFont != NULL)
923       currentFontName = *winFont;
924   }
925 
926   // Determine properties of the given font.
927   unsigned int panose_vals[10];
928   if (string_contains(currentFontName,",")) {
929     // Split the font name at commas into <full name>,<family name>,<panose>.
930     stringstream fontname_stream(currentFontName.c_str());
931     string fullname;
932     string familyname;
933     string panose_str;
934     if (getline(fontname_stream, fullname, ',') &&
935         getline(fontname_stream, familyname, ',') &&
936         getline(fontname_stream, panose_str, ',') &&
937         sscanf(panose_str.c_str(),
938                "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
939                &panose_vals[0], &panose_vals[1], &panose_vals[2],
940                &panose_vals[3], &panose_vals[4], &panose_vals[5],
941                &panose_vals[6], &panose_vals[7], &panose_vals[8],
942                &panose_vals[9]) == 10) {
943       // Compute everything from the given PANOSE categorization.
944       typeface->assign(familyname.c_str());
945       panose->assign(panose_str.c_str());
946       *isBold = panose_vals[2] >= 7;      // "Demi" and up
947       *isItalic = panose_vals[7] >= 9;    // "Oblique" letterform
948       *pitchFamily = panose2pitch(panose_vals);
949       return;
950     }
951   }
952 
953   // If we're here, then either the user didn't specify a font map
954   // that includes PANOSE data or the current font was not found in
955   // the map.  Do the best we can heuristically.
956   *typeface = textinfo.currentFontFamilyName;
957   if (*typeface == "Courier" || *typeface == "unknown"
958       || typeface->length() < currentFontName.length())
959     *typeface = currentFontName;
960   for (int i = 0; i < 10; i++)
961     panose_vals[i] = 0;
962   static const RSString Sans("Sans");
963   if (string_contains(currentFontName,Sans) || string_contains(textinfo.currentFontFullName,Sans)) {
964     panose_vals[0] = 2;   // "Latin text"
965     panose_vals[1] = 11;  // "Normal sans"
966   }
967   else {
968 	static const RSString Script("Script");
969 	static const RSString Hand("Hand");
970     if (string_contains(currentFontName,Script) || string_contains(textinfo.currentFontFullName,Script)
971         || string_contains(currentFontName,Hand) || string_contains(textinfo.currentFontFullName,Hand))
972       panose_vals[0] = 3;   // "Latin script"
973     else {
974       panose_vals[0] = 2;   // "Latin text"
975       panose_vals[1] = 2;   // "Cove"
976     }
977   }
978   static const RSString Bold("Bold");
979   if (string_contains(currentFontName,Bold) || string_contains(textinfo.currentFontFullName,Bold)) {
980     *isBold = true;
981     panose_vals[2] = 8;   // "Bold"
982   }
983   else {
984     *isBold = false;
985     panose_vals[2] = 5;   // "Book"
986   }
987   static const RSString Italic("Italic");
988   static const RSString Oblique("Oblique");
989   if (string_contains(currentFontName,Italic) || string_contains(textinfo.currentFontFullName,Italic)
990       || string_contains(currentFontName,Oblique) || string_contains(textinfo.currentFontFullName,Oblique)) {
991     *isItalic = true;
992     panose_vals[9] = 9;   // "Contact/oblique"
993   }
994   else {
995     *isItalic = false;
996     panose_vals[9] = 2;   // "Contact/normal"
997   }
998   static const RSString Mono("Mono");
999   if (string_contains(currentFontName,Mono) || string_contains(textinfo.currentFontFullName,Mono))
1000     panose_vals[4] = 9;   // "Monospaced"
1001   else
1002     panose_vals[4] = 3;   // "Modern"
1003   char panose_str[21];
1004   sprintf(panose_str, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1005           panose_vals[0], panose_vals[1], panose_vals[2], panose_vals[3],
1006           panose_vals[4], panose_vals[5], panose_vals[6], panose_vals[7],
1007           panose_vals[8], panose_vals[9]);
1008   panose->assign(panose_str);
1009   *pitchFamily = panose2pitch(panose_vals);
1010 }
1011 
1012 // Fabricate a TextInfo structure from an EOT file header.
eot2texinfo(const string & eotfilename,TextInfo & textinfo)1013 void drvPPTX::eot2texinfo(const string& eotfilename, TextInfo & textinfo)
1014 {
1015   unsigned char panose_vals[10];
1016   unsigned char charvals[4];
1017 
1018   // Parse the EOT header.
1019   ifstream eotfile(eotfilename.c_str());
1020   eotfile.ignore(4+4+4+4);                  // Size, font data size, version, flags
1021   eotfile.read((char *)panose_vals, 10);    // PANOSE values
1022   eotfile.ignore(1+1+4);                    // Character set, italic, weight
1023   eotfile.read((char *)charvals, 2);        // Embedding restrictions
1024   short fstype = charvals[1]<<8 | charvals[0];
1025   eotfile.read((char *)charvals, 2);        // Magic number
1026   unsigned short magicnum = charvals[1]<<8 | charvals[0];
1027   if (magicnum != 0x504c) {
1028     RSString errmessage("ERROR: ");
1029     errmessage += eotfilename.c_str() ;
1030     errmessage += " is not a valid Embedded OpenType (EOT) font file" ;
1031     errorMessage(errmessage.c_str());
1032     abort();
1033   }
1034   eotfile.ignore(4+4+4+4+4+4);              // Unicode ranges 1-4 and code page ranges 1-2
1035   eotfile.ignore(4+4+4+4+4+2);              // Checksum adjustment, reserved 1-4, padding
1036   eotfile.read((char *)charvals, 2);        // Family-name length
1037   unsigned short namesize = charvals[1]<<8 | charvals[0];
1038   char *familyname = new char[namesize];
1039   eotfile.read(familyname, namesize);       // Family name
1040   for (unsigned short i = 0; i < namesize/2; i++)
1041     // Cheesy conversion from Unicode to ASCII
1042     familyname[i] = familyname[i*2];
1043   textinfo.currentFontFamilyName = RSString(familyname, namesize/2);
1044   delete[] familyname;
1045   eotfile.ignore(2);                        // Padding
1046   eotfile.read((char *)charvals, 2);        // Style-name length
1047   namesize = charvals[1]<<8 | charvals[0];
1048   eotfile.ignore(namesize);                 // Style name
1049   eotfile.ignore(2);                        // Padding
1050   eotfile.read((char *)charvals, 2);        // Version-name length
1051   namesize = charvals[1]<<8 | charvals[0];
1052   eotfile.ignore(namesize);                 // Version name
1053   eotfile.ignore(2);                        // Padding
1054   eotfile.read((char *)charvals, 2);        // Full-name length
1055   namesize = charvals[1]<<8 | charvals[0];
1056   char *fullname = new char[namesize];
1057   eotfile.read(fullname, namesize);         // Full name
1058   for (unsigned short i = 0; i < namesize/2; i++)
1059     // Cheesy conversion from Unicode to ASCII
1060     fullname[i] = fullname[i*2];
1061   textinfo.currentFontFullName = RSString(fullname, namesize/2);
1062   delete[] fullname;
1063   eotfile.close();
1064 
1065   // Warn the user if the font has embedding restrictions.
1066   if (fstype == 0x0002)
1067     errf << "WARNING: Font " << textinfo.currentFontFullName << " ("
1068          << eotfilename << ") indicates that it must not be modified,"
1069          << " embedded, or exchanged in any manner without first obtaining"
1070          << " permission from the legal owner.  Do not embed this font"
1071          << " unless you have obtained such permission.\n";
1072 
1073   // Concatenate the PANOSE data to the font name so get_font_props()
1074   // can extract and process it.
1075   char panose_str[22];
1076   sprintf(panose_str, ",%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1077           panose_vals[0], panose_vals[1], panose_vals[2],
1078           panose_vals[3], panose_vals[4], panose_vals[5],
1079           panose_vals[6], panose_vals[7], panose_vals[8],
1080           panose_vals[9]);
1081   textinfo.currentFontName = textinfo.currentFontFullName;
1082   textinfo.currentFontName += ',';
1083   textinfo.currentFontName += textinfo.currentFontFamilyName;
1084   textinfo.currentFontName += panose_str;
1085 }
1086 
1087 // Given two 2-D vectors (represented as Points), return the angle
1088 // from the first to the second.
1089 // http://stackoverflow.com/questions/5188561/signed-angle-between-two-3d-vectors-with-same-origin-within-the-same-plane-reci
1090 // was useful here.
angle_between(Point first,Point second)1091 float drvPPTX::angle_between(Point first, Point second)
1092 {
1093   // Normalize each vector.
1094   float scale = pythagoras(first.x_, first.y_);
1095   first.x_ /= scale;
1096   first.y_ /= scale;
1097   scale = pythagoras(second.x_, second.y_);
1098   second.x_ /= scale;
1099   second.y_ /= scale;
1100 
1101   // Determine the direction of the rotation.
1102   float direction = first.x_*second.y_ - first.y_*second.x_;
1103 
1104   // Determine the rotation itself.
1105   float angle = acos(first.x_*second.x_ + first.y_*second.y_) * 180.0f/(float)M_PI;
1106   if (direction < 0)
1107     angle = -angle;
1108   return angle;
1109 }
1110 
1111 // Given a six-element PostScript transformation matrix, determine the
1112 // components of the transformation it represents.
parse_xform_matrix(const float * origMatrix,bool * mirrored,float * xscale,float * yscale,float * rotation,float * x_trans,float * y_trans)1113 void drvPPTX::parse_xform_matrix(const float * origMatrix,
1114                                  bool * mirrored,
1115                                  float * xscale, float * yscale,
1116                                  float * rotation,
1117                                  float * x_trans, float * y_trans)
1118 {
1119   // Return the translation then remove it from the matrix.
1120   float matrix[6];
1121   for (int i = 0; i < 6; i++)
1122     matrix[i] = origMatrix[i];
1123   *x_trans = matrix[4];
1124   *y_trans = matrix[5];
1125   matrix[4] = 0.0f;
1126   matrix[5] = 0.0f;
1127 
1128   // Determine whether the transformation includes mirroring.
1129   Point xunit(1.0f, 0.0f);
1130   Point xunit_xform = xunit.transform(matrix);
1131   Point yunit(0.0f, 1.0f);
1132   Point yunit_xform = yunit.transform(matrix);
1133   float rot90 = angle_between(xunit_xform, yunit_xform);
1134   *mirrored = rot90 < 0;
1135 
1136   // Compute the rotation angle.
1137   *rotation = angle_between(xunit, xunit_xform);
1138   if (*mirrored)
1139     *rotation = fmodf(*rotation + 180.0f, 360.0f);
1140 
1141   // Compute the scaling.
1142   *xscale = pythagoras(xunit_xform.x_, xunit_xform.y_);
1143   *yscale = pythagoras(yunit_xform.x_, yunit_xform.y_);
1144 }
1145 
show_text(const TextInfo & textinfo)1146 void drvPPTX::show_text(const TextInfo & textinfo)
1147 {
1148   // Output the non-visual shape properties.
1149   slidef << "      <p:sp>\n"
1150          << "        <p:nvSpPr>\n"
1151          << "          <p:cNvPr id=\"" << next_id << "\" name=\"pstoedit " << next_id << "\"/>\n"
1152          << "          <p:cNvSpPr/>\n"
1153          << "          <p:nvPr/>\n"
1154          << "        </p:nvSpPr>\n";
1155   next_id++;
1156 
1157   // Compute the unrotated text width and height.
1158   float text_width =                                 // Unrotated width
1159     pythagoras(textinfo.x_end - textinfo.x, textinfo.y_end - textinfo.y);
1160   float text_height = textinfo.currentFontSize;      // Unrotated height
1161 
1162   // Determine if the text is flipped horizontally.  We don't test for
1163   // vertical flipping because this is isomorphic to a horizontal flip
1164   // plus a rotation.
1165   bool flipH;
1166   float xscale, yscale, angle, x_trans, y_trans;
1167   parse_xform_matrix(textinfo.FontMatrix, &flipH, &xscale, &yscale,
1168                      &angle, &x_trans, &y_trans);
1169   if (flipH)
1170     angle = -angle;
1171 
1172   // Compute the upper-left corner of the rotated text.
1173   Point text_pivot(textinfo.x, textinfo.y);   // Unrotated lower left
1174   Point text_ul(textinfo.x, textinfo.y + text_height);   // Unrotated upper left
1175   Point text_c = text_pivot + Point(text_width/2.0f, text_height/2.0f);   // Unrotated center
1176   if (flipH) {
1177     text_ul.x_ -= text_width;
1178     text_c.x_ -= text_width;
1179   }
1180 
1181   // Rotate the upper-left corner and center around the original
1182   // lower-left corner, then unrotate the upper-left corner around the
1183   // new center.
1184   Point text_ul_rot = rotate_pt_around(text_ul, angle, text_pivot);
1185   Point text_c_rot = rotate_pt_around(text_c, angle, text_pivot);
1186   Point text_ofs = rotate_pt_around(text_ul_rot, -angle, text_c_rot);
1187 
1188   // Output the visual shape properties.
1189   slidef << "        <p:spPr>\n";
1190   slidef << "          <a:xfrm";
1191   if (angle != 0.0f)
1192     slidef << " rot=\"" << -angle*60000 << '"';
1193   if (flipH)
1194     slidef << " flipH=\"1\"";
1195   slidef << ">\n";
1196   slidef << "            <a:off " << pt2emu(text_ofs.x_, text_ofs.y_) << "/>\n";
1197   slidef << "            <a:ext " << pt2emu(text_width, text_height,
1198                                             0, 0, "cx", "cy", true) << "/>\n"
1199          << "          </a:xfrm>\n"
1200          << "          <a:prstGeom prst=\"rect\"/>\n"
1201          << "        </p:spPr>\n";
1202 
1203   // Get information about the current font.
1204   RSString typeface;
1205   RSString panose;
1206   bool isBold;
1207   bool isItalic;
1208   unsigned char pitchFamily;
1209   get_font_props(textinfo, &typeface, &panose, &isBold, &isItalic, &pitchFamily);
1210 
1211   // Output the text itself.
1212   slidef << "        <p:txBody>\n"
1213          << "          <a:bodyPr wrap=\"none\" lIns=\"0\" tIns=\"0\" rIns=\"0\" bIns=\"0\" rtlCol=\"0\">\n"
1214          << "            <a:spAutoFit/>\n"
1215          << "          </a:bodyPr>\n"
1216          << "          <a:p>\n"
1217          << "            <a:r>\n"
1218          << "              <a:rPr dirty=\"1\" smtClean=\"0\" sz=\""
1219          << textinfo.currentFontSize*100.0 << '"'
1220          << (isBold ? " b=\"1\"" : "")
1221          << (isItalic ? " i=\"1\"" : "");
1222   if (textinfo.ax != 0)
1223     slidef << " spc=\"" << textinfo.ax*100.0 << '"';
1224   slidef << ">\n";
1225   print_color(16, textinfo.currentR, textinfo.currentG, textinfo.currentB);
1226   switch (font_type) {
1227   case F_WINDOWS:
1228   case F_NATIVE:
1229     slidef << "                <a:latin typeface=\"" << typeface
1230            << "\" pitchFamily=\"" << (unsigned int)pitchFamily
1231            << "\" panose=\"" << panose
1232            << "\" charset=\"0\"/>\n";
1233     break;
1234   case F_THEME:
1235     // Use the theme's default font.
1236     break;
1237   default:
1238     errorMessage("ERROR: Unknown font type");
1239     abort();
1240     break;
1241   }
1242   slidef << "              </a:rPr>\n"
1243          << "              <a:t>";
1244   static bool warned_invalid_char = false;  // true=already issued an invalid-character warning
1245   for (size_t c = 0; c < textinfo.thetext.length(); c++) {
1246     unsigned char onechar = textinfo.thetext[c];
1247     if (onechar < 32 || (onechar >= 128 && onechar < 192)) {
1248       if (!warned_invalid_char) {
1249         errf << "Warning: Character " << (unsigned int)onechar << " is not allowed in OOXML text; ignoring\n";
1250         warned_invalid_char = true;
1251       }
1252     }
1253     else
1254       switch (onechar) {
1255       case '<':
1256         slidef << "&lt;";
1257         break;
1258       case '>':
1259         slidef << "&gt;";
1260         break;
1261       case '&':
1262         slidef << "&amp;";
1263         break;
1264       default:
1265         if (onechar < 128)
1266           slidef << onechar;
1267         else
1268           slidef << char(0xC0 | (onechar>>6)) << char(0x80 | (onechar&0x3F));
1269         break;
1270       }
1271   }
1272   slidef << "</a:t>\n"
1273          << "            </a:r>\n"
1274          << "            <a:endParaRPr dirty=\"1\">\n";
1275   print_color(14, textinfo.currentR, textinfo.currentG, textinfo.currentB);
1276   slidef << "            </a:endParaRPr>\n"
1277          << "          </a:p>\n"
1278          << "        </p:txBody>\n"
1279          << "      </p:sp>\n";
1280 }
1281 
1282 
1283 
bp2emu(float bp)1284 long int drvPPTX::bp2emu (float bp) {
1285           return lroundf(bp * 12700.0f);
1286 }
1287 
1288 
pt2emu(float x_bp,float y_bp,long int xshift_emu,long int yshift_emu,RSString x_name,RSString y_name,bool scaleOnly) const1289 const char * drvPPTX::pt2emu(float x_bp, float y_bp,
1290                              long int xshift_emu, long int yshift_emu,
1291                              RSString x_name, RSString y_name,
1292                              bool scaleOnly) const
1293 {
1294   // Convert a PostScript (x, y) coordinate (in big points with its
1295   // origin in the lower left) to an XML string (in EMUs with its
1296   // origin in the upper left).  If scaleOnly is true, then scale the
1297   // units without translating the coordinates (useful for widths and
1298   // heights).  Note that the result will get overwritten on the
1299   // subsequent call.
1300   static char emuString[100];   // Should be more than enough in practice
1301 
1302   if (scaleOnly)
1303     sprintf(emuString, "%s=\"%ld\" %s=\"%ld\"",
1304             x_name.c_str(), bp2emu(x_bp),
1305             y_name.c_str(), bp2emu(y_bp));
1306   else
1307     sprintf(emuString, "%s=\"%ld\" %s=\"%ld\"",
1308             x_name.c_str(), xtrans(x_bp) + xshift_emu,
1309             y_name.c_str(), ytrans(y_bp) + yshift_emu);
1310   return emuString;
1311 }
1312 
1313 
1314 
1315 // Compute the centroid of a polygon.  Note that we might not have
1316 // been given a polygon, but we'll at least return *something*.
pathCentroid()1317 Point drvPPTX::pathCentroid()
1318 {
1319   // We start by finding a cycle of knots.
1320   unsigned int numElts = numberOfElementsInPath();
1321   Point * allKnots = new Point[numElts + 1];
1322   unsigned int numKnots = 0;
1323   unsigned int movetos = 0;
1324   for (unsigned int n = 0; n < numElts; n++) {
1325     const basedrawingelement & elem = pathElement(n);
1326     if (elem.getType() == moveto)
1327       movetos++;
1328     if (elem.getNrOfPoints() == 0)
1329       continue;
1330     allKnots[numKnots++] = elem.getPoint(elem.getNrOfPoints() - 1);
1331   }
1332   if (allKnots[numKnots - 1] == allKnots[0])
1333     numKnots--;
1334   else
1335     allKnots[numKnots] = allKnots[0];
1336 
1337   // Otherwise, we compute the area bounded by the knots.
1338   float area = 0.0f;
1339   for (unsigned int n = 0; n < numKnots; n++)
1340     area += allKnots[n].x_*allKnots[n+1].y_ - allKnots[n+1].x_*allKnots[n].y_;
1341   area /= 2.0f;
1342 
1343   Point result;
1344   // If we were given a disjoint path or the area is zero, simply
1345   // average all of the knot coordinates and return that.
1346   if ((numKnots > 0) && (movetos > 1 || area == 0.0f)) {
1347     Point centroid;
1348     for (unsigned int n = 0; n < numKnots; n++)
1349       centroid += allKnots[n];
1350     centroid.x_ /= numKnots;
1351     centroid.y_ /= numKnots;
1352     result = centroid;
1353   } else if (area > 0.0f) {
1354 
1355     // Finally, we compute the centroid of the polygon.
1356     Point p;
1357     for (unsigned int n = 0; n < numKnots; n++) {
1358       float partial = allKnots[n].x_*allKnots[n+1].y_ - allKnots[n+1].x_*allKnots[n].y_;
1359       p.x_ += (allKnots[n].x_ + allKnots[n+1].x_)*partial;
1360       p.y_ += (allKnots[n].y_ + allKnots[n+1].y_)*partial;
1361     }
1362     p.x_ /= area*6.0f;
1363     p.y_ /= area*6.0f;
1364     result = p;
1365   }
1366   delete[] allKnots;
1367   return result;
1368 }
1369 
1370 // Rotate point PT by ANGLE degrees around point PIVOT.
rotate_pt_around(const Point & pt,float angle,const Point & pivot)1371 Point drvPPTX::rotate_pt_around  (const Point & pt, float angle, const Point & pivot)
1372 {
1373   Point shiftedPt = pt;
1374   shiftedPt.x_ -= pivot.x_;   // Shift the pivot to the origin.
1375   shiftedPt.y_ -= pivot.y_;
1376   float angle_rad = angle * (float)M_PI / 180.0f;
1377   Point rotatedPt(shiftedPt.x_*cosf(angle_rad) - shiftedPt.y_*sinf(angle_rad),  // Rotate the shifted point.
1378                   shiftedPt.x_*sinf(angle_rad) + shiftedPt.y_*cosf(angle_rad));
1379   return rotatedPt + pivot;   // Shift back to the original pivot.
1380 }
1381 
print_connections(const BBox & pathBBox)1382 void drvPPTX::print_connections(const BBox & pathBBox)
1383 {
1384   // Output shape connection sites (knots and centroid).
1385   Point centroid = pathCentroid();
1386   long int xshift_emu = -xtrans(pathBBox.ll.x_);
1387   long int yshift_emu = -ytrans(pathBBox.ur.y_);
1388   slidef << "            <a:cxnLst>\n"
1389          << "              <a:cxn ang=\"0\">\n"
1390          << "                <a:pos " << pt2emu(centroid.x_, centroid.y_,
1391                                                 xshift_emu, yshift_emu) << "/>\n"
1392          << "              </a:cxn>\n";
1393   for (unsigned int n = 0; n < numberOfElementsInPath(); n++) {
1394     const basedrawingelement & elem = pathElement(n);
1395     if (elem.getNrOfPoints() == 0)
1396       continue;
1397     const Point & p = elem.getPoint(elem.getNrOfPoints() - 1);
1398     float angle = atan2f(centroid.y_ - p.y_, p.x_ - centroid.x_);
1399     slidef << "              <a:cxn ang=\"" << angle*60000.0*180.0/M_PI << "\">\n"
1400            << "                <a:pos " << pt2emu(p.x_, p.y_,
1401                                                   xshift_emu, yshift_emu) << "/>\n"
1402            << "              </a:cxn>\n";
1403   }
1404   slidef << "            </a:cxnLst>\n";
1405 }
1406 
print_color(int baseIndent,float redF,float greenF,float blueF)1407 void drvPPTX::print_color(int baseIndent, float redF, float greenF, float blueF)
1408 {
1409   // Output an OOXML solid color.
1410   string indentStr = string(baseIndent, ' ');
1411   unsigned int red = (unsigned int)lroundf(redF * 255);
1412   unsigned int green = (unsigned int)lroundf(greenF * 255);
1413   unsigned int blue = (unsigned int)lroundf(blueF * 255);
1414   unsigned int rgb = blue + 256*(green + 256*red);
1415   slidef << indentStr << "<a:solidFill>\n";
1416   switch (color_type) {
1417   case C_ORIGINAL:
1418     // With -colors=original, output the color exactly as specified.
1419     slidef << indentStr << "  <a:srgbClr val=\""
1420            << hex << setw(6) << setfill('0') << rgb << dec << "\"/>\n";
1421     break;
1422 
1423   case C_THEME:
1424   case C_THEME_PURE:
1425     // With -colors=theme, randomly select a theme color.
1426     if (rgb == 0)
1427       // Black -> dark 1
1428       slidef << indentStr << "  <a:schemeClr val=\"dk1\"/>\n";
1429     else if (rgb == 0xffffff)
1430       // White -> light 1
1431       slidef << indentStr << "  <a:schemeClr val=\"lt1\"/>\n";
1432     else {
1433       // Randomly select a theme color, but remember it for next time.
1434       const ThemeColor * colorInfo = rgb2theme.getValue(rgb);
1435       ThemeColor newColorInfo;
1436       if (colorInfo == NULL) {
1437         // This is the first time we've seen this RGB color.
1438         static const char *colorList[] = {
1439           "dk2", "lt2", "accent1", "accent2", "accent3",
1440           "accent4", "accent5", "accent6"
1441         };
1442         newColorInfo.name = colorList[random() % (sizeof(colorList)/sizeof(colorList[0]))];
1443         if (color_type == C_THEME) {
1444           // Randomly alter the luminosity with the constraint that
1445           // light colors map to light colors and dark colors map to
1446           // dark colors.
1447           float origLum = sqrtf(0.241f*redF*redF + 0.691f*greenF*greenF + 0.068f*blueF*blueF);
1448           if (origLum >= 0.5)
1449             newColorInfo.lum = 50000 + random()%40000;  // Map to [50%, 90%].
1450           else
1451             newColorInfo.lum = 30000 + random()%20000;  // Map to [30%, 50%].
1452         }
1453         rgb2theme.insert(rgb, newColorInfo);
1454         colorInfo = &newColorInfo;
1455       }
1456 
1457       // Output the, possibly altered, theme color.
1458       if (colorInfo->lum == ~0U)
1459         slidef << indentStr << "  <a:schemeClr val=\"" << colorInfo->name << "\"/>\n";
1460       else {
1461         slidef << indentStr << "  <a:schemeClr val=\"" << colorInfo->name << "\">\n"
1462                << indentStr << "    <a:lum val=\"" << colorInfo->lum << "\"/>\n"
1463                << indentStr << "  </a:schemeClr>\n";
1464       }
1465     }
1466     break;
1467 
1468   default:
1469     errorMessage("ERROR: Unexpected color type");
1470     abort();
1471     break;
1472   }
1473   slidef << indentStr << "</a:solidFill>\n";
1474 }
1475 
print_join()1476 void drvPPTX::print_join()
1477 {
1478   // Output the current line join in OOXML format.
1479   switch (currentLineJoin()) {
1480   case 0:
1481     slidef << "            <a:miter/>\n";
1482     break;
1483   case 1:
1484     slidef << "            <a:round/>\n";
1485     break;
1486   case 2:
1487     slidef << "            <a:bevel/>\n";
1488     break;
1489   default:
1490     errorMessage("ERROR: unknown linejoin");
1491     abort();
1492     break;
1493   }
1494 }
1495 
print_dash()1496 void drvPPTX::print_dash()
1497 {
1498   // Parse a PostScript dash pattern.
1499   istringstream dashStr(dashPattern());
1500   float *pattern = new float[2*string(dashPattern()).length()];   // Very generous allocation but expected to be short
1501   size_t patternLen = 0;    // Number of floats in the above
1502   string oneToken;
1503   dashStr >> oneToken;   // "["
1504   while (dashStr) {
1505     // Read floats until we reach the "]".  Ignore that and anything
1506     // that follows it.
1507     dashStr >> pattern[patternLen];
1508     if (dashStr.fail())
1509       break;
1510     patternLen++;
1511   }
1512 
1513   // Output an OOXML custom dash.
1514   if (patternLen > 0) {
1515     // Repeat odd patterns to make them even.
1516     size_t p;
1517     if (patternLen % 2 == 1) {
1518       for (p = 0; p < patternLen; p++)
1519         pattern[p + patternLen] = pattern[p];
1520       patternLen *= 2;
1521     }
1522 
1523     // Output {dash, space} pairs.
1524     float lineWidth = currentLineWidth();
1525     slidef << "            <a:custDash>\n";
1526     for (p = 0; p < patternLen; p += 2)
1527       slidef << "              <a:ds d=\"" << 100000.0*pattern[p]/lineWidth
1528              << "\" sp=\"" << 100000.0*pattern[p+1]/lineWidth << "\"/>\n";
1529     slidef << "            </a:custDash>\n";
1530   }
1531   delete[] pattern;
1532 }
1533 
show_path()1534 void drvPPTX::show_path()
1535 {
1536   // Output the non-visible shape properties.
1537   slidef << "      <p:sp>\n"
1538          << "        <p:nvSpPr>\n"
1539          << "          <p:cNvPr id=\"" << next_id << "\" name=\"pstoedit " << next_id << "\"/>\n"
1540          << "          <p:cNvSpPr/>\n"
1541          << "          <p:nvPr/>\n"
1542          << "        </p:nvSpPr>\n";
1543   next_id++;
1544 
1545   // Compute the path's bounding box.  This might not be perfectly
1546   // tight due to the way we process curves, but that's not a show
1547   // stopper.
1548   BBox pathBBox;
1549   pathBBox.ll.x_ = FLT_MAX;
1550   pathBBox.ll.y_ = FLT_MAX;
1551   pathBBox.ur.x_ = -FLT_MAX;
1552   pathBBox.ur.y_ = -FLT_MAX;
1553   Point prevPoint;
1554   for (unsigned int e = 0; e < numberOfElementsInPath(); e++) {
1555     // Non-curves are handled by considering each knot in the
1556     // bounding-box calcuation.
1557     const basedrawingelement & elem = pathElement(e);
1558     unsigned int numPoints = elem.getNrOfPoints();
1559     if (elem.getType() != curveto)
1560       for (unsigned int p = 0; p < numPoints; p++) {
1561         Point thisPt = elem.getPoint(p);
1562         pathBBox.ll.x_ = min(pathBBox.ll.x_, thisPt.x_);
1563         pathBBox.ll.y_ = min(pathBBox.ll.y_, thisPt.y_);
1564         pathBBox.ur.x_ = max(pathBBox.ur.x_, thisPt.x_);
1565         pathBBox.ur.y_ = max(pathBBox.ur.y_, thisPt.y_);
1566       }
1567 
1568     // Rather than attempt to compute the true bounding box of a
1569     // curve, we simply sample a large number of evenly spaced points
1570     // along the curve for our bounding-box calcuation.  This is a lot
1571     // easier and probably works in virtually all cases.
1572     if (elem.getType() == curveto) {
1573       const float numSamples = 100.0f;
1574       for (float t = 0.0f; t <= 1.0f; t += 1.0f/numSamples) {
1575         Point bPoint = PointOnBezier(t, prevPoint, elem.getPoint(0),
1576                                      elem.getPoint(1), elem.getPoint(2));
1577         pathBBox.ll.x_ = min(pathBBox.ll.x_, bPoint.x_);
1578         pathBBox.ll.y_ = min(pathBBox.ll.y_, bPoint.y_);
1579         pathBBox.ur.x_ = max(pathBBox.ur.x_, bPoint.x_);
1580         pathBBox.ur.y_ = max(pathBBox.ur.y_, bPoint.y_);
1581       }
1582     }
1583 
1584     // Keep track of the current point.
1585     if (numPoints > 0)
1586       prevPoint = elem.getPoint(numPoints - 1);
1587   }
1588 
1589   // Output the 2-D transform for the graphic frame (i.e., the shape's
1590   // offset and size).
1591   slidef << "        <p:spPr>\n"
1592          << "          <a:xfrm>\n";
1593   slidef << "            <a:off " << pt2emu(pathBBox.ll.x_, pathBBox.ur.y_) << "/>\n";
1594   slidef << "            <a:ext " << pt2emu(pathBBox.ur.x_ - pathBBox.ll.x_,
1595                                             pathBBox.ur.y_ - pathBBox.ll.y_,
1596                                             0, 0, "cx", "cy", true) << "/>\n"
1597          << "          </a:xfrm>\n";
1598 
1599   // For the user's convenience, make each knot a connection site, and
1600   // specify that any text the user adds should fill the shape's bounding box.
1601   slidef << "          <a:custGeom>\n";
1602   print_connections(pathBBox);
1603   slidef << "            <a:rect l=\"l\" t=\"t\" r=\"r\" b=\"b\"/>\n";
1604 
1605   // Define the coordinate system for the shape within its frame.
1606   slidef << "            <a:pathLst>\n"
1607          << "              <a:path " << pt2emu(pathBBox.ur.x_ - pathBBox.ll.x_,
1608                                                pathBBox.ur.y_ - pathBBox.ll.y_,
1609                                                0, 0, "w", "h", true) << ">\n";
1610   // Output all of the shape's lines and curves.
1611   print_coords(pathBBox);
1612   slidef << "              </a:path>\n"
1613          << "            </a:pathLst>\n"
1614          << "          </a:custGeom>\n";
1615 
1616   // Output a stroke and/or fill.
1617   if (pathWasMerged() || currentShowType() == drvbase::fill || currentShowType() == drvbase::eofill)
1618     // Filled region
1619     print_color(10, fillR(), fillG(), fillB());
1620   if (pathWasMerged() || currentShowType() == drvbase::stroke) {
1621     // Stroked line
1622     slidef << "          <a:ln w=\"" << currentLineWidth()*12700.0
1623            << "\" cap=\"";
1624     switch (currentLineCap()) {
1625     case 0:
1626       slidef << "flat";
1627       break;
1628     case 1:
1629       slidef << "rnd";
1630       break;
1631     case 2:
1632       slidef << "sq";
1633       break;
1634     default:
1635       errorMessage("ERROR: unknown linecap");
1636       abort();
1637       break;
1638     }
1639     slidef << "\">\n";
1640     print_color(12, edgeR(), edgeG(), edgeB());
1641     print_dash();
1642     print_join();
1643     slidef << "          </a:ln>\n";
1644   }
1645 
1646   // Indicate that if the shape is to be contain a text box, then the
1647   // text box should be middle-centered within the shape, and the text
1648   // itself should be centered horizontally within the text box.
1649   slidef << "        </p:spPr>\n"
1650          << "        <p:txBody>\n"
1651          << "          <a:bodyPr wrap=\"none\" lIns=\"0\" tIns=\"0\" rIns=\"0\" bIns=\"0\" rtlCol=\"0\" anchor=\"ctr\" anchorCtr=\"1\"/>\n"
1652          << "          <a:lstStyle/>\n"
1653          << "          <a:p>\n"
1654          << "            <a:pPr algn=\"ctr\"/>\n"
1655          << "            <a:endParaRPr dirty=\"1\"/>\n"
1656          << "          </a:p>\n"
1657          << "        </p:txBody>\n"
1658          << "      </p:sp>\n";
1659 }
1660 
show_rectangle(const float llx,const float lly,const float urx,const float ury)1661 void drvPPTX::show_rectangle(const float llx, const float lly, const float urx, const float ury)
1662 {
1663   // We could probably generate a rectangle preset if we felt like it.
1664   unused(&llx); unused(&lly); unused(&urx); unused(&ury); // avoid the compiler warning
1665   show_path();
1666 }
1667 
show_image(const PSImage & imageinfo)1668 void drvPPTX::show_image(const PSImage & imageinfo)
1669 {
1670   // One might think that imageinfo.imageMatrix, which the PostScript
1671   // Language Reference Manual suggests using to map the unit square
1672   // to the bounds of the source image, would be useful here.
1673   // However, experiments suggest that
1674   // imageinfo.normalizedImageCurrentMatrix in fact provides all the
1675   // information we need to place an image on a slide.
1676   const float * ctm = imageinfo.normalizedImageCurrentMatrix;
1677 
1678   // Determine the image's orientation, scale, rotation, and position
1679   // on the slide.
1680   // http://stackoverflow.com/questions/4361242/extract-rotation-scale-values-from-2d-transformation-matrix
1681   // and
1682   // http://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
1683   // were helpful here.
1684   bool flipH = ctm[0] < 0;
1685   bool flipV = ctm[3] > 0;    // Reversed sense because we're already flipping the coordinate system
1686   float xscale = pythagoras(ctm[0], ctm[2]);
1687   float yscale = pythagoras(ctm[1], ctm[3]);
1688   float angle = atan2f(ctm[2], ctm[0]) * (float)(180.0f/M_PI);
1689   if (flipH)
1690     angle = 180.0f - angle;
1691   if (flipV)
1692     angle = -angle;
1693   long int angle_int = lroundf(-60000.0f*angle);
1694 
1695   // DrawingML rotates the image *after* the location for the image's
1696   // upper-left corner is specified.  Hence, we determine the image's
1697   // unrotated upper-left corner by applying the CTM to the image's
1698   // center and taking an offset from that.
1699   Point center_orig(imageinfo.width/2.0f, imageinfo.height/2.0f);
1700   Point center_xform = center_orig.transform(ctm);
1701   Point ofs = center_xform + Point(-xscale*imageinfo.width/2.0f, yscale*imageinfo.height/2.0f);
1702 
1703   // Place the image on the slide.
1704   total_images++;
1705   page_images++;
1706   slidef << "      <p:pic>\n"
1707          << "        <p:nvPicPr>\n"
1708          << "          <p:cNvPr id=\"" << next_id << "\" name=\"pstoedit " << next_id << "\"/>\n"
1709          << "          <p:cNvPicPr/>\n"
1710          << "          <p:nvPr/>\n"
1711          << "        </p:nvPicPr>\n";
1712   next_id++;
1713   slidef << "        <p:blipFill>\n"
1714          << "          <a:blip r:embed=\"rId" << page_images + 1 << "\"/>\n"
1715          << "          <a:srcRect/>\n"
1716          << "          <a:stretch>\n"
1717          << "            <a:fillRect/>\n"
1718          << "          </a:stretch>\n"
1719          << "        </p:blipFill>\n";
1720   slidef << "        <p:spPr bwMode=\"auto\">\n"
1721          << "          <a:xfrm";
1722   if (angle_int != 0)
1723     slidef << " rot=\"" << angle_int << '"';
1724   if (flipH)
1725     slidef << " flipH=\"1\"";
1726   if (flipV)
1727     slidef << " flipV=\"1\"";
1728   float cx = imageinfo.width*xscale;
1729   float cy = imageinfo.height*yscale;
1730   slidef << ">\n"
1731          << "            <a:off " << pt2emu(ofs.x_, ofs.y_) << "/>\n";
1732   slidef << "            <a:ext " << pt2emu(cx, cy, 0, 0, "cx", "cy", true) << "/>\n"
1733          << "          </a:xfrm>\n"
1734          << "          <a:prstGeom prst=\"rect\"/>\n"
1735          << "          <a:noFill/>\n"
1736          << "        </p:spPr>\n"
1737          << "      </p:pic>\n";
1738 
1739   // Embed the image in the PPTX file.
1740   struct zip_source *img_file = zip_source_file(outzip, imageinfo.FileName.c_str(), 0, -1);
1741   if (img_file == NULL) {
1742     RSString errmessage("ERROR: Failed to embed image file ");
1743     errmessage += imageinfo.FileName;
1744     errmessage += " (";
1745     errmessage += zip_strerror(outzip);
1746     errmessage += ")";
1747     errorMessage(errmessage.c_str());
1748     abort();
1749   }
1750   ostringstream img_filename;
1751   img_filename << "ppt/media/image" << total_images << ".png";
1752   if (zip_add(outzip, img_filename.str().c_str(), img_file) == -1) {
1753 	RSString errmessage("ERROR: Failed to embed image file ");
1754 	errmessage += imageinfo.FileName;
1755 	errmessage +=" as ";
1756 	errmessage +=img_filename.str().c_str();
1757 	errmessage += " (";
1758 	errmessage +=zip_strerror(outzip);
1759 	errmessage +=")";
1760     errorMessage(errmessage.c_str());
1761     abort();
1762   }
1763 }
1764 
1765 static DriverDescriptionT < drvPPTX >
1766 D_pptx("pptx",
1767        "PresentationML (PowerPoint) format",
1768        "This is the format used internally by Microsoft PowerPoint.  LibreOffice can also read/write PowerPoint files albeit with some lack of functionality.",
1769        "pptx",
1770        true,   // backend supports subpathes
1771        // if subpathes are supported, the backend must deal with
1772        // sequences of the following form
1773        // moveto (start of subpath)
1774        // lineto (a line segment)
1775        // lineto
1776        // moveto (start of a new subpath)
1777        // lineto (a line segment)
1778        // lineto
1779        //
1780        // If this argument is set to false each subpath is drawn
1781        // individually which might not necessarily represent
1782        // the original drawing.
1783        true,   // backend supports curves
1784        true,   // backend supports elements which are filled and have edges
1785        true,   // backend supports text
1786        DriverDescription::png,         // support for PNG images
1787        DriverDescription::noopen,      // we create the output file ourself
1788        true,   // if format supports multiple pages in one file
1789        false  // clipping
1790        );
1791 
1792 #endif