1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <string_view>
23
24 #include <pdfiprocessor.hxx>
25 #include <xmlemitter.hxx>
26 #include <pdfihelper.hxx>
27 #include <imagecontainer.hxx>
28 #include "style.hxx"
29 #include "writertreevisiting.hxx"
30 #include <genericelements.hxx>
31
32 #include <basegfx/polygon/b2dpolypolygontools.hxx>
33 #include <osl/diagnose.h>
34 #include <com/sun/star/i18n/CharacterClassification.hpp>
35 #include <com/sun/star/i18n/DirectionProperty.hpp>
36 #include <comphelper/string.hxx>
37
38 using namespace ::com::sun::star;
39 using namespace ::com::sun::star::lang;
40 using namespace ::com::sun::star::i18n;
41 using namespace ::com::sun::star::uno;
42
43 namespace pdfi
44 {
45
GetBreakIterator()46 const Reference<XBreakIterator>& WriterXmlOptimizer::GetBreakIterator()
47 {
48 if (!mxBreakIter.is())
49 {
50 mxBreakIter = BreakIterator::create(m_rProcessor.m_xContext);
51 }
52 return mxBreakIter;
53 }
54
GetCharacterClassification()55 const Reference< XCharacterClassification >& WriterXmlEmitter::GetCharacterClassification()
56 {
57 if ( !mxCharClass.is() )
58 {
59 Reference< XComponentContext > xContext( m_rEmitContext.m_xContext, uno::UNO_SET_THROW );
60 mxCharClass = CharacterClassification::create(xContext);
61 }
62 return mxCharClass;
63 }
64
visit(HyperlinkElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)65 void WriterXmlEmitter::visit( HyperlinkElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
66 {
67 if( elem.Children.empty() )
68 return;
69
70 const char* pType = dynamic_cast<DrawElement*>(elem.Children.front().get()) ? "draw:a" : "text:a";
71
72 PropertyMap aProps;
73 aProps[ u"xlink:type"_ustr ] = "simple";
74 aProps[ u"xlink:href"_ustr ] = elem.URI;
75 aProps[ u"office:target-frame-name"_ustr ] = "_blank";
76 aProps[ u"xlink:show"_ustr ] = "new";
77
78 m_rEmitContext.rEmitter.beginTag( pType, aProps );
79 auto this_it = elem.Children.begin();
80 while( this_it != elem.Children.end() && this_it->get() != &elem )
81 {
82 (*this_it)->visitedBy( *this, this_it );
83 ++this_it;
84 }
85 m_rEmitContext.rEmitter.endTag( pType );
86 }
87
visit(TextElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)88 void WriterXmlEmitter::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
89 {
90 if( elem.Text.isEmpty() )
91 return;
92
93 PropertyMap aProps = {};
94 const sal_Unicode strSpace = 0x0020;
95 const sal_Unicode strNbSpace = 0x00A0;
96 const sal_Unicode tabSpace = 0x0009;
97
98 if( elem.StyleId != -1 )
99 {
100 aProps[ u"text:style-name"_ustr ] =
101 m_rEmitContext.rStyles.getStyleName( elem.StyleId );
102 }
103
104 OUString str(elem.Text.toString());
105
106 // Check for RTL
107 bool isRTL = false;
108 Reference< i18n::XCharacterClassification > xCC( GetCharacterClassification() );
109 if( xCC.is() )
110 {
111 for(int i=1; i< elem.Text.getLength(); i++)
112 {
113 i18n::DirectionProperty nType = static_cast<i18n::DirectionProperty>(xCC->getCharacterDirection( str, i ));
114 if ( nType == i18n::DirectionProperty_RIGHT_TO_LEFT ||
115 nType == i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC ||
116 nType == i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING ||
117 nType == i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE
118 )
119 isRTL = true;
120 }
121 }
122
123 if (isRTL) // If so, reverse string
124 {
125 // First, produce mirrored-image for each code point which has the Bidi_Mirrored property.
126 str = PDFIProcessor::SubstituteBidiMirrored(str);
127 // Then, reverse the code points in the string, in backward order.
128 str = ::comphelper::string::reverseCodePoints(str);
129 }
130
131 m_rEmitContext.rEmitter.beginTag( "text:span", aProps );
132
133 sal_Unicode strToken;
134 for (int i = 0; i < elem.Text.getLength(); i++)
135 {
136 strToken = str[i];
137 if (strToken == strSpace || strToken == strNbSpace)
138 {
139 aProps[u"text:c"_ustr] = "1";
140 m_rEmitContext.rEmitter.beginTag("text:s", aProps);
141 m_rEmitContext.rEmitter.endTag("text:s");
142 }
143 else if (strToken == tabSpace)
144 {
145 m_rEmitContext.rEmitter.beginTag("text:tab", aProps);
146 m_rEmitContext.rEmitter.endTag("text:tab");
147 }
148 else
149 m_rEmitContext.rEmitter.write(OUString(strToken));
150 }
151
152 auto this_it = elem.Children.begin();
153 while( this_it != elem.Children.end() && this_it->get() != &elem )
154 {
155 (*this_it)->visitedBy( *this, this_it );
156 ++this_it;
157 }
158
159 m_rEmitContext.rEmitter.endTag( "text:span" );
160 }
161
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)162 void WriterXmlEmitter::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
163 {
164 PropertyMap aProps;
165 if( elem.StyleId != -1 )
166 {
167 aProps[ u"text:style-name"_ustr ] = m_rEmitContext.rStyles.getStyleName( elem.StyleId );
168 }
169 const char* pTagType = "text:p";
170 if( elem.Type == ParagraphElement::Headline )
171 pTagType = "text:h";
172 m_rEmitContext.rEmitter.beginTag( pTagType, aProps );
173
174 auto this_it = elem.Children.begin();
175 while( this_it != elem.Children.end() && this_it->get() != &elem )
176 {
177 (*this_it)->visitedBy( *this, this_it );
178 ++this_it;
179 }
180
181 m_rEmitContext.rEmitter.endTag( pTagType );
182 }
183
fillFrameProps(DrawElement & rElem,PropertyMap & rProps,const EmitContext & rEmitContext)184 void WriterXmlEmitter::fillFrameProps( DrawElement& rElem,
185 PropertyMap& rProps,
186 const EmitContext& rEmitContext )
187 {
188 double rel_x = rElem.x, rel_y = rElem.y;
189
190 // find anchor type by recursing though parents
191 Element* pAnchor = &rElem;
192 ParagraphElement* pParaElt = nullptr;
193 PageElement* pPage = nullptr;
194 while ((pAnchor = pAnchor->Parent))
195 {
196 if ((pParaElt = dynamic_cast<ParagraphElement*>(pAnchor)))
197 break;
198 if ((pPage = dynamic_cast<PageElement*>(pAnchor)))
199 break;
200 }
201 if( pAnchor )
202 {
203 if (pParaElt)
204 {
205 rProps[ u"text:anchor-type"_ustr ] = rElem.isCharacter
206 ? std::u16string_view(u"character") : std::u16string_view(u"paragraph");
207 }
208 else
209 {
210 assert(pPage); // guaranteed by the while loop above
211 rProps[ u"text:anchor-type"_ustr ] = "page";
212 rProps[ u"text:anchor-page-number"_ustr ] = OUString::number(pPage->PageNumber);
213 }
214 rel_x -= pAnchor->x;
215 rel_y -= pAnchor->y;
216 }
217
218 rProps[ u"draw:z-index"_ustr ] = OUString::number( rElem.ZOrder );
219 rProps[ u"draw:style-name"_ustr] = rEmitContext.rStyles.getStyleName( rElem.StyleId );
220 rProps[ u"svg:width"_ustr ] = convertPixelToUnitString( rElem.w );
221 rProps[ u"svg:height"_ustr ] = convertPixelToUnitString( rElem.h );
222
223 const GraphicsContext& rGC =
224 rEmitContext.rProcessor.getGraphicsContext( rElem.GCId );
225 if( rGC.Transformation.isIdentity() )
226 {
227 if( !rElem.isCharacter )
228 {
229 rProps[ u"svg:x"_ustr ] = convertPixelToUnitString( rel_x );
230 rProps[ u"svg:y"_ustr ] = convertPixelToUnitString( rel_y );
231 }
232 }
233 else
234 {
235 basegfx::B2DTuple aScale, aTranslation;
236 double fRotate, fShearX;
237
238 rGC.Transformation.decompose( aScale, aTranslation, fRotate, fShearX );
239
240 OUStringBuffer aBuf( 256 );
241
242 // TODO(F2): general transformation case missing; if implemented, note
243 // that ODF rotation is oriented the other way
244
245 // build transformation string
246 if (rElem.MirrorVertical)
247 {
248 // At some point, rElem.h may start arriving positive,
249 // so use robust adjusting math
250 rel_y -= std::abs(rElem.h);
251 if (!aBuf.isEmpty())
252 aBuf.append(' ');
253 aBuf.append("scale( 1.0 -1.0 )");
254 }
255 if( fShearX != 0.0 )
256 {
257 aBuf.append( "skewX( " + OUString::number(fShearX) + " )" );
258 }
259 if( fRotate != 0.0 )
260 {
261 if( !aBuf.isEmpty() )
262 aBuf.append( ' ' );
263 aBuf.append( "rotate( " + OUString::number(-fRotate) + " )" );
264
265 }
266 if( ! rElem.isCharacter )
267 {
268 if( !aBuf.isEmpty() )
269 aBuf.append( ' ' );
270 aBuf.append( "translate( "
271 + convertPixelToUnitString( rel_x )
272 + " "
273 + convertPixelToUnitString( rel_y )
274 + " )" );
275 }
276
277 rProps[ u"draw:transform"_ustr ] = aBuf.makeStringAndClear();
278 }
279 }
280
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)281 void WriterXmlEmitter::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
282 {
283 if( elem.Children.empty() )
284 return;
285
286 bool bTextBox = (dynamic_cast<ParagraphElement*>(elem.Children.front().get()) != nullptr);
287 PropertyMap aFrameProps;
288 fillFrameProps( elem, aFrameProps, m_rEmitContext );
289 m_rEmitContext.rEmitter.beginTag( "draw:frame", aFrameProps );
290 if( bTextBox )
291 m_rEmitContext.rEmitter.beginTag( "draw:text-box", PropertyMap() );
292
293 auto this_it = elem.Children.begin();
294 while( this_it != elem.Children.end() && this_it->get() != &elem )
295 {
296 (*this_it)->visitedBy( *this, this_it );
297 ++this_it;
298 }
299
300 if( bTextBox )
301 m_rEmitContext.rEmitter.endTag( "draw:text-box" );
302 m_rEmitContext.rEmitter.endTag( "draw:frame" );
303 }
304
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)305 void WriterXmlEmitter::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
306 {
307 elem.updateGeometry();
308 /* note:
309 * aw recommends using 100dth of mm in all respects since the xml import
310 * (a) is buggy (see issue 37213)
311 * (b) is optimized for 100dth of mm and does not scale itself then,
312 * this does not gain us speed but makes for smaller rounding errors since
313 * the xml importer coordinates are integer based
314 */
315 for (sal_uInt32 i = 0; i< elem.PolyPoly.count(); i++)
316 {
317 basegfx::B2DPolygon b2dPolygon = elem.PolyPoly.getB2DPolygon( i );
318
319 for ( sal_uInt32 j = 0; j< b2dPolygon.count(); j++ )
320 {
321 basegfx::B2DPoint point;
322 basegfx::B2DPoint nextPoint;
323 point = b2dPolygon.getB2DPoint( j );
324
325 basegfx::B2DPoint prevPoint = b2dPolygon.getPrevControlPoint( j ) ;
326
327 point.setX( convPx2mmPrec2( point.getX() )*100.0 );
328 point.setY( convPx2mmPrec2( point.getY() )*100.0 );
329
330 if ( b2dPolygon.isPrevControlPointUsed( j ) )
331 {
332 prevPoint.setX( convPx2mmPrec2( prevPoint.getX() )*100.0 );
333 prevPoint.setY( convPx2mmPrec2( prevPoint.getY() )*100.0 );
334 }
335
336 if ( b2dPolygon.isNextControlPointUsed( j ) )
337 {
338 nextPoint = b2dPolygon.getNextControlPoint( j ) ;
339 nextPoint.setX( convPx2mmPrec2( nextPoint.getX() )*100.0 );
340 nextPoint.setY( convPx2mmPrec2( nextPoint.getY() )*100.0 );
341 }
342
343 b2dPolygon.setB2DPoint( j, point );
344
345 if ( b2dPolygon.isPrevControlPointUsed( j ) )
346 b2dPolygon.setPrevControlPoint( j , prevPoint ) ;
347
348 if ( b2dPolygon.isNextControlPointUsed( j ) )
349 b2dPolygon.setNextControlPoint( j , nextPoint ) ;
350 }
351
352 elem.PolyPoly.setB2DPolygon( i, b2dPolygon );
353 }
354
355 PropertyMap aProps;
356 fillFrameProps( elem, aProps, m_rEmitContext );
357 aProps[ u"svg:viewBox"_ustr ] =
358 "0 0 "
359 + OUString::number(convPx2mmPrec2(elem.w)*100.0)
360 + " "
361 + OUString::number( convPx2mmPrec2(elem.h)*100.0 );
362 aProps[ u"svg:d"_ustr ] = basegfx::utils::exportToSvgD( elem.PolyPoly, true, true, false );
363
364 m_rEmitContext.rEmitter.beginTag( "draw:path", aProps );
365 m_rEmitContext.rEmitter.endTag( "draw:path" );
366 }
367
visit(ImageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)368 void WriterXmlEmitter::visit( ImageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
369 {
370 PropertyMap aImageProps;
371 m_rEmitContext.rEmitter.beginTag( "draw:image", aImageProps );
372 m_rEmitContext.rEmitter.beginTag( "office:binary-data", PropertyMap() );
373 m_rEmitContext.rImages.writeBase64EncodedStream( elem.Image, m_rEmitContext);
374 m_rEmitContext.rEmitter.endTag( "office:binary-data" );
375 m_rEmitContext.rEmitter.endTag( "draw:image" );
376 }
377
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)378 void WriterXmlEmitter::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
379 {
380 if( m_rEmitContext.xStatusIndicator.is() )
381 m_rEmitContext.xStatusIndicator->setValue( elem.PageNumber );
382
383 auto this_it = elem.Children.begin();
384 while( this_it != elem.Children.end() && this_it->get() != &elem )
385 {
386 (*this_it)->visitedBy( *this, this_it );
387 ++this_it;
388 }
389 }
390
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)391 void WriterXmlEmitter::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
392 {
393 m_rEmitContext.rEmitter.beginTag( "office:body", PropertyMap() );
394 m_rEmitContext.rEmitter.beginTag( "office:text", PropertyMap() );
395
396 for( const auto& rxChild : elem.Children )
397 {
398 PageElement* pPage = dynamic_cast<PageElement*>(rxChild.get());
399 if( pPage )
400 {
401 // emit only page anchored objects
402 // currently these are only DrawElement types
403 for( auto child_it = pPage->Children.begin(); child_it != pPage->Children.end(); ++child_it )
404 {
405 if( dynamic_cast<DrawElement*>(child_it->get()) != nullptr )
406 (*child_it)->visitedBy( *this, child_it );
407 }
408 }
409 }
410
411 // do not emit page anchored objects, they are emitted before
412 // (must precede all pages in writer document) currently these are
413 // only DrawElement types
414 for( auto it = elem.Children.begin(); it != elem.Children.end(); ++it )
415 {
416 if( dynamic_cast<DrawElement*>(it->get()) == nullptr )
417 (*it)->visitedBy( *this, it );
418 }
419
420 m_rEmitContext.rEmitter.endTag( "office:text" );
421 m_rEmitContext.rEmitter.endTag( "office:body" );
422 }
423
424
visit(HyperlinkElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)425 void WriterXmlOptimizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
426 {
427 }
428
visit(TextElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)429 void WriterXmlOptimizer::visit( TextElement&, const std::list< std::unique_ptr<Element> >::const_iterator&)
430 {
431 }
432
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)433 void WriterXmlOptimizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
434 {
435 elem.applyToChildren(*this);
436 }
437
visit(ImageElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)438 void WriterXmlOptimizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
439 {
440 }
441
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & elemIt)442 void WriterXmlOptimizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& elemIt )
443 {
444 /* note: optimize two consecutive PolyPolyElements that
445 * have the same path but one of which is a stroke while
446 * the other is a fill
447 */
448 if( !elem.Parent )
449 return;
450 // find following PolyPolyElement in parent's children list
451 if( elemIt == elem.Parent->Children.end() )
452 return;
453 auto next_it = elemIt;
454 ++next_it;
455 if( next_it == elem.Parent->Children.end() )
456 return;
457
458 PolyPolyElement* pNext = dynamic_cast<PolyPolyElement*>(next_it->get());
459 if( !pNext || pNext->PolyPoly != elem.PolyPoly )
460 return;
461
462 const GraphicsContext& rNextGC =
463 m_rProcessor.getGraphicsContext( pNext->GCId );
464 const GraphicsContext& rThisGC =
465 m_rProcessor.getGraphicsContext( elem.GCId );
466
467 if( !(rThisGC.BlendMode == rNextGC.BlendMode &&
468 rThisGC.Flatness == rNextGC.Flatness &&
469 rThisGC.Transformation == rNextGC.Transformation &&
470 rThisGC.Clip == rNextGC.Clip &&
471 pNext->Action == PATH_STROKE &&
472 (elem.Action == PATH_FILL || elem.Action == PATH_EOFILL)) )
473 return;
474
475 GraphicsContext aGC = rThisGC;
476 aGC.LineJoin = rNextGC.LineJoin;
477 aGC.LineCap = rNextGC.LineCap;
478 aGC.LineWidth = rNextGC.LineWidth;
479 aGC.MiterLimit= rNextGC.MiterLimit;
480 aGC.DashArray = rNextGC.DashArray;
481 aGC.LineColor = rNextGC.LineColor;
482 elem.GCId = m_rProcessor.getGCId( aGC );
483
484 elem.Action |= pNext->Action;
485
486 elem.Children.splice( elem.Children.end(), pNext->Children );
487 elem.Parent->Children.erase(next_it);
488 }
489
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)490 void WriterXmlOptimizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt)
491 {
492 optimizeTextElements( elem );
493
494 elem.applyToChildren(*this);
495
496 if( !(elem.Parent && rParentIt != elem.Parent->Children.end()) )
497 return;
498
499 // find if there is a previous paragraph that might be a heading for this one
500 auto prev = rParentIt;
501 ParagraphElement* pPrevPara = nullptr;
502 while( prev != elem.Parent->Children.begin() )
503 {
504 --prev;
505 pPrevPara = dynamic_cast< ParagraphElement* >(prev->get());
506 if( pPrevPara )
507 {
508 /* What constitutes a heading ? current hints are:
509 * - one line only
510 * - not too far away from this paragraph (two heading height max ?)
511 * - font larger or bold
512 * this is of course incomplete
513 * FIXME: improve hints for heading
514 */
515 // check for single line
516 if( pPrevPara->isSingleLined( m_rProcessor ) )
517 {
518 double head_line_height = pPrevPara->getLineHeight( m_rProcessor );
519 if( pPrevPara->y + pPrevPara->h + 2*head_line_height > elem.y )
520 {
521 // check for larger font
522 if( head_line_height > elem.getLineHeight( m_rProcessor ) )
523 {
524 pPrevPara->Type = ParagraphElement::Headline;
525 }
526 else
527 {
528 // check whether text of pPrevPara is bold (at least first text element)
529 // and this para is not bold (ditto)
530 TextElement* pPrevText = pPrevPara->getFirstTextChild();
531 TextElement* pThisText = elem.getFirstTextChild();
532 if( pPrevText && pThisText )
533 {
534 const FontAttributes& rPrevFont = m_rProcessor.getFont( pPrevText->FontId );
535 const FontAttributes& rThisFont = m_rProcessor.getFont( pThisText->FontId );
536 if ( (rPrevFont.fontWeight == u"600" ||
537 rPrevFont.fontWeight == u"bold" ||
538 rPrevFont.fontWeight == u"800" ||
539 rPrevFont.fontWeight == u"900" ) &&
540 (rThisFont.fontWeight == u"600" ||
541 rThisFont.fontWeight == u"bold" ||
542 rThisFont.fontWeight == u"800" ||
543 rThisFont.fontWeight == u"900" ) )
544 {
545 pPrevPara->Type = ParagraphElement::Headline;
546 }
547 }
548 }
549 }
550 }
551 break;
552 }
553 }
554 }
555
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)556 void WriterXmlOptimizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
557 {
558 if( m_rProcessor.getStatusIndicator().is() )
559 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
560
561 // resolve hyperlinks
562 elem.resolveHyperlinks();
563
564 elem.resolveFontStyles( m_rProcessor ); // underlines and such
565
566 // FIXME: until hyperlinks and font effects are adjusted for
567 // geometrical search handle them before sorting
568 PDFIProcessor::sortElements( &elem );
569
570 // find paragraphs in text
571 ParagraphElement* pCurPara = nullptr;
572 std::list< std::unique_ptr<Element> >::iterator page_element, next_page_element;
573 next_page_element = elem.Children.begin();
574 double fCurLineHeight = 0.0; // average height of text items in current para
575 int nCurLineElements = 0; // number of line contributing elements in current para
576 double line_left = elem.w, line_right = 0.0;
577 double column_width = elem.w*0.75; // estimate text width
578 // TODO: guess columns
579 while( next_page_element != elem.Children.end() )
580 {
581 page_element = next_page_element++;
582 ParagraphElement* pPagePara = dynamic_cast<ParagraphElement*>(page_element->get());
583 if( pPagePara )
584 {
585 pCurPara = pPagePara;
586 // adjust line height and text items
587 fCurLineHeight = 0.0;
588 nCurLineElements = 0;
589 for( const auto& rxChild : pCurPara->Children )
590 {
591 TextElement* pTestText = rxChild->dynCastAsTextElement();
592 if( pTestText )
593 {
594 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pTestText->h)/double(nCurLineElements+1);
595 nCurLineElements++;
596 }
597 }
598 continue;
599 }
600
601 HyperlinkElement* pLink = dynamic_cast<HyperlinkElement*>(page_element->get());
602 DrawElement* pDraw = dynamic_cast<DrawElement*>(page_element->get());
603 if( ! pDraw && pLink && ! pLink->Children.empty() )
604 pDraw = dynamic_cast<DrawElement*>(pLink->Children.front().get() );
605 if( pDraw )
606 {
607 // insert small drawing objects as character, else leave them page bound
608
609 bool bInsertToParagraph = false;
610 // first check if this is either inside the paragraph
611 if( pCurPara && pDraw->y < pCurPara->y + pCurPara->h )
612 {
613 if( pDraw->h < fCurLineHeight * 1.5 )
614 {
615 bInsertToParagraph = true;
616 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pDraw->h)/double(nCurLineElements+1);
617 nCurLineElements++;
618 // mark draw element as character
619 pDraw->isCharacter = true;
620 }
621 }
622 // or perhaps the draw element begins a new paragraph
623 else if( next_page_element != elem.Children.end() )
624 {
625 TextElement* pText = (*next_page_element)->dynCastAsTextElement();
626 if( ! pText )
627 {
628 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(next_page_element->get());
629 if( pPara && ! pPara->Children.empty() )
630 pText = pPara->Children.front()->dynCastAsTextElement();
631 }
632 if( pText && // check there is a text
633 pDraw->h < pText->h*1.5 && // and it is approx the same height
634 // and either upper or lower edge of pDraw is inside text's vertical range
635 ( ( pDraw->y >= pText->y && pDraw->y <= pText->y+pText->h ) ||
636 ( pDraw->y+pDraw->h >= pText->y && pDraw->y+pDraw->h <= pText->y+pText->h )
637 )
638 )
639 {
640 bInsertToParagraph = true;
641 fCurLineHeight = pDraw->h;
642 nCurLineElements = 1;
643 line_left = pDraw->x;
644 line_right = pDraw->x + pDraw->w;
645 // begin a new paragraph
646 pCurPara = nullptr;
647 // mark draw element as character
648 pDraw->isCharacter = true;
649 }
650 }
651
652 if( ! bInsertToParagraph )
653 {
654 pCurPara = nullptr;
655 continue;
656 }
657 }
658
659 TextElement* pText = (*page_element)->dynCastAsTextElement();
660 if( ! pText && pLink && ! pLink->Children.empty() )
661 pText = pLink->Children.front()->dynCastAsTextElement();
662 if( pText )
663 {
664 Element* pGeo = pLink ? static_cast<Element*>(pLink) :
665 static_cast<Element*>(pText);
666 if( pCurPara )
667 {
668 // there was already a text element, check for a new paragraph
669 if( nCurLineElements > 0 )
670 {
671 // if the new text is significantly distant from the paragraph
672 // begin a new paragraph
673 if( pGeo->y > pCurPara->y+pCurPara->h + fCurLineHeight*0.5 )
674 pCurPara = nullptr; // insert new paragraph
675 else if( pGeo->y > (pCurPara->y+pCurPara->h - fCurLineHeight*0.05) )
676 {
677 // new paragraph if either the last line of the paragraph
678 // was significantly shorter than the paragraph as a whole
679 if( (line_right - line_left) < pCurPara->w*0.75 )
680 pCurPara = nullptr;
681 // or the last line was significantly smaller than the column width
682 else if( (line_right - line_left) < column_width*0.75 )
683 pCurPara = nullptr;
684 }
685 }
686 }
687 // update line height/width
688 if( pCurPara )
689 {
690 fCurLineHeight = (fCurLineHeight*double(nCurLineElements) + pGeo->h)/double(nCurLineElements+1);
691 nCurLineElements++;
692 if( pGeo->x < line_left )
693 line_left = pGeo->x;
694 if( pGeo->x+pGeo->w > line_right )
695 line_right = pGeo->x+pGeo->w;
696 }
697 else
698 {
699 fCurLineHeight = pGeo->h;
700 nCurLineElements = 1;
701 line_left = pGeo->x;
702 line_right = pGeo->x + pGeo->w;
703 }
704 }
705
706 // move element to current paragraph
707 if( ! pCurPara ) // new paragraph, insert one
708 {
709 pCurPara = ElementFactory::createParagraphElement( nullptr );
710 assert(pCurPara);
711 // set parent
712 pCurPara->Parent = &elem;
713 //insert new paragraph before current element
714 page_element = elem.Children.insert( page_element, std::unique_ptr<Element>(pCurPara) );
715 // forward iterator to current element again
716 ++ page_element;
717 // update next_element which is now invalid
718 next_page_element = page_element;
719 ++ next_page_element;
720 }
721 Element* pCurEle = page_element->get();
722 Element::setParent( page_element, pCurPara );
723 OSL_ENSURE( !pText || pCurEle == pText || pCurEle == pLink, "paragraph child list in disorder" );
724 if( pText || pDraw )
725 pCurPara->updateGeometryWith( pCurEle );
726 }
727
728 // process children
729 elem.applyToChildren(*this);
730
731 // find possible header and footer
732 checkHeaderAndFooter( elem );
733 }
734
checkHeaderAndFooter(PageElement & rElem)735 void WriterXmlOptimizer::checkHeaderAndFooter( PageElement& rElem )
736 {
737 /* indicators for a header:
738 * - single line paragraph at top of page (inside 15% page height)
739 * - at least lineheight above the next paragraph
740 *
741 * indicators for a footer likewise:
742 * - single line paragraph at bottom of page (inside 15% page height)
743 * - at least lineheight below the previous paragraph
744 */
745
746 auto isParagraphElement = [](std::unique_ptr<Element>& rxChild) -> bool {
747 return dynamic_cast<ParagraphElement*>(rxChild.get()) != nullptr;
748 };
749
750 // detect header
751 // Note: the following assumes that the pages' children have been
752 // sorted geometrically
753 auto it = std::find_if(rElem.Children.begin(), rElem.Children.end(), isParagraphElement);
754 if (it != rElem.Children.end())
755 {
756 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**it);
757 if( rPara.y+rPara.h < rElem.h*0.15 && rPara.isSingleLined( m_rProcessor ) )
758 {
759 auto next_it = it;
760 ParagraphElement* pNextPara = nullptr;
761 while( ++next_it != rElem.Children.end() && pNextPara == nullptr )
762 {
763 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
764 }
765 if( pNextPara && pNextPara->y > rPara.y+rPara.h*2 )
766 {
767 rElem.HeaderElement = std::move(*it);
768 rPara.Parent = nullptr;
769 rElem.Children.erase( it );
770 }
771 }
772 }
773
774 // detect footer
775 auto rit = std::find_if(rElem.Children.rbegin(), rElem.Children.rend(), isParagraphElement);
776 if (rit == rElem.Children.rend())
777 return;
778
779 ParagraphElement& rPara = dynamic_cast<ParagraphElement&>(**rit);
780 if( !(rPara.y > rElem.h*0.85 && rPara.isSingleLined( m_rProcessor )) )
781 return;
782
783 std::list< std::unique_ptr<Element> >::reverse_iterator next_it = rit;
784 ParagraphElement* pNextPara = nullptr;
785 while( ++next_it != rElem.Children.rend() && pNextPara == nullptr )
786 {
787 pNextPara = dynamic_cast<ParagraphElement*>(next_it->get());
788 }
789 if( pNextPara && pNextPara->y < rPara.y-rPara.h*2 )
790 {
791 rElem.FooterElement = std::move(*rit);
792 rPara.Parent = nullptr;
793 rElem.Children.erase( std::next(rit).base() );
794 }
795 }
796
optimizeTextElements(Element & rParent)797 void WriterXmlOptimizer::optimizeTextElements(Element& rParent)
798 {
799 if( rParent.Children.empty() ) // this should not happen
800 {
801 OSL_FAIL( "empty paragraph optimized" );
802 return;
803 }
804
805 // concatenate child elements with same font id
806 auto next = rParent.Children.begin();
807 auto it = next++;
808 FrameElement* pFrame = dynamic_cast<FrameElement*>(rParent.Parent);
809 bool bRotatedFrame = false;
810 if( pFrame )
811 {
812 const GraphicsContext& rFrameGC = m_rProcessor.getGraphicsContext( pFrame->GCId );
813 if( rFrameGC.isRotatedOrSkewed() )
814 bRotatedFrame = true;
815 }
816 while( next != rParent.Children.end() )
817 {
818 bool bConcat = false;
819 TextElement* pCur = (*it)->dynCastAsTextElement();
820 if( pCur )
821 {
822 TextElement* pNext = dynamic_cast<TextElement*>(next->get());
823 OUString str;
824 bool bPara = strspn("ParagraphElement", typeid(rParent).name());
825 ParagraphElement* pPara = dynamic_cast<ParagraphElement*>(&rParent);
826 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
827 pPara->bRtl = true;
828 if( pNext )
829 {
830 const GraphicsContext& rCurGC = m_rProcessor.getGraphicsContext( pCur->GCId );
831 const GraphicsContext& rNextGC = m_rProcessor.getGraphicsContext( pNext->GCId );
832
833 // line and space optimization; works only in strictly horizontal mode
834
835 if( !bRotatedFrame
836 && ! rCurGC.isRotatedOrSkewed()
837 && ! rNextGC.isRotatedOrSkewed()
838 && ! pNext->Text.isEmpty()
839 && pNext->Text[0] != ' '
840 && ! pCur->Text.isEmpty()
841 && pCur->Text[pCur->Text.getLength() - 1] != ' '
842 )
843 {
844 // check for new line in paragraph
845 if( pNext->y > pCur->y+pCur->h )
846 {
847 // new line begins
848 // check whether a space would should be inserted or a hyphen removed
849 sal_Unicode aLastCode = pCur->Text[pCur->Text.getLength() - 1];
850 if( aLastCode == '-'
851 || aLastCode == 0x2010
852 || (aLastCode >= 0x2012 && aLastCode <= 0x2015)
853 || aLastCode == 0xff0d
854 )
855 {
856 // cut a hyphen
857 pCur->Text.setLength( pCur->Text.getLength()-1 );
858 }
859 // append a space unless there is a non breaking hyphen
860 else if( aLastCode != 0x2011 )
861 {
862 pCur->Text.append( ' ' );
863 }
864 }
865 else // we're continuing the same line
866 {
867 // check whether a space would should be inserted
868 // check for a small horizontal offset
869 if( pCur->x + pCur->w + pNext->h*0.15 < pNext->x )
870 {
871 pCur->Text.append( ' ' );
872 }
873 }
874 }
875 // concatenate consecutive text elements unless there is a
876 // font or text color change, leave a new span in that case
877 if( pCur->FontId == pNext->FontId &&
878 rCurGC.FillColor.Red == rNextGC.FillColor.Red &&
879 rCurGC.FillColor.Green == rNextGC.FillColor.Green &&
880 rCurGC.FillColor.Blue == rNextGC.FillColor.Blue &&
881 rCurGC.FillColor.Alpha == rNextGC.FillColor.Alpha
882 )
883 {
884 pCur->updateGeometryWith( pNext );
885 if (pPara && pPara->bRtl)
886 {
887 // Tdf#152083: If RTL, reverse the text in pNext so that its correct order is
888 // restored when the combined text is reversed in WriterXmlEmitter::visit.
889 OUString tempStr;
890 bool bNeedReverse=false;
891 str = pNext->Text.toString();
892 for (sal_Int32 i=0; i < str.getLength(); i++)
893 {
894 if (str[i] == u' ')
895 { // Space char (e.g. the space as in " م") needs special treatment.
896 // First, append the space char to pCur.
897 pCur->Text.append(OUStringChar(str[i]));
898 // Then, check whether the tmpStr needs reverse, if so then reverse and append.
899 if (bNeedReverse)
900 {
901 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
902 pCur->Text.append(tempStr);
903 tempStr = u""_ustr;
904 }
905 bNeedReverse = false;
906 }
907 else
908 {
909 tempStr += OUStringChar(str[i]);
910 bNeedReverse = true;
911 }
912 }
913 // Do the last append
914 if (bNeedReverse)
915 {
916 tempStr = ::comphelper::string::reverseCodePoints(tempStr);
917 pCur->Text.append(tempStr);
918 }
919 else
920 {
921 pCur->Text.append(tempStr);
922 }
923 }
924 else
925 {
926 // append text to current element directly without reverse
927 pCur->Text.append(pNext->Text);
928 }
929 if (bPara && pPara && isComplex(GetBreakIterator(), pCur))
930 pPara->bRtl = true;
931 // append eventual children to current element
932 // and clear children (else the children just
933 // appended to pCur would be destroyed)
934 pCur->Children.splice( pCur->Children.end(), pNext->Children );
935 // get rid of the now useless element
936 rParent.Children.erase( next );
937 bConcat = true;
938 }
939 }
940 }
941 else if( dynamic_cast<HyperlinkElement*>(it->get()) )
942 optimizeTextElements( **it );
943 if( bConcat )
944 {
945 next = it;
946 ++next;
947 }
948 else
949 {
950 ++it;
951 ++next;
952 }
953 }
954 }
955
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)956 void WriterXmlOptimizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
957 {
958 elem.applyToChildren(*this);
959 }
960
961
visit(PolyPolyElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)962 void WriterXmlFinalizer::visit( PolyPolyElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
963 {
964 // xxx TODO copied from DrawElement
965 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext(elem.GCId );
966 PropertyMap aProps;
967 aProps[ u"style:family"_ustr ] = "graphic";
968
969 PropertyMap aGCProps;
970 if (elem.Action & PATH_STROKE)
971 {
972 double scale = GetAverageTransformationScale(rGC.Transformation);
973 if (rGC.DashArray.size() < 2)
974 {
975 aGCProps[ u"draw:stroke"_ustr ] = "solid";
976 }
977 else
978 {
979 PropertyMap props;
980 FillDashStyleProps(props, rGC.DashArray, scale);
981 StyleContainer::Style style("draw:stroke-dash"_ostr, std::move(props));
982
983 aGCProps[ u"draw:stroke"_ustr ] = "dash";
984 aGCProps[ u"draw:stroke-dash"_ustr ] =
985 m_rStyleContainer.getStyleName(
986 m_rStyleContainer.getStyleId(style));
987 }
988
989 aGCProps[ u"svg:stroke-color"_ustr ] = getColorString(rGC.LineColor);
990 aGCProps[ u"svg:stroke-width"_ustr ] = convertPixelToUnitString(rGC.LineWidth * scale);
991 aGCProps[ u"draw:stroke-linejoin"_ustr ] = rGC.GetLineJoinString();
992 aGCProps[ u"svg:stroke-linecap"_ustr ] = rGC.GetLineCapString();
993 }
994 else
995 {
996 aGCProps[ u"draw:stroke"_ustr ] = "none";
997 }
998
999 if (elem.FillImage != -1)
1000 {
1001 PropertyMap props;
1002 // The image isn't actually in a prop, it's in an extra chunk inside.
1003 StyleContainer::Style style("draw:fill-image"_ostr, std::move(props));
1004 style.Contents = m_rProcessor.getImages().asBase64EncodedString(elem.FillImage);
1005 aGCProps[ u"draw:fill-image-name"_ustr ] =
1006 m_rStyleContainer.getStyleName(
1007 m_rStyleContainer.getStyleId(style));
1008 aGCProps[ u"draw:fill-image-width"_ustr ] = unitMMString(convPx2mm(elem.TileWidth));
1009 aGCProps[ u"draw:fill-image-height"_ustr ] = unitMMString(convPx2mm(elem.TileHeight));
1010 }
1011
1012 // TODO(F1): check whether stuff could be emulated by gradient/bitmap/hatch
1013 if( elem.Action & (PATH_FILL | PATH_EOFILL) )
1014 {
1015 if (elem.FillImage == -1)
1016 {
1017 aGCProps[ u"draw:fill"_ustr ] = "solid";
1018 }
1019 else
1020 {
1021 aGCProps[ u"draw:fill"_ustr ] = "bitmap";
1022 }
1023 aGCProps[ u"draw:fill-color"_ustr ] = getColorString( rGC.FillColor );
1024 }
1025 else
1026 {
1027 aGCProps[ u"draw:fill"_ustr ] = "none";
1028 }
1029
1030 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1031 StyleContainer::Style aSubStyle( "style:graphic-properties"_ostr, std::move(aGCProps) );
1032 aStyle.SubStyles.push_back( &aSubStyle );
1033
1034 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1035 }
1036
visit(HyperlinkElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)1037 void WriterXmlFinalizer::visit( HyperlinkElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1038 {
1039 }
1040
visit(TextElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1041 void WriterXmlFinalizer::visit( TextElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1042 {
1043 const FontAttributes& rFont = m_rProcessor.getFont( elem.FontId );
1044 PropertyMap aProps;
1045 aProps[ u"style:family"_ustr ] = "text";
1046
1047 PropertyMap aFontProps;
1048
1049 // family name
1050 // TODO: tdf#143095: use system font name rather than PSName
1051 SAL_INFO("sdext.pdfimport", "The font used in xml is: " << rFont.familyName);
1052 aFontProps[ u"fo:font-family"_ustr ] = rFont.familyName;
1053 aFontProps[ u"style:font-family-asian"_ustr ] = rFont.familyName;
1054 aFontProps[ u"style:font-family-complex"_ustr ] = rFont.familyName;
1055
1056 // bold
1057 aFontProps[ u"fo:font-weight"_ustr ] = rFont.fontWeight;
1058 aFontProps[ u"style:font-weight-asian"_ustr ] = rFont.fontWeight;
1059 aFontProps[ u"style:font-weight-complex"_ustr ] = rFont.fontWeight;
1060
1061 // italic
1062 if( rFont.isItalic )
1063 {
1064 aFontProps[ u"fo:font-style"_ustr ] = "italic";
1065 aFontProps[ u"style:font-style-asian"_ustr ] = "italic";
1066 aFontProps[ u"style:font-style-complex"_ustr ] = "italic";
1067 }
1068
1069 // underline
1070 if( rFont.isUnderline )
1071 {
1072 aFontProps[ u"style:text-underline-style"_ustr ] = "solid";
1073 aFontProps[ u"style:text-underline-width"_ustr ] = "auto";
1074 aFontProps[ u"style:text-underline-color"_ustr ] = "font-color";
1075 }
1076
1077 // outline
1078 if( rFont.isOutline )
1079 aFontProps[ u"style:text-outline"_ustr ] = "true";
1080
1081 // size
1082 OUString aFSize = OUString::number( rFont.size*72/PDFI_OUTDEV_RESOLUTION ) + "pt";
1083 aFontProps[ u"fo:font-size"_ustr ] = aFSize;
1084 aFontProps[ u"style:font-size-asian"_ustr ] = aFSize;
1085 aFontProps[ u"style:font-size-complex"_ustr ] = aFSize;
1086
1087 // color
1088 const GraphicsContext& rGC = m_rProcessor.getGraphicsContext( elem.GCId );
1089 aFontProps[ u"fo:color"_ustr ] = getColorString( rFont.isOutline ? rGC.LineColor : rGC.FillColor );
1090
1091 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1092 StyleContainer::Style aSubStyle( "style:text-properties"_ostr, std::move(aFontProps) );
1093 aStyle.SubStyles.push_back( &aSubStyle );
1094 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1095 }
1096
visit(ParagraphElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator & rParentIt)1097 void WriterXmlFinalizer::visit( ParagraphElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& rParentIt )
1098 {
1099 PropertyMap aParaProps;
1100
1101 if( elem.Parent )
1102 {
1103 // check for center alignment
1104 // criterion: paragraph is small relative to parent and distributed around its center
1105 double p_x = elem.Parent->x;
1106 double p_w = elem.Parent->w;
1107
1108 PageElement* pPage = dynamic_cast<PageElement*>(elem.Parent);
1109 if( pPage )
1110 {
1111 p_x += pPage->LeftMargin;
1112 p_w -= pPage->LeftMargin+pPage->RightMargin;
1113 }
1114 bool bIsCenter = false;
1115 if( elem.w < ( p_w/2) )
1116 {
1117 double delta = elem.w/4;
1118 // allow very small paragraphs to deviate a little more
1119 // relative to parent's center
1120 if( elem.w < p_w/8 )
1121 delta = elem.w;
1122 if( fabs( elem.x+elem.w/2 - ( p_x+ p_w/2) ) < delta ||
1123 (pPage && fabs( elem.x+elem.w/2 - (pPage->x + pPage->w/2) ) < delta) )
1124 {
1125 bIsCenter = true;
1126 aParaProps[ u"fo:text-align"_ustr ] = "center";
1127 }
1128 }
1129 if( ! bIsCenter && elem.x > p_x + p_w/10 )
1130 {
1131 // indent
1132 aParaProps[ u"fo:margin-left"_ustr ] = OUString::number(convPx2mm( elem.x - p_x )) + "mm";
1133 }
1134
1135 // check whether to leave some space to next paragraph
1136 // find whether there is a next paragraph
1137 auto it = rParentIt;
1138 const ParagraphElement* pNextPara = nullptr;
1139 while( ++it != elem.Parent->Children.end() && ! pNextPara )
1140 pNextPara = dynamic_cast< const ParagraphElement* >(it->get());
1141 if( pNextPara )
1142 {
1143 if( pNextPara->y - (elem.y+elem.h) > convmm2Px( 10 ) )
1144 {
1145 aParaProps[ u"fo:margin-bottom"_ustr ] =
1146 OUString::number( convPx2mm( pNextPara->y - (elem.y+elem.h) ) ) + "mm";
1147 }
1148 }
1149 }
1150
1151 if( ! aParaProps.empty() )
1152 {
1153 PropertyMap aProps;
1154 aProps[ u"style:family"_ustr ] = "paragraph";
1155 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1156 StyleContainer::Style aSubStyle( "style:paragraph-properties"_ostr, std::move(aParaProps) );
1157 aStyle.SubStyles.push_back( &aSubStyle );
1158 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1159 }
1160
1161 elem.applyToChildren(*this);
1162 }
1163
visit(FrameElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1164 void WriterXmlFinalizer::visit( FrameElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator&)
1165 {
1166 PropertyMap aProps;
1167 aProps[ u"style:family"_ustr ] = "graphic";
1168
1169 PropertyMap aGCProps;
1170
1171 aGCProps[ u"draw:stroke"_ustr ] = "none";
1172 aGCProps[ u"draw:fill"_ustr ] = "none";
1173 aGCProps[ u"draw:auto-grow-height"_ustr ] = "true";
1174 aGCProps[ u"draw:auto-grow-width"_ustr ] = "true";
1175 aGCProps[ u"draw:textarea-horizontal-align"_ustr ] = "left";
1176 aGCProps[ u"draw:textarea-vertical-align"_ustr ] = "top";
1177 aGCProps[ u"fo:min-height"_ustr] = "0cm";
1178 aGCProps[ u"fo:min-width"_ustr] = "0cm";
1179 aGCProps[ u"fo:padding-top"_ustr ] = "0cm";
1180 aGCProps[ u"fo:padding-left"_ustr ] = "0cm";
1181 aGCProps[ u"fo:padding-right"_ustr ] = "0cm";
1182 aGCProps[ u"fo:padding-bottom"_ustr ] = "0cm";
1183
1184 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1185 StyleContainer::Style aSubStyle( "style:graphic-properties"_ostr, std::move(aGCProps) );
1186 aStyle.SubStyles.push_back( &aSubStyle );
1187
1188 elem.StyleId = m_rStyleContainer.getStyleId( aStyle );
1189 elem.applyToChildren(*this);
1190 }
1191
visit(ImageElement &,const std::list<std::unique_ptr<Element>>::const_iterator &)1192 void WriterXmlFinalizer::visit( ImageElement&, const std::list< std::unique_ptr<Element> >::const_iterator& )
1193 {
1194 }
1195
setFirstOnPage(ParagraphElement & rElem,StyleContainer & rStyles,const OUString & rMasterPageName)1196 void WriterXmlFinalizer::setFirstOnPage( ParagraphElement& rElem,
1197 StyleContainer& rStyles,
1198 const OUString& rMasterPageName )
1199 {
1200 PropertyMap aProps;
1201 if( rElem.StyleId != -1 )
1202 {
1203 const PropertyMap* pProps = rStyles.getProperties( rElem.StyleId );
1204 if( pProps )
1205 aProps = *pProps;
1206 }
1207
1208 aProps[ u"style:family"_ustr ] = "paragraph";
1209 aProps[ u"style:master-page-name"_ustr ] = rMasterPageName;
1210
1211 if( rElem.StyleId != -1 )
1212 rElem.StyleId = rStyles.setProperties( rElem.StyleId, std::move(aProps) );
1213 else
1214 {
1215 StyleContainer::Style aStyle( "style:style"_ostr, std::move(aProps) );
1216 rElem.StyleId = rStyles.getStyleId( aStyle );
1217 }
1218 }
1219
visit(PageElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1220 void WriterXmlFinalizer::visit( PageElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1221 {
1222 if( m_rProcessor.getStatusIndicator().is() )
1223 m_rProcessor.getStatusIndicator()->setValue( elem.PageNumber );
1224
1225 // transform from pixel to mm
1226 double page_width = convPx2mm( elem.w ), page_height = convPx2mm( elem.h );
1227
1228 // calculate page margins out of the relevant children (paragraphs)
1229 elem.TopMargin = elem.h;
1230 elem.BottomMargin = 0;
1231 elem.LeftMargin = elem.w;
1232 elem.RightMargin = 0;
1233 // first element should be a paragraph
1234 ParagraphElement* pFirstPara = nullptr;
1235 for( const auto& rxChild : elem.Children )
1236 {
1237 if( dynamic_cast<ParagraphElement*>( rxChild.get() ) )
1238 {
1239 if( rxChild->x < elem.LeftMargin )
1240 elem.LeftMargin = rxChild->x;
1241 if( rxChild->y < elem.TopMargin )
1242 elem.TopMargin = rxChild->y;
1243 if( rxChild->x + rxChild->w > elem.w - elem.RightMargin )
1244 elem.RightMargin = elem.w - (rxChild->x + rxChild->w);
1245 if( rxChild->y + rxChild->h > elem.h - elem.BottomMargin )
1246 elem.BottomMargin = elem.h - (rxChild->y + rxChild->h);
1247 if( ! pFirstPara )
1248 pFirstPara = dynamic_cast<ParagraphElement*>( rxChild.get() );
1249 }
1250 }
1251 if( elem.HeaderElement && elem.HeaderElement->y < elem.TopMargin )
1252 elem.TopMargin = elem.HeaderElement->y;
1253 if( elem.FooterElement && elem.FooterElement->y+elem.FooterElement->h > elem.h - elem.BottomMargin )
1254 elem.BottomMargin = elem.h - (elem.FooterElement->y + elem.FooterElement->h);
1255
1256 // transform margins to mm
1257 double left_margin = convPx2mm( elem.LeftMargin );
1258 double right_margin = convPx2mm( elem.RightMargin );
1259 double top_margin = convPx2mm( elem.TopMargin );
1260 double bottom_margin = convPx2mm( elem.BottomMargin );
1261 if( ! pFirstPara )
1262 {
1263 // use default page margins
1264 left_margin = 10;
1265 right_margin = 10;
1266 top_margin = 10;
1267 bottom_margin = 10;
1268 }
1269
1270 // round left/top margin to nearest mm
1271 left_margin = rtl_math_round( left_margin, 0, rtl_math_RoundingMode_Floor );
1272 top_margin = rtl_math_round( top_margin, 0, rtl_math_RoundingMode_Floor );
1273 // round (fuzzy) right/bottom margin to nearest cm
1274 right_margin = rtl_math_round( right_margin, right_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1275 bottom_margin = rtl_math_round( bottom_margin, bottom_margin >= 10 ? -1 : 0, rtl_math_RoundingMode_Floor );
1276
1277 // set reasonable default in case of way too large margins
1278 // e.g. no paragraph case
1279 if( left_margin > page_width/2.0 - 10 )
1280 left_margin = 10;
1281 if( right_margin > page_width/2.0 - 10 )
1282 right_margin = 10;
1283 if( top_margin > page_height/2.0 - 10 )
1284 top_margin = 10;
1285 if( bottom_margin > page_height/2.0 - 10 )
1286 bottom_margin = 10;
1287
1288 // catch the weird cases
1289 if( left_margin < 0 )
1290 left_margin = 0;
1291 if( right_margin < 0 )
1292 right_margin = 0;
1293 if( top_margin < 0 )
1294 top_margin = 0;
1295 if( bottom_margin < 0 )
1296 bottom_margin = 0;
1297
1298 // widely differing margins are unlikely to be correct
1299 if( right_margin > left_margin*1.5 )
1300 right_margin = left_margin;
1301
1302 elem.LeftMargin = convmm2Px( left_margin );
1303 elem.RightMargin = convmm2Px( right_margin );
1304 elem.TopMargin = convmm2Px( top_margin );
1305 elem.BottomMargin = convmm2Px( bottom_margin );
1306
1307 // get styles for paragraphs
1308 PropertyMap aPageProps;
1309 PropertyMap aPageLayoutProps;
1310 aPageLayoutProps[ u"fo:page-width"_ustr ] = unitMMString( page_width );
1311 aPageLayoutProps[ u"fo:page-height"_ustr ] = unitMMString( page_height );
1312 aPageLayoutProps[ u"style:print-orientation"_ustr ]
1313 = elem.w < elem.h ? std::u16string_view(u"portrait") : std::u16string_view(u"landscape");
1314 aPageLayoutProps[ u"fo:margin-top"_ustr ] = unitMMString( top_margin );
1315 aPageLayoutProps[ u"fo:margin-bottom"_ustr ] = unitMMString( bottom_margin );
1316 aPageLayoutProps[ u"fo:margin-left"_ustr ] = unitMMString( left_margin );
1317 aPageLayoutProps[ u"fo:margin-right"_ustr ] = unitMMString( right_margin );
1318 aPageLayoutProps[ u"style:writing-mode"_ustr ]= "lr-tb";
1319
1320 StyleContainer::Style aStyle( "style:page-layout"_ostr, std::move(aPageProps));
1321 StyleContainer::Style aSubStyle( "style:page-layout-properties"_ostr, std::move(aPageLayoutProps));
1322 aStyle.SubStyles.push_back(&aSubStyle);
1323 sal_Int32 nPageStyle = m_rStyleContainer.impl_getStyleId( aStyle, false );
1324
1325 // create master page
1326 OUString aMasterPageLayoutName = m_rStyleContainer.getStyleName( nPageStyle );
1327 aPageProps[ u"style:page-layout-name"_ustr ] = aMasterPageLayoutName;
1328 StyleContainer::Style aMPStyle( "style:master-page"_ostr, std::move(aPageProps) );
1329 StyleContainer::Style aHeaderStyle( "style:header"_ostr, PropertyMap() );
1330 StyleContainer::Style aFooterStyle( "style:footer"_ostr, PropertyMap() );
1331 if( elem.HeaderElement )
1332 {
1333 elem.HeaderElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1334 aHeaderStyle.ContainedElement = elem.HeaderElement.get();
1335 aMPStyle.SubStyles.push_back( &aHeaderStyle );
1336 }
1337 if( elem.FooterElement )
1338 {
1339 elem.FooterElement->visitedBy( *this, std::list<std::unique_ptr<Element>>::iterator() );
1340 aFooterStyle.ContainedElement = elem.FooterElement.get();
1341 aMPStyle.SubStyles.push_back( &aFooterStyle );
1342 }
1343 elem.StyleId = m_rStyleContainer.impl_getStyleId( aMPStyle,false );
1344
1345
1346 OUString aMasterPageName = m_rStyleContainer.getStyleName( elem.StyleId );
1347
1348 // create styles for children
1349 elem.applyToChildren(*this);
1350
1351 // no paragraph or other elements before the first paragraph
1352 if( ! pFirstPara )
1353 {
1354 pFirstPara = ElementFactory::createParagraphElement( nullptr );
1355 pFirstPara->Parent = &elem;
1356 elem.Children.push_front( std::unique_ptr<Element>(pFirstPara) );
1357 }
1358 setFirstOnPage(*pFirstPara, m_rStyleContainer, aMasterPageName);
1359 }
1360
visit(DocumentElement & elem,const std::list<std::unique_ptr<Element>>::const_iterator &)1361 void WriterXmlFinalizer::visit( DocumentElement& elem, const std::list< std::unique_ptr<Element> >::const_iterator& )
1362 {
1363 elem.applyToChildren(*this);
1364 }
1365
1366 }
1367
1368 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1369