1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 /** @file
3 * OpenDocument (drawing) input and output
4 *//*
5 * Authors:
6 * Bob Jamison
7 * Abhishek Sharma
8 * Kris De Gussem
9 *
10 * Copyright (C) 2018 Authors
11 * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
12 */
13 /*
14 * This is an an entry in the extensions mechanism to begin to enable
15 * the inputting and outputting of OpenDocument Format (ODF) files from
16 * within Inkscape. Although the initial implementations will be very lossy
17 * due to the differences in the models of SVG and ODF, they will hopefully
18 * improve greatly with time. People should consider this to be a framework
19 * that can be continuously upgraded for ever improving fidelity. Potential
20 * developers should especially look in preprocess() and writeTree() to see how
21 * the SVG tree is scanned, read, translated, and then written to ODF.
22 *
23 * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
24 *
25 */
26
27 #include "odf.h"
28
29 //# System includes
30 #include <cstdio>
31 #include <ctime>
32 #include <vector>
33 #include <cmath>
34
35 //# Inkscape includes
36 #include "clear-n_.h"
37 #include "inkscape.h"
38 #include "display/curve.h"
39 #include <2geom/pathvector.h>
40 #include <2geom/curves.h>
41 #include <2geom/transforms.h>
42 #include <helper/geom.h>
43 #include "helper/geom-curves.h"
44 #include "extension/system.h"
45
46 #include "xml/repr.h"
47 #include "xml/attribute-record.h"
48 #include "object/sp-image.h"
49 #include "object/sp-gradient.h"
50 #include "object/sp-stop.h"
51 #include "object/sp-linear-gradient.h"
52 #include "object/sp-radial-gradient.h"
53 #include "object/sp-root.h"
54 #include "object/sp-path.h"
55 #include "object/sp-text.h"
56 #include "object/sp-flowtext.h"
57 #include "object/uri.h"
58 #include "style.h"
59
60 #include "svg/svg.h"
61 #include "text-editing.h"
62 #include "util/units.h"
63
64
65 #include "inkscape-version.h"
66 #include "document.h"
67 #include "extension/extension.h"
68
69 #include "io/stream/bufferstream.h"
70 #include "io/stream/stringstream.h"
71 #include "io/sys.h"
72 #include <util/ziptool.h>
73 #include <iomanip>
74 namespace Inkscape
75 {
76 namespace Extension
77 {
78 namespace Internal
79 {
80 //# Shorthand notation
81 typedef Inkscape::IO::BufferOutputStream BufferOutputStream;
82 typedef Inkscape::IO::OutputStreamWriter OutputStreamWriter;
83 typedef Inkscape::IO::StringOutputStream StringOutputStream;
84
85
86 //########################################################################
87 //# C L A S S SingularValueDecomposition
88 //########################################################################
89 #include <cmath>
90
91 class SVDMatrix
92 {
93 public:
94
SVDMatrix()95 SVDMatrix()
96 {
97 init();
98 }
99
SVDMatrix(unsigned int rowSize,unsigned int colSize)100 SVDMatrix(unsigned int rowSize, unsigned int colSize)
101 {
102 init();
103 rows = rowSize;
104 cols = colSize;
105 size = rows * cols;
106 d = new double[size];
107 for (unsigned int i=0 ; i<size ; i++)
108 d[i] = 0.0;
109 }
110
SVDMatrix(double * vals,unsigned int rowSize,unsigned int colSize)111 SVDMatrix(double *vals, unsigned int rowSize, unsigned int colSize)
112 {
113 init();
114 rows = rowSize;
115 cols = colSize;
116 size = rows * cols;
117 d = new double[size];
118 for (unsigned int i=0 ; i<size ; i++)
119 d[i] = vals[i];
120 }
121
122
SVDMatrix(const SVDMatrix & other)123 SVDMatrix(const SVDMatrix &other)
124 {
125 init();
126 assign(other);
127 }
128
operator =(const SVDMatrix & other)129 SVDMatrix &operator=(const SVDMatrix &other)
130 {
131 assign(other);
132 return *this;
133 }
134
~SVDMatrix()135 virtual ~SVDMatrix()
136 {
137 delete[] d;
138 }
139
operator ()(unsigned int row,unsigned int col)140 double& operator() (unsigned int row, unsigned int col)
141 {
142 if (row >= rows || col >= cols)
143 return badval;
144 return d[cols*row + col];
145 }
146
operator ()(unsigned int row,unsigned int col) const147 double operator() (unsigned int row, unsigned int col) const
148 {
149 if (row >= rows || col >= cols)
150 return badval;
151 return d[cols*row + col];
152 }
153
getRows()154 unsigned int getRows()
155 {
156 return rows;
157 }
158
getCols()159 unsigned int getCols()
160 {
161 return cols;
162 }
163
multiply(const SVDMatrix & other)164 SVDMatrix multiply(const SVDMatrix &other)
165 {
166 if (cols != other.rows)
167 {
168 SVDMatrix dummy;
169 return dummy;
170 }
171 SVDMatrix result(rows, other.cols);
172 for (unsigned int i=0 ; i<rows ; i++)
173 {
174 for (unsigned int j=0 ; j<other.cols ; j++)
175 {
176 double sum = 0.0;
177 for (unsigned int k=0 ; k<cols ; k++)
178 {
179 //sum += a[i][k] * b[k][j];
180 sum += d[i*cols +k] * other(k, j);
181 }
182 result(i, j) = sum;
183 }
184
185 }
186 return result;
187 }
188
transpose()189 SVDMatrix transpose()
190 {
191 SVDMatrix result(cols, rows);
192 for (unsigned int i=0 ; i<rows ; i++){
193 for (unsigned int j=0 ; j<cols ; j++){
194 result(j, i) = d[i*cols + j];
195 }
196 }
197 return result;
198 }
199
200 private:
201
202
init()203 virtual void init()
204 {
205 badval = 0.0;
206 d = nullptr;
207 rows = 0;
208 cols = 0;
209 size = 0;
210 }
211
assign(const SVDMatrix & other)212 void assign(const SVDMatrix &other)
213 {
214 if (d)
215 {
216 delete[] d;
217 d = nullptr;
218 }
219 rows = other.rows;
220 cols = other.cols;
221 size = other.size;
222 badval = other.badval;
223 d = new double[size];
224 for (unsigned int i=0 ; i<size ; i++){
225 d[i] = other.d[i];
226 }
227 }
228
229 double badval;
230
231 double *d;
232 unsigned int rows;
233 unsigned int cols;
234 unsigned int size;
235 };
236
237
238
239 /**
240 *
241 * ====================================================
242 *
243 * NOTE:
244 * This class is ported almost verbatim from the public domain
245 * JAMA Matrix package. It is modified to handle only 3x3 matrices
246 * and our Geom::Affine affine transform class. We give full
247 * attribution to them, along with many thanks. JAMA can be found at:
248 * http://math.nist.gov/javanumerics/jama
249 *
250 * ====================================================
251 *
252 * Singular Value Decomposition.
253 * <P>
254 * For an m-by-n matrix A with m >= n, the singular value decomposition is
255 * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
256 * an n-by-n orthogonal matrix V so that A = U*S*V'.
257 * <P>
258 * The singular values, sigma[k] = S[k][k], are ordered so that
259 * sigma[0] >= sigma[1] >= ... >= sigma[n-1].
260 * <P>
261 * The singular value decomposition always exists, so the constructor will
262 * never fail. The matrix condition number and the effective numerical
263 * rank can be computed from this decomposition.
264 */
265 class SingularValueDecomposition
266 {
267 public:
268
269 /** Construct the singular value decomposition
270 @param A Rectangular matrix
271 @return Structure to access U, S and V.
272 */
273
SingularValueDecomposition(const SVDMatrix & mat)274 SingularValueDecomposition (const SVDMatrix &mat) :
275 A (mat),
276 U (),
277 s (nullptr),
278 s_size (0),
279 V ()
280 {
281 calculate();
282 }
283
~SingularValueDecomposition()284 virtual ~SingularValueDecomposition()
285 {
286 delete[] s;
287 }
288
289 /**
290 * Return the left singular vectors
291 * @return U
292 */
293 SVDMatrix &getU();
294
295 /**
296 * Return the right singular vectors
297 * @return V
298 */
299 SVDMatrix &getV();
300
301 /**
302 * Return the s[index] value
303 */ double getS(unsigned int index);
304
305 /**
306 * Two norm
307 * @return max(S)
308 */
309 double norm2();
310
311 /**
312 * Two norm condition number
313 * @return max(S)/min(S)
314 */
315 double cond();
316
317 /**
318 * Effective numerical matrix rank
319 * @return Number of nonnegligible singular values.
320 */
321 int rank();
322
323 private:
324
325 void calculate();
326
327 SVDMatrix A;
328 SVDMatrix U;
329 double *s;
330 unsigned int s_size;
331 SVDMatrix V;
332
333 };
334
335
svd_hypot(double a,double b)336 static double svd_hypot(double a, double b)
337 {
338 double r;
339
340 if (fabs(a) > fabs(b))
341 {
342 r = b/a;
343 r = fabs(a) * sqrt(1+r*r);
344 }
345 else if (b != 0)
346 {
347 r = a/b;
348 r = fabs(b) * sqrt(1+r*r);
349 }
350 else
351 {
352 r = 0.0;
353 }
354 return r;
355 }
356
357
358
calculate()359 void SingularValueDecomposition::calculate()
360 {
361 // Initialize.
362 int m = A.getRows();
363 int n = A.getCols();
364
365 int nu = (m > n) ? m : n;
366 s_size = (m+1 < n) ? m+1 : n;
367 s = new double[s_size];
368 U = SVDMatrix(m, nu);
369 V = SVDMatrix(n, n);
370 double *e = new double[n];
371 double *work = new double[m];
372 bool wantu = true;
373 bool wantv = true;
374
375 // Reduce A to bidiagonal form, storing the diagonal elements
376 // in s and the super-diagonal elements in e.
377
378 int nct = (m-1<n) ? m-1 : n;
379 int nrtx = (n-2<m) ? n-2 : m;
380 int nrt = (nrtx>0) ? nrtx : 0;
381 for (int k = 0; k < 2; k++) {
382 if (k < nct) {
383
384 // Compute the transformation for the k-th column and
385 // place the k-th diagonal in s[k].
386 // Compute 2-norm of k-th column without under/overflow.
387 s[k] = 0;
388 for (int i = k; i < m; i++) {
389 s[k] = svd_hypot(s[k],A(i, k));
390 }
391 if (s[k] != 0.0) {
392 if (A(k, k) < 0.0) {
393 s[k] = -s[k];
394 }
395 for (int i = k; i < m; i++) {
396 A(i, k) /= s[k];
397 }
398 A(k, k) += 1.0;
399 }
400 s[k] = -s[k];
401 }
402 for (int j = k+1; j < n; j++) {
403 if ((k < nct) & (s[k] != 0.0)) {
404
405 // Apply the transformation.
406
407 double t = 0;
408 for (int i = k; i < m; i++) {
409 t += A(i, k) * A(i, j);
410 }
411 t = -t/A(k, k);
412 for (int i = k; i < m; i++) {
413 A(i, j) += t*A(i, k);
414 }
415 }
416
417 // Place the k-th row of A into e for the
418 // subsequent calculation of the row transformation.
419
420 e[j] = A(k, j);
421 }
422 if (wantu & (k < nct)) {
423
424 // Place the transformation in U for subsequent back
425 // multiplication.
426
427 for (int i = k; i < m; i++) {
428 U(i, k) = A(i, k);
429 }
430 }
431 if (k < nrt) {
432
433 // Compute the k-th row transformation and place the
434 // k-th super-diagonal in e[k].
435 // Compute 2-norm without under/overflow.
436 e[k] = 0;
437 for (int i = k+1; i < n; i++) {
438 e[k] = svd_hypot(e[k],e[i]);
439 }
440 if (e[k] != 0.0) {
441 if (e[k+1] < 0.0) {
442 e[k] = -e[k];
443 }
444 for (int i = k+1; i < n; i++) {
445 e[i] /= e[k];
446 }
447 e[k+1] += 1.0;
448 }
449 e[k] = -e[k];
450 if ((k+1 < m) & (e[k] != 0.0)) {
451
452 // Apply the transformation.
453
454 for (int i = k+1; i < m; i++) {
455 work[i] = 0.0;
456 }
457 for (int j = k+1; j < n; j++) {
458 for (int i = k+1; i < m; i++) {
459 work[i] += e[j]*A(i, j);
460 }
461 }
462 for (int j = k+1; j < n; j++) {
463 double t = -e[j]/e[k+1];
464 for (int i = k+1; i < m; i++) {
465 A(i, j) += t*work[i];
466 }
467 }
468 }
469 if (wantv) {
470
471 // Place the transformation in V for subsequent
472 // back multiplication.
473
474 for (int i = k+1; i < n; i++) {
475 V(i, k) = e[i];
476 }
477 }
478 }
479 }
480
481 // Set up the final bidiagonal matrix or order p.
482
483 int p = (n < m+1) ? n : m+1;
484 if (nct < n) {
485 s[nct] = A(nct, nct);
486 }
487 if (m < p) {
488 s[p-1] = 0.0;
489 }
490 if (nrt+1 < p) {
491 e[nrt] = A(nrt, p-1);
492 }
493 e[p-1] = 0.0;
494
495 // If required, generate U.
496
497 if (wantu) {
498 for (int j = nct; j < nu; j++) {
499 for (int i = 0; i < m; i++) {
500 U(i, j) = 0.0;
501 }
502 U(j, j) = 1.0;
503 }
504 for (int k = nct-1; k >= 0; k--) {
505 if (s[k] != 0.0) {
506 for (int j = k+1; j < nu; j++) {
507 double t = 0;
508 for (int i = k; i < m; i++) {
509 t += U(i, k)*U(i, j);
510 }
511 t = -t/U(k, k);
512 for (int i = k; i < m; i++) {
513 U(i, j) += t*U(i, k);
514 }
515 }
516 for (int i = k; i < m; i++ ) {
517 U(i, k) = -U(i, k);
518 }
519 U(k, k) = 1.0 + U(k, k);
520 for (int i = 0; i < k-1; i++) {
521 U(i, k) = 0.0;
522 }
523 } else {
524 for (int i = 0; i < m; i++) {
525 U(i, k) = 0.0;
526 }
527 U(k, k) = 1.0;
528 }
529 }
530 }
531
532 // If required, generate V.
533
534 if (wantv) {
535 for (int k = n-1; k >= 0; k--) {
536 if ((k < nrt) & (e[k] != 0.0)) {
537 for (int j = k+1; j < nu; j++) {
538 double t = 0;
539 for (int i = k+1; i < n; i++) {
540 t += V(i, k)*V(i, j);
541 }
542 t = -t/V(k+1, k);
543 for (int i = k+1; i < n; i++) {
544 V(i, j) += t*V(i, k);
545 }
546 }
547 }
548 for (int i = 0; i < n; i++) {
549 V(i, k) = 0.0;
550 }
551 V(k, k) = 1.0;
552 }
553 }
554
555 // Main iteration loop for the singular values.
556
557 int pp = p-1;
558 //double eps = pow(2.0,-52.0);
559 //double tiny = pow(2.0,-966.0);
560 //let's just calculate these now
561 //a double can be e ± 308.25, so this is safe
562 double eps = 2.22e-16;
563 double tiny = 1.6e-291;
564 while (p > 0) {
565 int k,kase;
566
567 // Here is where a test for too many iterations would go.
568
569 // This section of the program inspects for
570 // negligible elements in the s and e arrays. On
571 // completion the variables kase and k are set as follows.
572
573 // kase = 1 if s(p) and e[k-1] are negligible and k<p
574 // kase = 2 if s(k) is negligible and k<p
575 // kase = 3 if e[k-1] is negligible, k<p, and
576 // s(k), ..., s(p) are not negligible (qr step).
577 // kase = 4 if e(p-1) is negligible (convergence).
578
579 for (k = p-2; k >= -1; k--) {
580 if (k == -1) {
581 break;
582 }
583 if (fabs(e[k]) <=
584 tiny + eps*(fabs(s[k]) + fabs(s[k+1]))) {
585 e[k] = 0.0;
586 break;
587 }
588 }
589 if (k == p-2) {
590 kase = 4;
591 } else {
592 int ks;
593 for (ks = p-1; ks >= k; ks--) {
594 if (ks == k) {
595 break;
596 }
597 double t = (ks != p ? fabs(e[ks]) : 0.) +
598 (ks != k+1 ? fabs(e[ks-1]) : 0.);
599 if (fabs(s[ks]) <= tiny + eps*t) {
600 s[ks] = 0.0;
601 break;
602 }
603 }
604 if (ks == k) {
605 kase = 3;
606 } else if (ks == p-1) {
607 kase = 1;
608 } else {
609 kase = 2;
610 k = ks;
611 }
612 }
613 k++;
614
615 // Perform the task indicated by kase.
616
617 switch (kase) {
618
619 // Deflate negligible s(p).
620
621 case 1: {
622 double f = e[p-2];
623 e[p-2] = 0.0;
624 for (int j = p-2; j >= k; j--) {
625 double t = svd_hypot(s[j],f);
626 double cs = s[j]/t;
627 double sn = f/t;
628 s[j] = t;
629 if (j != k) {
630 f = -sn*e[j-1];
631 e[j-1] = cs*e[j-1];
632 }
633 if (wantv) {
634 for (int i = 0; i < n; i++) {
635 t = cs*V(i, j) + sn*V(i, p-1);
636 V(i, p-1) = -sn*V(i, j) + cs*V(i, p-1);
637 V(i, j) = t;
638 }
639 }
640 }
641 }
642 break;
643
644 // Split at negligible s(k).
645
646 case 2: {
647 double f = e[k-1];
648 e[k-1] = 0.0;
649 for (int j = k; j < p; j++) {
650 double t = svd_hypot(s[j],f);
651 double cs = s[j]/t;
652 double sn = f/t;
653 s[j] = t;
654 f = -sn*e[j];
655 e[j] = cs*e[j];
656 if (wantu) {
657 for (int i = 0; i < m; i++) {
658 t = cs*U(i, j) + sn*U(i, k-1);
659 U(i, k-1) = -sn*U(i, j) + cs*U(i, k-1);
660 U(i, j) = t;
661 }
662 }
663 }
664 }
665 break;
666
667 // Perform one qr step.
668
669 case 3: {
670
671 // Calculate the shift.
672
673 double scale = fabs(s[p-1]);
674 double d = fabs(s[p-2]);
675 if (d>scale) scale=d;
676 d = fabs(e[p-2]);
677 if (d>scale) scale=d;
678 d = fabs(s[k]);
679 if (d>scale) scale=d;
680 d = fabs(e[k]);
681 if (d>scale) scale=d;
682 double sp = s[p-1]/scale;
683 double spm1 = s[p-2]/scale;
684 double epm1 = e[p-2]/scale;
685 double sk = s[k]/scale;
686 double ek = e[k]/scale;
687 double b = ((spm1 + sp)*(spm1 - sp) + epm1*epm1)/2.0;
688 double c = (sp*epm1)*(sp*epm1);
689 double shift = 0.0;
690 if ((b != 0.0) | (c != 0.0)) {
691 shift = sqrt(b*b + c);
692 if (b < 0.0) {
693 shift = -shift;
694 }
695 shift = c/(b + shift);
696 }
697 double f = (sk + sp)*(sk - sp) + shift;
698 double g = sk*ek;
699
700 // Chase zeros.
701
702 for (int j = k; j < p-1; j++) {
703 double t = svd_hypot(f,g);
704 double cs = f/t;
705 double sn = g/t;
706 if (j != k) {
707 e[j-1] = t;
708 }
709 f = cs*s[j] + sn*e[j];
710 e[j] = cs*e[j] - sn*s[j];
711 g = sn*s[j+1];
712 s[j+1] = cs*s[j+1];
713 if (wantv) {
714 for (int i = 0; i < n; i++) {
715 t = cs*V(i, j) + sn*V(i, j+1);
716 V(i, j+1) = -sn*V(i, j) + cs*V(i, j+1);
717 V(i, j) = t;
718 }
719 }
720 t = svd_hypot(f,g);
721 cs = f/t;
722 sn = g/t;
723 s[j] = t;
724 f = cs*e[j] + sn*s[j+1];
725 s[j+1] = -sn*e[j] + cs*s[j+1];
726 g = sn*e[j+1];
727 e[j+1] = cs*e[j+1];
728 if (wantu && (j < m-1)) {
729 for (int i = 0; i < m; i++) {
730 t = cs*U(i, j) + sn*U(i, j+1);
731 U(i, j+1) = -sn*U(i, j) + cs*U(i, j+1);
732 U(i, j) = t;
733 }
734 }
735 }
736 e[p-2] = f;
737 }
738 break;
739
740 // Convergence.
741
742 case 4: {
743
744 // Make the singular values positive.
745
746 if (s[k] <= 0.0) {
747 s[k] = (s[k] < 0.0 ? -s[k] : 0.0);
748 if (wantv) {
749 for (int i = 0; i <= pp; i++) {
750 V(i, k) = -V(i, k);
751 }
752 }
753 }
754
755 // Order the singular values.
756
757 while (k < pp) {
758 if (s[k] >= s[k+1]) {
759 break;
760 }
761 double t = s[k];
762 s[k] = s[k+1];
763 s[k+1] = t;
764 if (wantv && (k < n-1)) {
765 for (int i = 0; i < n; i++) {
766 t = V(i, k+1); V(i, k+1) = V(i, k); V(i, k) = t;
767 }
768 }
769 if (wantu && (k < m-1)) {
770 for (int i = 0; i < m; i++) {
771 t = U(i, k+1); U(i, k+1) = U(i, k); U(i, k) = t;
772 }
773 }
774 k++;
775 }
776 p--;
777 }
778 break;
779 }
780 }
781
782 delete [] e;
783 delete [] work;
784
785 }
786
787
788 /**
789 * Return the left singular vectors
790 * @return U
791 */
getU()792 SVDMatrix &SingularValueDecomposition::getU()
793 {
794 return U;
795 }
796
797 /**
798 * Return the right singular vectors
799 * @return V
800 */
801
getV()802 SVDMatrix &SingularValueDecomposition::getV()
803 {
804 return V;
805 }
806
807 /**
808 * Return the s[0] value
809 */
getS(unsigned int index)810 double SingularValueDecomposition::getS(unsigned int index)
811 {
812 if (index >= s_size)
813 return 0.0;
814 return s[index];
815 }
816
817 /**
818 * Two norm
819 * @return max(S)
820 */
norm2()821 double SingularValueDecomposition::norm2()
822 {
823 return s[0];
824 }
825
826 /**
827 * Two norm condition number
828 * @return max(S)/min(S)
829 */
830
cond()831 double SingularValueDecomposition::cond()
832 {
833 return s[0]/s[2];
834 }
835
836 /**
837 * Effective numerical matrix rank
838 * @return Number of nonnegligible singular values.
839 */
rank()840 int SingularValueDecomposition::rank()
841 {
842 double eps = pow(2.0,-52.0);
843 double tol = 3.0*s[0]*eps;
844 int r = 0;
845 for (int i = 0; i < 3; i++)
846 {
847 if (s[i] > tol)
848 r++;
849 }
850 return r;
851 }
852
853 //########################################################################
854 //# E N D C L A S S SingularValueDecomposition
855 //########################################################################
856
857
858
859
860
861 //#define pxToCm 0.0275
862 #define pxToCm 0.03
863
864
865 //########################################################################
866 //# O U T P U T
867 //########################################################################
868
869 /**
870 * Get the value of a node/attribute pair
871 */
getAttribute(Inkscape::XML::Node * node,char const * attrName)872 static Glib::ustring getAttribute( Inkscape::XML::Node *node, char const *attrName)
873 {
874 Glib::ustring val;
875 char const *valstr = node->attribute(attrName);
876 if (valstr)
877 val = valstr;
878 return val;
879 }
880
881
formatTransform(Geom::Affine & tf)882 static Glib::ustring formatTransform(Geom::Affine &tf)
883 {
884 Glib::ustring str;
885 if (!tf.isIdentity())
886 {
887 StringOutputStream outs;
888 OutputStreamWriter out(outs);
889 out.printf("matrix(%.3f %.3f %.3f %.3f %.3f %.3f)",
890 tf[0], tf[1], tf[2], tf[3], tf[4], tf[5]);
891 str = outs.getString();
892 }
893 return str;
894 }
895
896
897 /**
898 * Get the general transform from SVG pixels to
899 * ODF cm
900 */
getODFTransform(const SPItem * item)901 static Geom::Affine getODFTransform(const SPItem *item)
902 {
903 //### Get SVG-to-ODF transform
904 Geom::Affine tf (item->i2doc_affine());
905 tf = tf * Geom::Affine(Geom::Scale(pxToCm));
906 return tf;
907 }
908
909
910 /**
911 * Get the bounding box of an item, as mapped onto
912 * an ODF document, in cm.
913 */
getODFBoundingBox(const SPItem * item)914 static Geom::OptRect getODFBoundingBox(const SPItem *item)
915 {
916 // TODO: geometric or visual?
917 Geom::OptRect bbox = item->documentVisualBounds();
918 if (bbox) {
919 *bbox *= Geom::Affine(Geom::Scale(pxToCm));
920 }
921 return bbox;
922 }
923
924
925 /**
926 * Get the transform for an item, including parents, but without
927 * root viewBox transformation.
928 */
getODFItemTransform(const SPItem * item)929 static Geom::Affine getODFItemTransform(const SPItem *item)
930 {
931 Geom::Affine itemTransform (item->i2doc_affine() *
932 SP_ACTIVE_DOCUMENT->getRoot()->c2p.inverse());
933 return itemTransform;
934 }
935
936
937 /**
938 * Get some fun facts from the transform
939 */
analyzeTransform(Geom::Affine & tf,double & rotate,double &,double &,double & xscale,double & yscale)940 static void analyzeTransform(Geom::Affine &tf,
941 double &rotate, double &/*xskew*/, double &/*yskew*/,
942 double &xscale, double &yscale)
943 {
944 SVDMatrix mat(2, 2);
945 mat(0, 0) = tf[0];
946 mat(0, 1) = tf[1];
947 mat(1, 0) = tf[2];
948 mat(1, 1) = tf[3];
949
950 SingularValueDecomposition svd(mat);
951
952 SVDMatrix U = svd.getU();
953 SVDMatrix V = svd.getV();
954 SVDMatrix Vt = V.transpose();
955 SVDMatrix UVt = U.multiply(Vt);
956 double s0 = svd.getS(0);
957 double s1 = svd.getS(1);
958 xscale = s0;
959 yscale = s1;
960 rotate = UVt(0,0);
961 }
962
gatherText(Inkscape::XML::Node * node,Glib::ustring & buf)963 static void gatherText(Inkscape::XML::Node *node, Glib::ustring &buf)
964 {
965 if (node->type() == Inkscape::XML::NodeType::TEXT_NODE)
966 {
967 char *s = (char *)node->content();
968 if (s)
969 buf.append(s);
970 }
971
972 for (Inkscape::XML::Node *child = node->firstChild() ;
973 child != nullptr; child = child->next())
974 {
975 gatherText(child, buf);
976 }
977
978 }
979
980
981 /**
982 * FIRST PASS.
983 * Method descends into the repr tree, converting image, style, and gradient info
984 * into forms compatible in ODF.
985 */
preprocess(ZipFile & zf,Inkscape::XML::Node * node)986 void OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node)
987 {
988 Glib::ustring nodeName = node->name();
989 Glib::ustring id = getAttribute(node, "id");
990
991 //### First, check for metadata
992 if (nodeName == "metadata" || nodeName == "svg:metadata")
993 {
994 Inkscape::XML::Node *mchild = node->firstChild() ;
995 if (!mchild || strcmp(mchild->name(), "rdf:RDF"))
996 return;
997 Inkscape::XML::Node *rchild = mchild->firstChild() ;
998 if (!rchild || strcmp(rchild->name(), "cc:Work"))
999 return;
1000 for (Inkscape::XML::Node *cchild = rchild->firstChild() ;
1001 cchild ; cchild = cchild->next())
1002 {
1003 Glib::ustring ccName = cchild->name();
1004 Glib::ustring ccVal;
1005 gatherText(cchild, ccVal);
1006 //g_message("ccName: %s ccVal:%s", ccName.c_str(), ccVal.c_str());
1007 metadata[ccName] = ccVal;
1008 }
1009 return;
1010 }
1011
1012 //Now consider items.
1013 SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(node);
1014 if (!reprobj)
1015 {
1016 return;
1017 }
1018 if (!SP_IS_ITEM(reprobj))
1019 {
1020 return;
1021 }
1022
1023 if (nodeName == "image" || nodeName == "svg:image") {
1024 Glib::ustring href = getAttribute(node, "xlink:href");
1025 if (href.size() > 0 && imageTable.count(href) == 0) {
1026 try {
1027 auto uri = Inkscape::URI(href.c_str(), docBaseUri.c_str());
1028 auto mimetype = uri.getMimeType();
1029
1030 if (mimetype.substr(0, 6) != "image/") {
1031 return;
1032 }
1033
1034 auto ext = mimetype.substr(6);
1035 auto newName = Glib::ustring("Pictures/image") + std::to_string(imageTable.size()) + "." + ext;
1036
1037 imageTable[href] = newName;
1038
1039 auto ze = zf.newEntry(newName.raw(), "");
1040 ze->setUncompressedData(uri.getContents());
1041 ze->finish();
1042 } catch (...) {
1043 g_warning("Could not handle URI '%.100s'", href.c_str());
1044 }
1045 }
1046 }
1047
1048 for (Inkscape::XML::Node *child = node->firstChild() ;
1049 child ; child = child->next())
1050 preprocess(zf, child);
1051 }
1052
1053
1054 /**
1055 * Writes the manifest. Currently it only changes according to the
1056 * file names of images packed into the zip file.
1057 */
writeManifest(ZipFile & zf)1058 bool OdfOutput::writeManifest(ZipFile &zf)
1059 {
1060 BufferOutputStream bouts;
1061 OutputStreamWriter outs(bouts);
1062
1063 time_t tim;
1064 time(&tim);
1065
1066 outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1067 outs.writeString("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n");
1068 outs.writeString("\n");
1069 outs.writeString("\n");
1070 outs.writeString("<!--\n");
1071 outs.writeString("*************************************************************************\n");
1072 outs.writeString(" file: manifest.xml\n");
1073 outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
1074 outs.writeString(" http://www.inkscape.org\n");
1075 outs.writeString("*************************************************************************\n");
1076 outs.writeString("-->\n");
1077 outs.writeString("\n");
1078 outs.writeString("\n");
1079 outs.writeString("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n");
1080 outs.writeString(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n");
1081 outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n");
1082 outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>\n");
1083 outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n");
1084 outs.writeString(" <!--List our images here-->\n");
1085 std::map<Glib::ustring, Glib::ustring>::iterator iter;
1086 for (iter = imageTable.begin() ; iter!=imageTable.end() ; ++iter)
1087 {
1088 Glib::ustring newName = iter->second;
1089
1090 // note: mime subtype was added as file extension in OdfOutput::preprocess
1091 Glib::ustring mimesubtype = Inkscape::IO::get_file_extension(newName);
1092
1093 outs.printf(" <manifest:file-entry manifest:media-type=\"");
1094 outs.printf("image/");
1095 outs.printf("%s", mimesubtype.c_str());
1096 outs.printf("\" manifest:full-path=\"");
1097 outs.writeString(newName.c_str());
1098 outs.printf("\"/>\n");
1099 }
1100 outs.printf("</manifest:manifest>\n");
1101
1102 outs.close();
1103
1104 //Make our entry
1105 ZipEntry *ze = zf.newEntry("META-INF/manifest.xml", "ODF file manifest");
1106 ze->setUncompressedData(bouts.getBuffer());
1107 ze->finish();
1108
1109 return true;
1110 }
1111
1112
1113 /**
1114 * This writes the document meta information to meta.xml
1115 */
writeMeta(ZipFile & zf)1116 bool OdfOutput::writeMeta(ZipFile &zf)
1117 {
1118 BufferOutputStream bouts;
1119 OutputStreamWriter outs(bouts);
1120
1121 time_t tim;
1122 time(&tim);
1123
1124 std::map<Glib::ustring, Glib::ustring>::iterator iter;
1125 Glib::ustring InkscapeVersion = Glib::ustring("Inkscape.org - ") + Inkscape::version_string;
1126 Glib::ustring creator = InkscapeVersion;
1127 iter = metadata.find("dc:creator");
1128 if (iter != metadata.end())
1129 {
1130 creator = iter->second;
1131 }
1132
1133 Glib::ustring date;
1134 Glib::ustring moddate;
1135 char buf [80];
1136 time_t rawtime;
1137 struct tm * timeinfo;
1138 time (&rawtime);
1139 timeinfo = localtime (&rawtime);
1140 strftime (buf,80,"%Y-%m-%d %H:%M:%S",timeinfo);
1141 moddate = Glib::ustring(buf);
1142
1143 iter = metadata.find("dc:date");
1144 if (iter != metadata.end())
1145 {
1146 date = iter->second;
1147 }
1148 else
1149 {
1150 date = moddate;
1151 }
1152
1153 outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1154 outs.writeString("\n");
1155 outs.writeString("<!--\n");
1156 outs.writeString("*************************************************************************\n");
1157 outs.writeString(" file: meta.xml\n");
1158 outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
1159 outs.writeString(" http://www.inkscape.org\n");
1160 outs.writeString("*************************************************************************\n");
1161 outs.writeString("-->\n");
1162 outs.writeString("\n");
1163 outs.writeString("<office:document-meta\n");
1164 outs.writeString("xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
1165 outs.writeString("xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
1166 outs.writeString("xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
1167 outs.writeString("xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
1168 outs.writeString("xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
1169 outs.writeString("xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
1170 outs.writeString("xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
1171 outs.writeString("xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
1172 outs.writeString("office:version=\"1.0\">\n");
1173 outs.writeString("<office:meta>\n");
1174 Glib::ustring tmp = Glib::ustring::compose(" <meta:generator>%1</meta:generator>\n", InkscapeVersion);
1175 tmp += Glib::ustring::compose(" <meta:initial-creator>%1</meta:initial-creator>\n", creator);
1176 tmp += Glib::ustring::compose(" <meta:creation-date>%1</meta:creation-date>\n", date);
1177 tmp += Glib::ustring::compose(" <dc:date>%1</dc:date>\n", moddate);
1178 outs.writeUString(tmp);
1179 for (iter = metadata.begin() ; iter != metadata.end() ; ++iter)
1180 {
1181 Glib::ustring name = iter->first;
1182 Glib::ustring value = iter->second;
1183 if (!name.empty() && !value.empty())
1184 {
1185 tmp = Glib::ustring::compose(" <%1>%2</%3>\n", name, value, name);
1186 outs.writeUString(tmp);
1187 }
1188 }
1189 // outs.writeString(" <meta:editing-cycles>2</meta:editing-cycles>\n");
1190 // outs.writeString(" <meta:editing-duration>PT56S</meta:editing-duration>\n");
1191 // outs.writeString(" <meta:user-defined meta:name=\"Info 1\"/>\n");
1192 // outs.writeString(" <meta:user-defined meta:name=\"Info 2\"/>\n");
1193 // outs.writeString(" <meta:user-defined meta:name=\"Info 3\"/>\n");
1194 // outs.writeString(" <meta:user-defined meta:name=\"Info 4\"/>\n");
1195 // outs.writeString(" <meta:document-statistic meta:object-count=\"2\"/>\n");
1196 outs.writeString("</office:meta>\n");
1197 outs.writeString("</office:document-meta>\n");
1198 outs.close();
1199
1200 //Make our entry
1201 ZipEntry *ze = zf.newEntry("meta.xml", "ODF info file");
1202 ze->setUncompressedData(bouts.getBuffer());
1203 ze->finish();
1204
1205 return true;
1206 }
1207
1208
1209 /**
1210 * Writes an SVG path as an ODF <draw:path> and returns the number of points written
1211 */
1212 static int
writePath(Writer & outs,Geom::PathVector const & pathv,Geom::Affine const & tf,double xoff,double yoff)1213 writePath(Writer &outs, Geom::PathVector const &pathv,
1214 Geom::Affine const &tf, double xoff, double yoff)
1215 {
1216 using Geom::X;
1217 using Geom::Y;
1218
1219 int nrPoints = 0;
1220
1221 // convert the path to only lineto's and cubic curveto's:
1222 Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * tf * Geom::Translate(xoff, yoff) * Geom::Scale(1000.));
1223
1224 for (const auto & pit : pv) {
1225
1226 double destx = pit.initialPoint()[X];
1227 double desty = pit.initialPoint()[Y];
1228 if (fabs(destx)<1.0) destx = 0.0; // Why is this needed? Shouldn't we just round all numbers then?
1229 if (fabs(desty)<1.0) desty = 0.0;
1230 outs.printf("M %.3f %.3f ", destx, desty);
1231 nrPoints++;
1232
1233 for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit) {
1234
1235 if( is_straight_curve(*cit) )
1236 {
1237 double destx = cit->finalPoint()[X];
1238 double desty = cit->finalPoint()[Y];
1239 if (fabs(destx)<1.0) destx = 0.0; // Why is this needed? Shouldn't we just round all numbers then?
1240 if (fabs(desty)<1.0) desty = 0.0;
1241 outs.printf("L %.3f %.3f ", destx, desty);
1242 }
1243 else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
1244 std::vector<Geom::Point> points = cubic->controlPoints();
1245 for (unsigned i = 1; i <= 3; i++) {
1246 if (fabs(points[i][X])<1.0) points[i][X] = 0.0; // Why is this needed? Shouldn't we just round all numbers then?
1247 if (fabs(points[i][Y])<1.0) points[i][Y] = 0.0;
1248 }
1249 outs.printf("C %.3f %.3f %.3f %.3f %.3f %.3f ", points[1][X],points[1][Y], points[2][X],points[2][Y], points[3][X],points[3][Y]);
1250 }
1251 else {
1252 g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used");
1253 }
1254
1255 nrPoints++;
1256 }
1257
1258 if (pit.closed()) {
1259 outs.printf("Z");
1260 }
1261 }
1262
1263 return nrPoints;
1264 }
1265
processStyle(SPItem * item,const Glib::ustring & id,const Glib::ustring & gradientNameFill,const Glib::ustring & gradientNameStroke,Glib::ustring & output)1266 bool OdfOutput::processStyle(SPItem *item, const Glib::ustring &id, const Glib::ustring &gradientNameFill, const Glib::ustring &gradientNameStroke, Glib::ustring& output)
1267 {
1268 output.clear();
1269 if (!item)
1270 {
1271 return false;
1272 }
1273
1274 SPStyle *style = item->style;
1275 if (!style)
1276 {
1277 return false;
1278 }
1279
1280 StyleInfo si;
1281
1282 // FILL
1283 if (style->fill.isColor())
1284 {
1285 guint32 fillCol = style->fill.value.color.toRGBA32( 0 );
1286 char buf[16];
1287 int r = (fillCol >> 24) & 0xff;
1288 int g = (fillCol >> 16) & 0xff;
1289 int b = (fillCol >> 8) & 0xff;
1290 snprintf(buf, 15, "#%02x%02x%02x", r, g, b);
1291 si.fillColor = buf;
1292 si.fill = "solid";
1293 double opacityPercent = 100.0 *
1294 (SP_SCALE24_TO_FLOAT(style->fill_opacity.value));
1295 snprintf(buf, 15, "%.3f%%", opacityPercent);
1296 si.fillOpacity = buf;
1297 }
1298 else if (style->fill.isPaintserver())
1299 {
1300 SPGradient *gradient = SP_GRADIENT(SP_STYLE_FILL_SERVER(style));
1301 if (gradient)
1302 {
1303 si.fill = "gradient";
1304 }
1305 }
1306
1307 // STROKE
1308 if (style->stroke.isColor())
1309 {
1310 guint32 strokeCol = style->stroke.value.color.toRGBA32( 0 );
1311 char buf[16];
1312 int r = (strokeCol >> 24) & 0xff;
1313 int g = (strokeCol >> 16) & 0xff;
1314 int b = (strokeCol >> 8) & 0xff;
1315 snprintf(buf, 15, "#%02x%02x%02x", r, g, b);
1316 si.strokeColor = buf;
1317 snprintf(buf, 15, "%.3fpt", style->stroke_width.value);
1318 si.strokeWidth = buf;
1319 si.stroke = "solid";
1320 double opacityPercent = 100.0 *
1321 (SP_SCALE24_TO_FLOAT(style->stroke_opacity.value));
1322 snprintf(buf, 15, "%.3f%%", opacityPercent);
1323 si.strokeOpacity = buf;
1324 }
1325 else if (style->stroke.isPaintserver())
1326 {
1327 SPGradient *gradient = SP_GRADIENT(SP_STYLE_STROKE_SERVER(style));
1328 if (gradient)
1329 {
1330 si.stroke = "gradient";
1331 }
1332 }
1333
1334 //Look for existing identical style;
1335 bool styleMatch = false;
1336 std::vector<StyleInfo>::iterator iter;
1337 for (iter=styleTable.begin() ; iter!=styleTable.end() ; ++iter)
1338 {
1339 if (si.equals(*iter))
1340 {
1341 //map to existing styleTable entry
1342 Glib::ustring styleName = iter->name;
1343 styleLookupTable[id] = styleName;
1344 styleMatch = true;
1345 break;
1346 }
1347 }
1348
1349 // Don't need a new style
1350 if (styleMatch)
1351 {
1352 return false;
1353 }
1354
1355 Glib::ustring styleName = Glib::ustring::compose("style%1", styleTable.size());
1356 si.name = styleName;
1357 styleTable.push_back(si);
1358 styleLookupTable[id] = styleName;
1359
1360 output = Glib::ustring::compose ("<style:style style:name=\"%1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n", si.name);
1361 output += "<style:graphic-properties";
1362 if (si.fill == "gradient")
1363 {
1364 output += Glib::ustring::compose (" draw:fill=\"gradient\" draw:fill-gradient-name=\"%1\"", gradientNameFill);
1365 }
1366 else
1367 {
1368 output += Glib::ustring(" draw:fill=\"") + si.fill + "\"";
1369 if(si.fill != "none")
1370 {
1371 output += Glib::ustring::compose(" draw:fill-color=\"%1\"", si.fillColor);
1372 }
1373 }
1374 if (si.stroke == "gradient")
1375 {
1376 //does not seem to be supported by Open Office.org
1377 output += Glib::ustring::compose (" draw:stroke=\"gradient\" draw:stroke-gradient-name=\"%1\"", gradientNameStroke);
1378 }
1379 else
1380 {
1381 output += Glib::ustring(" draw:stroke=\"") + si.stroke + "\"";
1382 if (si.stroke != "none")
1383 {
1384 output += Glib::ustring::compose (" svg:stroke-width=\"%1\" svg:stroke-color=\"%2\" ", si.strokeWidth, si.strokeColor);
1385 }
1386 }
1387 output += "/>\n</style:style>\n";
1388
1389 return true;
1390 }
1391
processGradient(SPItem * item,const Glib::ustring & id,Geom::Affine &,Glib::ustring & gradientName,Glib::ustring & output,bool checkFillGradient)1392 bool OdfOutput::processGradient(SPItem *item,
1393 const Glib::ustring &id, Geom::Affine &/*tf*/,
1394 Glib::ustring& gradientName, Glib::ustring& output, bool checkFillGradient)
1395 {
1396 output.clear();
1397 if (!item)
1398 {
1399 return false;
1400 }
1401
1402 SPStyle *style = item->style;
1403 if (!style)
1404 {
1405 return false;
1406 }
1407
1408 if ((checkFillGradient? (!style->fill.isPaintserver()) : (!style->stroke.isPaintserver())))
1409 {
1410 return false;
1411 }
1412
1413 //## Gradient
1414 SPGradient *gradient = SP_GRADIENT((checkFillGradient?(SP_STYLE_FILL_SERVER(style)) :(SP_STYLE_STROKE_SERVER(style))));
1415
1416 if (gradient == nullptr)
1417 {
1418 return false;
1419 }
1420 GradientInfo gi;
1421 SPGradient *grvec = gradient->getVector(FALSE);
1422 for (SPStop *stop = grvec->getFirstStop();
1423 stop ; stop = stop->getNextStop())
1424 {
1425 unsigned long rgba = stop->get_rgba32();
1426 unsigned long rgb = (rgba >> 8) & 0xffffff;
1427 double opacity = (static_cast<double>(rgba & 0xff)) / 256.0;
1428 GradientStop gs(rgb, opacity);
1429 gi.stops.push_back(gs);
1430 }
1431
1432 Glib::ustring gradientName2;
1433 if (SP_IS_LINEARGRADIENT(gradient))
1434 {
1435 gi.style = "linear";
1436 SPLinearGradient *linGrad = SP_LINEARGRADIENT(gradient);
1437 gi.x1 = linGrad->x1.value;
1438 gi.y1 = linGrad->y1.value;
1439 gi.x2 = linGrad->x2.value;
1440 gi.y2 = linGrad->y2.value;
1441 gradientName2 = Glib::ustring::compose("ImportedLinearGradient%1", gradientTable.size());
1442 }
1443 else if (SP_IS_RADIALGRADIENT(gradient))
1444 {
1445 gi.style = "radial";
1446 SPRadialGradient *radGrad = SP_RADIALGRADIENT(gradient);
1447 Geom::OptRect bbox = item->documentVisualBounds();
1448 gi.cx = (radGrad->cx.value-bbox->left())/bbox->width();
1449 gi.cy = (radGrad->cy.value-bbox->top())/bbox->height();
1450 gradientName2 = Glib::ustring::compose("ImportedRadialGradient%1", gradientTable.size());
1451 }
1452 else
1453 {
1454 g_warning("not a supported gradient type");
1455 return false;
1456 }
1457
1458 //Look for existing identical style;
1459 bool gradientMatch = false;
1460 std::vector<GradientInfo>::iterator iter;
1461 for (iter=gradientTable.begin() ; iter!=gradientTable.end() ; ++iter)
1462 {
1463 if (gi.equals(*iter))
1464 {
1465 //map to existing gradientTable entry
1466 gradientName = iter->name;
1467 gradientLookupTable[id] = gradientName;
1468 gradientMatch = true;
1469 break;
1470 }
1471 }
1472
1473 if (gradientMatch)
1474 {
1475 return true;
1476 }
1477
1478 // No match, let us write a new entry
1479 gradientName = gradientName2;
1480 gi.name = gradientName;
1481 gradientTable.push_back(gi);
1482 gradientLookupTable[id] = gradientName;
1483
1484 // int gradientCount = gradientTable.size();
1485 char buf[128];
1486 if (gi.style == "linear")
1487 {
1488 /*
1489 ===================================================================
1490 LINEAR gradient. We need something that looks like this:
1491 <draw:gradient draw:name="Gradient_20_7"
1492 draw:display-name="Gradient 7"
1493 draw:style="linear"
1494 draw:start-color="#008080" draw:end-color="#993366"
1495 draw:start-intensity="100%" draw:end-intensity="100%"
1496 draw:angle="150" draw:border="0%"/>
1497 ===================================================================
1498 */
1499 if (gi.stops.size() < 2)
1500 {
1501 g_warning("Need at least 2 stops for a linear gradient");
1502 return false;
1503 }
1504 output += Glib::ustring::compose("<draw:gradient draw:name=\"%1\"", gi.name);
1505 output += Glib::ustring::compose(" draw:display-name=\"%1\"", gi.name);
1506 output += " draw:style=\"linear\"";
1507 snprintf(buf, 127, " draw:start-color=\"#%06lx\" draw:end-color=\"#%06lx\"", gi.stops[0].rgb, gi.stops[1].rgb);
1508 output += buf;
1509 //TODO: apply maths, to define begin of gradient, taking gradient begin and end, as well as object boundary into account
1510 double angle = (gi.y2-gi.y1);
1511 angle = (angle != 0.) ? (atan((gi.x2-gi.x1)/(gi.y2-gi.y1))* 180. / M_PI) : 90;
1512 angle = (angle < 0)?(180+angle):angle;
1513 angle = angle * 10; //why do we need this: precision?????????????
1514 output += Glib::ustring::compose(" draw:start-intensity=\"%1\" draw:end-intensity=\"%2\" draw:angle=\"%3\"/>\n",
1515 gi.stops[0].opacity * 100.0, gi.stops[1].opacity * 100.0, angle);// draw:border=\"0%%\"
1516 }
1517 else if (gi.style == "radial")
1518 {
1519 /*
1520 ===================================================================
1521 RADIAL gradient. We need something that looks like this:
1522 <!-- radial gradient, light gray to white, centered, 0% border -->
1523 <draw:gradient draw:name="radial_20_borderless"
1524 draw:display-name="radial borderless"
1525 draw:style="radial"
1526 draw:cx="50%" draw:cy="50%"
1527 draw:start-color="#999999" draw:end-color="#ffffff"
1528 draw:border="0%"/>
1529 ===================================================================
1530 */
1531 if (gi.stops.size() < 2)
1532 {
1533 g_warning("Need at least 2 stops for a radial gradient");
1534 return false;
1535 }
1536 output += Glib::ustring::compose("<draw:gradient draw:name=\"%1\" draw:display-name=\"%1\" ", gi.name);
1537 snprintf(buf, 127, "draw:cx=\"%05.3f\" draw:cy=\"%05.3f\" ", gi.cx*100, gi.cy*100);
1538 output += Glib::ustring("draw:style=\"radial\" ") + buf;
1539 snprintf(buf, 127, "draw:start-color=\"#%06lx\" draw:end-color=\"#%06lx\" ", gi.stops[0].rgb, gi.stops[1].rgb);
1540 output += buf;
1541 snprintf(buf, 127, "draw:start-intensity=\"%f%%\" draw:end-intensity=\"%f%%\" ", gi.stops[0].opacity*100.0, gi.stops[1].opacity*100.0);
1542 output += buf;
1543 output += "/>\n";//draw:border=\"0%\"
1544 }
1545 else
1546 {
1547 g_warning("unsupported gradient style '%s'", gi.style.c_str());
1548 return false;
1549 }
1550 return true;
1551 }
1552
1553
1554 /**
1555 * SECOND PASS.
1556 * This is the main SPObject tree output to ODF.
1557 */
writeTree(Writer & couts,Writer & souts,Inkscape::XML::Node * node)1558 bool OdfOutput::writeTree(Writer &couts, Writer &souts,
1559 Inkscape::XML::Node *node)
1560 {
1561 //# Get the SPItem, if applicable
1562 SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(node);
1563 if (!reprobj)
1564 {
1565 return true;
1566 }
1567 if (!SP_IS_ITEM(reprobj))
1568 {
1569 return true;
1570 }
1571 SPItem *item = SP_ITEM(reprobj);
1572
1573 Glib::ustring nodeName = node->name();
1574 Glib::ustring id = getAttribute(node, "id");
1575 Geom::Affine tf = getODFTransform(item);//Get SVG-to-ODF transform
1576 Geom::OptRect bbox = getODFBoundingBox(item);//Get ODF bounding box params for item
1577 if (!bbox) {
1578 return true;
1579 }
1580
1581 double bbox_x = bbox->min()[Geom::X];
1582 double bbox_y = bbox->min()[Geom::Y];
1583 double bbox_width = (*bbox)[Geom::X].extent();
1584 double bbox_height = (*bbox)[Geom::Y].extent();
1585
1586 double rotate;
1587 double xskew;
1588 double yskew;
1589 double xscale;
1590 double yscale;
1591 analyzeTransform(tf, rotate, xskew, yskew, xscale, yscale);
1592
1593 //# Do our stuff
1594
1595 if (nodeName == "svg" || nodeName == "svg:svg")
1596 {
1597 //# Iterate through the children
1598 for (Inkscape::XML::Node *child = node->firstChild() ;
1599 child ; child = child->next())
1600 {
1601 if (!writeTree(couts, souts, child))
1602 {
1603 return false;
1604 }
1605 }
1606 return true;
1607 }
1608 else if (nodeName == "g" || nodeName == "svg:g")
1609 {
1610 if (!id.empty())
1611 {
1612 couts.printf("<draw:g id=\"%s\">\n", id.c_str());
1613 }
1614 else
1615 {
1616 couts.printf("<draw:g>\n");
1617 }
1618 //# Iterate through the children
1619 for (Inkscape::XML::Node *child = node->firstChild() ;
1620 child ; child = child->next())
1621 {
1622 if (!writeTree(couts, souts, child))
1623 {
1624 return false;
1625 }
1626 }
1627 if (!id.empty())
1628 {
1629 couts.printf("</draw:g> <!-- id=\"%s\" -->\n", id.c_str());
1630 }
1631 else
1632 {
1633 couts.printf("</draw:g>\n");
1634 }
1635 return true;
1636 }
1637
1638 //# GRADIENT
1639 Glib::ustring gradientNameFill;
1640 Glib::ustring gradientNameStroke;
1641 Glib::ustring outputFill;
1642 Glib::ustring outputStroke;
1643 Glib::ustring outputStyle;
1644
1645 processGradient(item, id, tf, gradientNameFill, outputFill, true);
1646 processGradient(item, id, tf, gradientNameStroke, outputStroke, false);
1647 souts.writeUString(outputFill);
1648 souts.writeUString(outputStroke);
1649
1650 //# STYLE
1651 processStyle(item, id, gradientNameFill, gradientNameStroke, outputStyle);
1652 souts.writeUString(outputStyle);
1653
1654 //# ITEM DATA
1655 if (nodeName == "image" || nodeName == "svg:image")
1656 {
1657 if (!SP_IS_IMAGE(item))
1658 {
1659 g_warning("<image> is not an SPImage.");
1660 return false;
1661 }
1662
1663 SPImage *img = SP_IMAGE(item);
1664 double ix = img->x.value;
1665 double iy = img->y.value;
1666 double iwidth = img->width.value;
1667 double iheight = img->height.value;
1668
1669 Geom::Point ibbox_min = Geom::Point(ix, iy) * tf;
1670 ix = ibbox_min.x();
1671 iy = ibbox_min.y();
1672 iwidth = xscale * iwidth;
1673 iheight = yscale * iheight;
1674
1675 Geom::Affine itemTransform = getODFItemTransform(item);
1676
1677 Glib::ustring itemTransformString = formatTransform(itemTransform);
1678
1679 Glib::ustring href = getAttribute(node, "xlink:href");
1680 std::map<Glib::ustring, Glib::ustring>::iterator iter = imageTable.find(href);
1681 if (iter == imageTable.end())
1682 {
1683 g_warning("image '%s' not in table", href.c_str());
1684 return false;
1685 }
1686 Glib::ustring newName = iter->second;
1687
1688 couts.printf("<draw:frame ");
1689 if (!id.empty())
1690 {
1691 couts.printf("id=\"%s\" ", id.c_str());
1692 }
1693 couts.printf("draw:style-name=\"gr1\" draw:text-style-name=\"P1\" draw:layer=\"layout\" ");
1694 //no x or y. make them the translate transform, last one
1695 couts.printf("svg:width=\"%.3fcm\" svg:height=\"%.3fcm\" ",
1696 iwidth, iheight);
1697 if (!itemTransformString.empty())
1698 {
1699 couts.printf("draw:transform=\"%s translate(%.3fcm, %.3fcm)\" ",
1700 itemTransformString.c_str(), ix, iy);
1701 }
1702 else
1703 {
1704 couts.printf("draw:transform=\"translate(%.3fcm, %.3fcm)\" ", ix, iy);
1705 }
1706
1707 couts.writeString(">\n");
1708 couts.printf(" <draw:image xlink:href=\"%s\" xlink:type=\"simple\"\n",
1709 newName.c_str());
1710 couts.writeString(" xlink:show=\"embed\" xlink:actuate=\"onLoad\">\n");
1711 couts.writeString(" <text:p/>\n");
1712 couts.writeString(" </draw:image>\n");
1713 couts.writeString("</draw:frame>\n");
1714 return true;
1715 }
1716
1717 std::unique_ptr<SPCurve> curve;
1718
1719 if (auto shape = dynamic_cast<SPShape const *>(item)) {
1720 curve = SPCurve::copy(shape->curve());
1721 } else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1722 curve = te_get_layout(item)->convertToCurves();
1723 }
1724
1725 if (curve)
1726 {
1727 //### Default <path> output
1728 couts.writeString("<draw:path ");
1729 if (!id.empty())
1730 {
1731 couts.printf("id=\"%s\" ", id.c_str());
1732 }
1733
1734 std::map<Glib::ustring, Glib::ustring>::iterator siter;
1735 siter = styleLookupTable.find(id);
1736 if (siter != styleLookupTable.end())
1737 {
1738 Glib::ustring styleName = siter->second;
1739 couts.printf("draw:style-name=\"%s\" ", styleName.c_str());
1740 }
1741
1742 couts.printf("draw:layer=\"layout\" svg:x=\"%.3fcm\" svg:y=\"%.3fcm\" ",
1743 bbox_x, bbox_y);
1744 couts.printf("svg:width=\"%.3fcm\" svg:height=\"%.3fcm\" ",
1745 bbox_width, bbox_height);
1746 couts.printf("svg:viewBox=\"0.0 0.0 %.3f %.3f\"",
1747 bbox_width * 1000.0, bbox_height * 1000.0);
1748
1749 couts.printf(" svg:d=\"");
1750 int nrPoints = writePath(couts, curve->get_pathvector(),
1751 tf, bbox_x, bbox_y);
1752 couts.writeString("\"");
1753
1754 couts.writeString(">\n");
1755 couts.printf(" <!-- %d nodes -->\n", nrPoints);
1756 couts.writeString("</draw:path>\n\n");
1757 }
1758
1759 return true;
1760 }
1761
1762
1763 /**
1764 * Write the header for the content.xml file
1765 */
writeStyleHeader(Writer & outs)1766 bool OdfOutput::writeStyleHeader(Writer &outs)
1767 {
1768 time_t tim;
1769 time(&tim);
1770
1771 outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1772 outs.writeString("\n");
1773 outs.writeString("<!--\n");
1774 outs.writeString("*************************************************************************\n");
1775 outs.writeString(" file: styles.xml\n");
1776 outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
1777 outs.writeString(" http://www.inkscape.org\n");
1778 outs.writeString("*************************************************************************\n");
1779 outs.writeString("-->\n");
1780 outs.writeString("\n");
1781 outs.writeString("<office:document-styles\n");
1782 outs.writeString(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
1783 outs.writeString(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
1784 outs.writeString(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
1785 outs.writeString(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
1786 outs.writeString(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
1787 outs.writeString(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
1788 outs.writeString(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
1789 outs.writeString(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
1790 outs.writeString(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
1791 outs.writeString(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
1792 outs.writeString(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
1793 outs.writeString(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
1794 outs.writeString(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
1795 outs.writeString(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
1796 outs.writeString(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
1797 outs.writeString(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
1798 outs.writeString(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
1799 outs.writeString(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
1800 outs.writeString(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
1801 outs.writeString(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
1802 outs.writeString(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
1803 outs.writeString(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
1804 outs.writeString(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
1805 outs.writeString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
1806 outs.writeString(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
1807 outs.writeString(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
1808 outs.writeString(" office:version=\"1.0\">\n");
1809 outs.writeString("\n");
1810 outs.writeString("<!--\n");
1811 outs.writeString("*************************************************************************\n");
1812 outs.writeString(" S T Y L E S\n");
1813 outs.writeString(" Style entries have been pulled from the svg style and\n");
1814 outs.writeString(" representation attributes in the SVG tree. The tree elements\n");
1815 outs.writeString(" then refer to them by name, in the ODF manner\n");
1816 outs.writeString("*************************************************************************\n");
1817 outs.writeString("-->\n");
1818 outs.writeString("\n");
1819 outs.writeString("<office:styles>\n");
1820 outs.writeString("\n");
1821
1822 return true;
1823 }
1824
1825
1826 /**
1827 * Write the footer for the style.xml file
1828 */
writeStyleFooter(Writer & outs)1829 bool OdfOutput::writeStyleFooter(Writer &outs)
1830 {
1831 outs.writeString("\n");
1832 outs.writeString("</office:styles>\n");
1833 outs.writeString("\n");
1834 outs.writeString("<office:automatic-styles>\n");
1835 outs.writeString("<!-- ####### 'Standard' styles ####### -->\n");
1836 outs.writeString("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n");
1837 outs.writeString("<style:style style:name=\"standard\" style:family=\"graphic\">\n");
1838
1839 ///TODO: add default document style here
1840
1841 outs.writeString("</style:style>\n");
1842 outs.writeString("<style:style style:name=\"gr1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
1843 outs.writeString(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"none\"\n");
1844 outs.writeString(" draw:textarea-horizontal-align=\"center\"\n");
1845 outs.writeString(" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\"\n");
1846 outs.writeString(" draw:luminance=\"0%\" draw:contrast=\"0%\" draw:gamma=\"100%\" draw:red=\"0%\"\n");
1847 outs.writeString(" draw:green=\"0%\" draw:blue=\"0%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\"\n");
1848 outs.writeString(" draw:image-opacity=\"100%\" style:mirror=\"none\"/>\n");
1849 outs.writeString("</style:style>\n");
1850 outs.writeString("<style:style style:name=\"P1\" style:family=\"paragraph\">\n");
1851 outs.writeString(" <style:paragraph-properties fo:text-align=\"center\"/>\n");
1852 outs.writeString("</style:style>\n");
1853 outs.writeString("</office:automatic-styles>\n");
1854 outs.writeString("\n");
1855 outs.writeString("<office:master-styles>\n");
1856 outs.writeString("<draw:layer-set>\n");
1857 outs.writeString(" <draw:layer draw:name=\"layout\"/>\n");
1858 outs.writeString(" <draw:layer draw:name=\"background\"/>\n");
1859 outs.writeString(" <draw:layer draw:name=\"backgroundobjects\"/>\n");
1860 outs.writeString(" <draw:layer draw:name=\"controls\"/>\n");
1861 outs.writeString(" <draw:layer draw:name=\"measurelines\"/>\n");
1862 outs.writeString("</draw:layer-set>\n");
1863 outs.writeString("\n");
1864 outs.writeString("<style:master-page style:name=\"Default\"\n");
1865 outs.writeString(" style:page-master-name=\"PM1\" draw:style-name=\"dp1\"/>\n");
1866 outs.writeString("</office:master-styles>\n");
1867 outs.writeString("\n");
1868 outs.writeString("</office:document-styles>\n");
1869
1870 return true;
1871 }
1872
1873
1874 /**
1875 * Write the header for the content.xml file
1876 */
writeContentHeader(Writer & outs)1877 bool OdfOutput::writeContentHeader(Writer &outs)
1878 {
1879 time_t tim;
1880 time(&tim);
1881
1882 outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1883 outs.writeString("\n");
1884 outs.writeString("<!--\n");
1885 outs.writeString("*************************************************************************\n");
1886 outs.writeString(" file: content.xml\n");
1887 outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
1888 outs.writeString(" http://www.inkscape.org\n");
1889 outs.writeString("*************************************************************************\n");
1890 outs.writeString("-->\n");
1891 outs.writeString("\n");
1892 outs.writeString("<office:document-content\n");
1893 outs.writeString(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
1894 outs.writeString(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
1895 outs.writeString(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
1896 outs.writeString(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
1897 outs.writeString(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
1898 outs.writeString(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
1899 outs.writeString(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
1900 outs.writeString(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
1901 outs.writeString(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
1902 outs.writeString(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
1903 outs.writeString(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
1904 outs.writeString(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
1905 outs.writeString(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
1906 outs.writeString(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
1907 outs.writeString(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
1908 outs.writeString(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
1909 outs.writeString(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
1910 outs.writeString(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
1911 outs.writeString(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
1912 outs.writeString(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
1913 outs.writeString(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
1914 outs.writeString(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
1915 outs.writeString(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
1916 outs.writeString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
1917 outs.writeString(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
1918 outs.writeString(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
1919 outs.writeString(" office:version=\"1.0\">\n");
1920 outs.writeString("<office:scripts/>\n");
1921 outs.writeString("\n");
1922 outs.writeString("<!--\n");
1923 outs.writeString("*************************************************************************\n");
1924 outs.writeString(" D R A W I N G\n");
1925 outs.writeString(" This section is the heart of SVG-ODF conversion. We are\n");
1926 outs.writeString(" starting with simple conversions, and will slowly evolve\n");
1927 outs.writeString(" into a 'smarter' translation as time progresses. Any help\n");
1928 outs.writeString(" in improving .odg export is welcome.\n");
1929 outs.writeString("*************************************************************************\n");
1930 outs.writeString("-->\n");
1931 outs.writeString("\n");
1932 outs.writeString("<office:body>\n");
1933 outs.writeString("<office:drawing>\n");
1934 outs.writeString("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\"\n");
1935 outs.writeString(" draw:master-page-name=\"Default\">\n");
1936 outs.writeString("\n");
1937 return true;
1938 }
1939
1940
1941 /**
1942 * Write the footer for the content.xml file
1943 */
writeContentFooter(Writer & outs)1944 bool OdfOutput::writeContentFooter(Writer &outs)
1945 {
1946 outs.writeString("\n");
1947 outs.writeString("</draw:page>\n");
1948 outs.writeString("</office:drawing>\n");
1949 outs.writeString("\n");
1950 outs.writeString("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n");
1951 outs.writeString("\n");
1952 outs.writeString("</office:body>\n");
1953 outs.writeString("</office:document-content>\n");
1954 return true;
1955 }
1956
1957
1958 /**
1959 * Write the content.xml file. Writes the namesspace headers, then
1960 * calls writeTree().
1961 */
writeContent(ZipFile & zf,Inkscape::XML::Node * node)1962 bool OdfOutput::writeContent(ZipFile &zf, Inkscape::XML::Node *node)
1963 {
1964 //Content.xml stream
1965 BufferOutputStream cbouts;
1966 OutputStreamWriter couts(cbouts);
1967
1968 if (!writeContentHeader(couts))
1969 {
1970 return false;
1971 }
1972
1973 //Style.xml stream
1974 BufferOutputStream sbouts;
1975 OutputStreamWriter souts(sbouts);
1976
1977 if (!writeStyleHeader(souts))
1978 {
1979 return false;
1980 }
1981
1982 //# Descend into the tree, doing all of our conversions
1983 //# to both files at the same time
1984 char *oldlocale = g_strdup (setlocale (LC_NUMERIC, nullptr));
1985 setlocale (LC_NUMERIC, "C");
1986 if (!writeTree(couts, souts, node))
1987 {
1988 g_warning("Failed to convert SVG tree");
1989 setlocale (LC_NUMERIC, oldlocale);
1990 g_free (oldlocale);
1991 return false;
1992 }
1993 setlocale (LC_NUMERIC, oldlocale);
1994 g_free (oldlocale);
1995
1996 //# Finish content file
1997 if (!writeContentFooter(couts))
1998 {
1999 return false;
2000 }
2001
2002 ZipEntry *ze = zf.newEntry("content.xml", "ODF master content file");
2003 ze->setUncompressedData(cbouts.getBuffer());
2004 ze->finish();
2005
2006 //# Finish style file
2007 if (!writeStyleFooter(souts))
2008 {
2009 return false;
2010 }
2011
2012 ze = zf.newEntry("styles.xml", "ODF style file");
2013 ze->setUncompressedData(sbouts.getBuffer());
2014 ze->finish();
2015
2016 return true;
2017 }
2018
2019
2020 /**
2021 * Resets class to its pristine condition, ready to use again
2022 */
reset()2023 void OdfOutput::reset()
2024 {
2025 metadata.clear();
2026 styleTable.clear();
2027 styleLookupTable.clear();
2028 gradientTable.clear();
2029 gradientLookupTable.clear();
2030 imageTable.clear();
2031 }
2032
2033
2034 /**
2035 * Descends into the SVG tree, mapping things to ODF when appropriate
2036 */
save(Inkscape::Extension::Output *,SPDocument * doc,gchar const * filename)2037 void OdfOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar const *filename)
2038 {
2039 if (doc != SP_ACTIVE_DOCUMENT) {
2040 g_warning("OdfOutput can only save the active document");
2041 return;
2042 }
2043
2044 reset();
2045
2046 docBaseUri = Inkscape::URI::from_dirname(doc->getDocumentBase()).str();
2047
2048 ZipFile zf;
2049 preprocess(zf, doc->getReprRoot());
2050
2051 if (!writeManifest(zf))
2052 {
2053 g_warning("Failed to write manifest");
2054 return;
2055 }
2056
2057 if (!writeContent(zf, doc->getReprRoot()))
2058 {
2059 g_warning("Failed to write content");
2060 return;
2061 }
2062
2063 if (!writeMeta(zf))
2064 {
2065 g_warning("Failed to write metafile");
2066 return;
2067 }
2068
2069 if (!zf.writeFile(filename))
2070 {
2071 return;
2072 }
2073 }
2074
2075
2076 /**
2077 * This is the definition of PovRay output. This function just
2078 * calls the extension system with the memory allocated XML that
2079 * describes the data.
2080 */
init()2081 void OdfOutput::init()
2082 {
2083 // clang-format off
2084 Inkscape::Extension::build_from_mem(
2085 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
2086 "<name>" N_("OpenDocument Drawing Output") "</name>\n"
2087 "<id>org.inkscape.output.odf</id>\n"
2088 "<output>\n"
2089 "<extension>.odg</extension>\n"
2090 "<mimetype>text/x-povray-script</mimetype>\n"
2091 "<filetypename>" N_("OpenDocument drawing (*.odg)") "</filetypename>\n"
2092 "<filetypetooltip>" N_("OpenDocument drawing file") "</filetypetooltip>\n"
2093 "</output>\n"
2094 "</inkscape-extension>",
2095 new OdfOutput());
2096 // clang-format on
2097 }
2098
2099 /**
2100 * Make sure that we are in the database
2101 */
check(Inkscape::Extension::Extension *)2102 bool OdfOutput::check (Inkscape::Extension::Extension */*module*/)
2103 {
2104 /* We don't need a Key
2105 if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV))
2106 return FALSE;
2107 */
2108
2109 return TRUE;
2110 }
2111
2112 } //namespace Internal
2113 } //namespace Extension
2114 } //namespace Inkscape
2115
2116
2117 //########################################################################
2118 //# E N D O F F I L E
2119 //########################################################################
2120
2121 /*
2122 Local Variables:
2123 mode:c++
2124 c-file-style:"stroustrup"
2125 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2126 indent-tabs-mode:nil
2127 fill-column:99
2128 End:
2129 */
2130 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
2131