Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / filter / source / flash / swfwriter1.cxx
blob5416ab869442c010a6b837378250fb95036fe8b6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <com/sun/star/i18n/BreakIterator.hpp>
21 #include <com/sun/star/i18n/ScriptType.hpp>
22 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
23 #include <comphelper/processfactory.hxx>
24 #include "swfwriter.hxx"
25 #include <vcl/metaact.hxx>
26 #include <vcl/gdimtf.hxx>
27 #include <vcl/bitmapaccess.hxx>
28 #include <vcl/virdev.hxx>
29 #include <vcl/metric.hxx>
30 #include <basegfx/matrix/b2dhommatrixtools.hxx>
31 #include <vcl/graphicfilter.hxx>
32 #include <vcl/graphictools.hxx>
33 #include <sal/log.hxx>
34 #include <tools/helpers.hxx>
35 #include <tools/debug.hxx>
37 #include <zlib.h>
39 #include <basegfx/polygon/b2dpolygon.hxx>
40 #include <basegfx/polygon/b2dpolypolygon.hxx>
41 #include <memory>
43 using namespace ::swf;
44 using namespace ::com::sun::star::i18n;
45 using namespace ::com::sun::star::uno;
46 using namespace ::com::sun::star::lang;
47 using namespace ::com::sun::star::io;
48 using namespace ::com::sun::star::beans;
50 static MapMode aTWIPSMode( MapUnit::MapTwip );
51 static MapMode a100thmmMode( MapUnit::Map100thMM );
54 Point Writer::map( const Point& rPoint ) const
56 const MapMode& aSourceMapMode = mpVDev->GetMapMode();
58 Point retPoint = mpVDev->LogicToLogic( rPoint, &aSourceMapMode, &aTWIPSMode );
60 // AS: Produces a 'possible loss of data' warning that we can't fix without
61 // hurting code readability.
62 retPoint.setX( static_cast<long>( retPoint.X() * mnDocXScale ) );
63 retPoint.setY( static_cast<long>( retPoint.Y() * mnDocYScale ) );
65 return retPoint;
69 Size Writer::map( const Size& rSize ) const
71 const MapMode& aSourceMapMode = mpVDev->GetMapMode();
73 Size retSize = mpVDev->LogicToLogic( rSize, &aSourceMapMode, &aTWIPSMode );
75 // AS: Produces a 'possible loss of data' warning that we can't fix without
76 // hurting code readability.
77 retSize.setWidth( static_cast<long>( retSize.Width() * mnDocXScale ) );
78 retSize.setHeight( static_cast<long>( retSize.Height() * mnDocYScale ) );
80 return retSize;
84 void Writer::map( tools::PolyPolygon& rPolyPolygon ) const
86 const sal_uInt16 nPolyCount = rPolyPolygon.Count();
87 if( nPolyCount )
89 sal_uInt16 nPoly, nPoint, nPointCount;
90 for( nPoly = 0; nPoly < nPolyCount; nPoly++ )
92 tools::Polygon& rPoly = rPolyPolygon[nPoly];
93 nPointCount = rPoly.GetSize();
95 for( nPoint = 0; nPoint < nPointCount; nPoint++ )
97 rPoly[nPoint] = map( rPoly[nPoint] );
104 sal_Int32 Writer::mapRelative( sal_Int32 n100thMM ) const
106 MapMode aSourceMapMode( mpVDev->GetMapMode() );
107 aSourceMapMode.SetOrigin( Point() );
109 sal_Int32 nTwips = mpVDev->LogicToLogic( Point( n100thMM, n100thMM ), &aSourceMapMode, &aTWIPSMode ).X();
110 return nTwips;
114 void Writer::Impl_addPolygon( BitStream& rBits, const tools::Polygon& rPoly, bool bFilled )
116 Point aLastPoint( rPoly[0] );
118 Impl_addShapeRecordChange( rBits, Int16_(aLastPoint.X()),Int16_(aLastPoint.Y()), bFilled );
120 sal_uInt16 i = 0, nSize = rPoly.GetSize();
122 double d = 16.0f;
124 // points
125 while( ( i + 1 ) < nSize )
127 if( ( i + 3 ) < nSize )
129 PolyFlags P1( rPoly.GetFlags( i ) );
130 PolyFlags P4( rPoly.GetFlags( i + 3 ) );
132 if( ( PolyFlags::Normal == P1 || PolyFlags::Smooth == P1 || PolyFlags::Symmetric == P1 ) &&
133 ( PolyFlags::Control == rPoly.GetFlags( i + 1 ) ) &&
134 ( PolyFlags::Control == rPoly.GetFlags( i + 2 ) ) &&
135 ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
137 Impl_quadBezierApprox( rBits, aLastPoint, d*d,
138 rPoly.GetPoint( i ).X(), rPoly.GetPoint( i ).Y(),
139 rPoly.GetPoint( i+1 ).X(), rPoly.GetPoint( i+1 ).Y(),
140 rPoly.GetPoint( i+2 ).X(), rPoly.GetPoint( i+2 ).Y(),
141 rPoly.GetPoint( i+3 ).X(), rPoly.GetPoint( i+3 ).Y() );
142 i += 3;
143 continue;
147 ++i;
149 const Point aPolyPoint( rPoly[ i ] );
150 if( aPolyPoint != aLastPoint )
152 Impl_addStraightEdgeRecord( rBits, Int16_(aPolyPoint.X() - aLastPoint.X()),Int16_(aPolyPoint.Y() - aLastPoint.Y()));
153 aLastPoint = aPolyPoint;
157 if( bFilled && (rPoly[0] != rPoly[nSize-1]))
159 const Point aPolyPoint( rPoly[ 0 ] );
160 if( aPolyPoint != aLastPoint )
162 Impl_addStraightEdgeRecord( rBits, Int16_(aPolyPoint.X() - aLastPoint.X()),Int16_(aPolyPoint.Y() - aLastPoint.Y()));
168 /** Exports a style change record with a move to (x,y) and depending on bFilled a line style 1 or fill style 1
170 void Writer::Impl_addShapeRecordChange( BitStream& rBits, sal_Int16 dx, sal_Int16 dy, bool bFilled )
172 rBits.writeUB( 0, 1 ); // TypeFlag
173 rBits.writeUB( 0, 1 ); // StateNewStyles
174 rBits.writeUB( sal_uInt32(!bFilled), 1 ); // StateLineStyle
175 rBits.writeUB( 0, 1 ); // StateFillStyle0
176 rBits.writeUB( bFilled ? 1 : 0, 1 ); // StateFillStyle1
177 rBits.writeUB( 1, 1 ); // StateMoveTo
179 sal_uInt16 nMoveBits = std::max( getMaxBitsSigned( dx ), getMaxBitsSigned( dy ) );
181 rBits.writeUB( nMoveBits, 5 ); // Number of bits per value
182 // TODO: Optimize horizontal and vertical lines
183 rBits.writeSB( dx, nMoveBits ); // DeltaX
184 rBits.writeSB( dy, nMoveBits ); // DeltaY
186 rBits.writeUB( 1, 1 ); // set FillStyle1 or LineStyle to 1
190 /** Exports a straight edge record
192 void Writer::Impl_addStraightEdgeRecord( BitStream& rBits, sal_Int16 dx, sal_Int16 dy )
194 rBits.writeUB( 1, 1 ); // TypeFlag
195 rBits.writeUB( 1, 1 ); // StraightFlag
197 sal_uInt16 nBits = std::max( getMaxBitsSigned( dx ), getMaxBitsSigned( dy ) );
199 rBits.writeUB( nBits - 2, 4 ); // Number of bits per value
201 if( (dx != 0) && (dy != 0) )
203 rBits.writeUB( 1, 1 ); // GeneralLineFlag
204 rBits.writeSB( dx, nBits ); // DeltaX
205 rBits.writeSB( dy, nBits ); // DeltaY
207 else
209 rBits.writeUB( 0, 1 );
210 rBits.writeUB( sal_uInt32( dx == 0 ), 1 );
211 if( dx == 0 )
213 rBits.writeSB( dy, nBits ); // DeltaY
215 else
217 rBits.writeSB( dx, nBits ); // DeltaX
223 /** Exports a curved edge record
225 void Writer::Impl_addCurvedEdgeRecord( BitStream& rBits, sal_Int16 control_dx, sal_Int16 control_dy, sal_Int16 anchor_dx, sal_Int16 anchor_dy )
227 rBits.writeUB( 1, 1 ); // TypeFlag
228 rBits.writeUB( 0, 1 ); // CurvedFlag
230 sal_uInt8 nBits = static_cast<sal_uInt8>(
231 std::max( getMaxBitsSigned( control_dx ),
232 std::max( getMaxBitsSigned( control_dy ),
233 std::max( getMaxBitsSigned( anchor_dx ),
234 std::max( getMaxBitsSigned( anchor_dy ), sal_uInt16(3) ) ) ) ) );
236 rBits.writeUB( nBits - 2, 4 ); // Number of bits per value
238 rBits.writeSB( control_dx, nBits ); // DeltaX
239 rBits.writeSB( control_dy, nBits ); // DeltaY
240 rBits.writeSB( anchor_dx, nBits ); // DeltaX
241 rBits.writeSB( anchor_dy, nBits ); // DeltaY
245 /** Exports an end shape record
247 void Writer::Impl_addEndShapeRecord( BitStream& rBits )
249 rBits.writeUB( 0, 6 );
253 void Writer::Impl_writePolygon( const tools::Polygon& rPoly, bool bFilled )
255 tools::PolyPolygon aPolyPoly( rPoly );
256 Impl_writePolyPolygon( aPolyPoly, bFilled );
260 void Writer::Impl_writePolygon( const tools::Polygon& rPoly, bool bFilled, const Color& rFillColor, const Color& rLineColor )
262 tools::PolyPolygon aPolyPoly( rPoly );
263 Impl_writePolyPolygon( aPolyPoly, bFilled, rFillColor, rLineColor );
267 void Writer::Impl_writePolyPolygon( const tools::PolyPolygon& rPolyPoly, bool bFilled, sal_uInt8 nTransparence /* = 0 */ )
269 Color aLineColor( mpVDev->GetLineColor() );
270 if( 0 == aLineColor.GetTransparency() )
271 aLineColor.SetTransparency( nTransparence );
272 Color aFillColor( mpVDev->GetFillColor() );
273 if( 0 == aFillColor.GetTransparency() )
274 aFillColor.SetTransparency( nTransparence );
275 Impl_writePolyPolygon(rPolyPoly, bFilled, aFillColor, aLineColor );
279 void Writer::Impl_writePolyPolygon( const tools::PolyPolygon& rPolyPoly, bool bFilled, const Color& rFillColor, const Color& rLineColor )
281 tools::PolyPolygon aPolyPoly( rPolyPoly );
283 if( aPolyPoly.Count() )
285 map( aPolyPoly );
287 if( mpClipPolyPolygon )
288 rPolyPoly.GetIntersection( *mpClipPolyPolygon, aPolyPoly );
290 sal_uInt16 nID;
291 if( bFilled )
293 Color aFillColor( rFillColor );
294 if( 0 != mnGlobalTransparency )
295 aFillColor.SetTransparency( mnGlobalTransparency );
297 FillStyle aStyle( aFillColor );
298 nID = defineShape( aPolyPoly, aStyle );
300 else
302 Color aLineColor( rLineColor );
303 if( 0 != mnGlobalTransparency )
304 aLineColor.SetTransparency( mnGlobalTransparency );
306 nID = defineShape( aPolyPoly, 1, aLineColor );
308 maShapeIds.push_back( nID );
313 /** A gradient is a transition from one color to another, rendered inside a given polypolygon */
314 void Writer::Impl_writeGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient )
316 if( rPolyPoly.Count() )
318 tools::PolyPolygon aPolyPolygon( rPolyPoly );
319 map( aPolyPolygon );
321 if( (rGradient.GetStyle() == GradientStyle::Linear && rGradient.GetAngle() == 900) || (rGradient.GetStyle() == GradientStyle::Radial) )
323 const tools::Rectangle aBoundRect( aPolyPolygon.GetBoundRect() );
325 FillStyle aFillStyle( aBoundRect, rGradient );
327 sal_uInt16 nShapeId = defineShape( aPolyPolygon, aFillStyle );
328 maShapeIds.push_back( nShapeId );
330 else
332 setClipping( &aPolyPolygon );
334 // render the gradient filling to simple polygons
336 GDIMetaFile aTmpMtf;
337 mpVDev->AddGradientActions( aPolyPolygon.GetBoundRect(), rGradient, aTmpMtf );
338 Impl_writeActions( aTmpMtf );
341 setClipping( nullptr );
347 void Writer::setClipping( const tools::PolyPolygon* pClipPolyPolygon )
349 mpClipPolyPolygon = pClipPolyPolygon;
353 // AS: Just comparing fonts straight up is too literal. There are some
354 // differences in font that actually require different glyphs to be defined,
355 // and some that don't. This function is meant to capture all the differences
356 // that we care about.
357 static bool compare_fonts_for_me(const vcl::Font& rFont1, const vcl::Font& rFont2)
359 return rFont1.GetFamilyName() == rFont2.GetFamilyName() &&
360 rFont1.GetWeight() == rFont2.GetWeight() &&
361 rFont1.GetItalic() == rFont2.GetItalic() &&
362 rFont1.IsOutline() == rFont2.IsOutline() &&
363 rFont1.IsShadow() == rFont2.IsShadow() &&
364 rFont1.GetRelief() == rFont2.GetRelief();
368 FlashFont& Writer::Impl_getFont( const vcl::Font& rFont )
370 for (auto const& font : maFonts)
372 const vcl::Font tempFont = font->getFont();
373 if( compare_fonts_for_me(tempFont, rFont) )
375 return *font;
379 FlashFont* pFont = new FlashFont( rFont, createID() );
380 maFonts.emplace_back( pFont );
381 return *pFont;
385 void Writer::Impl_writeText( const Point& rPos, const OUString& rText, const long* pDXArray, long nWidth )
387 const FontMetric aMetric( mpVDev->GetFontMetric() );
389 bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE);
391 if( !bTextSpecial )
393 Impl_writeText( rPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
395 else
397 if( aMetric.GetRelief() != FontRelief::NONE )
399 Color aReliefColor( COL_LIGHTGRAY );
400 Color aTextColor( mpVDev->GetTextColor() );
402 if ( aTextColor == COL_BLACK )
403 aTextColor = COL_WHITE;
405 if ( aTextColor == COL_WHITE )
406 aReliefColor = COL_BLACK;
409 Point aPos( rPos );
410 Point aOffset( 6,6 );
412 if ( aMetric.GetRelief() == FontRelief::Engraved )
414 aPos -= aOffset;
416 else
418 aPos += aOffset;
421 Impl_writeText( aPos, rText, pDXArray, nWidth, aReliefColor );
422 Impl_writeText( rPos, rText, pDXArray, nWidth, aTextColor );
424 else
426 if( aMetric.IsShadow() )
428 long nOff = 1 + ((aMetric.GetLineHeight()-24)/24);
429 if ( aMetric.IsOutline() )
430 nOff += 6;
432 Color aTextColor( mpVDev->GetTextColor() );
433 Color aShadowColor( COL_BLACK );
435 if ( (aTextColor == COL_BLACK) || (aTextColor.GetLuminance() < 8) )
436 aShadowColor = COL_LIGHTGRAY;
438 Point aPos( rPos );
439 aPos += Point( nOff, nOff );
440 Impl_writeText( aPos, rText, pDXArray, nWidth, aShadowColor );
442 if( !aMetric.IsOutline() )
444 Impl_writeText( rPos, rText, pDXArray, nWidth, aTextColor );
448 if( aMetric.IsOutline() )
450 Point aPos = rPos + Point( -6, -6 );
451 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
452 aPos = rPos + Point(+6,+6);
453 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
454 aPos = rPos + Point(-6,+0);
455 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
456 aPos = rPos + Point(-6,+6);
457 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
458 aPos = rPos + Point(+0,+6);
459 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
460 aPos = rPos + Point(+0,-6);
461 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
462 aPos = rPos + Point(+6,-1);
463 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
464 aPos = rPos + Point(+6,+0);
465 Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
467 Impl_writeText( rPos, rText, pDXArray, nWidth, COL_WHITE );
473 void Writer::Impl_writeText( const Point& rPos, const OUString& rText, const long* pDXArray, long nWidth, Color aTextColor )
475 sal_Int32 nLen = rText.getLength();
477 if( !nLen )
478 return;
480 const bool bRTL = bool(mpVDev->GetLayoutMode() & ComplexTextLayoutFlags::BiDiRtl);
482 sal_Int16 nScriptType = ScriptType::LATIN;
483 Reference < XBreakIterator > xBI( Impl_GetBreakIterator() );
484 if( xBI.is() )
486 nScriptType = xBI->getScriptType( rText, 0 );
489 // if the text is either right to left or complex or asian, we
490 // ask the output device for a polygon representation.
491 // On complex and asian text, each unicode character can have
492 // different glyph representation, based on context. Also positioning
493 // is not trivial so we let the output device do it for us.
494 if( bRTL || (nScriptType != ScriptType::LATIN) )
496 // todo: optimize me as this will generate a huge amount of duplicate polygons
497 tools::PolyPolygon aPolyPoygon;
498 mpVDev->GetTextOutline( aPolyPoygon, rText, static_cast<sal_uInt16>(nLen), nWidth, pDXArray );
499 aPolyPoygon.Translate( rPos );
500 Impl_writePolyPolygon( aPolyPoygon, true, aTextColor, aTextColor );
502 else
504 Size aNormSize;
505 std::unique_ptr<long[]> pOwnArray;
506 long* pDX;
508 // get text sizes
509 if( pDXArray )
511 aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 );
512 pDX = const_cast<long*>(pDXArray);
514 else
516 pOwnArray.reset(new long[ nLen ]);
517 aNormSize = Size( mpVDev->GetTextArray( rText, pOwnArray.get() ), 0 );
518 pDX = pOwnArray.get();
521 if( nLen > 1 )
523 aNormSize.setWidth( pDX[ nLen - 2 ] + mpVDev->GetTextWidth( OUString(rText[nLen - 1]) ) );
525 if( nWidth && aNormSize.Width() && ( nWidth != aNormSize.Width() ) )
527 const double fFactor = static_cast<double>(nWidth) / aNormSize.Width();
529 for( sal_Int32 i = 0; i < ( nLen - 1 ); i++ )
530 pDX[ i ] = FRound( pDX[ i ] * fFactor );
534 vcl::Font aOldFont( mpVDev->GetFont() );
535 Point aBaseLinePos( rPos );
537 vcl::Font aFont(aOldFont);
538 short nOrientation = aFont.GetOrientation();
539 aFont.SetOrientation( 0 );
540 aFont.SetUnderline(LINESTYLE_NONE);
541 aFont.SetStrikeout(STRIKEOUT_NONE);
542 mpVDev->SetFont( aFont );
544 const FontMetric aMetric( mpVDev->GetFontMetric() );
546 FlashFont& rFlashFont = Impl_getFont( aFont );
548 // always adjust text position to match baseline alignment
549 switch( aOldFont.GetAlignment() )
551 case ALIGN_TOP:
552 aBaseLinePos.AdjustY(aMetric.GetAscent() );
553 break;
555 case ALIGN_BOTTOM:
556 aBaseLinePos.AdjustY( -(aMetric.GetDescent()) );
557 break;
559 default:
560 break;
563 // get mapped text position
564 const Point aPt( map( aBaseLinePos ) );
566 // write text element
568 #if 0 // makes the calculated bound rect visible for debugging
570 tools::Polygon aTmpPoly( aPoly );
571 sal_uInt16 nID = FlashGeometryExporter::writePolygonShape( aMovieStream, aTmpPoly, false, COL_MAGENTA, COL_MAGENTA, mpClipPolyPolygon );
572 ImplPlaceObject( nID );
574 #endif
576 // CL: This is still a hack until we figure out how to calculate a correct bound rect
577 // for rotated text
578 tools::Rectangle textBounds( 0, 0, static_cast<long>(mnDocWidth*mnDocXScale), static_cast<long>(mnDocHeight*mnDocYScale) );
579 double scale = 1.0;
581 // scale width if we have a stretched text
582 if( 0 != aFont.GetFontSize().Width() )
584 vcl::Font aTmpFont( aFont );
585 aTmpFont.SetAverageFontWidth(0);
586 mpVDev->SetFont( aTmpFont );
588 const FontMetric aMetric2( mpVDev->GetFontMetric() );
589 mpVDev->SetFont( aFont );
591 const long n1 = aFont.GetFontSize().Width();
592 const long n2 = aMetric2.GetFontSize().Width();
593 scale = static_cast<double>(n1) / static_cast<double>(n2);
596 basegfx::B2DHomMatrix m(basegfx::utils::createRotateB2DHomMatrix(static_cast<double>(nOrientation) * F_PI1800));
597 m.translate( aPt.X() / scale, double(aPt.Y()) );
598 m.scale( scale, scale );
600 sal_Int16 nHeight = Int16_( map( Size( 0, aFont.GetFontHeight() ) ).Height() );
602 startTag( TAG_DEFINETEXT );
604 sal_uInt16 nTextId = createID();
606 mpTag->addUI16( nTextId );
607 mpTag->addRect( textBounds );
608 mpTag->addMatrix( m );
610 sal_uInt8 nGlyphBits = 16;
611 sal_uInt8 nAdvanceBits = 16;
613 mpTag->addUI8( nGlyphBits );
614 mpTag->addUI8( nAdvanceBits );
616 // text style change record
617 mpTag->addUI8( 0x8c );
618 mpTag->addUI16( rFlashFont.getID() );
619 mpTag->addRGB( aTextColor );
620 mpTag->addUI16( uInt16_( nHeight ) );
622 DBG_ASSERT( nLen <= 127, "TODO: handle text with more than 127 characters" );
624 // Glyph record
625 mpTag->addUI8( static_cast<sal_uInt8>(nLen) );
627 BitStream aBits;
629 sal_Int32 nLastDX = 0;
630 sal_Int32 nAdvance;
631 for( sal_Int32 i = 0; i < nLen; i++ )
633 if( i < (nLen-1) )
635 nAdvance = pDX[i] - nLastDX;
636 nLastDX = pDX[i];
638 else
640 nAdvance = 0;
643 aBits.writeUB( rFlashFont.getGlyph(rText[i],mpVDev), nGlyphBits );
644 aBits.writeSB( Int16_(map( Size( static_cast<long>( nAdvance / scale ), 0 ) ).Width() ), nAdvanceBits );
647 mpTag->addBits( aBits );
648 mpTag->addUI8( 0 );
650 endTag();
652 maShapeIds.push_back( nTextId );
654 // AS: Write strikeout and underline, if necessary. This code was originally taken from the SVG
655 // export facility, although the positioning had to be tweaked a little. I can't explain the
656 // numbers, but the flash lines up very well with the original OOo document. All of this should
657 // probably be converted to polygons as part of the meta file, though, as we don't handle any
658 // fancy lines (like dashes).
659 if( ( aOldFont.GetStrikeout() != STRIKEOUT_NONE ) || ( aOldFont.GetUnderline() != LINESTYLE_NONE ) )
661 tools::Polygon aPoly( 4 );
662 const long nLineHeight = std::max<long>( FRound( aMetric.GetLineHeight() * 0.05 ), 1 );
664 if( aOldFont.GetStrikeout() != STRIKEOUT_NONE )
666 aPoly[ 0 ].setX( aBaseLinePos.X() );
667 aPoly[ 0 ].setY( aBaseLinePos.Y() - FRound( aMetric.GetAscent() * 0.26 ) - nLineHeight );
668 aPoly[ 1 ].setX( aPoly[ 0 ].X() + aNormSize.Width() - 1 );
669 aPoly[ 1 ].setY( aPoly[ 0 ].Y() );
670 aPoly[ 2 ].setX( aPoly[ 1 ].X() );
671 aPoly[ 2 ].setY( aPoly[ 1 ].Y() + nLineHeight - 1 );
672 aPoly[ 3 ].setX( aPoly[ 0 ].X() );
673 aPoly[ 3 ].setY( aPoly[ 2 ].Y() );
675 Impl_writePolygon( aPoly, true, aTextColor, aTextColor );
678 // AS: The factor of 1.5 on the nLineHeight is a magic number. I'm not sure why it works,
679 // but it looks good to me.
680 if( aOldFont.GetUnderline() != LINESTYLE_NONE )
682 aPoly[ 0 ].setX( aBaseLinePos.X() );
683 aPoly[ 0 ].setY( static_cast<long>(aBaseLinePos.Y() + 1.5*nLineHeight) );
684 aPoly[ 1 ].setX( aPoly[ 0 ].X() + aNormSize.Width() - 1 );
685 aPoly[ 1 ].setY( aPoly[ 0 ].Y() );
686 aPoly[ 2 ].setX( aPoly[ 1 ].X() );
687 aPoly[ 2 ].setY( aPoly[ 1 ].Y() + nLineHeight - 1 );
688 aPoly[ 3 ].setX( aPoly[ 0 ].X() );
689 aPoly[ 3 ].setY( aPoly[ 2 ].Y() );
691 Impl_writePolygon( aPoly, true, aTextColor, aTextColor );
695 mpVDev->SetFont( aOldFont );
699 sal_uInt16 Writer::defineBitmap( const BitmapEx &bmpSource, sal_Int32 nJPEGQualityLevel )
701 BitmapChecksum bmpChecksum = bmpSource.GetChecksum();
703 ChecksumCache::iterator it = mBitmapCache.find(bmpChecksum);
705 // AS: We already exported this bitmap, so just return its ID.
706 if (mBitmapCache.end() != it)
707 return it->second;
709 sal_uInt16 nBitmapId = createID();
710 mBitmapCache[bmpChecksum] = nBitmapId;
712 // AS: OK, we have a good image, so now we decide whether or not to JPEG it or
713 // or Lossless compress it.
715 // Figure out lossless size
716 std::vector<sal_uInt8> aImageData, aAlphaData;
718 sal_uInt32 width = bmpSource.GetPrefSize().Width();
719 sal_uInt32 height = bmpSource.GetPrefSize().Height();
720 bmpSource.GetSplitData(aImageData, aAlphaData );
721 sal_uInt32 raw_size = width * height * 4;
722 uLongf compressed_size = raw_size + static_cast<sal_uInt32>(raw_size/100) + 12;
723 std::unique_ptr<sal_uInt8[]> pCompressed(new sal_uInt8[ compressed_size ]);
725 if(compress2(pCompressed.get(), &compressed_size, aImageData.data(), raw_size, Z_BEST_COMPRESSION) != Z_OK)
727 SAL_WARN( "filter.flash", "compress2 failed!" ); ((void)0);
730 // AS: SWF files let you provide an Alpha mask for JPEG images, but we have
731 // to ZLIB compress the alpha channel separately.
732 uLong alpha_compressed_size = 0;
733 std::unique_ptr<sal_uInt8[]> pAlphaCompressed;
734 if (bmpSource.IsAlpha() || bmpSource.IsTransparent())
736 alpha_compressed_size = uLongf(width * height + static_cast<sal_uInt32>(raw_size/100) + 12);
737 pAlphaCompressed.reset(new sal_uInt8[ compressed_size ]);
739 if(compress2(pAlphaCompressed.get(), &alpha_compressed_size, aAlphaData.data(), width * height, Z_BEST_COMPRESSION) != Z_OK)
741 SAL_WARN( "filter.flash", "compress2 failed!" ); ((void)0);
745 // clear these early for less peak memory usage
746 aImageData.resize(0);
747 aAlphaData.resize(0);
749 // Figure out JPEG size
750 const sal_uInt8* pJpgData = nullptr;
751 sal_uInt32 nJpgDataLength = 0xffffffff;
753 Graphic aGraphic( bmpSource );
754 SvMemoryStream aDstStm( 65535, 65535 );
756 GraphicFilter aFilter;
758 Sequence< PropertyValue > aFilterData(sal_Int32(nJPEGQualityLevel != -1));
759 if( nJPEGQualityLevel != -1 )
761 aFilterData[0].Name = "Quality";
762 aFilterData[0].Value <<= nJPEGQualityLevel;
765 if( aFilter.ExportGraphic( aGraphic, OUString(), aDstStm,
766 aFilter.GetExportFormatNumberForShortName( JPG_SHORTNAME ), &aFilterData ) == ERRCODE_NONE )
768 pJpgData = static_cast<const sal_uInt8*>(aDstStm.GetData());
769 nJpgDataLength = aDstStm.TellEnd();
772 // AS: Ok, now go ahead and use whichever is smaller. If JPEG is smaller, then
773 // we have to export as TAG_DEFINEBITSJPEG3 in the case that there is alpha
774 // channel data.
775 if ( pJpgData && ( nJpgDataLength + alpha_compressed_size < compressed_size) )
776 Impl_writeJPEG(nBitmapId, pJpgData, nJpgDataLength, pAlphaCompressed.get(), alpha_compressed_size );
777 else
778 Impl_writeBmp( nBitmapId, width, height, pCompressed.get(), compressed_size );
780 return nBitmapId;
784 void Writer::Impl_writeImage( const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, const Point& /* rSrcPt */, const Size& /* rSrcSz */, const tools::Rectangle& rClipRect, bool bNeedToMapClipRect )
786 if( !!rBmpEx )
788 BitmapEx bmpSource( rBmpEx );
790 tools::Rectangle originalPixelRect(Point(), bmpSource.GetSizePixel());
792 Point srcPt( map(rPt) );
793 Size srcSize( map(rSz) );
794 tools::Rectangle destRect( srcPt, srcSize );
796 // AS: Christian, my scaling factors are different than yours, and work better for me.
797 // However, I can't explain why exactly. I got some of this by trial and error.
798 double XScale = destRect.GetWidth() ? static_cast<double>(originalPixelRect.GetWidth())/destRect.GetWidth() : 1.0;
799 double YScale = destRect.GetHeight() ? static_cast<double>(originalPixelRect.GetHeight())/destRect.GetHeight() : 1.0;
801 // AS: If rClipRect has a value set, then we need to crop the bmp appropriately.
802 // If a map event already occurred in the metafile, then we do not need to map
803 // the clip rect as it's already been done.
804 if (!rClipRect.IsEmpty())
806 // AS: Christian, I also don't understand why bNeedToMapClipRect is necessary, but it
807 // works like a charm. Usually, the map event in the meta file does not cause the
808 // clipping rectangle to get mapped. However, sometimes there are multiple layers
809 // of mapping which eventually do cause the clipping rect to be mapped.
810 Size clipSize( bNeedToMapClipRect ? map(rClipRect.GetSize()) : rClipRect.GetSize() );
811 tools::Rectangle clipRect(Point(), clipSize);
812 destRect.Intersection( clipRect );
814 tools::Rectangle cropRect(destRect);
816 // AS: The bmp origin is always 0,0 so we have to adjust before we crop.
817 cropRect.Move(-srcPt.X(), -srcPt.Y());
818 // AS: Rectangle has no scale function (?!) so I do it manually...
819 tools::Rectangle cropPixelRect(static_cast<long>(cropRect.Left()*XScale),
820 static_cast<long>(cropRect.Top()*YScale),
821 static_cast<long>(cropRect.Right()*XScale),
822 static_cast<long>(cropRect.Bottom()*YScale));
824 bmpSource.Crop(cropPixelRect);
827 if( !!bmpSource )
829 // #105949# fix images that are under 16 pixels width or height by
830 // expanding them. Some swf players can't display such small
831 // bitmaps
832 const Size& rSizePixel = bmpSource.GetSizePixel();
833 if( (rSizePixel.Width() < 16) || (rSizePixel.Height() < 16) )
835 const sal_uInt32 nDX = rSizePixel.Width() < 16 ? 16 - rSizePixel.Width() : 0;
836 const sal_uInt32 nDY = rSizePixel.Height() < 16 ? 16 - rSizePixel.Height() : 0;
837 bmpSource.Expand( nDX, nDY );
840 sal_Int32 nJPEGQuality = mnJPEGCompressMode;
842 Size szDestPixel = mpVDev->LogicToPixel(srcSize, aTWIPSMode);
844 double pixXScale = originalPixelRect.GetWidth() ? static_cast<double>(szDestPixel.Width()) / originalPixelRect.GetWidth() : 1.0;
845 double pixYScale = originalPixelRect.GetHeight() ? static_cast<double>(szDestPixel.Height()) / originalPixelRect.GetHeight() : 1.0;
847 // AS: If the image has been scaled down, then scale down the quality
848 // that we use for JPEG compression.
849 if (pixXScale < 1.0 && pixYScale < 1.0)
852 double qualityScale = (pixXScale + pixYScale)/2;
854 nJPEGQuality = static_cast<sal_Int32>( nJPEGQuality * qualityScale );
856 if (nJPEGQuality < 10)
857 nJPEGQuality += 3;
860 sal_uInt16 nBitmapId = defineBitmap(bmpSource, nJPEGQuality);
862 tools::Polygon aPoly( destRect );
864 // AS: Since images are being cropped now, no translation is normally necessary.
865 // However, some things like graphical bullet points still get translated.
866 ::basegfx::B2DHomMatrix m; // #i73264#
867 m.scale(1.0/XScale, 1.0/YScale );
868 if (destRect.Left() || destRect.Top())
869 m.translate(destRect.Left(), destRect.Top());
871 FillStyle aFillStyle( nBitmapId, true, m );
873 sal_uInt16 nShapeId = defineShape( aPoly, aFillStyle );
875 maShapeIds.push_back( nShapeId );
881 void Writer::Impl_writeBmp( sal_uInt16 nBitmapId, sal_uInt32 width, sal_uInt32 height, sal_uInt8 const *pCompressed, sal_uInt32 compressed_size )
883 startTag( TAG_DEFINEBITSLOSSLESS2 );
885 mpTag->addUI16( nBitmapId );
886 mpTag->addUI8( 5 );
887 mpTag->addUI16( uInt16_(width) );
888 mpTag->addUI16( uInt16_(height) );
890 mpTag->WriteBytes(pCompressed, compressed_size);
892 endTag();
896 void Writer::Impl_writeJPEG(sal_uInt16 nBitmapId, const sal_uInt8* pJpgData, sal_uInt32 nJpgDataLength, sal_uInt8 const *pAlphaCompressed, sal_uInt32 alpha_compressed_size )
898 // AS: Go through the actual JPEG bits, separating out the
899 // header fields from the actual image fields. Fields are
900 // identified by 0xFFXX where XX is the field type. Both
901 // the header and the image need start and stop (D8 and D9),
902 // so that's why you see those written to both. I don't
903 // really know what the rest of these are, I got it to work
904 // kind of by trial and error and by comparing with known
905 // good SWF files.
906 sal_uInt8 cType = 0x01;
907 const sal_uInt8* pJpgSearch = pJpgData;
909 int nLength = 0;
911 SvMemoryStream EncodingTableStream;
912 SvMemoryStream ImageBitsStream;
913 for (;pJpgSearch < pJpgData + nJpgDataLength; pJpgSearch += nLength)
916 #ifdef DBG_UTIL
917 if (0xFF != *pJpgSearch)
919 OSL_FAIL( "Expected JPEG marker." ); ((void)0);
921 #endif
923 cType = *(pJpgSearch + 1);
925 if (0xD8 == cType || 0xD9 == cType)
927 nLength = 2;
929 else if (0xDA == cType)
931 //AS: This is the actual image data, and runs to the
932 // end of the file (as best I know), minus 2 bytes
933 // for the closing 0xFFD9.
934 nLength = nJpgDataLength - (pJpgSearch - pJpgData) - 2;
936 else
938 // AS: Lengths are big endian.
940 // Beware. pJpgSearch is not necessarily word-aligned,
941 // so we access it byte-wise.
943 // AS: Add 2 to the length to include the 0xFFXX itself.
944 nLength = 2 + (pJpgSearch[2]<<8) + pJpgSearch[3];
947 // AS: I'm referring to libjpeg for a list of what these
948 // markers are. See jdmarker.c for a list.
949 // AS: I'm ignoring application specific markers 0xE1...0xEF
950 // and comments 0xFE. I don't know what
951 // 0xF0 or 0xFD are for, and they don't come up.
952 // Additionally, 0xDE and 0xDF aren't clear to me.
953 switch(cType)
955 case 0xD8:
956 case 0xD9:
957 EncodingTableStream.WriteBytes(pJpgSearch, nLength);
958 ImageBitsStream.WriteBytes(pJpgSearch, nLength);
959 break;
961 case 0x01:
962 case 0xDB:
963 case 0xDC:
964 case 0xDD:
965 case 0xC4:
966 EncodingTableStream.WriteBytes(pJpgSearch, nLength);
967 break;
969 case 0xC0:
970 case 0xC1:
971 case 0xC2:
972 case 0xC3:
973 case 0xC5:
974 case 0xC6:
975 case 0xC7:
976 // case 0xC8: Apparently reserved for JPEG extensions?
977 case 0xC9:
978 case 0xCA:
979 case 0xCB:
980 case 0xCD:
981 case 0xCE:
982 case 0xCF:
983 case 0xDA:
984 case 0xE0:
985 ImageBitsStream.WriteBytes(pJpgSearch, nLength);
986 break;
988 default:
989 OSL_FAIL( "JPEG marker I didn't handle!" );
994 sal_uInt32 nEncodingTableSize = EncodingTableStream.TellEnd();
995 EncodingTableStream.Seek( STREAM_SEEK_TO_BEGIN );
997 sal_uInt32 nImageBitsSize = ImageBitsStream.TellEnd();
998 ImageBitsStream.Seek( STREAM_SEEK_TO_BEGIN );
1000 // AS: If we need alpha support, use TAG_DEFINEBITSJPEG3.
1001 if (alpha_compressed_size > 0)
1003 startTag( TAG_DEFINEBITSJPEG3 );
1005 mpTag->addUI16( nBitmapId );
1007 mpTag->addUI32( nEncodingTableSize + nImageBitsSize );
1009 mpTag->WriteBytes(EncodingTableStream.GetData(), nEncodingTableSize);
1010 mpTag->WriteBytes(ImageBitsStream.GetData(), nImageBitsSize);
1012 mpTag->WriteBytes(pAlphaCompressed, alpha_compressed_size);
1014 endTag();
1016 else
1018 startTag( TAG_DEFINEBITSJPEG2 );
1020 mpTag->addUI16( nBitmapId );
1022 mpTag->WriteBytes(EncodingTableStream.GetData(), nEncodingTableSize);
1023 mpTag->WriteBytes(ImageBitsStream.GetData(), nImageBitsSize);
1025 endTag();
1030 void Writer::Impl_writeLine( const Point& rPt1, const Point& rPt2, const Color* pLineColor )
1032 Color aOldColor( mpVDev->GetLineColor() );
1033 if( pLineColor )
1034 mpVDev->SetLineColor( *pLineColor );
1036 const Point aPtAry[2] = { rPt1, rPt2 };
1037 tools::Polygon aPoly( 2, aPtAry );
1038 Impl_writePolyPolygon( aPoly, false );
1040 mpVDev->SetLineColor( aOldColor );
1044 void Writer::Impl_writeRect( const tools::Rectangle& rRect, long nRadX, long nRadY )
1046 if( (rRect.Top() == rRect.Bottom()) || (rRect.Left() == rRect.Right()) )
1048 Color aColor( mpVDev->GetFillColor() );
1049 Impl_writeLine( rRect.TopLeft(), rRect.BottomRight(), &aColor );
1051 else
1053 tools::Polygon aPoly( rRect, nRadX, nRadY );
1054 Impl_writePolyPolygon( aPoly, true );
1059 void Writer::Impl_writeEllipse( const Point& rCenter, long nRadX, long nRadY )
1061 tools::Polygon aPoly( rCenter, nRadX, nRadY );
1062 Impl_writePolyPolygon( aPoly, false );
1066 /** Writes the stroke defined by SvtGraphicStroke and returns true or it returns
1067 false if it can't handle this stroke.
1069 bool Writer::Impl_writeStroke( SvtGraphicStroke const & rStroke )
1071 tools::Polygon aPolygon;
1072 rStroke.getPath( aPolygon );
1073 tools::PolyPolygon aPolyPolygon( aPolygon );
1075 map( aPolyPolygon );
1077 // as log as not LINESTYLE2 and DefineShape4 is used (which
1078 // added support for LineJoin), only round LineJoins are
1079 // supported. Fallback to MetaActionType::POLYLINE and MetaActionType::LINE
1080 if(SvtGraphicStroke::joinRound != rStroke.getJoinType())
1081 return false;
1083 tools::PolyPolygon aStartArrow;
1084 rStroke.getStartArrow( aStartArrow );
1085 if( 0 != aStartArrow.Count() )
1086 return false; // todo: Implement line ends
1088 tools::PolyPolygon aEndArrow;
1089 rStroke.getEndArrow( aEndArrow );
1090 if( 0 != aEndArrow.Count() )
1091 return false; // todo: Implement line ends
1093 SvtGraphicStroke::DashArray aDashArray;
1094 rStroke.getDashArray( aDashArray );
1095 if( !aDashArray.empty() )
1096 return false; // todo: implement dashes
1098 Color aColor( mpVDev->GetLineColor() );
1100 if( 0.0 != rStroke.getTransparency() )
1101 aColor.SetTransparency( sal::static_int_cast<sal_uInt8>( MinMax( static_cast<long int>( rStroke.getTransparency() * 0xff ), 0, 0xff ) ) );
1103 sal_uInt16 nShapeId = defineShape( aPolyPolygon, sal::static_int_cast<sal_uInt16>( mapRelative( static_cast<sal_Int32>( rStroke.getStrokeWidth() ) ) ), aColor );
1104 maShapeIds.push_back( nShapeId );
1105 return true;
1109 /** Writes the filling defined by SvtGraphicFill and returns true or it returns
1110 false if it can't handle this filling.
1112 bool Writer::Impl_writeFilling( SvtGraphicFill const & rFilling )
1114 tools::PolyPolygon aPolyPolygon;
1115 rFilling.getPath( aPolyPolygon );
1117 tools::Rectangle aOldRect( aPolyPolygon.GetBoundRect() );
1119 map( aPolyPolygon );
1121 tools::Rectangle aNewRect( aPolyPolygon.GetBoundRect() );
1123 switch( rFilling.getFillType() )
1125 case SvtGraphicFill::fillSolid:
1127 Color aColor( rFilling.getFillColor() );
1129 if( 0.0 != rFilling.getTransparency() )
1130 aColor.SetTransparency( sal::static_int_cast<sal_uInt8>( MinMax( static_cast<long int>( rFilling.getTransparency() * 0xff ) , 0, 0xff ) ) );
1132 FillStyle aFillStyle( aColor );
1134 sal_uInt16 nShapeId = defineShape( aPolyPolygon, aFillStyle );
1135 maShapeIds.push_back( nShapeId );
1137 break;
1138 case SvtGraphicFill::fillGradient:
1139 return false;
1140 case SvtGraphicFill::fillHatch:
1141 return false;
1142 case SvtGraphicFill::fillTexture:
1144 Graphic aGraphic;
1145 rFilling.getGraphic( aGraphic );
1147 // CL->AS: Should we also scale down the quality here depending on image scale?
1148 sal_uInt16 nBitmapId = defineBitmap( aGraphic.GetBitmapEx(), mnJPEGCompressMode );
1150 ::basegfx::B2DHomMatrix aMatrix; // #i73264#
1152 SvtGraphicFill::Transform aTransform;
1154 rFilling.getTransform( aTransform );
1156 sal_uInt16 a,b;
1157 for( a = 0; a < 2; a++ )
1159 for( b = 0; b < 3; b++ )
1161 aMatrix.set(a, b, aTransform.matrix[a*3+b]);
1164 aMatrix.set(2, 0, 0.0);
1165 aMatrix.set(2, 1, 0.0);
1166 aMatrix.set(2, 2, 1.0);
1168 // scale bitmap
1169 double XScale = aOldRect.GetWidth() ? static_cast<double>(aNewRect.GetWidth())/aOldRect.GetWidth() : 1.0;
1170 double YScale = aOldRect.GetHeight() ? static_cast<double>(aNewRect.GetHeight())/aOldRect.GetHeight() : 1.0;
1172 aMatrix.scale( XScale, YScale );
1174 FillStyle aFillStyle( nBitmapId, !rFilling.IsTiling(), aMatrix );
1176 sal_uInt16 nShapeId = defineShape( aPolyPolygon, aFillStyle );
1177 maShapeIds.push_back( nShapeId );
1179 break;
1181 return true;
1185 /* CL: The idea was to export page fields as text fields that get their
1186 string from a variable set with actionscript by each page. This didn't
1187 work out since the formatting is always wrong when text follows the
1188 page number field since pages greater one may require more space than
1189 page 1
1191 #if 0
1192 bool Writer::Impl_writePageField( Rectangle& rTextBounds )
1194 startTag( TAG_DEFINEEDITTEXT );
1196 sal_uInt16 nTextId = createID();
1198 mpTag->addUI16( nTextId );
1199 mpTag->addRect( rTextBounds );
1201 BitStream aBits;
1202 aBits.writeUB( 1, 1 ); // HasText
1203 aBits.writeUB( 0, 1 ); // WordWrap
1204 aBits.writeUB( 0, 1 ); // MultiLine
1205 aBits.writeUB( 0, 1 ); // Password
1206 aBits.writeUB( 1, 1 ); // HasTextColor
1207 aBits.writeUB( 0, 1 ); // HasMaxLength
1208 aBits.writeUB( 0, 1 ); // HasFont
1209 aBits.writeUB( 0, 1 ); // Reserved
1210 aBits.writeUB( 0, 1 ); // AutoSize
1211 aBits.writeUB( 0, 1 ); // HasLayout
1212 aBits.writeUB( 1, 1 ); // NoSelect
1213 aBits.writeUB( 1, 1 ); // Border
1214 aBits.writeUB( 0, 1 ); // Reserved
1215 aBits.writeUB( 0, 1 ); // HTML
1216 aBits.writeUB( 0, 1 ); // UseOutlines
1217 mpTag->addBits( aBits );
1219 Color aCOL_BLACK );
1220 mpTag->addRGB( aColor );
1221 mpTag->addString( "PageNumber" );
1222 mpTag->addString( "XXX" );
1224 endTag();
1226 maShapeIds.push_back( nTextId );
1228 return true;
1230 #endif
1233 void Writer::Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon)
1235 if(rLinePolygon.count())
1237 basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon);
1238 basegfx::B2DPolyPolygon aFillPolyPolygon;
1240 rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon);
1242 if(aLinePolyPolygon.count())
1244 for(sal_uInt32 a(0); a < aLinePolyPolygon.count(); a++)
1246 const basegfx::B2DPolygon& aCandidate(aLinePolyPolygon.getB2DPolygon(a));
1247 Impl_writePolygon( tools::Polygon(aCandidate), false );
1251 if(aFillPolyPolygon.count())
1253 const Color aOldLineColor(mpVDev->GetLineColor());
1254 const Color aOldFillColor(mpVDev->GetFillColor());
1256 mpVDev->SetLineColor();
1257 mpVDev->SetFillColor(aOldLineColor);
1259 for(sal_uInt32 a(0); a < aFillPolyPolygon.count(); a++)
1261 const tools::Polygon aPolygon(aFillPolyPolygon.getB2DPolygon(a));
1262 Impl_writePolyPolygon(tools::PolyPolygon(aPolygon), true );
1265 mpVDev->SetLineColor(aOldLineColor);
1266 mpVDev->SetFillColor(aOldFillColor);
1272 void Writer::Impl_writeActions( const GDIMetaFile& rMtf )
1274 tools::Rectangle clipRect;
1275 int bMap = 0;
1276 for( size_t i = 0, nCount = rMtf.GetActionSize(); i < nCount; i++ )
1278 const MetaAction* pAction = rMtf.GetAction( i );
1279 const MetaActionType nType = pAction->GetType();
1281 switch( nType )
1283 case MetaActionType::PIXEL:
1285 const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
1287 Impl_writeLine( pA->GetPoint(), pA->GetPoint(), &pA->GetColor() );
1289 break;
1291 case MetaActionType::POINT:
1293 const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
1295 Impl_writeLine( pA->GetPoint(), pA->GetPoint() );
1297 break;
1299 case MetaActionType::LINE:
1301 const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
1303 if(pA->GetLineInfo().IsDefault())
1305 Impl_writeLine( pA->GetStartPoint(), pA->GetEndPoint() );
1307 else
1309 // LineInfo used; handle Dash/Dot and fat lines
1310 basegfx::B2DPolygon aPolygon;
1311 aPolygon.append(basegfx::B2DPoint(pA->GetStartPoint().X(), pA->GetStartPoint().Y()));
1312 aPolygon.append(basegfx::B2DPoint(pA->GetEndPoint().X(), pA->GetEndPoint().Y()));
1313 Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon);
1316 break;
1318 case MetaActionType::RECT:
1320 Impl_writeRect( static_cast<const MetaRectAction*>(pAction)->GetRect(), 0, 0 );
1322 break;
1324 case MetaActionType::ROUNDRECT:
1326 const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
1328 Impl_writeRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
1330 break;
1332 case MetaActionType::ELLIPSE:
1334 const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
1335 const tools::Rectangle& rRect = pA->GetRect();
1337 Impl_writeEllipse( rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1 );
1339 break;
1341 case MetaActionType::ARC:
1342 case MetaActionType::PIE:
1343 case MetaActionType::CHORD:
1344 case MetaActionType::POLYGON:
1346 tools::Polygon aPoly;
1348 switch( nType )
1350 case MetaActionType::ARC:
1352 const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
1353 aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc );
1355 break;
1357 case MetaActionType::PIE:
1359 const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction);
1360 aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie );
1362 break;
1364 case MetaActionType::CHORD:
1366 const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
1367 aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord );
1369 break;
1371 case MetaActionType::POLYGON:
1372 aPoly = static_cast<const MetaPolygonAction*>(pAction)->GetPolygon();
1373 break;
1374 default: break;
1377 if( aPoly.GetSize() )
1379 Impl_writePolygon( aPoly, true );
1382 break;
1384 case MetaActionType::POLYLINE:
1386 const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
1387 const tools::Polygon& rPoly = pA->GetPolygon();
1389 if( rPoly.GetSize() )
1391 if(pA->GetLineInfo().IsDefault())
1393 Impl_writePolygon( rPoly, false );
1395 else
1397 // LineInfo used; handle Dash/Dot and fat lines
1398 Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon());
1402 break;
1404 case MetaActionType::POLYPOLYGON:
1406 const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
1407 const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
1409 if( rPolyPoly.Count() )
1410 Impl_writePolyPolygon( rPolyPoly, true );
1412 break;
1414 case MetaActionType::GRADIENT:
1416 const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
1418 tools::Polygon aPoly( pA->GetRect() );
1419 Impl_writeGradientEx( aPoly, pA->GetGradient() );
1421 break;
1423 case MetaActionType::GRADIENTEX:
1425 const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction);
1426 Impl_writeGradientEx( pA->GetPolyPolygon(), pA->GetGradient() );
1428 break;
1430 case MetaActionType::HATCH:
1432 const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
1433 GDIMetaFile aTmpMtf;
1435 mpVDev->AddHatchActions( pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf );
1436 Impl_writeActions( aTmpMtf );
1438 break;
1440 case MetaActionType::Transparent:
1442 const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
1443 const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
1445 if( rPolyPoly.Count() )
1447 // convert transparence from percent into 0x00 - 0xff
1448 sal_uInt8 nTransparence = static_cast<sal_uInt8>(MinMax( FRound( pA->GetTransparence() * 2.55 ), 0, 255 ));
1449 Impl_writePolyPolygon( rPolyPoly, true, nTransparence );
1452 break;
1454 case MetaActionType::FLOATTRANSPARENT:
1456 const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
1457 GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
1458 Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() );
1459 const Size aSrcSize( aTmpMtf.GetPrefSize() );
1460 const Point aDestPt( pA->GetPoint() );
1461 const Size aDestSize( pA->GetSize() );
1462 const double fScaleX = aSrcSize.Width() ? static_cast<double>(aDestSize.Width()) / aSrcSize.Width() : 1.0;
1463 const double fScaleY = aSrcSize.Height() ? static_cast<double>(aDestSize.Height()) / aSrcSize.Height() : 1.0;
1464 long nMoveX, nMoveY;
1466 if( fScaleX != 1.0 || fScaleY != 1.0 )
1468 aTmpMtf.Scale( fScaleX, fScaleY );
1469 aSrcPt.setX( FRound( aSrcPt.X() * fScaleX ) );
1470 aSrcPt.setY( FRound( aSrcPt.Y() * fScaleY ) );
1473 nMoveX = aDestPt.X() - aSrcPt.X();
1474 nMoveY = aDestPt.Y() - aSrcPt.Y();
1476 if( nMoveX || nMoveY )
1477 aTmpMtf.Move( nMoveX, nMoveY );
1479 const Gradient& rGradient = pA->GetGradient();
1480 sal_uInt32 nLuminance = (static_cast<sal_Int32>(rGradient.GetStartColor().GetLuminance()) + static_cast<sal_Int32>(rGradient.GetEndColor().GetLuminance()) ) >> 1;
1482 sal_uInt8 nOldGlobalTransparency = mnGlobalTransparency;
1483 mnGlobalTransparency = static_cast<sal_uInt8>(MinMax( nLuminance, 0, 0xff ));
1485 mpVDev->Push();
1486 Impl_writeActions( aTmpMtf );
1487 mpVDev->Pop();
1489 mnGlobalTransparency = nOldGlobalTransparency;
1491 break;
1493 case MetaActionType::EPS:
1495 const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
1496 const GDIMetaFile& aGDIMetaFile( pA->GetSubstitute() );
1497 bool bFound = false;
1499 for( size_t j = 0, nC = aGDIMetaFile.GetActionSize(); ( j < nC ) && !bFound; j++ )
1501 const MetaAction* pSubstAct = aGDIMetaFile.GetAction( j );
1503 if( pSubstAct->GetType() == MetaActionType::BMPSCALE )
1505 bFound = true;
1506 const MetaBmpScaleAction* pBmpScaleAction = static_cast<const MetaBmpScaleAction*>(pSubstAct);
1507 Impl_writeImage( BitmapEx(pBmpScaleAction->GetBitmap()),
1508 pA->GetPoint(), pA->GetSize(),
1509 Point(), pBmpScaleAction->GetBitmap().GetSizePixel(), clipRect, 1 == bMap );
1513 break;
1515 case MetaActionType::COMMENT:
1517 const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
1518 const sal_uInt8* pData = pA->GetData();
1520 if( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") )
1522 const MetaGradientExAction* pGradAction = nullptr;
1523 bool bDone = false;
1525 while( !bDone && ( ++i < nCount ) )
1527 pAction = rMtf.GetAction( i );
1529 if( pAction->GetType() == MetaActionType::GRADIENTEX )
1530 pGradAction = static_cast<const MetaGradientExAction*>(pAction);
1531 else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
1532 ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) )
1534 bDone = true;
1538 if( pGradAction )
1539 Impl_writeGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient());
1541 else if( pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") && pData )
1544 // this comment encapsulates all high level information for a filling that caused
1545 // the meta actions between the "XPATHFILL_SEQ_BEGIN" and "XPATHFILL_SEQ_END" comment.
1547 SvtGraphicFill aFilling;
1548 SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ );
1550 // read the fill info
1551 ReadSvtGraphicFill( aMemStm, aFilling );
1553 // if impl_writeFilling can handle this high level filling, it returns true and we
1554 // skip all meta actions until "XPATHFILL_SEQ_END"
1555 if( Impl_writeFilling( aFilling ) )
1557 bool bDone = false;
1559 while( !bDone && ( ++i < nCount ) )
1561 pAction = rMtf.GetAction( i );
1563 if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
1564 ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) )
1566 bDone = true;
1571 else if( pA->GetComment().equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN") && pData )
1574 // this comment encapsulates all high level information for a filling that caused
1575 // the meta actions between the "XPATHFILL_SEQ_BEGIN" and "XPATHFILL_SEQ_END" comment.
1577 SvtGraphicStroke aStroke;
1578 SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pA->GetDataSize(), StreamMode::READ );
1580 // read the fill info
1581 ReadSvtGraphicStroke( aMemStm, aStroke );
1583 // if impl_writeStroke can handle this high level stroke, it returns true and we
1584 // skip all meta actions until "XPATHSTROKE_SEQ_END"
1585 if( Impl_writeStroke( aStroke ) )
1587 bool bDone = false;
1589 while( !bDone && ( ++i < nCount ) )
1591 pAction = rMtf.GetAction( i );
1593 if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
1594 ( static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_END") ) )
1596 bDone = true;
1602 break;
1604 case MetaActionType::BMPSCALE:
1606 const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
1608 Impl_writeImage( BitmapEx(pA->GetBitmap()),
1609 pA->GetPoint(), pA->GetSize(),
1610 Point(), pA->GetBitmap().GetSizePixel(), clipRect, 1 == bMap );
1612 break;
1614 case MetaActionType::BMP:
1616 const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
1617 Impl_writeImage( BitmapEx(pA->GetBitmap()),
1618 pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmap().GetSizePixel()),
1619 Point(), pA->GetBitmap().GetSizePixel(), clipRect, 1 ==bMap );
1621 break;
1623 case MetaActionType::BMPSCALEPART:
1625 const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
1626 Impl_writeImage( BitmapEx(pA->GetBitmap()),
1627 pA->GetDestPoint(), pA->GetDestSize(),
1628 pA->GetSrcPoint(), pA->GetSrcSize(), clipRect, 1 == bMap );
1630 break;
1632 case MetaActionType::BMPEX:
1634 const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
1635 Impl_writeImage( pA->GetBitmapEx(),
1636 pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmapEx().GetSizePixel() ),
1637 Point(), pA->GetBitmapEx().GetSizePixel(), clipRect, 1 == bMap );
1639 break;
1641 case MetaActionType::BMPEXSCALE:
1643 const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
1644 Impl_writeImage( pA->GetBitmapEx(),
1645 pA->GetPoint(), pA->GetSize(),
1646 Point(), pA->GetBitmapEx().GetSizePixel(), clipRect, 1 == bMap );
1648 break;
1650 case MetaActionType::BMPEXSCALEPART:
1652 const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
1653 Impl_writeImage( pA->GetBitmapEx(),
1654 pA->GetDestPoint(), pA->GetDestSize(),
1655 pA->GetSrcPoint(), pA->GetSrcSize(), clipRect, 1 == bMap );
1657 break;
1659 case MetaActionType::TEXT:
1661 const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
1662 Impl_writeText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), pA->GetLen() ), nullptr, 0);
1664 break;
1666 case MetaActionType::TEXTRECT:
1668 const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
1669 Impl_writeText( pA->GetRect().TopLeft(), pA->GetText(), nullptr, 0 );
1671 break;
1673 case MetaActionType::TEXTARRAY:
1675 const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
1676 Impl_writeText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), pA->GetLen() ), pA->GetDXArray(), 0 );
1678 break;
1680 case MetaActionType::STRETCHTEXT:
1682 const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
1683 Impl_writeText( pA->GetPoint(), pA->GetText().copy( pA->GetIndex(), pA->GetLen() ), nullptr, pA->GetWidth() );
1685 break;
1687 case MetaActionType::ISECTRECTCLIPREGION:
1689 const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction);
1690 clipRect = pA->GetRect();
1691 [[fallthrough]];
1693 case MetaActionType::CLIPREGION:
1694 case MetaActionType::ISECTREGIONCLIPREGION:
1695 case MetaActionType::MOVECLIPREGION:
1697 const_cast<MetaAction*>(pAction)->Execute( mpVDev );
1699 break;
1701 case MetaActionType::MAPMODE:
1703 bMap++;
1704 [[fallthrough]];
1706 case MetaActionType::REFPOINT:
1707 case MetaActionType::LINECOLOR:
1708 case MetaActionType::FILLCOLOR:
1709 case MetaActionType::TEXTLINECOLOR:
1710 case MetaActionType::TEXTFILLCOLOR:
1711 case MetaActionType::TEXTCOLOR:
1712 case MetaActionType::TEXTALIGN:
1713 case MetaActionType::FONT:
1714 case MetaActionType::PUSH:
1715 case MetaActionType::POP:
1716 case MetaActionType::LAYOUTMODE:
1718 const_cast<MetaAction*>(pAction)->Execute( mpVDev );
1720 break;
1722 case MetaActionType::RASTEROP:
1723 case MetaActionType::MASK:
1724 case MetaActionType::MASKSCALE:
1725 case MetaActionType::MASKSCALEPART:
1726 case MetaActionType::WALLPAPER:
1727 case MetaActionType::TEXTLINE:
1729 // !!! >>> we don't want to support these actions
1731 break;
1733 default:
1734 break;
1740 void Writer::Impl_addStraightLine( BitStream& rBits, Point& rLastPoint,
1741 const double P2x, const double P2y )
1743 Point aPoint( FRound(P2x), FRound(P2y) );
1745 Impl_addStraightEdgeRecord( rBits, Int16_(aPoint.X() - rLastPoint.X()),Int16_(aPoint.Y() - rLastPoint.Y()));
1746 rLastPoint = aPoint;
1751 void Writer::Impl_addQuadBezier( BitStream& rBits, Point& rLastPoint,
1752 const double P2x, const double P2y,
1753 const double P3x, const double P3y )
1756 Point aControlPoint( FRound(P2x), FRound(P2y) );
1757 Point aAnchorPoint( FRound(P3x), FRound(P3y) );
1759 Impl_addCurvedEdgeRecord( rBits,
1760 Int16_(aControlPoint.X() - rLastPoint.X()),Int16_(aControlPoint.Y() - rLastPoint.Y()),
1761 Int16_(aAnchorPoint.X() - aControlPoint.X()),Int16_(aAnchorPoint.Y() - aControlPoint.Y()) );
1762 rLastPoint = aAnchorPoint;
1766 /* Approximate given cubic bezier curve by quadratic bezier segments */
1767 void Writer::Impl_quadBezierApprox( BitStream& rBits,
1768 Point& rLastPoint,
1769 const double d2,
1770 const double P1x, const double P1y,
1771 const double P2x, const double P2y,
1772 const double P3x, const double P3y,
1773 const double P4x, const double P4y )
1775 // Check for degenerate case, where the given cubic bezier curve
1776 // is already quadratic: P4 == 3P3 - 3P2 + P1
1777 if( P4x == 3.0*P3x - 3.0*P2x + P1x &&
1778 P4y == 3.0*P3y - 3.0*P2y + P1y )
1780 Impl_addQuadBezier( rBits, rLastPoint,
1781 3.0/2.0*P2x - 1.0/2.0*P1x, 3.0/2.0*P2y - 1.0/2.0*P1y,
1782 P4x, P4y);
1784 else
1786 // Create quadratic segment for given cubic:
1787 // Start and end point must coincide, determine quadratic control
1788 // point in such a way that it lies on the intersection of the
1789 // tangents at start and end point, resp. Thus, both cubic and
1790 // quadratic curve segments will match in 0th and 1st derivative
1791 // at the start and end points
1793 // Intersection of P2P1 and P4P3
1794 // (P2y-P4y)(P3x-P4x)-(P2x-P4x)(P3y-P4y)
1795 // lambda = -------------------------------------
1796 // (P1x-P2x)(P3y-P4y)-(P1y-P2y)(P3x-P4x)
1798 // Intersection point IP is now
1799 // IP = P2 + lambda(P1-P2)
1801 const double nominator( (P2y-P4y)*(P3x-P4x) - (P2x-P4x)*(P3y-P4y) );
1802 const double denominator( (P1x-P2x)*(P3y-P4y) - (P1y-P2y)*(P3x-P4x) );
1803 const double lambda( nominator / denominator );
1805 const double IPx( P2x + lambda*( P1x - P2x) );
1806 const double IPy( P2y + lambda*( P1y - P2y) );
1808 // Introduce some alias names: quadratic start point is P1, end
1809 // point is P4, control point is IP
1810 const double QP1x( P1x );
1811 const double QP1y( P1y );
1812 const double QP2x( IPx );
1813 const double QP2y( IPy );
1814 const double QP3x( P4x );
1815 const double QP3y( P4y );
1817 // Adapted bezier flatness test (lecture notes from R. Schaback,
1818 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
1820 // ||C(t) - Q(t)|| <= max ||c_j - q_j||
1821 // 0<=j<=n
1823 // In this case, we don't need the distance from the cubic bezier
1824 // to a straight line, but to a quadratic bezier. The c_j's are
1825 // the cubic bezier's bernstein coefficients, the q_j's the
1826 // quadratic bezier's. We have the c_j's given, the q_j's can be
1827 // calculated from QPi like this (sorry, mixed index notation, we
1828 // use [1,n], formulas use [0,n-1]):
1830 // q_0 = QP1 = P1
1831 // q_1 = 1/3 QP1 + 2/3 QP2
1832 // q_2 = 2/3 QP2 + 1/3 QP3
1833 // q_3 = QP3 = P4
1835 // We can drop case 0 and 3, since there the curves coincide
1836 // (distance is zero)
1838 // calculate argument of max for j=1 and j=2
1839 const double fJ1x( P2x - 1.0/3.0*QP1x - 2.0/3.0*QP2x );
1840 const double fJ1y( P2y - 1.0/3.0*QP1y - 2.0/3.0*QP2y );
1841 const double fJ2x( P3x - 2.0/3.0*QP2x - 1.0/3.0*QP3x );
1842 const double fJ2y( P3y - 2.0/3.0*QP2y - 1.0/3.0*QP3y );
1844 // stop if distance from cubic curve is guaranteed to be bounded by d
1845 // Should denominator be 0: then P1P2 and P3P4 are parallel (P1P2^T R[90,P3P4] = 0.0),
1846 // meaning that either we have a straight line or an inflexion point (see else block below)
1847 if( 0.0 != denominator &&
1848 ::std::max( fJ1x*fJ1x + fJ1y*fJ1y,
1849 fJ2x*fJ2x + fJ2y*fJ2y) < d2 )
1851 // requested resolution reached.
1852 // Add end points to output file.
1853 // order is preserved, since this is so to say depth first traversal.
1854 Impl_addQuadBezier( rBits, rLastPoint,
1855 QP2x, QP2y,
1856 QP3x, QP3y);
1858 else
1860 // Maybe subdivide further
1862 // This is for robustness reasons, since the line intersection
1863 // method below gets instable if the curve gets closer to a
1864 // straight line. If the given cubic bezier does not deviate by
1865 // more than d/4 from a straight line, either:
1866 // - take the line (that's what we do here)
1867 // - express the line by a quadratic bezier
1869 // Perform bezier flatness test (lecture notes from R. Schaback,
1870 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
1872 // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
1873 // 0<=j<=n
1875 // What is calculated here is an upper bound to the distance from
1876 // a line through b_0 and b_3 (P1 and P4 in our notation) and the
1877 // curve. We can drop 0 and n from the running indices, since the
1878 // argument of max becomes zero for those cases.
1879 const double fJ1x2( P2x - P1x - 1.0/3.0*(P4x - P1x) );
1880 const double fJ1y2( P2y - P1y - 1.0/3.0*(P4y - P1y) );
1881 const double fJ2x2( P3x - P1x - 2.0/3.0*(P4x - P1x) );
1882 const double fJ2y2( P3y - P1y - 2.0/3.0*(P4y - P1y) );
1884 // stop if distance from line is guaranteed to be bounded by d/4
1885 if( ::std::max( fJ1x2*fJ1x2 + fJ1y2*fJ1y2,
1886 fJ2x2*fJ2x2 + fJ2y2*fJ2y2) < d2/16.0 )
1888 // do not subdivide further, add straight line instead
1889 Impl_addStraightLine( rBits, rLastPoint, P4x, P4y);
1891 else
1893 // deCasteljau bezier arc, split at t=0.5
1894 // Foley/vanDam, p. 508
1895 const double L1x( P1x ), L1y( P1y );
1896 const double L2x( (P1x + P2x)*0.5 ), L2y( (P1y + P2y)*0.5 );
1897 const double Hx ( (P2x + P3x)*0.5 ), Hy ( (P2y + P3y)*0.5 );
1898 const double L3x( (L2x + Hx)*0.5 ), L3y( (L2y + Hy)*0.5 );
1899 const double R4x( P4x ), R4y( P4y );
1900 const double R3x( (P3x + P4x)*0.5 ), R3y( (P3y + P4y)*0.5 );
1901 const double R2x( (Hx + R3x)*0.5 ), R2y( (Hy + R3y)*0.5 );
1902 const double R1x( (L3x + R2x)*0.5 ), R1y( (L3y + R2y)*0.5 );
1903 const double L4x( R1x ), L4y( R1y );
1905 // subdivide further
1906 Impl_quadBezierApprox(rBits, rLastPoint, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, L4y);
1907 Impl_quadBezierApprox(rBits, rLastPoint, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, R4y);
1913 Reference < XBreakIterator > const & Writer::Impl_GetBreakIterator()
1915 if ( !mxBreakIterator.is() )
1917 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1918 mxBreakIterator = BreakIterator::create(xContext);
1920 return mxBreakIterator;
1923 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */