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 << "<";
1257 break;
1258 case '>':
1259 slidef << ">";
1260 break;
1261 case '&':
1262 slidef << "&";
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