xref: /core/vcl/source/gdi/pdfwriter_impl.cxx (revision a75cc634)
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 <config_features.h>
21 
22 #include <sal/types.h>
23 
24 #include <math.h>
25 #include <algorithm>
26 
27 #if defined __GNUC__ && __cplusplus > 201402L
28 #pragma GCC diagnostic push
29 #pragma GCC diagnostic ignored "-Wpragmas"
30 #pragma GCC diagnostic ignored "-Wregister"
31 #endif
32 #include <lcms2.h>
33 #if defined __GNUC__ && __cplusplus > 201402L
34 #pragma GCC diagnostic pop
35 #endif
36 
37 #include <basegfx/matrix/b2dhommatrix.hxx>
38 #include <basegfx/polygon/b2dpolygon.hxx>
39 #include <basegfx/polygon/b2dpolygontools.hxx>
40 #include <basegfx/polygon/b2dpolypolygon.hxx>
41 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
42 #include <basegfx/polygon/b2dpolypolygontools.hxx>
43 #include <memory>
44 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
45 #include <com/sun/star/util/URL.hpp>
46 #include <com/sun/star/util/URLTransformer.hpp>
47 #include <comphelper/processfactory.hxx>
48 #include <comphelper/string.hxx>
49 #include <cppuhelper/implbase.hxx>
50 #include <i18nlangtag/languagetag.hxx>
51 #include <o3tl/numeric.hxx>
52 #include <o3tl/make_unique.hxx>
53 #include <osl/file.hxx>
54 #include <osl/thread.h>
55 #include <rtl/crc.h>
56 #include <rtl/digest.h>
57 #include <rtl/ustrbuf.hxx>
58 #include <svl/urihelper.hxx>
59 #include <tools/debug.hxx>
60 #include <tools/fract.hxx>
61 #include <tools/stream.hxx>
62 #include <tools/urlobj.hxx>
63 #include <tools/zcodec.hxx>
64 #include <svl/cryptosign.hxx>
65 #include <vcl/bitmapex.hxx>
66 #include <vcl/bitmapaccess.hxx>
67 #include <vcl/cvtgrf.hxx>
68 #include <vcl/image.hxx>
69 #include <vcl/lineinfo.hxx>
70 #include <vcl/metric.hxx>
71 #include <vcl/settings.hxx>
72 #include <vcl/strhelper.hxx>
73 #include <vcl/svapp.hxx>
74 #include <vcl/virdev.hxx>
75 #include <vcl/filter/pdfdocument.hxx>
76 #include <comphelper/hash.hxx>
77 
78 #include <fontsubset.hxx>
79 #include <outdev.h>
80 #include <PhysicalFontFace.hxx>
81 #include <salgdi.hxx>
82 #include <sallayout.hxx>
83 #include <textlayout.hxx>
84 #include <textlineinfo.hxx>
85 #include <bitmapwriteaccess.hxx>
86 
87 #include "pdfwriter_impl.hxx"
88 
89 #ifdef _WIN32
90 // WinCrypt headers for PDF signing
91 // Note: this uses Windows 7 APIs and requires the relevant data types
92 #include <prewin.h>
93 #include <wincrypt.h>
94 #include <postwin.h>
95 #endif
96 
97 #include <config_eot.h>
98 
99 #if ENABLE_EOT
100 #include <libeot/libeot.h>
101 #endif
102 
103 using namespace vcl;
104 using namespace::com::sun::star;
105 
106 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
107 
108 #if HAVE_FEATURE_NSS
109 // Is this length truly the maximum possible, or just a number that
110 // seemed large enough when the author tested this (with some type of
111 // certificates)? I suspect the latter.
112 
113 // Used to be 0x4000 = 16384, but a sample signed PDF (produced by
114 // some other software) provided by the customer has a signature
115 // content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
116 // Adobe has one that is 21942 bytes. So let's be careful. Pity this
117 // can't be dynamic, at least not without restructuring the code. Also
118 // note that the checks in the code for this being too small
119 // apparently are broken, if this overflows you end up with an invalid
120 // PDF. Need to fix that.
121 
122 #define MAX_SIGNATURE_CONTENT_LENGTH 50000
123 #endif
124 
125 #ifdef DO_TEST_PDF
126 class PDFTestOutputStream : public PDFOutputStream
127 {
128     public:
129     virtual ~PDFTestOutputStream();
130     virtual void write( const css::uno::Reference< css::io::XOutputStream >& xStream );
131 };
132 
133 PDFTestOutputStream::~PDFTestOutputStream()
134 {
135 }
136 
137 void PDFTestOutputStream::write( const css::uno::Reference< css::io::XOutputStream >& xStream )
138 {
139     OString aStr( "lalala\ntest\ntest\ntest" );
140     css::uno::Sequence< sal_Int8 > aData( aStr.getLength() );
141     memcpy( aData.getArray(), aStr.getStr(), aStr.getLength() );
142     xStream->writeBytes( aData );
143 }
144 
145 // this test code cannot be used to test PDF/A-1 because it forces
146 // control item (widgets) to bypass the structure controlling
147 // the embedding of such elements in actual run
148 void doTestCode()
149 {
150     static const char* pHome = getenv( "HOME"  );
151     OUString aTestFile( "file://"  );
152     aTestFile += OUString( pHome, strlen( pHome ), RTL_TEXTENCODING_MS_1252 );
153     aTestFile += "/pdf_export_test.pdf";
154 
155     PDFWriter::PDFWriterContext aContext;
156     aContext.URL            = aTestFile;
157     aContext.Version        = PDFWriter::PDF_1_4;
158     aContext.Tagged         = true;
159     aContext.InitialPage    = 2;
160     aContext.DocumentInfo.Title = "PDF export test document";
161     aContext.DocumentInfo.Producer = "VCL";
162 
163     aContext.SignPDF        = true;
164     aContext.SignLocation   = "Burdur";
165     aContext.SignReason     = "Some valid reason to sign";
166     aContext.SignContact    = "signer@example.com";
167 
168     css::uno::Reference< css::beans::XMaterialHolder > xEnc;
169     PDFWriter aWriter( aContext, xEnc );
170     aWriter.NewPage( 595, 842 );
171     aWriter.BeginStructureElement( PDFWriter::Document );
172     // set duration of 3 sec for first page
173     aWriter.SetAutoAdvanceTime( 3 );
174     aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
175 
176     aWriter.SetFillColor( COL_LIGHTRED );
177     aWriter.SetLineColor( COL_LIGHTGREEN );
178     aWriter.DrawRect( Rectangle( Point( 2000, 200 ), Size( 8000, 3000 ) ), 5000, 2000 );
179 
180     aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
181     aWriter.SetTextColor( COL_BLACK );
182     aWriter.SetLineColor( COL_BLACK );
183     aWriter.SetFillColor( COL_LIGHTBLUE );
184 
185     Rectangle aRect( Point( 5000, 5000 ), Size( 6000, 3000 ) );
186     aWriter.DrawRect( aRect );
187     aWriter.DrawText( aRect, OUString( "Link annot 1" ) );
188     sal_Int32 nFirstLink = aWriter.CreateLink( aRect );
189     PDFNote aNote;
190     aNote.Title = "A small test note";
191     aNote.Contents = "There is no business like show business like no business i know. Everything about it is appealing.";
192     aWriter.CreateNote( Rectangle( Point( aRect.Right(), aRect.Top() ), Size( 6000, 3000 ) ), aNote );
193 
194     Rectangle aTargetRect( Point( 3000, 23000 ), Size( 12000, 6000 ) );
195     aWriter.SetFillColor( COL_LIGHTGREEN );
196     aWriter.DrawRect( aTargetRect );
197     aWriter.DrawText( aTargetRect, "Dest second link" );
198     sal_Int32 nSecondDest = aWriter.CreateDest( aTargetRect );
199 
200     aWriter.BeginStructureElement( PDFWriter::Section );
201     aWriter.BeginStructureElement( PDFWriter::Heading );
202     aWriter.DrawText( Point(4500, 9000), "A small structure test" );
203     aWriter.EndStructureElement();
204     aWriter.BeginStructureElement( PDFWriter::Paragraph );
205     aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
206     aWriter.SetStructureAttribute( PDFWriter::TextDecorationType, PDFWriter::Underline );
207     aWriter.DrawText( Rectangle( Point( 4500, 10000 ), Size( 12000, 6000 ) ),
208                      "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now.",
209                       DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
210                       );
211     aWriter.SetActualText( "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now." );
212     aWriter.SetAlternateText( "This paragraph contains some lengthy nonsense to test structural element emission of PDFWriter." );
213     aWriter.EndStructureElement();
214     aWriter.BeginStructureElement( PDFWriter::Paragraph );
215     aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
216     aWriter.DrawText( Rectangle( Point( 4500, 19000 ), Size( 12000, 1000 ) ),
217                       "This paragraph is nothing special either but ends on the next page structurewise",
218                       DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
219                       );
220 
221     aWriter.NewPage( 595, 842 );
222     // test AddStream interface
223     aWriter.AddStream( "text/plain", new PDFTestOutputStream(), true );
224     // set transitional mode
225     aWriter.SetPageTransition( PDFWriter::WipeRightToLeft, 1500 );
226     aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
227     aWriter.SetTextColor( COL_BLACK );
228     aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
229     aWriter.DrawText( Rectangle( Point( 4500, 1500 ), Size( 12000, 3000 ) ),
230                       "Here's where all things come to an end ... well at least the paragraph from the last page.",
231                       DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
232                       );
233     aWriter.EndStructureElement();
234 
235     aWriter.SetFillColor( COL_LIGHTBLUE );
236     // disable structure
237     aWriter.BeginStructureElement( PDFWriter::NonStructElement );
238     aWriter.DrawRect( aRect );
239     aWriter.BeginStructureElement( PDFWriter::Paragraph );
240     aWriter.DrawText( aRect, "Link annot 2" );
241     sal_Int32 nSecondLink = aWriter.CreateLink( aRect );
242 
243     aWriter.SetFillColor( COL_LIGHTGREEN );
244     aWriter.BeginStructureElement( PDFWriter::ListItem );
245     aWriter.DrawRect( aTargetRect );
246     aWriter.DrawText( aTargetRect, "Dest first link" );
247     sal_Int32 nFirstDest = aWriter.CreateDest( aTargetRect );
248     // enable structure
249     aWriter.EndStructureElement();
250 
251     aWriter.EndStructureElement();
252     aWriter.EndStructureElement();
253     aWriter.BeginStructureElement( PDFWriter::Figure );
254     aWriter.BeginStructureElement( PDFWriter::Caption );
255     aWriter.DrawText( Point( 4500, 9000 ), "Some drawing stuff inside the structure" );
256     aWriter.EndStructureElement();
257 
258     // test clipping
259     basegfx::B2DPolyPolygon aClip;
260     basegfx::B2DPolygon aClipPoly;
261     aClipPoly.append( basegfx::B2DPoint( 8250, 9600 ) );
262     aClipPoly.append( basegfx::B2DPoint( 16500, 11100 ) );
263     aClipPoly.append( basegfx::B2DPoint( 8250, 12600 ) );
264     aClipPoly.append( basegfx::B2DPoint( 4500, 11100 ) );
265     aClipPoly.setClosed( true );
266     aClip.append( aClipPoly );
267 
268     aWriter.Push( PushFlags::CLIPREGION | PushFlags::FILLCOLOR );
269     aWriter.SetClipRegion( aClip );
270     aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
271     aWriter.MoveClipRegion( 1000, 500 );
272     aWriter.SetFillColor( COL_RED );
273     aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
274     aWriter.Pop();
275     // test transparency
276     // draw background
277     Rectangle aTranspRect( Point( 7500, 13500 ), Size( 9000, 6000 ) );
278     aWriter.SetFillColor( COL_LIGHTRED );
279     aWriter.DrawRect( aTranspRect );
280     aWriter.BeginTransparencyGroup();
281 
282     aWriter.SetFillColor( COL_LIGHTGREEN );
283     aWriter.DrawEllipse( aTranspRect );
284     aWriter.SetTextColor( COL_LIGHTBLUE );
285     aWriter.DrawText( aTranspRect,
286                       "Some transparent text",
287                       DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
288 
289     aWriter.EndTransparencyGroup( aTranspRect, 50 );
290 
291     // prepare an alpha mask
292     Bitmap aTransMask( Size( 256, 256 ), 8, &Bitmap::GetGreyPalette( 256 ) );
293     BitmapScopedWriteAccess pAcc(aTransMask);
294     for( int nX = 0; nX < 256; nX++ )
295         for( int nY = 0; nY < 256; nY++ )
296             pAcc->SetPixel( nX, nY, BitmapColor( (sal_uInt8)((nX+nY)/2) ) );
297     pAcc.reset();
298     aTransMask.SetPrefMapMode( MapUnit::MapMM );
299     aTransMask.SetPrefSize( Size( 10, 10 ) );
300 
301     aWriter.DrawBitmap( Point( 600, 13500 ), Size( 3000, 3000 ), aTransMask );
302 
303     aTranspRect = Rectangle( Point( 4200, 13500 ), Size( 3000, 3000 ) );
304     aWriter.SetFillColor( COL_LIGHTRED );
305     aWriter.DrawRect( aTranspRect );
306     aWriter.SetFillColor( COL_LIGHTGREEN );
307     aWriter.DrawEllipse( aTranspRect );
308     aWriter.SetTextColor( COL_LIGHTBLUE );
309     aWriter.DrawText( aTranspRect,
310                       "Some transparent text",
311                       DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
312     aTranspRect = Rectangle( Point( 1500, 16500 ), Size( 4800, 3000 ) );
313     aWriter.SetFillColor( COL_LIGHTRED );
314     aWriter.DrawRect( aTranspRect );
315 
316     Bitmap aImageBmp( Size( 256, 256 ), 24 );
317     pAcc = BitmapScopedWriteAccess(aImageBmp);
318     pAcc->SetFillColor( Color( 0xff, 0, 0xff ) );
319     pAcc->FillRect( Rectangle( Point( 0, 0 ), Size( 256, 256 ) ) );
320     pAcc.reset();
321     BitmapEx aBmpEx( aImageBmp, AlphaMask( aTransMask ) );
322     aWriter.DrawBitmapEx( Point( 1500, 19500 ), Size( 4800, 3000 ), aBmpEx );
323 
324     aWriter.EndStructureElement();
325     aWriter.EndStructureElement();
326 
327     LineInfo aLI( LineStyle::Dash, 3 );
328     aLI.SetDashCount( 2 );
329     aLI.SetDashLen( 50 );
330     aLI.SetDotCount( 2 );
331     aLI.SetDotLen( 25 );
332     aLI.SetDistance( 15 );
333     Point aLIPoints[] = { Point( 4000, 10000 ),
334                           Point( 8000, 12000 ),
335                           Point( 3000, 19000 ) };
336     tools::Polygon aLIPoly( 3, aLIPoints );
337     aWriter.SetLineColor( COL_BLUE );
338     aWriter.SetFillColor();
339     aWriter.DrawPolyLine( aLIPoly, aLI );
340 
341     aLI.SetDashCount( 4 );
342     aLIPoly.Move( 1000, 1000 );
343     aWriter.DrawPolyLine( aLIPoly, aLI );
344 
345     aWriter.NewPage( 595, 842 );
346     aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
347     Wallpaper aWall( aTransMask );
348     aWall.SetStyle( WallpaperStyle::Tile );
349     aWriter.DrawWallpaper( Rectangle( Point( 4400, 4200 ), Size( 10200, 6300 ) ), aWall );
350 
351     aWriter.NewPage( 595, 842 );
352     aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
353     aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
354     aWriter.SetTextColor( COL_BLACK );
355     aRect = Rectangle( Point( 4500, 6000 ), Size( 6000, 1500 ) );
356     aWriter.DrawRect( aRect );
357     aWriter.DrawText( aRect, "www.heise.de" );
358     sal_Int32 nURILink = aWriter.CreateLink( aRect );
359     aWriter.SetLinkURL( nURILink, OUString( "http://www.heise.de" ) );
360 
361     aWriter.SetLinkDest( nFirstLink, nFirstDest );
362     aWriter.SetLinkDest( nSecondLink, nSecondDest );
363 
364     // include a button
365     PDFWriter::PushButtonWidget aBtn;
366     aBtn.Name = "testButton";
367     aBtn.Description = "A test button";
368     aBtn.Text = "hit me";
369     aBtn.Location = Rectangle( Point( 4500, 9000 ), Size( 4500, 3000 ) );
370     aBtn.Border = aBtn.Background = true;
371     aWriter.CreateControl( aBtn );
372 
373     // include a uri button
374     PDFWriter::PushButtonWidget aUriBtn;
375     aUriBtn.Name = "wwwButton";
376     aUriBtn.Description = "A URI button";
377     aUriBtn.Text = "to www";
378     aUriBtn.Location = Rectangle( Point( 9500, 9000 ), Size( 4500, 3000 ) );
379     aUriBtn.Border = aUriBtn.Background = true;
380     aUriBtn.URL = "http://www.heise.de";
381     aWriter.CreateControl( aUriBtn );
382 
383     // include a dest button
384     PDFWriter::PushButtonWidget aDstBtn;
385     aDstBtn.Name = "destButton";
386     aDstBtn.Description = "A Dest button";
387     aDstBtn.Text = "to paragraph";
388     aDstBtn.Location = Rectangle( Point( 14500, 9000 ), Size( 4500, 3000 ) );
389     aDstBtn.Border = aDstBtn.Background = true;
390     aDstBtn.Dest = nFirstDest;
391     aWriter.CreateControl( aDstBtn );
392 
393     PDFWriter::CheckBoxWidget aCBox;
394     aCBox.Name = "textCheckBox";
395     aCBox.Description = "A test check box";
396     aCBox.Text = "check me";
397     aCBox.Location = Rectangle( Point( 4500, 13500 ), Size( 3000, 750 ) );
398     aCBox.Checked = true;
399     aCBox.Border = aCBox.Background = false;
400     aWriter.CreateControl( aCBox );
401 
402     PDFWriter::CheckBoxWidget aCBox2;
403     aCBox2.Name = "textCheckBox2";
404     aCBox2.Description = "Another test check box";
405     aCBox2.Text = "check me right";
406     aCBox2.Location = Rectangle( Point( 4500, 14250 ), Size( 3000, 750 ) );
407     aCBox2.Checked = true;
408     aCBox2.Border = aCBox2.Background = false;
409     aCBox2.ButtonIsLeft = false;
410     aWriter.CreateControl( aCBox2 );
411 
412     PDFWriter::RadioButtonWidget aRB1;
413     aRB1.Name = "rb1_1";
414     aRB1.Description = "radio 1 button 1";
415     aRB1.Text = "Despair";
416     aRB1.Location = Rectangle( Point( 4500, 15000 ), Size( 6000, 1000 ) );
417     aRB1.Selected = true;
418     aRB1.RadioGroup = 1;
419     aRB1.Border = aRB1.Background = true;
420     aRB1.ButtonIsLeft = false;
421     aRB1.BorderColor = COL_LIGHTGREEN;
422     aRB1.BackgroundColor = COL_LIGHTBLUE;
423     aRB1.TextColor = COL_LIGHTRED;
424     aRB1.TextFont = Font( OUString( "Courier" ), Size( 0, 800 ) );
425     aWriter.CreateControl( aRB1 );
426 
427     PDFWriter::RadioButtonWidget aRB2;
428     aRB2.Name = "rb2_1";
429     aRB2.Description = "radio 2 button 1";
430     aRB2.Text = "Joy";
431     aRB2.Location = Rectangle( Point( 10500, 15000 ), Size( 3000, 1000 ) );
432     aRB2.Selected = true;
433     aRB2.RadioGroup = 2;
434     aWriter.CreateControl( aRB2 );
435 
436     PDFWriter::RadioButtonWidget aRB3;
437     aRB3.Name = "rb1_2";
438     aRB3.Description = "radio 1 button 2";
439     aRB3.Text = "Desperation";
440     aRB3.Location = Rectangle( Point( 4500, 16000 ), Size( 3000, 1000 ) );
441     aRB3.Selected = true;
442     aRB3.RadioGroup = 1;
443     aWriter.CreateControl( aRB3 );
444 
445     PDFWriter::EditWidget aEditBox;
446     aEditBox.Name = "testEdit";
447     aEditBox.Description = "A test edit field";
448     aEditBox.Text = "A little test text";
449     aEditBox.TextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
450     aEditBox.Location = Rectangle( Point( 10000, 18000 ), Size( 5000, 1500 ) );
451     aEditBox.MaxLen = 100;
452     aEditBox.Border = aEditBox.Background = true;
453     aEditBox.BorderColor = COL_BLACK;
454     aWriter.CreateControl( aEditBox );
455 
456     // normal list box
457     PDFWriter::ListBoxWidget aLstBox;
458     aLstBox.Name = "testListBox";
459     aLstBox.Text = "One";
460     aLstBox.Description = "select me";
461     aLstBox.Location = Rectangle( Point( 4500, 18000 ), Size( 3000, 1500 ) );
462     aLstBox.Sort = true;
463     aLstBox.MultiSelect = true;
464     aLstBox.Border = aLstBox.Background = true;
465     aLstBox.BorderColor = COL_BLACK;
466     aLstBox.Entries.push_back( OUString( "One"  ) );
467     aLstBox.Entries.push_back( OUString( "Two"  ) );
468     aLstBox.Entries.push_back( OUString( "Three"  ) );
469     aLstBox.Entries.push_back( OUString( "Four"  ) );
470     aLstBox.SelectedEntries.push_back( 1 );
471     aLstBox.SelectedEntries.push_back( 2 );
472     aWriter.CreateControl( aLstBox );
473 
474     // dropdown list box
475     aLstBox.Name = "testDropDownListBox";
476     aLstBox.DropDown = true;
477     aLstBox.Location = Rectangle( Point( 4500, 19500 ), Size( 3000, 500 ) );
478     aWriter.CreateControl( aLstBox );
479 
480     // combo box
481     PDFWriter::ComboBoxWidget aComboBox;
482     aComboBox.Name = "testComboBox";
483     aComboBox.Text = "test a combobox";
484     aComboBox.Entries.push_back( OUString( "Larry"  ) );
485     aComboBox.Entries.push_back( OUString( "Curly"  ) );
486     aComboBox.Entries.push_back( OUString( "Moe"  ) );
487     aComboBox.Location = Rectangle( Point( 4500, 20000 ), Size( 3000, 500 ) );
488     aWriter.CreateControl( aComboBox );
489 
490     // test outlines
491     sal_Int32 nPage1OL = aWriter.CreateOutlineItem();
492     aWriter.SetOutlineItemText( nPage1OL, OUString( "Page 1"  ) );
493     aWriter.SetOutlineItemDest( nPage1OL, nSecondDest );
494     aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2"  ), nSecondDest );
495     aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2 revisited"  ), nSecondDest );
496     aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2 again"  ), nSecondDest );
497     sal_Int32 nPage2OL = aWriter.CreateOutlineItem();
498     aWriter.SetOutlineItemText( nPage2OL, OUString( "Page 2"  ) );
499     aWriter.CreateOutlineItem( nPage2OL, OUString( "Dest 1"  ), nFirstDest );
500 
501     aWriter.EndStructureElement(); // close document
502 
503     aWriter.Emit();
504 }
505 #endif
506 
507 static const sal_Int32 nLog10Divisor = 3;
508 static const double fDivisor = 1000.0;
509 
510 static inline double pixelToPoint( double px ) { return px/fDivisor; }
511 static inline sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
512 
513 const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
514 {
515     0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
516     0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
517 };
518 
519 static void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer )
520 {
521     static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
522                                            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
523     rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
524     rBuffer.append( pHexDigits[ nInt & 15 ] );
525 }
526 
527 static void appendName( const OUString& rStr, OStringBuffer& rBuffer )
528 {
529 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
530 // I guess than when reading the #xx sequence it will count for a single character.
531     OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
532     int nLen = aStr.getLength();
533     for( int i = 0; i < nLen; i++ )
534     {
535         /*  #i16920# PDF recommendation: output UTF8, any byte
536          *  outside the interval [33(=ASCII'!');126(=ASCII'~')]
537          *  should be escaped hexadecimal
538          *  for the sake of ghostscript which also reads PDF
539          *  but has a narrower acceptance rate we only pass
540          *  alphanumerics and '-' literally.
541          */
542         if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
543             (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
544             (aStr[i] >= '0' && aStr[i] <= '9' ) ||
545             aStr[i] == '-' )
546         {
547             rBuffer.append( aStr[i] );
548         }
549         else
550         {
551             rBuffer.append( '#' );
552             appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
553         }
554     }
555 }
556 
557 static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer )
558 {
559     // FIXME i59651 see above
560     while( pStr && *pStr )
561     {
562         if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
563             (*pStr >= 'a' && *pStr <= 'z' ) ||
564             (*pStr >= '0' && *pStr <= '9' ) ||
565             *pStr == '-' )
566         {
567             rBuffer.append( *pStr );
568         }
569         else
570         {
571             rBuffer.append( '#' );
572             appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
573         }
574         pStr++;
575     }
576 }
577 
578 //used only to emit encoded passwords
579 static void appendLiteralString( const sal_Char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
580 {
581     while( nLength )
582     {
583         switch( *pStr )
584         {
585         case '\n' :
586             rBuffer.append( "\\n" );
587             break;
588         case '\r' :
589             rBuffer.append( "\\r" );
590             break;
591         case '\t' :
592             rBuffer.append( "\\t" );
593             break;
594         case '\b' :
595             rBuffer.append( "\\b" );
596             break;
597         case '\f' :
598             rBuffer.append( "\\f" );
599             break;
600         case '(' :
601         case ')' :
602         case '\\' :
603             rBuffer.append( "\\" );
604             rBuffer.append( static_cast<sal_Char>(*pStr) );
605             break;
606         default:
607             rBuffer.append( static_cast<sal_Char>(*pStr) );
608             break;
609         }
610         pStr++;
611         nLength--;
612     }
613 }
614 
615 /*
616  * Convert a string before using it.
617  *
618  * This string conversion function is needed because the destination name
619  * in a PDF file seen through an Internet browser should be
620  * specially crafted, in order to be used directly by the browser.
621  * In this way the fragment part of a hyperlink to a PDF file (e.g. something
622  * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
623  * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
624  * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
625  * and go to named destination thefragment using default zoom'.
626  * The conversion is needed because in case of a fragment in the form: Slide%201
627  * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
628  * using this conversion, in both the generated named destinations, fragment and GoToR
629  * destination.
630  *
631  * The names for destinations are name objects and so they don't need to be encrypted
632  * even though they expose the content of PDF file (e.g. guessing the PDF content from the
633  * destination name).
634  *
635  * Further limitation: it is advisable to use standard ASCII characters for
636  * OOo bookmarks.
637 */
638 static void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
639 {
640     const sal_Unicode* pStr = rString.getStr();
641     sal_Int32 nLen = rString.getLength();
642     for( int i = 0; i < nLen; i++ )
643     {
644         sal_Unicode aChar = pStr[i];
645         if( (aChar >= '0' && aChar <= '9' ) ||
646             (aChar >= 'a' && aChar <= 'z' ) ||
647             (aChar >= 'A' && aChar <= 'Z' ) ||
648             aChar == '-' )
649         {
650             rBuffer.append(static_cast<sal_Char>(aChar));
651         }
652         else
653         {
654             sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
655             if(aValueHigh > 0)
656                 appendHex( aValueHigh, rBuffer );
657             appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
658         }
659     }
660 }
661 
662 void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
663 {
664     rBuffer.append( "FEFF" );
665     const sal_Unicode* pStr = rString.getStr();
666     sal_Int32 nLen = rString.getLength();
667     for( int i = 0; i < nLen; i++ )
668     {
669         sal_Unicode aChar = pStr[i];
670         appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
671         appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
672     }
673 }
674 
675 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
676 {
677     /* #i80258# previously we use appendName here
678        however we need a slightly different coding scheme than the normal
679        name encoding for field names
680     */
681     const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
682     OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
683     int nLen = aStr.getLength();
684 
685     OStringBuffer aBuffer( rName.getLength()+64 );
686     for( int i = 0; i < nLen; i++ )
687     {
688         /*  #i16920# PDF recommendation: output UTF8, any byte
689          *  outside the interval [32(=ASCII' ');126(=ASCII'~')]
690          *  should be escaped hexadecimal
691          */
692         if( aStr[i] >= 32 && aStr[i] <= 126 )
693             aBuffer.append( aStr[i] );
694         else
695         {
696             aBuffer.append( '#' );
697             appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
698         }
699     }
700 
701     OString aFullName( aBuffer.makeStringAndClear() );
702 
703     /* #i82785# create hierarchical fields down to the for each dot in i_rName */
704     sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
705     OString aPartialName;
706     OString aDomain;
707     do
708     {
709         nLastTokenIndex = nTokenIndex;
710         aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
711         if( nTokenIndex != -1 )
712         {
713             // find or create a hierarchical field
714             // first find the fully qualified name up to this field
715             aDomain = aFullName.copy( 0, nTokenIndex-1 );
716             std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
717             if( it == m_aFieldNameMap.end() )
718             {
719                  // create new hierarchy field
720                 sal_Int32 nNewWidget = m_aWidgets.size();
721                 m_aWidgets.emplace_back( );
722                 m_aWidgets[nNewWidget].m_nObject = createObject();
723                 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
724                 m_aWidgets[nNewWidget].m_aName = aPartialName;
725                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
726                 m_aFieldNameMap[aDomain] = nNewWidget;
727                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
728                 if( nLastTokenIndex > 0 )
729                 {
730                     // this field is not a root field and
731                     // needs to be inserted to its parent
732                     OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
733                     it = m_aFieldNameMap.find( aParentDomain );
734                     OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
735                     if( it != m_aFieldNameMap.end()  )
736                     {
737                         OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
738                         if( it->second < sal_Int32(m_aWidgets.size()) )
739                         {
740                             PDFWidget& rParentField( m_aWidgets[it->second] );
741                             rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
742                             rParentField.m_aKidsIndex.push_back( nNewWidget );
743                             m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
744                         }
745                     }
746                 }
747             }
748             else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
749             {
750                 // this is invalid, someone tries to have a terminal field as parent
751                 // example: a button with the name foo.bar exists and
752                 // another button is named foo.bar.no
753                 // workaround: put the second terminal field as much up in the hierarchy as
754                 // necessary to have a non-terminal field as parent (or none at all)
755                 // since it->second already is terminal, we just need to use its parent
756                 aDomain.clear();
757                 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
758                 if( nLastTokenIndex > 0 )
759                 {
760                     aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
761                     OStringBuffer aBuf( aDomain.getLength() + 1 + aPartialName.getLength() );
762                     aBuf.append( aDomain );
763                     aBuf.append( '.' );
764                     aBuf.append( aPartialName );
765                     aFullName = aBuf.makeStringAndClear();
766                 }
767                 else
768                     aFullName = aPartialName;
769                 break;
770             }
771         }
772     } while( nTokenIndex != -1 );
773 
774     // insert widget into its hierarchy field
775     if( !aDomain.isEmpty() )
776     {
777         std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
778         if( it != m_aFieldNameMap.end() )
779         {
780             OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
781             if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) )
782             {
783                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
784                 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
785                 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
786             }
787         }
788     }
789 
790     if( aPartialName.isEmpty() )
791     {
792         // how funny, an empty field name
793         if( i_rControl.getType() == PDFWriter::RadioButton )
794         {
795             aPartialName  = "RadioGroup";
796             aPartialName += OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
797         }
798         else
799             aPartialName = OString( "Widget" );
800     }
801 
802     if( ! m_aContext.AllowDuplicateFieldNames )
803     {
804         std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
805 
806         if( it != m_aFieldNameMap.end() ) // not unique
807         {
808             std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
809             OString aTry;
810             sal_Int32 nTry = 2;
811             do
812             {
813                 OStringBuffer aUnique( aFullName.getLength() + 16 );
814                 aUnique.append( aFullName );
815                 aUnique.append( '_' );
816                 aUnique.append( nTry++ );
817                 aTry = aUnique.makeStringAndClear();
818                 check_it = m_aFieldNameMap.find( aTry );
819             } while( check_it != m_aFieldNameMap.end() );
820             aFullName = aTry;
821             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
822             aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
823         }
824         else
825             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
826     }
827 
828     // finally
829     m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
830 }
831 
832 static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
833 {
834     if( nValue < 0 )
835     {
836         rBuffer.append( '-' );
837         nValue = -nValue;
838     }
839     sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
840     while( nDiv-- )
841         nFactor *= 10;
842 
843     sal_Int32 nInt = nValue / nFactor;
844     rBuffer.append( nInt );
845     if (nFactor > 1 && nValue % nFactor)
846     {
847         rBuffer.append( '.' );
848         do
849         {
850             nFactor /= 10;
851             rBuffer.append((nValue / nFactor) % 10);
852         }
853         while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
854     }
855 }
856 
857 // appends a double. PDF does not accept exponential format, only fixed point
858 static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
859 {
860     bool bNeg = false;
861     if( fValue < 0.0 )
862     {
863         bNeg = true;
864         fValue=-fValue;
865     }
866 
867     sal_Int64 nInt = static_cast<sal_Int64>(fValue);
868     fValue -= static_cast<double>(nInt);
869     // optimizing hardware may lead to a value of 1.0 after the subtraction
870     if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
871     {
872         nInt++;
873         fValue = 0.0;
874     }
875     sal_Int64 nFrac = 0;
876     if( fValue )
877     {
878         fValue *= pow( 10.0, static_cast<double>(nPrecision) );
879         nFrac = static_cast<sal_Int64>(fValue);
880     }
881     if( bNeg && ( nInt || nFrac ) )
882         rBuffer.append( '-' );
883     rBuffer.append( nInt );
884     if( nFrac )
885     {
886         int i;
887         rBuffer.append( '.' );
888         sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
889         for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
890         {
891             sal_Int64 nNumb = nFrac / nBound;
892             nFrac -= nNumb * nBound;
893             rBuffer.append( nNumb );
894             nBound /= 10;
895         }
896     }
897 }
898 
899 static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
900 {
901 
902     if( rColor != COL_TRANSPARENT )
903     {
904         if( bConvertToGrey )
905         {
906             sal_uInt8 cByte = rColor.GetLuminance();
907             appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
908         }
909         else
910         {
911             appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
912             rBuffer.append( ' ' );
913             appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
914             rBuffer.append( ' ' );
915             appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
916         }
917     }
918 }
919 
920 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
921 {
922     if( rColor != COL_TRANSPARENT )
923     {
924         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
925         appendColor( rColor, rBuffer, bGrey );
926         rBuffer.append( bGrey ? " G" : " RG" );
927     }
928 }
929 
930 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
931 {
932     if( rColor != COL_TRANSPARENT )
933     {
934         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
935         appendColor( rColor, rBuffer, bGrey );
936         rBuffer.append( bGrey ? " g" : " rg" );
937     }
938 }
939 
940 // matrix helper class
941 // TODO: use basegfx matrix class instead or derive from it
942 namespace vcl // TODO: use anonymous namespace to keep this class local
943 {
944 /*  for sparse matrices of the form (2D linear transformations)
945  *  f[0] f[1] 0
946  *  f[2] f[3] 0
947  *  f[4] f[5] 1
948  */
949 class Matrix3
950 {
951     double f[6];
952 
953     void set( const double *pn ) { for( int i = 0 ; i < 6; i++ ) f[i] = pn[i]; }
954 public:
955     Matrix3();
956 
957     void skew( double alpha, double beta );
958     void scale( double sx, double sy );
959     void rotate( double angle );
960     void translate( double tx, double ty );
961     void invert();
962 
963     void append( PDFWriterImpl::PDFPage const & rPage, OStringBuffer& rBuffer );
964 
965     Point transform( const Point& rPoint ) const;
966 };
967 }
968 
969 Matrix3::Matrix3()
970 {
971     // initialize to unity
972     f[0] = 1.0;
973     f[1] = 0.0;
974     f[2] = 0.0;
975     f[3] = 1.0;
976     f[4] = 0.0;
977     f[5] = 0.0;
978 }
979 
980 Point Matrix3::transform( const Point& rOrig ) const
981 {
982     double x = static_cast<double>(rOrig.X()), y = static_cast<double>(rOrig.Y());
983     return Point( static_cast<int>(x*f[0] + y*f[2] + f[4]), static_cast<int>(x*f[1] + y*f[3] + f[5]) );
984 }
985 
986 void Matrix3::skew( double alpha, double beta )
987 {
988     double fn[6];
989     double tb = tan( beta );
990     fn[0] = f[0] + f[2]*tb;
991     fn[1] = f[1];
992     fn[2] = f[2] + f[3]*tb;
993     fn[3] = f[3];
994     fn[4] = f[4] + f[5]*tb;
995     fn[5] = f[5];
996     if( alpha != 0.0 )
997     {
998         double ta = tan( alpha );
999         fn[1] += f[0]*ta;
1000         fn[3] += f[2]*ta;
1001         fn[5] += f[4]*ta;
1002     }
1003     set( fn );
1004 }
1005 
1006 void Matrix3::scale( double sx, double sy )
1007 {
1008     double fn[6];
1009     fn[0] = sx*f[0];
1010     fn[1] = sy*f[1];
1011     fn[2] = sx*f[2];
1012     fn[3] = sy*f[3];
1013     fn[4] = sx*f[4];
1014     fn[5] = sy*f[5];
1015     set( fn );
1016 }
1017 
1018 void Matrix3::rotate( double angle )
1019 {
1020     double fn[6];
1021     double fSin = sin(angle);
1022     double fCos = cos(angle);
1023     fn[0] = f[0]*fCos - f[1]*fSin;
1024     fn[1] = f[0]*fSin + f[1]*fCos;
1025     fn[2] = f[2]*fCos - f[3]*fSin;
1026     fn[3] = f[2]*fSin + f[3]*fCos;
1027     fn[4] = f[4]*fCos - f[5]*fSin;
1028     fn[5] = f[4]*fSin + f[5]*fCos;
1029     set( fn );
1030 }
1031 
1032 void Matrix3::translate( double tx, double ty )
1033 {
1034     f[4] += tx;
1035     f[5] += ty;
1036 }
1037 
1038 void Matrix3::invert()
1039 {
1040     // short circuit trivial cases
1041     if( f[1]==f[2] && f[1]==0.0 && f[0]==f[3] && f[0]==1.0 )
1042     {
1043         f[4] = -f[4];
1044         f[5] = -f[5];
1045         return;
1046     }
1047 
1048     // check determinant
1049     const double fDet = f[0]*f[3]-f[1]*f[2];
1050     if( fDet == 0.0 )
1051         return;
1052 
1053     // invert the matrix
1054     double fn[6];
1055     fn[0] = +f[3] / fDet;
1056     fn[1] = -f[1] / fDet;
1057     fn[2] = -f[2] / fDet;
1058     fn[3] = +f[0] / fDet;
1059 
1060     // apply inversion to translation
1061     fn[4] = -(f[4]*fn[0] + f[5]*fn[2]);
1062     fn[5] = -(f[4]*fn[1] + f[5]*fn[3]);
1063 
1064     set( fn );
1065 }
1066 
1067 void Matrix3::append( PDFWriterImpl::PDFPage const & rPage, OStringBuffer& rBuffer )
1068 {
1069     appendDouble( f[0], rBuffer );
1070     rBuffer.append( ' ' );
1071     appendDouble( f[1], rBuffer );
1072     rBuffer.append( ' ' );
1073     appendDouble( f[2], rBuffer );
1074     rBuffer.append( ' ' );
1075     appendDouble( f[3], rBuffer );
1076     rBuffer.append( ' ' );
1077     rPage.appendPoint( Point( static_cast<long>(f[4]), static_cast<long>(f[5]) ), rBuffer );
1078 }
1079 
1080 static void appendResourceMap( OStringBuffer& rBuf, const char* pPrefix, const PDFWriterImpl::ResourceMap& rList )
1081 {
1082     if( rList.empty() )
1083         return;
1084     rBuf.append( '/' );
1085     rBuf.append( pPrefix );
1086     rBuf.append( "<<" );
1087     int ni = 0;
1088     for (auto const& item : rList)
1089     {
1090         if( !item.first.isEmpty() && item.second > 0 )
1091         {
1092             rBuf.append( '/' );
1093             rBuf.append( item.first );
1094             rBuf.append( ' ' );
1095             rBuf.append( item.second );
1096             rBuf.append( " 0 R" );
1097             if( ((++ni) & 7) == 0 )
1098                 rBuf.append( '\n' );
1099         }
1100     }
1101     rBuf.append( ">>\n" );
1102 }
1103 
1104 void PDFWriterImpl::ResourceDict::append( OStringBuffer& rBuf, sal_Int32 nFontDictObject )
1105 {
1106     rBuf.append( "<</Font " );
1107     rBuf.append( nFontDictObject );
1108     rBuf.append( " 0 R\n" );
1109     appendResourceMap( rBuf, "XObject", m_aXObjects );
1110     appendResourceMap( rBuf, "ExtGState", m_aExtGStates );
1111     appendResourceMap( rBuf, "Shading", m_aShadings );
1112     appendResourceMap( rBuf, "Pattern", m_aPatterns );
1113     rBuf.append( "/ProcSet[/PDF/Text" );
1114     if( !m_aXObjects.empty() )
1115         rBuf.append( "/ImageC/ImageI/ImageB" );
1116     rBuf.append( "]\n>>\n" );
1117 };
1118 
1119 PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1120         :
1121         m_pWriter( pWriter ),
1122         m_nPageWidth( nPageWidth ),
1123         m_nPageHeight( nPageHeight ),
1124         m_eOrientation( eOrientation ),
1125         m_nPageObject( 0 ),  // invalid object number
1126         m_nStreamLengthObject( 0 ),
1127         m_nBeginStreamPos( 0 ),
1128         m_eTransition( PDFWriter::PageTransition::Regular ),
1129         m_nTransTime( 0 )
1130 {
1131     // object ref must be only ever updated in emit()
1132     m_nPageObject = m_pWriter->createObject();
1133 }
1134 
1135 PDFWriterImpl::PDFPage::~PDFPage()
1136 {
1137 }
1138 
1139 void PDFWriterImpl::PDFPage::beginStream()
1140 {
1141     if (g_bDebugDisableCompression)
1142     {
1143         m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
1144     }
1145     m_aStreamObjects.push_back(m_pWriter->createObject());
1146     if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
1147         return;
1148 
1149     m_nStreamLengthObject = m_pWriter->createObject();
1150     // write content stream header
1151     OStringBuffer aLine;
1152     aLine.append( m_aStreamObjects.back() );
1153     aLine.append( " 0 obj\n<</Length " );
1154     aLine.append( m_nStreamLengthObject );
1155     aLine.append( " 0 R" );
1156     if (!g_bDebugDisableCompression)
1157         aLine.append( "/Filter/FlateDecode" );
1158     aLine.append( ">>\nstream\n" );
1159     if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
1160         return;
1161     if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
1162     {
1163         m_pWriter->m_aFile.close();
1164         m_pWriter->m_bOpen = false;
1165     }
1166     if (!g_bDebugDisableCompression)
1167         m_pWriter->beginCompression();
1168     m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
1169 }
1170 
1171 void PDFWriterImpl::PDFPage::endStream()
1172 {
1173     if (!g_bDebugDisableCompression)
1174         m_pWriter->endCompression();
1175     sal_uInt64 nEndStreamPos;
1176     if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
1177     {
1178         m_pWriter->m_aFile.close();
1179         m_pWriter->m_bOpen = false;
1180         return;
1181     }
1182     m_pWriter->disableStreamEncryption();
1183     if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
1184         return;
1185     // emit stream length object
1186     if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
1187         return;
1188     OStringBuffer aLine;
1189     aLine.append( m_nStreamLengthObject );
1190     aLine.append( " 0 obj\n" );
1191     aLine.append( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) );
1192     aLine.append( "\nendobj\n\n" );
1193     m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
1194 }
1195 
1196 bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject )
1197 {
1198     // emit page object
1199     if( ! m_pWriter->updateObject( m_nPageObject ) )
1200         return false;
1201     OStringBuffer aLine;
1202 
1203     aLine.append( m_nPageObject );
1204     aLine.append( " 0 obj\n"
1205                   "<</Type/Page/Parent " );
1206     aLine.append( nParentObject );
1207     aLine.append( " 0 R" );
1208     aLine.append( "/Resources " );
1209     aLine.append( m_pWriter->getResourceDictObj() );
1210     aLine.append( " 0 R" );
1211     if( m_nPageWidth && m_nPageHeight )
1212     {
1213         aLine.append( "/MediaBox[0 0 " );
1214         aLine.append( m_nPageWidth );
1215         aLine.append( ' ' );
1216         aLine.append( m_nPageHeight );
1217         aLine.append( "]" );
1218     }
1219     switch( m_eOrientation )
1220     {
1221         case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
1222         case PDFWriter::Orientation::Inherit:  break;
1223     }
1224     int nAnnots = m_aAnnotations.size();
1225     if( nAnnots > 0 )
1226     {
1227         aLine.append( "/Annots[\n" );
1228         for( int i = 0; i < nAnnots; i++ )
1229         {
1230             aLine.append( m_aAnnotations[i] );
1231             aLine.append( " 0 R" );
1232             aLine.append( ((i+1)%15) ? " " : "\n" );
1233         }
1234         aLine.append( "]\n" );
1235     }
1236     if( m_aMCIDParents.size() > 0 )
1237     {
1238         OStringBuffer aStructParents( 1024 );
1239         aStructParents.append( "[ " );
1240         int nParents = m_aMCIDParents.size();
1241         for( int i = 0; i < nParents; i++ )
1242         {
1243             aStructParents.append( m_aMCIDParents[i] );
1244             aStructParents.append( " 0 R" );
1245             aStructParents.append( ((i%10) == 9) ? "\n" : " " );
1246         }
1247         aStructParents.append( "]" );
1248         m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
1249 
1250         aLine.append( "/StructParents " );
1251         aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
1252         aLine.append( "\n" );
1253     }
1254     if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
1255     {
1256         // transition duration
1257         aLine.append( "/Trans<</D " );
1258         appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
1259         aLine.append( "\n" );
1260         const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
1261         switch( m_eTransition )
1262         {
1263             case PDFWriter::PageTransition::SplitHorizontalInward:
1264                 pStyle = "Split"; pDm = "H"; pM = "I"; break;
1265             case PDFWriter::PageTransition::SplitHorizontalOutward:
1266                 pStyle = "Split"; pDm = "H"; pM = "O"; break;
1267             case PDFWriter::PageTransition::SplitVerticalInward:
1268                 pStyle = "Split"; pDm = "V"; pM = "I"; break;
1269             case PDFWriter::PageTransition::SplitVerticalOutward:
1270                 pStyle = "Split"; pDm = "V"; pM = "O"; break;
1271             case PDFWriter::PageTransition::BlindsHorizontal:
1272                 pStyle = "Blinds"; pDm = "H"; break;
1273             case PDFWriter::PageTransition::BlindsVertical:
1274                 pStyle = "Blinds"; pDm = "V"; break;
1275             case PDFWriter::PageTransition::BoxInward:
1276                 pStyle = "Box"; pM = "I"; break;
1277             case PDFWriter::PageTransition::BoxOutward:
1278                 pStyle = "Box"; pM = "O"; break;
1279             case PDFWriter::PageTransition::WipeLeftToRight:
1280                 pStyle = "Wipe"; pDi = "0"; break;
1281             case PDFWriter::PageTransition::WipeBottomToTop:
1282                 pStyle = "Wipe"; pDi = "90"; break;
1283             case PDFWriter::PageTransition::WipeRightToLeft:
1284                 pStyle = "Wipe"; pDi = "180"; break;
1285             case PDFWriter::PageTransition::WipeTopToBottom:
1286                 pStyle = "Wipe"; pDi = "270"; break;
1287             case PDFWriter::PageTransition::Dissolve:
1288                 pStyle = "Dissolve"; break;
1289             case PDFWriter::PageTransition::Regular:
1290                 break;
1291         }
1292         // transition style
1293         if( pStyle )
1294         {
1295             aLine.append( "/S/" );
1296             aLine.append( pStyle );
1297             aLine.append( "\n" );
1298         }
1299         if( pDm )
1300         {
1301             aLine.append( "/Dm/" );
1302             aLine.append( pDm );
1303             aLine.append( "\n" );
1304         }
1305         if( pM )
1306         {
1307             aLine.append( "/M/" );
1308             aLine.append( pM );
1309             aLine.append( "\n" );
1310         }
1311         if( pDi  )
1312         {
1313             aLine.append( "/Di " );
1314             aLine.append( pDi );
1315             aLine.append( "\n" );
1316         }
1317         aLine.append( ">>\n" );
1318     }
1319     if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
1320     {
1321         aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
1322     }
1323     aLine.append( "/Contents" );
1324     unsigned int nStreamObjects = m_aStreamObjects.size();
1325     if( nStreamObjects > 1 )
1326         aLine.append( '[' );
1327     for(sal_Int32 i : m_aStreamObjects)
1328     {
1329         aLine.append( ' ' );
1330         aLine.append( i );
1331         aLine.append( " 0 R" );
1332     }
1333     if( nStreamObjects > 1 )
1334         aLine.append( ']' );
1335     aLine.append( ">>\nendobj\n\n" );
1336     return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
1337 }
1338 
1339 namespace vcl
1340 {
1341 template < class GEOMETRY >
1342 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
1343 {
1344     GEOMETRY aPoint;
1345     if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
1346     {
1347         aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
1348     }
1349     else
1350     {
1351         aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
1352     }
1353     return aPoint;
1354 }
1355 }
1356 
1357 void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
1358 {
1359     Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1360                                m_pWriter->m_aMapMode,
1361                                m_pWriter->getReferenceDevice(),
1362                                rPoint ) );
1363 
1364     sal_Int32 nValue    = aPoint.X();
1365 
1366     appendFixedInt( nValue, rBuffer );
1367 
1368     rBuffer.append( ' ' );
1369 
1370     nValue      = pointToPixel(getHeight()) - aPoint.Y();
1371 
1372     appendFixedInt( nValue, rBuffer );
1373 }
1374 
1375 void PDFWriterImpl::PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
1376 {
1377     double fValue   = pixelToPoint(rPoint.getX());
1378 
1379     appendDouble( fValue, rBuffer, nLog10Divisor );
1380     rBuffer.append( ' ' );
1381     fValue      = getHeight() - pixelToPoint(rPoint.getY());
1382     appendDouble( fValue, rBuffer, nLog10Divisor );
1383 }
1384 
1385 void PDFWriterImpl::PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
1386 {
1387     appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
1388     rBuffer.append( ' ' );
1389     appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
1390     rBuffer.append( ' ' );
1391     appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
1392     rBuffer.append( " re" );
1393 }
1394 
1395 void PDFWriterImpl::PDFPage::convertRect( tools::Rectangle& rRect ) const
1396 {
1397     Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1398                              m_pWriter->m_aMapMode,
1399                              m_pWriter->getReferenceDevice(),
1400                              rRect.BottomLeft() + Point( 0, 1 )
1401                              );
1402     Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1403                               m_pWriter->m_aMapMode,
1404                               m_pWriter->getReferenceDevice(),
1405                               rRect.GetSize() );
1406     rRect.SetLeft( aLL.X() );
1407     rRect.SetRight( aLL.X() + aSize.Width() );
1408     rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
1409     rRect.SetBottom( rRect.Top() + aSize.Height() );
1410 }
1411 
1412 void PDFWriterImpl::PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
1413 {
1414     sal_uInt16 nPoints = rPoly.GetSize();
1415     /*
1416      *  #108582# applications do weird things
1417      */
1418     sal_uInt32 nBufLen = rBuffer.getLength();
1419     if( nPoints > 0 )
1420     {
1421         const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
1422         appendPoint( rPoly[0], rBuffer );
1423         rBuffer.append( " m\n" );
1424         for( sal_uInt16 i = 1; i < nPoints; i++ )
1425         {
1426             if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
1427             {
1428                 // bezier
1429                 SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
1430                 appendPoint( rPoly[i], rBuffer );
1431                 rBuffer.append( " " );
1432                 appendPoint( rPoly[i+1], rBuffer );
1433                 rBuffer.append( " " );
1434                 appendPoint( rPoly[i+2], rBuffer );
1435                 rBuffer.append( " c" );
1436                 i += 2; // add additionally consumed points
1437             }
1438             else
1439             {
1440                 // line
1441                 appendPoint( rPoly[i], rBuffer );
1442                 rBuffer.append( " l" );
1443             }
1444             if( (rBuffer.getLength() - nBufLen) > 65 )
1445             {
1446                 rBuffer.append( "\n" );
1447                 nBufLen = rBuffer.getLength();
1448             }
1449             else
1450                 rBuffer.append( " " );
1451         }
1452         if( bClose )
1453             rBuffer.append( "h\n" );
1454     }
1455 }
1456 
1457 void PDFWriterImpl::PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
1458 {
1459     basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1460                                             m_pWriter->m_aMapMode,
1461                                             m_pWriter->getReferenceDevice(),
1462                                             rPoly ) );
1463 
1464     if( basegfx::utils::isRectangle( aPoly ) )
1465     {
1466         basegfx::B2DRange aRange( aPoly.getB2DRange() );
1467         basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
1468         appendPixelPoint( aBL, rBuffer );
1469         rBuffer.append( ' ' );
1470         appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
1471         rBuffer.append( ' ' );
1472         appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
1473         rBuffer.append( " re\n" );
1474         return;
1475     }
1476     sal_uInt32 nPoints = aPoly.count();
1477     if( nPoints > 0 )
1478     {
1479         sal_uInt32 nBufLen = rBuffer.getLength();
1480         basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
1481         appendPixelPoint( aLastPoint, rBuffer );
1482         rBuffer.append( " m\n" );
1483         for( sal_uInt32 i = 1; i <= nPoints; i++ )
1484         {
1485             if( i != nPoints || aPoly.isClosed() )
1486             {
1487                 sal_uInt32 nCurPoint  = i % nPoints;
1488                 sal_uInt32 nLastPoint = i-1;
1489                 basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
1490                 if( aPoly.isNextControlPointUsed( nLastPoint ) &&
1491                     aPoly.isPrevControlPointUsed( nCurPoint ) )
1492                 {
1493                     appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1494                     rBuffer.append( ' ' );
1495                     appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1496                     rBuffer.append( ' ' );
1497                     appendPixelPoint( aPoint, rBuffer );
1498                     rBuffer.append( " c" );
1499                 }
1500                 else if( aPoly.isNextControlPointUsed( nLastPoint ) )
1501                 {
1502                     appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1503                     rBuffer.append( ' ' );
1504                     appendPixelPoint( aPoint, rBuffer );
1505                     rBuffer.append( " y" );
1506                 }
1507                 else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
1508                 {
1509                     appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1510                     rBuffer.append( ' ' );
1511                     appendPixelPoint( aPoint, rBuffer );
1512                     rBuffer.append( " v" );
1513                 }
1514                 else
1515                 {
1516                     appendPixelPoint( aPoint, rBuffer );
1517                     rBuffer.append( " l" );
1518                 }
1519                 if( (rBuffer.getLength() - nBufLen) > 65 )
1520                 {
1521                     rBuffer.append( "\n" );
1522                     nBufLen = rBuffer.getLength();
1523                 }
1524                 else
1525                     rBuffer.append( " " );
1526             }
1527         }
1528         rBuffer.append( "h\n" );
1529     }
1530 }
1531 
1532 void PDFWriterImpl::PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1533 {
1534     sal_uInt16 nPolygons = rPolyPoly.Count();
1535     for( sal_uInt16 n = 0; n < nPolygons; n++ )
1536         appendPolygon( rPolyPoly[n], rBuffer );
1537 }
1538 
1539 void PDFWriterImpl::PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1540 {
1541     sal_uInt32 nPolygons = rPolyPoly.count();
1542     for( sal_uInt32 n = 0; n < nPolygons; n++ )
1543         appendPolygon( rPolyPoly.getB2DPolygon( n ), rBuffer );
1544 }
1545 
1546 void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1547 {
1548     sal_Int32 nValue = nLength;
1549     if ( nLength < 0 )
1550     {
1551         rBuffer.append( '-' );
1552         nValue = -nLength;
1553     }
1554     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1555                              m_pWriter->m_aMapMode,
1556                              m_pWriter->getReferenceDevice(),
1557                              Size( nValue, nValue ) ) );
1558     nValue = bVertical ? aSize.Height() : aSize.Width();
1559     if( pOutLength )
1560         *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1561 
1562     appendFixedInt( nValue, rBuffer );
1563 }
1564 
1565 void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1566 {
1567     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1568                              m_pWriter->m_aMapMode,
1569                              m_pWriter->getReferenceDevice(),
1570                              Size( 1000, 1000 ) ) );
1571     fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1572     appendDouble( fLength, rBuffer, nPrecision );
1573 }
1574 
1575 bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1576 {
1577     if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1578     {
1579         // dashed and non-degraded case, check for implementation limits of dash array
1580         // in PDF reader apps (e.g. acroread)
1581         if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1582         {
1583             return false;
1584         }
1585     }
1586 
1587     if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1588     {
1589         // LineJoin used, ExtLineInfo required
1590         return false;
1591     }
1592 
1593     if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1594     {
1595         // LineCap used, ExtLineInfo required
1596         return false;
1597     }
1598 
1599     if( rInfo.GetStyle() == LineStyle::Dash )
1600     {
1601         rBuffer.append( "[ " );
1602         if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1603         {
1604             appendMappedLength( static_cast<sal_Int32>(rInfo.GetDashLen()), rBuffer );
1605             rBuffer.append( ' ' );
1606             appendMappedLength( static_cast<sal_Int32>(rInfo.GetDistance()), rBuffer );
1607             rBuffer.append( ' ' );
1608         }
1609         else
1610         {
1611             for( int n = 0; n < rInfo.GetDashCount(); n++ )
1612             {
1613                 appendMappedLength( static_cast<sal_Int32>(rInfo.GetDashLen()), rBuffer );
1614                 rBuffer.append( ' ' );
1615                 appendMappedLength( static_cast<sal_Int32>(rInfo.GetDistance()), rBuffer );
1616                 rBuffer.append( ' ' );
1617             }
1618             for( int m = 0; m < rInfo.GetDotCount(); m++ )
1619             {
1620                 appendMappedLength( static_cast<sal_Int32>(rInfo.GetDotLen()), rBuffer );
1621                 rBuffer.append( ' ' );
1622                 appendMappedLength( static_cast<sal_Int32>(rInfo.GetDistance()), rBuffer );
1623                 rBuffer.append( ' ' );
1624             }
1625         }
1626         rBuffer.append( "] 0 d\n" );
1627     }
1628 
1629     if( rInfo.GetWidth() > 1 )
1630     {
1631         appendMappedLength( static_cast<sal_Int32>(rInfo.GetWidth()), rBuffer );
1632         rBuffer.append( " w\n" );
1633     }
1634     else if( rInfo.GetWidth() == 0 )
1635     {
1636         // "pixel" line
1637         appendDouble( 72.0/double(m_pWriter->getReferenceDevice()->GetDPIX()), rBuffer );
1638         rBuffer.append( " w\n" );
1639     }
1640 
1641     return true;
1642 }
1643 
1644 void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1645 {
1646     if( nWidth <= 0 )
1647         return;
1648     if( nDelta < 1 )
1649         nDelta = 1;
1650 
1651     rBuffer.append( "0 " );
1652     appendMappedLength( nY, rBuffer );
1653     rBuffer.append( " m\n" );
1654     for( sal_Int32 n = 0; n < nWidth; )
1655     {
1656         n += nDelta;
1657         appendMappedLength( n, rBuffer, false );
1658         rBuffer.append( ' ' );
1659         appendMappedLength( nDelta+nY, rBuffer );
1660         rBuffer.append( ' ' );
1661         n += nDelta;
1662         appendMappedLength( n, rBuffer, false );
1663         rBuffer.append( ' ' );
1664         appendMappedLength( nY, rBuffer );
1665         rBuffer.append( " v " );
1666         if( n < nWidth )
1667         {
1668             n += nDelta;
1669             appendMappedLength( n, rBuffer, false );
1670             rBuffer.append( ' ' );
1671             appendMappedLength( nY-nDelta, rBuffer );
1672             rBuffer.append( ' ' );
1673             n += nDelta;
1674             appendMappedLength( n, rBuffer, false );
1675             rBuffer.append( ' ' );
1676             appendMappedLength( nY, rBuffer );
1677             rBuffer.append( " v\n" );
1678         }
1679     }
1680     rBuffer.append( "S\n" );
1681 }
1682 
1683  PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1684                                const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
1685                                PDFWriter& i_rOuterFace)
1686         :
1687         m_pReferenceDevice( nullptr ),
1688         m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1689         m_nCurrentStructElement( 0 ),
1690         m_bEmitStructure( true ),
1691         m_nNextFID( 1 ),
1692         m_nInheritedPageWidth( 595 ),  // default A4
1693         m_nInheritedPageHeight( 842 ), // default A4
1694         m_nCurrentPage( -1 ),
1695         m_nCatalogObject(0),
1696         m_nSignatureObject( -1 ),
1697         m_nSignatureContentOffset( 0 ),
1698         m_nSignatureLastByteRangeNoOffset( 0 ),
1699         m_nResourceDict( -1 ),
1700         m_nFontDictObject( -1 ),
1701         m_aContext(rContext),
1702         m_aFile(m_aContext.URL),
1703         m_bOpen(false),
1704         m_DocDigest(::comphelper::HashType::MD5),
1705         m_aCipher( nullptr ),
1706         m_nKeyLength(0),
1707         m_nRC4KeyLength(0),
1708         m_bEncryptThisStream( false ),
1709         m_nAccessPermissions(0),
1710         m_pEncryptionBuffer( nullptr ),
1711         m_nEncryptionBufferSize( 0 ),
1712         m_bIsPDF_A1( false ),
1713         m_rOuterFace( i_rOuterFace )
1714 {
1715 #ifdef DO_TEST_PDF
1716     static bool bOnce = true;
1717     if( bOnce )
1718     {
1719         bOnce = false;
1720         doTestCode();
1721     }
1722 #endif
1723     m_aStructure.emplace_back( );
1724     m_aStructure[0].m_nOwnElement       = 0;
1725     m_aStructure[0].m_nParentElement    = 0;
1726 
1727     Font aFont;
1728     aFont.SetFamilyName( "Times" );
1729     aFont.SetFontSize( Size( 0, 12 ) );
1730 
1731     GraphicsState aState;
1732     aState.m_aMapMode       = m_aMapMode;
1733     aState.m_aFont          = aFont;
1734     m_aGraphicsStack.push_front( aState );
1735 
1736     osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1737     if (aError != osl::File::E_None)
1738     {
1739         if (aError == osl::File::E_EXIST)
1740         {
1741             aError = m_aFile.open(osl_File_OpenFlag_Write);
1742             if (aError == osl::File::E_None)
1743                 aError = m_aFile.setSize(0);
1744         }
1745     }
1746     if (aError != osl::File::E_None)
1747         return;
1748 
1749     m_bOpen = true;
1750 
1751     // setup DocInfo
1752     setupDocInfo();
1753 
1754     /* prepare the cypher engine, can be done in CTOR, free in DTOR */
1755     m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
1756 
1757     /* the size of the Codec default maximum */
1758     /* is this 0x4000 required to be the same as MAX_SIGNATURE_CONTENT_LENGTH or just coincidentally the same at the moment? */
1759     if (!checkEncryptionBufferSize(0x4000))
1760     {
1761         m_aFile.close();
1762         m_bOpen = false;
1763         return;
1764     }
1765 
1766     if( xEnc.is() )
1767         prepareEncryption( xEnc );
1768 
1769     if( m_aContext.Encryption.Encrypt() )
1770     {
1771         // sanity check
1772         if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
1773             m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
1774             m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
1775            )
1776         {
1777             // the field lengths are invalid ? This was not setup by initEncryption.
1778             // do not encrypt after all
1779             m_aContext.Encryption.OValue.clear();
1780             m_aContext.Encryption.UValue.clear();
1781             OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
1782         }
1783         else // setup key lengths
1784             m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
1785     }
1786 
1787     // write header
1788     OStringBuffer aBuffer( 20 );
1789     aBuffer.append( "%PDF-" );
1790     switch( m_aContext.Version )
1791     {
1792         case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
1793         case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
1794         case PDFWriter::PDFVersion::PDF_A_1:
1795         case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
1796         default:
1797         case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
1798     }
1799     // append something binary as comment (suggested in PDF Reference)
1800     aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1801     if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
1802     {
1803         m_aFile.close();
1804         m_bOpen = false;
1805         return;
1806     }
1807 
1808     // insert outline root
1809     m_aOutline.emplace_back( );
1810 
1811     m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
1812     if( m_bIsPDF_A1 )
1813         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1814 }
1815 
1816 PDFWriterImpl::~PDFWriterImpl()
1817 {
1818     m_pReferenceDevice.disposeAndClear();
1819 
1820     if( m_aCipher )
1821         rtl_cipher_destroyARCFOUR( m_aCipher );
1822 
1823     rtl_freeMemory( m_pEncryptionBuffer );
1824 }
1825 
1826 void PDFWriterImpl::setupDocInfo()
1827 {
1828     std::vector< sal_uInt8 > aId;
1829     m_aCreationDateString = PDFWriter::GetDateTime();
1830     computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
1831     if( m_aContext.Encryption.DocumentIdentifier.empty() )
1832         m_aContext.Encryption.DocumentIdentifier = aId;
1833 }
1834 
1835 OString PDFWriter::GetDateTime()
1836 {
1837     OStringBuffer aRet;
1838 
1839     TimeValue aTVal, aGMT;
1840     oslDateTime aDT;
1841     osl_getSystemTime(&aGMT);
1842     osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1843     osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1844     aRet.append("D:");
1845     aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 1000) % 10)));
1846     aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 100) % 10)));
1847     aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 10) % 10)));
1848     aRet.append(static_cast<sal_Char>('0' + (aDT.Year % 10)));
1849     aRet.append(static_cast<sal_Char>('0' + ((aDT.Month / 10) % 10)));
1850     aRet.append(static_cast<sal_Char>('0' + (aDT.Month % 10)));
1851     aRet.append(static_cast<sal_Char>('0' + ((aDT.Day / 10) % 10)));
1852     aRet.append(static_cast<sal_Char>('0' + (aDT.Day % 10)));
1853     aRet.append(static_cast<sal_Char>('0' + ((aDT.Hours / 10) % 10)));
1854     aRet.append(static_cast<sal_Char>('0' + (aDT.Hours % 10)));
1855     aRet.append(static_cast<sal_Char>('0' + ((aDT.Minutes / 10) % 10)));
1856     aRet.append(static_cast<sal_Char>('0' + (aDT.Minutes % 10)));
1857     aRet.append(static_cast<sal_Char>('0' + ((aDT.Seconds / 10) % 10)));
1858     aRet.append(static_cast<sal_Char>('0' + (aDT.Seconds % 10)));
1859 
1860     sal_uInt32 nDelta = 0;
1861     if (aGMT.Seconds > aTVal.Seconds)
1862     {
1863         aRet.append("-");
1864         nDelta = aGMT.Seconds-aTVal.Seconds;
1865     }
1866     else if (aGMT.Seconds < aTVal.Seconds)
1867     {
1868         aRet.append("+");
1869         nDelta = aTVal.Seconds-aGMT.Seconds;
1870     }
1871     else
1872         aRet.append("Z");
1873 
1874     if (nDelta)
1875     {
1876         aRet.append(static_cast<sal_Char>('0' + ((nDelta / 36000) % 10)));
1877         aRet.append(static_cast<sal_Char>('0' + ((nDelta / 3600) % 10)));
1878         aRet.append("'");
1879         aRet.append(static_cast<sal_Char>('0' + ((nDelta / 600) % 6)));
1880         aRet.append(static_cast<sal_Char>('0' + ((nDelta / 60) % 10)));
1881     }
1882     aRet.append( "'" );
1883 
1884     return aRet.makeStringAndClear();
1885 }
1886 
1887 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1888                                                const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1889                                                const OString& i_rCString1,
1890                                                OString& o_rCString2
1891                                                )
1892 {
1893     o_rIdentifier.clear();
1894 
1895     //build the document id
1896     OString aInfoValuesOut;
1897     OStringBuffer aID( 1024 );
1898     if( !i_rDocInfo.Title.isEmpty() )
1899         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
1900     if( !i_rDocInfo.Author.isEmpty() )
1901         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
1902     if( !i_rDocInfo.Subject.isEmpty() )
1903         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
1904     if( !i_rDocInfo.Keywords.isEmpty() )
1905         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
1906     if( !i_rDocInfo.Creator.isEmpty() )
1907         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
1908     if( !i_rDocInfo.Producer.isEmpty() )
1909         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
1910 
1911     TimeValue aTVal, aGMT;
1912     oslDateTime aDT;
1913     osl_getSystemTime( &aGMT );
1914     osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1915     osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1916     OStringBuffer aCreationMetaDateString(64);
1917 
1918     // i59651: we fill the Metadata date string as well, if PDF/A is requested
1919     // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1920     // local time zone offset UTC only, whereas Acrobat 8 seems
1921     // to use the localtime notation only
1922     // according to a recommendation in XMP Specification (Jan 2004, page 75)
1923     // the Acrobat way seems the right approach
1924     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/1000)%10)) );
1925     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/100)%10)) );
1926     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/10)%10)) );
1927     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year)%10)) );
1928     aCreationMetaDateString.append( "-" );
1929     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Month/10)%10)) );
1930     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Month)%10)) );
1931     aCreationMetaDateString.append( "-" );
1932     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Day/10)%10)) );
1933     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Day)%10)) );
1934     aCreationMetaDateString.append( "T" );
1935     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Hours/10)%10)) );
1936     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Hours)%10)) );
1937     aCreationMetaDateString.append( ":" );
1938     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Minutes/10)%10)) );
1939     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Minutes)%10)) );
1940     aCreationMetaDateString.append( ":" );
1941     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Seconds/10)%10)) );
1942     aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Seconds)%10)) );
1943 
1944     sal_uInt32 nDelta = 0;
1945     if( aGMT.Seconds > aTVal.Seconds )
1946     {
1947         nDelta = aGMT.Seconds-aTVal.Seconds;
1948         aCreationMetaDateString.append( "-" );
1949     }
1950     else if( aGMT.Seconds < aTVal.Seconds )
1951     {
1952         nDelta = aTVal.Seconds-aGMT.Seconds;
1953         aCreationMetaDateString.append( "+" );
1954     }
1955     else
1956     {
1957         aCreationMetaDateString.append( "Z" );
1958 
1959     }
1960     if( nDelta )
1961     {
1962         aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/36000)%10)) );
1963         aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/3600)%10)) );
1964         aCreationMetaDateString.append( ":" );
1965         aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/600)%6)) );
1966         aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/60)%10)) );
1967     }
1968     aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1969 
1970     aInfoValuesOut = aID.makeStringAndClear();
1971     o_rCString2 = aCreationMetaDateString.makeStringAndClear();
1972 
1973     ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
1974     aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
1975     aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
1976     //the binary form of the doc id is needed for encryption stuff
1977     o_rIdentifier = aDigest.finalize();
1978 }
1979 
1980 /* i12626 methods */
1981 /*
1982 check if the Unicode string must be encrypted or not, perform the requested task,
1983 append the string as unicode hex, encrypted if needed
1984  */
1985 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1986 {
1987     rOutBuffer.append( "<" );
1988     if( m_aContext.Encryption.Encrypt() )
1989     {
1990         const sal_Unicode* pStr = rInString.getStr();
1991         sal_Int32 nLen = rInString.getLength();
1992         //prepare a unicode string, encrypt it
1993         if( checkEncryptionBufferSize( nLen*2 ) )
1994         {
1995             enableStringEncryption( nInObjectNumber );
1996             sal_uInt8 *pCopy = m_pEncryptionBuffer;
1997             sal_Int32 nChars = 2;
1998             *pCopy++ = 0xFE;
1999             *pCopy++ = 0xFF;
2000             // we need to prepare a byte stream from the unicode string buffer
2001             for( int i = 0; i < nLen; i++ )
2002             {
2003                 sal_Unicode aUnChar = pStr[i];
2004                 *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
2005                 *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
2006                 nChars += 2;
2007             }
2008             //encrypt in place
2009             rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChars, m_pEncryptionBuffer, nChars );
2010             //now append, hexadecimal (appendHex), the encrypted result
2011             for(int i = 0; i < nChars; i++)
2012                 appendHex( m_pEncryptionBuffer[i], rOutBuffer );
2013         }
2014     }
2015     else
2016         PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
2017     rOutBuffer.append( ">" );
2018 }
2019 
2020 inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
2021 {
2022     rOutBuffer.append( "(" );
2023     sal_Int32 nChars = rInString.getLength();
2024     //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
2025     if( m_aContext.Encryption.Encrypt() && checkEncryptionBufferSize( nChars ) )
2026     {
2027         //encrypt the string in a buffer, then append it
2028         enableStringEncryption( nInObjectNumber );
2029         rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_pEncryptionBuffer, nChars );
2030         appendLiteralString( reinterpret_cast<sal_Char*>(m_pEncryptionBuffer), nChars, rOutBuffer );
2031     }
2032     else
2033         appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
2034     rOutBuffer.append( ")" );
2035 }
2036 
2037 inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
2038 {
2039     OStringBuffer aBufferString( rInString );
2040     appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
2041 }
2042 
2043 void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
2044 {
2045     OString aBufferString( OUStringToOString( rInString, nEnc ) );
2046     sal_Int32 nLen = aBufferString.getLength();
2047     OStringBuffer aBuf( nLen );
2048     const sal_Char* pT = aBufferString.getStr();
2049 
2050     for( sal_Int32 i = 0; i < nLen; i++, pT++ )
2051     {
2052         if( (*pT & 0x80) == 0 )
2053             aBuf.append( *pT );
2054         else
2055         {
2056             aBuf.append( '<' );
2057             appendHex( *pT, aBuf );
2058             aBuf.append( '>' );
2059         }
2060     }
2061     aBufferString = aBuf.makeStringAndClear();
2062     appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
2063 }
2064 
2065 /* end i12626 methods */
2066 
2067 void PDFWriterImpl::emitComment( const char* pComment )
2068 {
2069     OStringBuffer aLine( 64 );
2070     aLine.append( "% " );
2071     aLine.append( pComment );
2072     aLine.append( "\n" );
2073     writeBuffer( aLine.getStr(), aLine.getLength() );
2074 }
2075 
2076 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
2077 {
2078     if (!g_bDebugDisableCompression)
2079     {
2080         pStream->Seek( STREAM_SEEK_TO_END );
2081         sal_uLong nEndPos = pStream->Tell();
2082         pStream->Seek( STREAM_SEEK_TO_BEGIN );
2083         ZCodec aCodec( 0x4000, 0x4000 );
2084         SvMemoryStream aStream;
2085         aCodec.BeginCompression();
2086         aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
2087         aCodec.EndCompression();
2088         nEndPos = aStream.Tell();
2089         pStream->Seek( STREAM_SEEK_TO_BEGIN );
2090         aStream.Seek( STREAM_SEEK_TO_BEGIN );
2091         pStream->SetStreamSize( nEndPos );
2092         pStream->WriteBytes( aStream.GetData(), nEndPos );
2093         return true;
2094     }
2095     else
2096         return false;
2097 }
2098 
2099 void PDFWriterImpl::beginCompression()
2100 {
2101     if (!g_bDebugDisableCompression)
2102     {
2103         m_pCodec = o3tl::make_unique<ZCodec>( 0x4000, 0x4000 );
2104         m_pMemStream = o3tl::make_unique<SvMemoryStream>();
2105         m_pCodec->BeginCompression();
2106     }
2107 }
2108 
2109 void PDFWriterImpl::endCompression()
2110 {
2111     if (!g_bDebugDisableCompression && m_pCodec)
2112     {
2113         m_pCodec->EndCompression();
2114         m_pCodec.reset();
2115         sal_uInt64 nLen = m_pMemStream->Tell();
2116         m_pMemStream->Seek( 0 );
2117         writeBuffer( m_pMemStream->GetData(), nLen );
2118         m_pMemStream.reset();
2119     }
2120 }
2121 
2122 bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
2123 {
2124     if( ! m_bOpen ) // we are already down the drain
2125         return false;
2126 
2127     if( ! nBytes ) // huh ?
2128         return true;
2129 
2130     if( !m_aOutputStreams.empty() )
2131     {
2132         m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
2133         m_aOutputStreams.front().m_pStream->WriteBytes(
2134                 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
2135         return true;
2136     }
2137 
2138     sal_uInt64 nWritten;
2139     if( m_pCodec )
2140     {
2141         m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
2142         nWritten = nBytes;
2143     }
2144     else
2145     {
2146         bool  buffOK = true;
2147         if( m_bEncryptThisStream )
2148         {
2149             /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
2150             buffOK = checkEncryptionBufferSize( static_cast<sal_Int32>(nBytes) );
2151             if( buffOK )
2152                 rtl_cipher_encodeARCFOUR( m_aCipher,
2153                                           pBuffer, static_cast<sal_Size>(nBytes),
2154                                           m_pEncryptionBuffer, static_cast<sal_Size>(nBytes) );
2155         }
2156 
2157         const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_pEncryptionBuffer  : pBuffer;
2158         m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
2159 
2160         if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
2161             nWritten = 0;
2162 
2163         if( nWritten != nBytes )
2164         {
2165             m_aFile.close();
2166             m_bOpen = false;
2167         }
2168     }
2169 
2170     return nWritten == nBytes;
2171 }
2172 
2173 OutputDevice* PDFWriterImpl::getReferenceDevice()
2174 {
2175     if( ! m_pReferenceDevice )
2176     {
2177         VclPtrInstance<VirtualDevice> pVDev(DeviceFormat::DEFAULT);
2178 
2179         m_pReferenceDevice = pVDev;
2180 
2181         if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
2182             pVDev->SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
2183         else
2184             pVDev->SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
2185 
2186         pVDev->SetOutputSizePixel( Size( 640, 480 ) );
2187         pVDev->SetMapMode(MapMode(MapUnit::MapMM));
2188 
2189         m_pReferenceDevice->mpPDFWriter = this;
2190         m_pReferenceDevice->ImplUpdateFontData();
2191     }
2192     return m_pReferenceDevice;
2193 }
2194 
2195 static FontAttributes GetDevFontAttributes( const PDFWriterImpl::BuiltinFont& rBuiltin )
2196 {
2197     FontAttributes aDFA;
2198     aDFA.SetFamilyName( OUString::createFromAscii( rBuiltin.m_pName ) );
2199     aDFA.SetStyleName( OUString::createFromAscii( rBuiltin.m_pStyleName ) );
2200     aDFA.SetFamilyType( rBuiltin.m_eFamily );
2201     aDFA.SetSymbolFlag( rBuiltin.m_eCharSet != RTL_TEXTENCODING_MS_1252 );
2202     aDFA.SetPitch( rBuiltin.m_ePitch );
2203     aDFA.SetWeight( rBuiltin.m_eWeight );
2204     aDFA.SetItalic( rBuiltin.m_eItalic );
2205     aDFA.SetWidthType( rBuiltin.m_eWidthType );
2206 
2207     aDFA.SetQuality( 50000 );
2208     return aDFA;
2209 }
2210 
2211 PdfBuiltinFontFace::PdfBuiltinFontFace( const PDFWriterImpl::BuiltinFont& rBuiltin )
2212 :   PhysicalFontFace( GetDevFontAttributes(rBuiltin) ),
2213     mrBuiltin( rBuiltin )
2214 {}
2215 
2216 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
2217 {
2218     endPage();
2219     m_nCurrentPage = m_aPages.size();
2220     m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
2221     m_aPages.back().beginStream();
2222 
2223     // setup global graphics state
2224     // linewidth is "1 pixel" by default
2225     OStringBuffer aBuf( 16 );
2226     appendDouble( 72.0/double(getReferenceDevice()->GetDPIX()), aBuf );
2227     aBuf.append( " w\n" );
2228     writeBuffer( aBuf.getStr(), aBuf.getLength() );
2229 }
2230 
2231 void PDFWriterImpl::endPage()
2232 {
2233     if( m_aPages.empty() )
2234         return;
2235 
2236     // close eventual MC sequence
2237     endStructureElementMCSeq();
2238 
2239     // sanity check
2240     if( !m_aOutputStreams.empty() )
2241     {
2242         OSL_FAIL( "redirection across pages !!!" );
2243         m_aOutputStreams.clear(); // leak !
2244         m_aMapMode.SetOrigin( Point() );
2245     }
2246 
2247     m_aGraphicsStack.clear();
2248     m_aGraphicsStack.emplace_back( );
2249 
2250     // this should pop the PDF graphics stack if necessary
2251     updateGraphicsState();
2252 
2253     m_aPages.back().endStream();
2254 
2255     // reset the default font
2256     Font aFont;
2257     aFont.SetFamilyName( "Times" );
2258     aFont.SetFontSize( Size( 0, 12 ) );
2259 
2260     m_aCurrentPDFState = m_aGraphicsStack.front();
2261     m_aGraphicsStack.front().m_aFont =  aFont;
2262 
2263     for (auto & bitmap : m_aBitmaps)
2264     {
2265         if( ! bitmap.m_aBitmap.IsEmpty() )
2266         {
2267             writeBitmapObject(bitmap);
2268             bitmap.m_aBitmap = BitmapEx();
2269         }
2270     }
2271     for (auto & jpeg : m_aJPGs)
2272     {
2273         if( jpeg.m_pStream )
2274         {
2275             writeJPG( jpeg );
2276             jpeg.m_pStream.reset();
2277             jpeg.m_aMask = Bitmap();
2278         }
2279     }
2280     for (auto & item : m_aTransparentObjects)
2281     {
2282         if( item.m_pContentStream )
2283         {
2284             writeTransparentObject(item);
2285             item.m_pContentStream.reset();
2286         }
2287     }
2288 
2289 }
2290 
2291 sal_Int32 PDFWriterImpl::createObject()
2292 {
2293     m_aObjects.push_back( ~0U );
2294     return m_aObjects.size();
2295 }
2296 
2297 bool PDFWriterImpl::updateObject( sal_Int32 n )
2298 {
2299     if( ! m_bOpen )
2300         return false;
2301 
2302     sal_uInt64 nOffset = ~0U;
2303     osl::File::RC aError = m_aFile.getPos(nOffset);
2304     SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
2305     if (aError != osl::File::E_None)
2306     {
2307         m_aFile.close();
2308         m_bOpen = false;
2309     }
2310     m_aObjects[ n-1 ] = nOffset;
2311     return aError == osl::File::E_None;
2312 }
2313 
2314 #define CHECK_RETURN( x ) if( !(x) ) return 0
2315 #define CHECK_RETURN2( x ) if( !(x) ) return
2316 
2317 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
2318 {
2319     if( nObject > 0 )
2320     {
2321         OStringBuffer aLine( 1024 );
2322 
2323         aLine.append( nObject );
2324         aLine.append( " 0 obj\n"
2325                       "<</Nums[\n" );
2326         sal_Int32 nTreeItems = m_aStructParentTree.size();
2327         for( sal_Int32 n = 0; n < nTreeItems; n++ )
2328         {
2329             aLine.append( n );
2330             aLine.append( ' ' );
2331             aLine.append( m_aStructParentTree[n] );
2332             aLine.append( "\n" );
2333         }
2334         aLine.append( "]>>\nendobj\n\n" );
2335         CHECK_RETURN( updateObject( nObject ) );
2336         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2337     }
2338     return nObject;
2339 }
2340 
2341 const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
2342 {
2343     static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
2344     // fill maps once
2345     if( aAttributeStrings.empty() )
2346     {
2347         aAttributeStrings[ PDFWriter::Placement ]           = "Placement";
2348         aAttributeStrings[ PDFWriter::WritingMode ]         = "WritingMode";
2349         aAttributeStrings[ PDFWriter::SpaceBefore ]         = "SpaceBefore";
2350         aAttributeStrings[ PDFWriter::SpaceAfter ]          = "SpaceAfter";
2351         aAttributeStrings[ PDFWriter::StartIndent ]         = "StartIndent";
2352         aAttributeStrings[ PDFWriter::EndIndent ]           = "EndIndent";
2353         aAttributeStrings[ PDFWriter::TextIndent ]          = "TextIndent";
2354         aAttributeStrings[ PDFWriter::TextAlign ]           = "TextAlign";
2355         aAttributeStrings[ PDFWriter::Width ]               = "Width";
2356         aAttributeStrings[ PDFWriter::Height ]              = "Height";
2357         aAttributeStrings[ PDFWriter::BlockAlign ]          = "BlockAlign";
2358         aAttributeStrings[ PDFWriter::InlineAlign ]         = "InlineAlign";
2359         aAttributeStrings[ PDFWriter::LineHeight ]          = "LineHeight";
2360         aAttributeStrings[ PDFWriter::BaselineShift ]       = "BaselineShift";
2361         aAttributeStrings[ PDFWriter::TextDecorationType ]  = "TextDecorationType";
2362         aAttributeStrings[ PDFWriter::ListNumbering ]       = "ListNumbering";
2363         aAttributeStrings[ PDFWriter::RowSpan ]             = "RowSpan";
2364         aAttributeStrings[ PDFWriter::ColSpan ]             = "ColSpan";
2365         aAttributeStrings[ PDFWriter::LinkAnnotation ]      = "LinkAnnotation";
2366     }
2367 
2368     std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
2369         aAttributeStrings.find( eAttr );
2370 
2371     if( it == aAttributeStrings.end() )
2372         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
2373 
2374     return it != aAttributeStrings.end() ? it->second : "";
2375 }
2376 
2377 const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
2378 {
2379     static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
2380 
2381     if( aValueStrings.empty() )
2382     {
2383         aValueStrings[ PDFWriter::NONE ]                    = "None";
2384         aValueStrings[ PDFWriter::Block ]                   = "Block";
2385         aValueStrings[ PDFWriter::Inline ]                  = "Inline";
2386         aValueStrings[ PDFWriter::Before ]                  = "Before";
2387         aValueStrings[ PDFWriter::After ]                   = "After";
2388         aValueStrings[ PDFWriter::Start ]                   = "Start";
2389         aValueStrings[ PDFWriter::End ]                     = "End";
2390         aValueStrings[ PDFWriter::LrTb ]                    = "LrTb";
2391         aValueStrings[ PDFWriter::RlTb ]                    = "RlTb";
2392         aValueStrings[ PDFWriter::TbRl ]                    = "TbRl";
2393         aValueStrings[ PDFWriter::Center ]                  = "Center";
2394         aValueStrings[ PDFWriter::Justify ]                 = "Justify";
2395         aValueStrings[ PDFWriter::Auto ]                    = "Auto";
2396         aValueStrings[ PDFWriter::Middle ]                  = "Middle";
2397         aValueStrings[ PDFWriter::Normal ]                  = "Normal";
2398         aValueStrings[ PDFWriter::Underline ]               = "Underline";
2399         aValueStrings[ PDFWriter::Overline ]                = "Overline";
2400         aValueStrings[ PDFWriter::LineThrough ]             = "LineThrough";
2401         aValueStrings[ PDFWriter::Disc ]                    = "Disc";
2402         aValueStrings[ PDFWriter::Circle ]                  = "Circle";
2403         aValueStrings[ PDFWriter::Square ]                  = "Square";
2404         aValueStrings[ PDFWriter::Decimal ]                 = "Decimal";
2405         aValueStrings[ PDFWriter::UpperRoman ]              = "UpperRoman";
2406         aValueStrings[ PDFWriter::LowerRoman ]              = "LowerRoman";
2407         aValueStrings[ PDFWriter::UpperAlpha ]              = "UpperAlpha";
2408         aValueStrings[ PDFWriter::LowerAlpha ]              = "LowerAlpha";
2409     }
2410 
2411     std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
2412         aValueStrings.find( eVal );
2413 
2414     if( it == aValueStrings.end() )
2415         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
2416 
2417     return it != aValueStrings.end() ? it->second : "";
2418 }
2419 
2420 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
2421 {
2422     o_rLine.append( "/" );
2423     o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
2424 
2425     if( i_rVal.eValue != PDFWriter::Invalid )
2426     {
2427         o_rLine.append( "/" );
2428         o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
2429     }
2430     else
2431     {
2432         // numerical value
2433         o_rLine.append( " " );
2434         if( i_bIsFixedInt )
2435             appendFixedInt( i_rVal.nValue, o_rLine );
2436         else
2437             o_rLine.append( i_rVal.nValue );
2438     }
2439     o_rLine.append( "\n" );
2440 }
2441 
2442 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
2443 {
2444     // create layout, list and table attribute sets
2445     OStringBuffer aLayout(256), aList(64), aTable(64);
2446     for (auto const& attribute : i_rEle.m_aAttributes)
2447     {
2448         if( attribute.first == PDFWriter::ListNumbering )
2449             appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
2450         else if( attribute.first == PDFWriter::RowSpan ||
2451                  attribute.first == PDFWriter::ColSpan )
2452             appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
2453         else if( attribute.first == PDFWriter::LinkAnnotation )
2454         {
2455             sal_Int32 nLink = attribute.second.nValue;
2456             std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
2457                 m_aLinkPropertyMap.find( nLink );
2458             if( link_it != m_aLinkPropertyMap.end() )
2459                 nLink = link_it->second;
2460             if( nLink >= 0 && nLink < static_cast<sal_Int32>(m_aLinks.size()) )
2461             {
2462                 // update struct parent of link
2463                 OStringBuffer aStructParentEntry( 32 );
2464                 aStructParentEntry.append( i_rEle.m_nObject );
2465                 aStructParentEntry.append( " 0 R" );
2466                 m_aStructParentTree.push_back( aStructParentEntry.makeStringAndClear() );
2467                 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
2468 
2469                 sal_Int32 nRefObject = createObject();
2470                 OStringBuffer aRef( 256 );
2471                 aRef.append( nRefObject );
2472                 aRef.append( " 0 obj\n"
2473                              "<</Type/OBJR/Obj " );
2474                 aRef.append( m_aLinks[ nLink ].m_nObject );
2475                 aRef.append( " 0 R>>\n"
2476                              "endobj\n\n"
2477                              );
2478                 if (updateObject(nRefObject))
2479                 {
2480                     writeBuffer( aRef.getStr(), aRef.getLength() );
2481                 }
2482 
2483                 i_rEle.m_aKids.emplace_back( nRefObject );
2484             }
2485             else
2486             {
2487                 OSL_FAIL( "unresolved link id for Link structure" );
2488                 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
2489                 if (g_bDebugDisableCompression)
2490                 {
2491                     OStringBuffer aLine( "unresolved link id " );
2492                     aLine.append( nLink );
2493                     aLine.append( " for Link structure" );
2494                     emitComment( aLine.getStr() );
2495                 }
2496             }
2497         }
2498         else
2499             appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
2500     }
2501     if( ! i_rEle.m_aBBox.IsEmpty() )
2502     {
2503         aLayout.append( "/BBox[" );
2504         appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
2505         aLayout.append( " " );
2506         appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
2507         aLayout.append( " " );
2508         appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
2509         aLayout.append( " " );
2510         appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
2511         aLayout.append( "]\n" );
2512     }
2513 
2514     std::vector< sal_Int32 > aAttribObjects;
2515     if( !aLayout.isEmpty() )
2516     {
2517         aAttribObjects.push_back( createObject() );
2518         if (updateObject( aAttribObjects.back() ))
2519         {
2520             OStringBuffer aObj( 64 );
2521             aObj.append( aAttribObjects.back() );
2522             aObj.append( " 0 obj\n"
2523                          "<</O/Layout\n" );
2524             aLayout.append( ">>\nendobj\n\n" );
2525             writeBuffer( aObj.getStr(), aObj.getLength() );
2526             writeBuffer( aLayout.getStr(), aLayout.getLength() );
2527         }
2528     }
2529     if( !aList.isEmpty() )
2530     {
2531         aAttribObjects.push_back( createObject() );
2532         if (updateObject( aAttribObjects.back() ))
2533         {
2534             OStringBuffer aObj( 64 );
2535             aObj.append( aAttribObjects.back() );
2536             aObj.append( " 0 obj\n"
2537                          "<</O/List\n" );
2538             aList.append( ">>\nendobj\n\n" );
2539             writeBuffer( aObj.getStr(), aObj.getLength() );
2540             writeBuffer( aList.getStr(), aList.getLength() );
2541         }
2542     }
2543     if( !aTable.isEmpty() )
2544     {
2545         aAttribObjects.push_back( createObject() );
2546         if (updateObject( aAttribObjects.back() ))
2547         {
2548             OStringBuffer aObj( 64 );
2549             aObj.append( aAttribObjects.back() );
2550             aObj.append( " 0 obj\n"
2551                          "<</O/Table\n" );
2552             aTable.append( ">>\nendobj\n\n" );
2553             writeBuffer( aObj.getStr(), aObj.getLength() );
2554             writeBuffer( aTable.getStr(), aTable.getLength() );
2555         }
2556     }
2557 
2558     OStringBuffer aRet( 64 );
2559     if( aAttribObjects.size() > 1 )
2560         aRet.append( " [" );
2561     for (auto const& attrib : aAttribObjects)
2562     {
2563         aRet.append( " " );
2564         aRet.append( attrib );
2565         aRet.append( " 0 R" );
2566     }
2567     if( aAttribObjects.size() > 1 )
2568         aRet.append( " ]" );
2569     return aRet.makeStringAndClear();
2570 }
2571 
2572 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
2573 {
2574     if(
2575        // do not emit NonStruct and its children
2576        rEle.m_eType == PDFWriter::NonStructElement &&
2577        rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
2578        )
2579         return 0;
2580 
2581     for (auto const& child : rEle.m_aChildren)
2582     {
2583         if( child > 0 && child < sal_Int32(m_aStructure.size()) )
2584         {
2585             PDFStructureElement& rChild = m_aStructure[ child ];
2586             if( rChild.m_eType != PDFWriter::NonStructElement )
2587             {
2588                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2589                     emitStructure( rChild );
2590                 else
2591                 {
2592                     OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2593                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
2594                 }
2595             }
2596         }
2597         else
2598         {
2599             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2600             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
2601         }
2602     }
2603 
2604     OStringBuffer aLine( 512 );
2605     aLine.append( rEle.m_nObject );
2606     aLine.append( " 0 obj\n"
2607                   "<</Type" );
2608     sal_Int32 nParentTree = -1;
2609     if( rEle.m_nOwnElement == rEle.m_nParentElement )
2610     {
2611         nParentTree = createObject();
2612         CHECK_RETURN( nParentTree );
2613         aLine.append( "/StructTreeRoot\n" );
2614         aLine.append( "/ParentTree " );
2615         aLine.append( nParentTree );
2616         aLine.append( " 0 R\n" );
2617         if( ! m_aRoleMap.empty() )
2618         {
2619             aLine.append( "/RoleMap<<" );
2620             for (auto const& role : m_aRoleMap)
2621             {
2622                 aLine.append( '/' );
2623                 aLine.append(role.first);
2624                 aLine.append( '/' );
2625                 aLine.append( role.second );
2626                 aLine.append( '\n' );
2627             }
2628             aLine.append( ">>\n" );
2629         }
2630     }
2631     else
2632     {
2633         aLine.append( "/StructElem\n"
2634                       "/S/" );
2635         if( !rEle.m_aAlias.isEmpty() )
2636             aLine.append( rEle.m_aAlias );
2637         else
2638             aLine.append( getStructureTag( rEle.m_eType ) );
2639         aLine.append( "\n"
2640                       "/P " );
2641         aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
2642         aLine.append( " 0 R\n"
2643                       "/Pg " );
2644         aLine.append( rEle.m_nFirstPageObject );
2645         aLine.append( " 0 R\n" );
2646         if( !rEle.m_aActualText.isEmpty() )
2647         {
2648             aLine.append( "/ActualText" );
2649             appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
2650             aLine.append( "\n" );
2651         }
2652         if( !rEle.m_aAltText.isEmpty() )
2653         {
2654             aLine.append( "/Alt" );
2655             appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
2656             aLine.append( "\n" );
2657         }
2658     }
2659     if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2660     {
2661         OString aAttribs =  emitStructureAttributes( rEle );
2662         if( !aAttribs.isEmpty() )
2663         {
2664             aLine.append( "/A" );
2665             aLine.append( aAttribs );
2666             aLine.append( "\n" );
2667         }
2668     }
2669     if( !rEle.m_aLocale.Language.isEmpty() )
2670     {
2671         /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2672          * include script tags and others.
2673          * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2674          * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2675          * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2676          * */
2677         LanguageTag aLanguageTag( rEle.m_aLocale);
2678         OUString aLanguage, aScript, aCountry;
2679         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2680         if (!aLanguage.isEmpty())
2681         {
2682             OUStringBuffer aLocBuf( 16 );
2683             aLocBuf.append( aLanguage );
2684             if( !aCountry.isEmpty() )
2685             {
2686                 aLocBuf.append( '-' );
2687                 aLocBuf.append( aCountry );
2688             }
2689             aLine.append( "/Lang" );
2690             appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
2691             aLine.append( "\n" );
2692         }
2693     }
2694     if( ! rEle.m_aKids.empty() )
2695     {
2696         unsigned int i = 0;
2697         aLine.append( "/K[" );
2698         for (auto const& kid : rEle.m_aKids)
2699         {
2700             if( kid.nMCID == -1 )
2701             {
2702                 aLine.append( kid.nObject );
2703                 aLine.append( " 0 R" );
2704                 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2705             }
2706             else
2707             {
2708                 if( kid.nObject == rEle.m_nFirstPageObject )
2709                 {
2710                     aLine.append( kid.nMCID );
2711                     aLine.append( " " );
2712                 }
2713                 else
2714                 {
2715                     aLine.append( "<</Type/MCR/Pg " );
2716                     aLine.append( kid.nObject );
2717                     aLine.append( " 0 R /MCID " );
2718                     aLine.append( kid.nMCID );
2719                     aLine.append( ">>\n" );
2720                 }
2721             }
2722             ++i;
2723         }
2724         aLine.append( "]\n" );
2725     }
2726     aLine.append( ">>\nendobj\n\n" );
2727 
2728     CHECK_RETURN( updateObject( rEle.m_nObject ) );
2729     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2730 
2731     CHECK_RETURN( emitStructParentTree( nParentTree ) );
2732 
2733     return rEle.m_nObject;
2734 }
2735 
2736 bool PDFWriterImpl::emitGradients()
2737 {
2738     for (auto const& gradient : m_aGradients)
2739     {
2740         if ( !writeGradientFunction( gradient ) ) return false;
2741     }
2742     return true;
2743 }
2744 
2745 bool PDFWriterImpl::emitTilings()
2746 {
2747     OStringBuffer aTilingObj( 1024 );
2748 
2749     for (auto & tiling : m_aTilings)
2750     {
2751         SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2752         if( ! tiling.m_pTilingStream )
2753             continue;
2754 
2755         aTilingObj.setLength( 0 );
2756 
2757         if (g_bDebugDisableCompression)
2758         {
2759             emitComment( "PDFWriterImpl::emitTilings" );
2760         }
2761 
2762         sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2763         sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2764         sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2765         sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2766         if( tiling.m_aCellSize.Width() == 0 )
2767             tiling.m_aCellSize.setWidth( nW );
2768         if( tiling.m_aCellSize.Height() == 0 )
2769             tiling.m_aCellSize.setHeight( nH );
2770 
2771         bool bDeflate = compressStream( tiling.m_pTilingStream );
2772         tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_END );
2773         sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->Tell();
2774         tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2775 
2776         // write pattern object
2777         aTilingObj.append( tiling.m_nObject );
2778         aTilingObj.append( " 0 obj\n" );
2779         aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
2780                            "/PaintType 1\n"
2781                            "/TilingType 2\n"
2782                            "/BBox[" );
2783         appendFixedInt( nX, aTilingObj );
2784         aTilingObj.append( ' ' );
2785         appendFixedInt( nY, aTilingObj );
2786         aTilingObj.append( ' ' );
2787         appendFixedInt( nX+nW, aTilingObj );
2788         aTilingObj.append( ' ' );
2789         appendFixedInt( nY+nH, aTilingObj );
2790         aTilingObj.append( "]\n"
2791                            "/XStep " );
2792         appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2793         aTilingObj.append( "\n"
2794                            "/YStep " );
2795         appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2796         aTilingObj.append( "\n" );
2797         if( tiling.m_aTransform.matrix[0] != 1.0 ||
2798             tiling.m_aTransform.matrix[1] != 0.0 ||
2799             tiling.m_aTransform.matrix[3] != 0.0 ||
2800             tiling.m_aTransform.matrix[4] != 1.0 ||
2801             tiling.m_aTransform.matrix[2] != 0.0 ||
2802             tiling.m_aTransform.matrix[5] != 0.0 )
2803         {
2804             aTilingObj.append( "/Matrix [" );
2805             // TODO: scaling, mirroring on y, etc
2806             appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2807             aTilingObj.append( ' ' );
2808             appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2809             aTilingObj.append( ' ' );
2810             appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2811             aTilingObj.append( ' ' );
2812             appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2813             aTilingObj.append( ' ' );
2814             appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2815             aTilingObj.append( ' ' );
2816             appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2817             aTilingObj.append( "]\n" );
2818         }
2819         aTilingObj.append( "/Resources" );
2820         tiling.m_aResources.append( aTilingObj, getFontDictObject() );
2821         if( bDeflate )
2822             aTilingObj.append( "/Filter/FlateDecode" );
2823         aTilingObj.append( "/Length " );
2824         aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
2825         aTilingObj.append( ">>\nstream\n" );
2826         if ( !updateObject( tiling.m_nObject ) ) return false;
2827         if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2828         checkAndEnableStreamEncryption( tiling.m_nObject );
2829         bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2830         delete tiling.m_pTilingStream;
2831         tiling.m_pTilingStream = nullptr;
2832         if( !written )
2833             return false;
2834         disableStreamEncryption();
2835         aTilingObj.setLength( 0 );
2836         aTilingObj.append( "\nendstream\nendobj\n\n" );
2837         if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2838     }
2839     return true;
2840 }
2841 
2842 sal_Int32 PDFWriterImpl::emitBuiltinFont( const PdfBuiltinFontFace* pFD, sal_Int32 nFontObject )
2843 {
2844     if( !pFD )
2845         return 0;
2846     const BuiltinFont& rBuiltinFont = pFD->GetBuiltinFont();
2847 
2848     OStringBuffer aLine( 1024 );
2849 
2850     if( nFontObject <= 0 )
2851         nFontObject = createObject();
2852     CHECK_RETURN( updateObject( nFontObject ) );
2853     aLine.append( nFontObject );
2854     aLine.append( " 0 obj\n"
2855                   "<</Type/Font/Subtype/Type1/BaseFont/" );
2856     appendName( rBuiltinFont.m_pPSName, aLine );
2857     aLine.append( "\n" );
2858     if( rBuiltinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2859          aLine.append( "/Encoding/WinAnsiEncoding\n" );
2860     aLine.append( ">>\nendobj\n\n" );
2861     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2862     return nFontObject;
2863 }
2864 
2865 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont const & rEmbed )
2866 {
2867     std::map< sal_Int32, sal_Int32 > aRet;
2868 
2869     sal_Int32 nFontDescriptor = 0;
2870     OString aSubType( "/Type1" );
2871     FontSubsetInfo aInfo;
2872     // fill in dummy values
2873     aInfo.m_nAscent = 1000;
2874     aInfo.m_nDescent = 200;
2875     aInfo.m_nCapHeight = 1000;
2876     aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2877     aInfo.m_aPSName = pFont->GetFamilyName();
2878     sal_Int32 pWidths[256];
2879     memset( pWidths, 0, sizeof(pWidths) );
2880 
2881     SalGraphics *pGraphics = m_pReferenceDevice->GetGraphics();
2882 
2883     assert(pGraphics);
2884 
2885     aSubType = OString( "/TrueType" );
2886     std::vector< sal_Int32 > aGlyphWidths;
2887     Ucs2UIntMap aUnicodeMap;
2888     pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
2889 
2890     OUString aTmpName;
2891     osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2892     sal_GlyphId aGlyphIds[ 256 ];
2893     sal_uInt8 pEncoding[ 256 ];
2894     sal_Int32 pDuWidths[ 256 ];
2895 
2896     memset( aGlyphIds, 0, sizeof( aGlyphIds ) );
2897     memset( pEncoding, 0, sizeof( pEncoding ) );
2898     memset( pDuWidths, 0, sizeof( pDuWidths ) );
2899 
2900     for( sal_Ucs c = 32; c < 256; c++ )
2901     {
2902         pEncoding[c] = c;
2903         aGlyphIds[c] = 0;
2904         if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
2905             pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
2906     }
2907     //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
2908     //had the right glyphids here then I imagine we could replace pDuWidths with
2909     //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
2910     //and map those to unicode rather than try and reverse map them ?
2911     pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
2912     osl_removeFile( aTmpName.pData );
2913 
2914     // write font descriptor
2915     nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
2916     if( nFontDescriptor )
2917     {
2918         // write font object
2919         sal_Int32 nObject = createObject();
2920         if( updateObject( nObject ) )
2921         {
2922             OStringBuffer aLine( 1024 );
2923             aLine.append( nObject );
2924             aLine.append( " 0 obj\n"
2925                           "<</Type/Font/Subtype" );
2926             aLine.append( aSubType );
2927             aLine.append( "/BaseFont/" );
2928             appendName( aInfo.m_aPSName, aLine );
2929             aLine.append( "\n" );
2930             if( !pFont->IsSymbolFont() )
2931                 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2932             aLine.append( "/FirstChar 32 /LastChar 255\n"
2933                           "/Widths[" );
2934             for( int i = 32; i < 256; i++ )
2935             {
2936                 aLine.append( pWidths[i] );
2937                 aLine.append( ((i&15) == 15) ? "\n" : " " );
2938             }
2939             aLine.append( "]\n"
2940                           "/FontDescriptor " );
2941             aLine.append( nFontDescriptor );
2942             aLine.append( " 0 R>>\n"
2943                           "endobj\n\n" );
2944             writeBuffer( aLine.getStr(), aLine.getLength() );
2945 
2946             aRet[ rEmbed.m_nNormalFontID ] = nObject;
2947         }
2948     }
2949 
2950     return aRet;
2951 }
2952 
2953 typedef int ThreeInts[3];
2954 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2955     ThreeInts& rSegmentLengths )
2956 {
2957     if( !pFontBytes || (nByteLen < 0) )
2958         return false;
2959     const unsigned char* pPtr = pFontBytes;
2960     const unsigned char* pEnd = pFontBytes + nByteLen;
2961 
2962     for(int & rSegmentLength : rSegmentLengths) {
2963         // read segment1 header
2964         if( pPtr+6 >= pEnd )
2965             return false;
2966         if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2967             return false;
2968         const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2969         if( nLen <= 0)
2970             return false;
2971         rSegmentLength = nLen;
2972         pPtr += nLen + 6;
2973     }
2974 
2975     // read segment-end header
2976     if( pPtr+2 >= pEnd )
2977         return false;
2978     if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2979         return false;
2980 
2981     return true;
2982 }
2983 
2984 static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
2985 {
2986     if( nSubsetID )
2987     {
2988         for( int i = 0; i < 6; i++ )
2989         {
2990             int nOffset = (nSubsetID % 26);
2991             nSubsetID /= 26;
2992             rBuffer.append( static_cast<sal_Char>('A'+nOffset) );
2993         }
2994         rBuffer.append( '+' );
2995     }
2996     appendName( rPSName, rBuffer );
2997 }
2998 
2999 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
3000                                               const sal_Ucs* pCodeUnits,
3001                                               const sal_Int32* pCodeUnitsPerGlyph,
3002                                               const sal_Int32* pEncToUnicodeIndex,
3003                                               int nGlyphs )
3004 {
3005     int nMapped = 0;
3006     for (int n = 0; n < nGlyphs; ++n)
3007         if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
3008             nMapped++;
3009 
3010     if( nMapped == 0 )
3011         return 0;
3012 
3013     sal_Int32 nStream = createObject();
3014     CHECK_RETURN( updateObject( nStream ) );
3015 
3016     OStringBuffer aContents( 1024 );
3017     aContents.append(
3018                      "/CIDInit/ProcSet findresource begin\n"
3019                      "12 dict begin\n"
3020                      "begincmap\n"
3021                      "/CIDSystemInfo<<\n"
3022                      "/Registry (Adobe)\n"
3023                      "/Ordering (UCS)\n"
3024                      "/Supplement 0\n"
3025                      ">> def\n"
3026                      "/CMapName/Adobe-Identity-UCS def\n"
3027                      "/CMapType 2 def\n"
3028                      "1 begincodespacerange\n"
3029                      "<00> <FF>\n"
3030                      "endcodespacerange\n"
3031                      );
3032     int nCount = 0;
3033     for (int n = 0; n < nGlyphs; ++n)
3034     {
3035         if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
3036         {
3037             if( (nCount % 100) == 0 )
3038             {
3039                 if( nCount )
3040                     aContents.append( "endbfchar\n" );
3041                 aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
3042                 aContents.append( " beginbfchar\n" );
3043             }
3044             aContents.append( '<' );
3045             appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
3046             aContents.append( "> <" );
3047             // TODO: handle code points>U+FFFF
3048             sal_Int32 nIndex = pEncToUnicodeIndex[n];
3049             for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
3050             {
3051                 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
3052                 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
3053             }
3054             aContents.append( ">\n" );
3055             nCount++;
3056         }
3057     }
3058     aContents.append( "endbfchar\n"
3059                       "endcmap\n"
3060                       "CMapName currentdict /CMap defineresource pop\n"
3061                       "end\n"
3062                       "end\n" );
3063     SvMemoryStream aStream;
3064     if (!g_bDebugDisableCompression)
3065     {
3066         ZCodec aCodec( 0x4000, 0x4000 );
3067         aCodec.BeginCompression();
3068         aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
3069         aCodec.EndCompression();
3070     }
3071 
3072     if (g_bDebugDisableCompression)
3073     {
3074         emitComment( "PDFWriterImpl::createToUnicodeCMap" );
3075     }
3076     OStringBuffer aLine( 40 );
3077 
3078     aLine.append( nStream );
3079     aLine.append( " 0 obj\n<</Length " );
3080     sal_Int32 nLen = 0;
3081     if (!g_bDebugDisableCompression)
3082     {
3083         nLen = static_cast<sal_Int32>(aStream.Tell());
3084         aStream.Seek( 0 );
3085         aLine.append( nLen );
3086         aLine.append( "/Filter/FlateDecode" );
3087     }
3088     else
3089         aLine.append( aContents.getLength() );
3090     aLine.append( ">>\nstream\n" );
3091     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3092     checkAndEnableStreamEncryption( nStream );
3093     if (!g_bDebugDisableCompression)
3094     {
3095         CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
3096     }
3097     else
3098     {
3099         CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
3100     }
3101     disableStreamEncryption();
3102     aLine.setLength( 0 );
3103     aLine.append( "\nendstream\n"
3104                   "endobj\n\n" );
3105     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3106     return nStream;
3107 }
3108 
3109 sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
3110 {
3111     OStringBuffer aLine( 1024 );
3112     // get font flags, see PDF reference 1.4 p. 358
3113     // possibly characters outside Adobe standard encoding
3114     // so set Symbolic flag
3115     sal_Int32 nFontFlags = (1<<2);
3116     if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
3117         nFontFlags |= (1 << 6);
3118     if( pFont->GetPitch() == PITCH_FIXED )
3119         nFontFlags |= 1;
3120     if( pFont->GetFamilyType() == FAMILY_SCRIPT )
3121         nFontFlags |= (1 << 3);
3122     else if( pFont->GetFamilyType() == FAMILY_ROMAN )
3123         nFontFlags |= (1 << 1);
3124 
3125     sal_Int32 nFontDescriptor = createObject();
3126     CHECK_RETURN( updateObject( nFontDescriptor ) );
3127     aLine.setLength( 0 );
3128     aLine.append( nFontDescriptor );
3129     aLine.append( " 0 obj\n"
3130                   "<</Type/FontDescriptor/FontName/" );
3131     appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
3132     aLine.append( "\n"
3133                   "/Flags " );
3134     aLine.append( nFontFlags );
3135     aLine.append( "\n"
3136                   "/FontBBox[" );
3137     // note: Top and Bottom are reversed in VCL and PDF rectangles
3138     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().X()) );
3139     aLine.append( ' ' );
3140     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().Y()) );
3141     aLine.append( ' ' );
3142     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().X()) );
3143     aLine.append( ' ' );
3144     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().Y()+1) );
3145     aLine.append( "]/ItalicAngle " );
3146     if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
3147         aLine.append( "-30" );
3148     else
3149         aLine.append( "0" );
3150     aLine.append( "\n"
3151                   "/Ascent " );
3152     aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
3153     aLine.append( "\n"
3154                   "/Descent " );
3155     aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
3156     aLine.append( "\n"
3157                   "/CapHeight " );
3158     aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
3159     // According to PDF reference 1.4 StemV is required
3160     // seems a tad strange to me, but well ...
3161     aLine.append( "\n"
3162                   "/StemV 80\n" );
3163     if( nFontStream )
3164     {
3165         aLine.append( "/FontFile" );
3166         switch( rInfo.m_nFontType )
3167         {
3168             case FontType::SFNT_TTF:
3169                 aLine.append( '2' );
3170                 break;
3171             case FontType::TYPE1_PFA:
3172             case FontType::TYPE1_PFB:
3173             case FontType::ANY_TYPE1:
3174                 break;
3175             default:
3176                 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
3177                 return 0;
3178         }
3179         aLine.append( ' ' );
3180         aLine.append( nFontStream );
3181         aLine.append( " 0 R\n" );
3182     }
3183     aLine.append( ">>\n"
3184                   "endobj\n\n" );
3185     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3186 
3187     return nFontDescriptor;
3188 }
3189 
3190 void PDFWriterImpl::appendBuiltinFontsToDict( OStringBuffer& rDict ) const
3191 {
3192     for (auto const& item : m_aBuiltinFontToObjectMap)
3193     {
3194         rDict.append( m_aBuiltinFonts[item.first].getNameObject() );
3195         rDict.append( ' ' );
3196         rDict.append( item.second );
3197         rDict.append( " 0 R" );
3198     }
3199 }
3200 
3201 bool PDFWriterImpl::emitFonts()
3202 {
3203     SalGraphics *pGraphics = m_pReferenceDevice->GetGraphics();
3204 
3205     if (!pGraphics)
3206         return false;
3207 
3208     OStringBuffer aLine( 1024 );
3209 
3210     std::map< sal_Int32, sal_Int32 > aFontIDToObject;
3211 
3212     OUString aTmpName;
3213     osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
3214     for (auto & subset : m_aSubsets)
3215     {
3216         for (auto & s_subset :subset.second.m_aSubsets)
3217         {
3218             sal_GlyphId aGlyphIds[ 256 ];
3219             sal_Int32 pWidths[ 256 ];
3220             sal_uInt8 pEncoding[ 256 ];
3221             sal_Int32 pEncToUnicodeIndex[ 256 ];
3222             sal_Int32 pCodeUnitsPerGlyph[ 256 ];
3223             std::vector<sal_Ucs> aCodeUnits;
3224             aCodeUnits.reserve( 256 );
3225             int nGlyphs = 1;
3226             // fill arrays and prepare encoding index map
3227             sal_Int32 nToUnicodeStream = 0;
3228 
3229             memset( aGlyphIds, 0, sizeof( aGlyphIds ) );
3230             memset( pEncoding, 0, sizeof( pEncoding ) );
3231             memset( pCodeUnitsPerGlyph, 0, sizeof( pCodeUnitsPerGlyph ) );
3232             memset( pEncToUnicodeIndex, 0, sizeof( pEncToUnicodeIndex ) );
3233             for (auto const& item : s_subset.m_aMapping)
3234             {
3235                 sal_uInt8 nEnc = item.second.getGlyphId();
3236 
3237                 SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
3238                 SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
3239 
3240                 aGlyphIds[ nEnc ] = item.first;
3241                 pEncoding[ nEnc ] = nEnc;
3242                 pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
3243                 pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
3244                 for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
3245                     aCodeUnits.push_back( item.second.getCode( n ) );
3246                 if( item.second.getCode(0) )
3247                     nToUnicodeStream = 1;
3248                 if( nGlyphs < 256 )
3249                     nGlyphs++;
3250                 else
3251                 {
3252                     OSL_FAIL( "too many glyphs for subset" );
3253                 }
3254             }
3255             FontSubsetInfo aSubsetInfo;
3256             if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
3257             {
3258                 // create font stream
3259                 osl::File aFontFile(aTmpName);
3260                 if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
3261                 // get file size
3262                 sal_uInt64 nLength1;
3263                 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
3264                 if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
3265                 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
3266 
3267                 if (g_bDebugDisableCompression)
3268                 {
3269                     emitComment( "PDFWriterImpl::emitFonts" );
3270                 }
3271                 sal_Int32 nFontStream = createObject();
3272                 sal_Int32 nStreamLengthObject = createObject();
3273                 if ( !updateObject( nFontStream ) ) return false;
3274                 aLine.setLength( 0 );
3275                 aLine.append( nFontStream );
3276                 aLine.append( " 0 obj\n"
3277                              "<</Length " );
3278                 aLine.append( nStreamLengthObject );
3279                 if (!g_bDebugDisableCompression)
3280                     aLine.append( " 0 R"
3281                                  "/Filter/FlateDecode"
3282                                  "/Length1 " );
3283                 else
3284                     aLine.append( " 0 R"
3285                                  "/Length1 " );
3286 
3287                 sal_uInt64 nStartPos = 0;
3288                 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
3289                 {
3290                     aLine.append( static_cast<sal_Int32>(nLength1) );
3291 
3292                     aLine.append( ">>\n"
3293                                  "stream\n" );
3294                     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3295                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3296 
3297                     // copy font file
3298                     beginCompression();
3299                     checkAndEnableStreamEncryption( nFontStream );
3300                     sal_Bool bEOF = false;
3301                     do
3302                     {
3303                         char buf[8192];
3304                         sal_uInt64 nRead;
3305                         if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
3306                         if ( !writeBuffer( buf, nRead ) ) return false;
3307                         if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
3308                     } while( ! bEOF );
3309                 }
3310                 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
3311                 {
3312                     // TODO: implement
3313                     OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
3314                 }
3315                 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
3316                 {
3317                     std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
3318 
3319                     sal_uInt64 nBytesRead = 0;
3320                     if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
3321                     SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
3322                     if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
3323                     // get the PFB-segment lengths
3324                     ThreeInts aSegmentLengths = {0,0,0};
3325                     getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
3326                     // the lengths below are mandatory for PDF-exported Type1 fonts
3327                     // because the PFB segment headers get stripped! WhyOhWhy.
3328                     aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
3329                     aLine.append( "/Length2 " );
3330                     aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
3331                     aLine.append( "/Length3 " );
3332                     aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
3333 
3334                     aLine.append( ">>\n"
3335                                  "stream\n" );
3336                     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3337                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3338 
3339                     // emit PFB-sections without section headers
3340                     beginCompression();
3341                     checkAndEnableStreamEncryption( nFontStream );
3342                     if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
3343                     if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
3344                     if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
3345                 }
3346                 else
3347                 {
3348                     SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
3349                     aLine.append( "0 >>\nstream\n" );
3350                 }
3351 
3352                 endCompression();
3353                 disableStreamEncryption();
3354                 // close the file
3355                 aFontFile.close();
3356 
3357                 sal_uInt64 nEndPos = 0;
3358                 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
3359                 // end the stream
3360                 aLine.setLength( 0 );
3361                 aLine.append( "\nendstream\nendobj\n\n" );
3362                 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3363 
3364                 // emit stream length object
3365                 if ( !updateObject( nStreamLengthObject ) ) return false;
3366                 aLine.setLength( 0 );
3367                 aLine.append( nStreamLengthObject );
3368                 aLine.append( " 0 obj\n" );
3369                 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
3370                 aLine.append( "\nendobj\n\n" );
3371                 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3372 
3373                 // write font descriptor
3374                 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
3375 
3376                 if( nToUnicodeStream )
3377                     nToUnicodeStream = createToUnicodeCMap( pEncoding, &aCodeUnits[0], pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
3378 
3379                 sal_Int32 nFontObject = createObject();
3380                 if ( !updateObject( nFontObject ) ) return false;
3381                 aLine.setLength( 0 );
3382                 aLine.append( nFontObject );
3383 
3384                 aLine.append( " 0 obj\n" );
3385                 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
3386                              "<</Type/Font/Subtype/Type1/BaseFont/" :
3387                              "<</Type/Font/Subtype/TrueType/BaseFont/" );
3388                 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
3389                 aLine.append( "\n"
3390                              "/FirstChar 0\n"
3391                              "/LastChar " );
3392                 aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
3393                 aLine.append( "\n"
3394                              "/Widths[" );
3395                 for( int i = 0; i < nGlyphs; i++ )
3396                 {
3397                     aLine.append( pWidths[ i ] );
3398                     aLine.append( ((i & 15) == 15) ? "\n" : " " );
3399                 }
3400                 aLine.append( "]\n"
3401                              "/FontDescriptor " );
3402                 aLine.append( nFontDescriptor );
3403                 aLine.append( " 0 R\n" );
3404                 if( nToUnicodeStream )
3405                 {
3406                     aLine.append( "/ToUnicode " );
3407                     aLine.append( nToUnicodeStream );
3408                     aLine.append( " 0 R\n" );
3409                 }
3410                 aLine.append( ">>\n"
3411                              "endobj\n\n" );
3412                 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3413 
3414                 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
3415             }
3416             else
3417             {
3418                 const PhysicalFontFace* pFont = subset.first;
3419                 OStringBuffer aErrorComment( 256 );
3420                 aErrorComment.append( "CreateFontSubset failed for font \"" );
3421                 aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
3422                 aErrorComment.append( '\"' );
3423                 if( pFont->GetItalic() == ITALIC_NORMAL )
3424                     aErrorComment.append( " italic" );
3425                 else if( pFont->GetItalic() == ITALIC_OBLIQUE )
3426                     aErrorComment.append( " oblique" );
3427                 aErrorComment.append( " weight=" );
3428                 aErrorComment.append( sal_Int32(pFont->GetWeight()) );
3429                 emitComment( aErrorComment.getStr() );
3430             }
3431         }
3432     }
3433     osl_removeFile( aTmpName.pData );
3434 
3435     // emit system fonts
3436     for (auto const& systemFont : m_aSystemFonts)
3437     {
3438         std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
3439         for (auto const& item : aObjects)
3440         {
3441             if ( !item.second ) return false;
3442             aFontIDToObject[ item.first ] = item.second;
3443         }
3444     }
3445 
3446     OStringBuffer aFontDict( 1024 );
3447     aFontDict.append( getFontDictObject() );
3448     aFontDict.append( " 0 obj\n"
3449                      "<<" );
3450     int ni = 0;
3451     for (auto const& itemMap : aFontIDToObject)
3452     {
3453         aFontDict.append( "/F" );
3454         aFontDict.append( itemMap.first );
3455         aFontDict.append( ' ' );
3456         aFontDict.append( itemMap.second );
3457         aFontDict.append( " 0 R" );
3458         if( ((++ni) & 7) == 0 )
3459             aFontDict.append( '\n' );
3460     }
3461     // emit builtin font for widget appearances / variable text
3462     for (auto & item : m_aBuiltinFontToObjectMap)
3463     {
3464         rtl::Reference<PdfBuiltinFontFace> aData(new PdfBuiltinFontFace(m_aBuiltinFonts[item.first]));
3465         item.second = emitBuiltinFont( aData.get(), item.second );
3466     }
3467     appendBuiltinFontsToDict( aFontDict );
3468     aFontDict.append( "\n>>\nendobj\n\n" );
3469 
3470     if ( !updateObject( getFontDictObject() ) ) return false;
3471     if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
3472     return true;
3473 }
3474 
3475 sal_Int32 PDFWriterImpl::emitResources()
3476 {
3477     // emit shadings
3478     if( ! m_aGradients.empty() )
3479         CHECK_RETURN( emitGradients() );
3480     // emit tilings
3481     if( ! m_aTilings.empty() )
3482         CHECK_RETURN( emitTilings() );
3483 
3484     // emit font dict
3485     CHECK_RETURN( emitFonts() );
3486 
3487     // emit Resource dict
3488     OStringBuffer aLine( 512 );
3489     sal_Int32 nResourceDict = getResourceDictObj();
3490     CHECK_RETURN( updateObject( nResourceDict ) );
3491     aLine.setLength( 0 );
3492     aLine.append( nResourceDict );
3493     aLine.append( " 0 obj\n" );
3494     m_aGlobalResourceDict.append( aLine, getFontDictObject() );
3495     aLine.append( "endobj\n\n" );
3496     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3497     return nResourceDict;
3498 }
3499 
3500 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3501                                                  sal_Int32 nItemLevel,
3502                                                  sal_Int32 nCurrentItemId )
3503 {
3504     /* The /Count number of an item is
3505        positive: the number of visible subitems
3506        negative: the negative number of subitems that will become visible if
3507                  the item gets opened
3508        see PDF ref 1.4 p 478
3509     */
3510 
3511     sal_Int32 nCount = 0;
3512 
3513     if( m_aContext.OpenBookmarkLevels < 0           || // all levels are visible
3514         m_aContext.OpenBookmarkLevels >= nItemLevel    // this level is visible
3515       )
3516     {
3517         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3518         sal_Int32 nChildren = rItem.m_aChildren.size();
3519         for( sal_Int32 i = 0; i < nChildren; i++ )
3520             nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3521         rCounts[nCurrentItemId] = nCount;
3522         // return 1 (this item) + visible sub items
3523         if( nCount < 0 )
3524             nCount = 0;
3525         nCount++;
3526     }
3527     else
3528     {
3529         // this bookmark level is invisible
3530         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3531         sal_Int32 nChildren = rItem.m_aChildren.size();
3532         rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3533         for( sal_Int32 i = 0; i < nChildren; i++ )
3534             updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3535         nCount = -1;
3536     }
3537 
3538     return nCount;
3539 }
3540 
3541 sal_Int32 PDFWriterImpl::emitOutline()
3542 {
3543     int i, nItems = m_aOutline.size();
3544 
3545     // do we have an outline at all ?
3546     if( nItems < 2 )
3547         return 0;
3548 
3549     // reserve object numbers for all outline items
3550     for( i = 0; i < nItems; ++i )
3551         m_aOutline[i].m_nObject = createObject();
3552 
3553     // update all parent, next and prev object ids
3554     for( i = 0; i < nItems; ++i )
3555     {
3556         PDFOutlineEntry& rItem = m_aOutline[i];
3557         int nChildren = rItem.m_aChildren.size();
3558 
3559         if( nChildren )
3560         {
3561             for( int n = 0; n < nChildren; ++n )
3562             {
3563                 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3564 
3565                 rChild.m_nParentObject = rItem.m_nObject;
3566                 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3567                 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3568             }
3569 
3570         }
3571     }
3572 
3573     // calculate Count entries for all items
3574     std::vector< sal_Int32 > aCounts( nItems );
3575     updateOutlineItemCount( aCounts, 0, 0 );
3576 
3577     // emit hierarchy
3578     for( i = 0; i < nItems; ++i )
3579     {
3580         PDFOutlineEntry& rItem = m_aOutline[i];
3581         OStringBuffer aLine( 1024 );
3582 
3583         CHECK_RETURN( updateObject( rItem.m_nObject ) );
3584         aLine.append( rItem.m_nObject );
3585         aLine.append( " 0 obj\n" );
3586         aLine.append( "<<" );
3587         // number of visible children (all levels)
3588         if( i > 0 || aCounts[0] > 0 )
3589         {
3590             aLine.append( "/Count " );
3591             aLine.append( aCounts[i] );
3592         }
3593         if( ! rItem.m_aChildren.empty() )
3594         {
3595             // children list: First, Last
3596             aLine.append( "/First " );
3597             aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
3598             aLine.append( " 0 R/Last " );
3599             aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
3600             aLine.append( " 0 R\n" );
3601         }
3602         if( i > 0 )
3603         {
3604             // Title, Dest, Parent, Prev, Next
3605             aLine.append( "/Title" );
3606             appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3607             aLine.append( "\n" );
3608             // Dest is not required
3609             if( rItem.m_nDestID >= 0 && rItem.m_nDestID < static_cast<sal_Int32>(m_aDests.size()) )
3610             {
3611                 aLine.append( "/Dest" );
3612                 appendDest( rItem.m_nDestID, aLine );
3613             }
3614             aLine.append( "/Parent " );
3615             aLine.append( rItem.m_nParentObject );
3616             aLine.append( " 0 R" );
3617             if( rItem.m_nPrevObject )
3618             {
3619                 aLine.append( "/Prev " );
3620                 aLine.append( rItem.m_nPrevObject );
3621                 aLine.append( " 0 R" );
3622             }
3623             if( rItem.m_nNextObject )
3624             {
3625                 aLine.append( "/Next " );
3626                 aLine.append( rItem.m_nNextObject );
3627                 aLine.append( " 0 R" );
3628             }
3629         }
3630         aLine.append( ">>\nendobj\n\n" );
3631         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3632     }
3633 
3634     return m_aOutline[0].m_nObject;
3635 }
3636 
3637 #undef CHECK_RETURN
3638 #define CHECK_RETURN( x ) if( !x ) return false
3639 
3640 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3641 {
3642     if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) )
3643     {
3644         SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3645         return false;
3646     }
3647 
3648     const PDFDest& rDest        = m_aDests[ nDestID ];
3649     const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
3650 
3651     rBuffer.append( '[' );
3652     rBuffer.append( rDestPage.m_nPageObject );
3653     rBuffer.append( " 0 R" );
3654 
3655     switch( rDest.m_eType )
3656     {
3657         case PDFWriter::DestAreaType::XYZ:
3658         default:
3659             rBuffer.append( "/XYZ " );
3660             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3661             rBuffer.append( ' ' );
3662             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3663             rBuffer.append( " 0" );
3664             break;
3665         case PDFWriter::DestAreaType::FitRectangle:
3666             rBuffer.append( "/FitR " );
3667             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3668             rBuffer.append( ' ' );
3669             appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3670             rBuffer.append( ' ' );
3671             appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3672             rBuffer.append( ' ' );
3673             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3674             break;
3675     }
3676     rBuffer.append( ']' );
3677 
3678     return true;
3679 }
3680 
3681 bool PDFWriterImpl::emitScreenAnnotations()
3682 {
3683     int nAnnots = m_aScreens.size();
3684     for (int i = 0; i < nAnnots; i++)
3685     {
3686         const PDFScreen& rScreen = m_aScreens[i];
3687 
3688         OStringBuffer aLine;
3689         bool bEmbed = false;
3690         if (!rScreen.m_aTempFileURL.isEmpty())
3691         {
3692             bEmbed = true;
3693             if (!updateObject(rScreen.m_nTempFileObject))
3694                 continue;
3695 
3696             SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3697             SvMemoryStream aMemoryStream;
3698             aMemoryStream.WriteStream(aFileStream);
3699 
3700             aLine.append(rScreen.m_nTempFileObject);
3701             aLine.append(" 0 obj\n");
3702             aLine.append("<< /Type /EmbeddedFile /Length ");
3703             aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3704             aLine.append(" >>\nstream\n");
3705             CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3706             aLine.setLength(0);
3707 
3708             CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3709 
3710             aLine.append("\nendstream\nendobj\n\n");
3711             CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3712             aLine.setLength(0);
3713         }
3714 
3715         if (!updateObject(rScreen.m_nObject))
3716             continue;
3717 
3718         // Annot dictionary.
3719         aLine.append(rScreen.m_nObject);
3720         aLine.append(" 0 obj\n");
3721         aLine.append("<</Type/Annot");
3722         aLine.append("/Subtype/Screen/Rect[");
3723         appendFixedInt(rScreen.m_aRect.Left(), aLine);
3724         aLine.append(' ');
3725         appendFixedInt(rScreen.m_aRect.Top(), aLine);
3726         aLine.append(' ');
3727         appendFixedInt(rScreen.m_aRect.Right(), aLine);
3728         aLine.append(' ');
3729         appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3730         aLine.append("]");
3731 
3732         // Action dictionary.
3733         aLine.append("/A<</Type/Action /S/Rendition /AN ");
3734         aLine.append(rScreen.m_nObject);
3735         aLine.append(" 0 R ");
3736 
3737         // Rendition dictionary.
3738         aLine.append("/R<</Type/Rendition /S/MR ");
3739 
3740         // MediaClip dictionary.
3741         aLine.append("/C<</Type/MediaClip /S/MCD ");
3742         if (bEmbed)
3743         {
3744             aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
3745             aLine.append(rScreen.m_nTempFileObject);
3746             aLine.append(" 0 R >> >>");
3747         }
3748         else
3749         {
3750             // Linked.
3751             aLine.append("/D << /Type /Filespec /FS /URL /F ");
3752             appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3753             aLine.append(" >>");
3754         }
3755         // Allow playing the video via a tempfile.
3756         aLine.append("/P <</TF (TEMPACCESS)>>");
3757         // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
3758         aLine.append("/CT (video/mp4)");
3759         aLine.append(">>");
3760 
3761         // End Rendition dictionary by requesting play/pause/stop controls.
3762         aLine.append("/P<</BE<</C true >>>>");
3763         aLine.append(">>");
3764 
3765         // End Action dictionary.
3766         aLine.append("/OP 0 >>");
3767 
3768         // End Annot dictionary.
3769         aLine.append("/P ");
3770         aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
3771         aLine.append(" 0 R\n>>\nendobj\n\n");
3772         CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3773     }
3774 
3775     return true;
3776 }
3777 
3778 bool PDFWriterImpl::emitLinkAnnotations()
3779 {
3780     int nAnnots = m_aLinks.size();
3781     for( int i = 0; i < nAnnots; i++ )
3782     {
3783         const PDFLink& rLink            = m_aLinks[i];
3784         if( ! updateObject( rLink.m_nObject ) )
3785             continue;
3786 
3787         OStringBuffer aLine( 1024 );
3788         aLine.append( rLink.m_nObject );
3789         aLine.append( " 0 obj\n" );
3790 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3791 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3792         aLine.append( "<</Type/Annot" );
3793         if( m_bIsPDF_A1 )
3794             aLine.append( "/F 4" );
3795         aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3796 
3797         appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3798         aLine.append( ' ' );
3799         appendFixedInt( rLink.m_aRect.Top(), aLine );
3800         aLine.append( ' ' );
3801         appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3802         aLine.append( ' ' );
3803         appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3804         aLine.append( "]" );
3805         if( rLink.m_nDest >= 0 )
3806         {
3807             aLine.append( "/Dest" );
3808             appendDest( rLink.m_nDest, aLine );
3809         }
3810         else
3811         {
3812 /*
3813 destination is external to the document, so
3814 we check in the following sequence:
3815 
3816  if target type is neither .pdf, nor .od[tpgs], then
3817           check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3818                              end processing
3819  else if target is .od[tpgs]: then
3820       if conversion of type from od[tpgs]  to pdf is requested, convert it and this becomes the new target file
3821       processing continue
3822 
3823  if (new)target is .pdf : then
3824      if GotToR is requested, then
3825            convert the target in GoToR where the fragment of the URI is
3826            considered the named destination in the target file, set relative or absolute as requested
3827      else strip the fragment from URL and then set URI or 'launch application' as requested
3828 */
3829 
3830 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3831 // are the correct one!!
3832 
3833 // extract target file type
3834             auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3835 
3836             INetURLObject aDocumentURL( m_aContext.BaseURL );
3837             INetURLObject aTargetURL( url );
3838             bool bSetGoToRMode = false;
3839             bool    bTargetHasPDFExtension = false;
3840             INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3841             bool    bIsUNCPath = false;
3842 
3843             // check if the protocol is a known one, or if there is no protocol at all (on target only)
3844             // if there is no protocol, make the target relative to the current document directory
3845             // getting the needed URL information from the current document path
3846             if( eTargetProtocol == INetProtocol::NotValid )
3847             {
3848                 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3849                 {
3850                     bIsUNCPath = true;
3851                 }
3852                 else
3853                 {
3854                     INetURLObject aNewBase( aDocumentURL );//duplicate document URL
3855                     aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
3856                                               //target document
3857                     aNewBase.insertName( url );
3858                     aTargetURL = aNewBase;//reassign the new target URL
3859                     //recompute the target protocol, with the new URL
3860                     //normal URL processing resumes
3861                     eTargetProtocol = aTargetURL.GetProtocol();
3862                 }
3863             }
3864 
3865             OUString aFileExtension = aTargetURL.GetFileExtension();
3866 
3867             // Check if the URL ends in '/': if yes it's a directory,
3868             // it will be forced to a URI link.
3869             // possibly a malformed URI, leave it as it is, force as URI
3870             if( aTargetURL.hasFinalSlash() )
3871                 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3872 
3873             if( !aFileExtension.isEmpty() )
3874             {
3875                 if( m_aContext.ConvertOOoTargetToPDFTarget )
3876                 {
3877                     bool bChangeFileExtensionToPDF = false;
3878                     //examine the file type (.odm .odt. .odp, odg, ods)
3879                     if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3880                         bChangeFileExtensionToPDF = true;
3881                     if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3882                         bChangeFileExtensionToPDF = true;
3883                     else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3884                         bChangeFileExtensionToPDF = true;
3885                     else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3886                         bChangeFileExtensionToPDF = true;
3887                     else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3888                         bChangeFileExtensionToPDF = true;
3889                     if( bChangeFileExtensionToPDF )
3890                         aTargetURL.setExtension("pdf" );
3891                 }
3892                 //check if extension is pdf, see if GoToR should be forced
3893                 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3894                 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3895                     bSetGoToRMode = true;
3896             }
3897             //prepare the URL, if relative or not
3898             INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3899             //queue the string common to all types of actions
3900             aLine.append( "/A<</Type/Action/S");
3901             if( bIsUNCPath ) // handle Win UNC paths
3902             {
3903                 aLine.append( "/Launch/Win<</F" );
3904                 // INetURLObject is not good with UNC paths, use original path
3905                 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3906                 aLine.append( ">>" );
3907             }
3908             else
3909             {
3910                 bool bSetRelative = false;
3911                 bool bFileSpec = false;
3912                 //check if relative file link is requested and if the protocol is 'file://'
3913                 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3914                     bSetRelative = true;
3915 
3916                 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3917                 if( !bSetGoToRMode )
3918                 {
3919                     switch( m_aContext.DefaultLinkAction )
3920                     {
3921                     default:
3922                     case PDFWriter::URIAction :
3923                     case PDFWriter::URIActionDestination :
3924                         aLine.append( "/URI/URI" );
3925                         break;
3926                     case PDFWriter::LaunchAction:
3927                         // now:
3928                         // if a launch action is requested and the hyperlink target has a fragment
3929                         // and the target file does not have a pdf extension, or it's not a 'file:://'
3930                         // protocol then force the uri action on it
3931                         // This code will permit the correct opening of application on web pages,
3932                         // the one that normally have fragments (but I may be wrong...)
3933                         // and will force the use of URI when the protocol is not file:
3934                         if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3935                                         eTargetProtocol != INetProtocol::File )
3936                         {
3937                             aLine.append( "/URI/URI" );
3938                         }
3939                         else
3940                         {
3941                             aLine.append( "/Launch/F" );
3942                             bFileSpec = true;
3943                         }
3944                         break;
3945                     }
3946                 }
3947 
3948                 //fragment are encoded in the same way as in the named destination processing
3949                 if( bSetGoToRMode )
3950                 {
3951                     //add the fragment
3952                     OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3953                     aLine.append("/GoToR");
3954                     aLine.append("/F");
3955                     appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3956                                                                                          INetURLObject::EncodeMechanism::WasEncoded,
3957                                                                                          INetURLObject::DecodeMechanism::WithCharset ) :
3958                                                                    aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3959                     if( !aFragment.isEmpty() )
3960                     {
3961                         aLine.append("/D/");
3962                         appendDestinationName( aFragment , aLine );
3963                     }
3964                 }
3965                 else
3966                 {
3967                     // change the fragment to accommodate the bookmark (only if the file extension
3968                     // is PDF and the requested action is of the correct type)
3969                     if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3970                                bTargetHasPDFExtension && !aFragment.isEmpty() )
3971                     {
3972                         OStringBuffer aLineLoc( 1024 );
3973                         appendDestinationName( aFragment , aLineLoc );
3974                         //substitute the fragment
3975                         aTargetURL.SetMark( OStringToOUString(aLineLoc.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
3976                     }
3977                     OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE );
3978                     appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3979                                                                                         INetURLObject::EncodeMechanism::WasEncoded,
3980                                                                                             bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3981                                                                                             ) :
3982                                                                                aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3983                 }
3984             }
3985             aLine.append( ">>\n" );
3986         }
3987         if( rLink.m_nStructParent > 0 )
3988         {
3989             aLine.append( "/StructParent " );
3990             aLine.append( rLink.m_nStructParent );
3991         }
3992         aLine.append( ">>\nendobj\n\n" );
3993         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3994     }
3995 
3996     return true;
3997 }
3998 
3999 bool PDFWriterImpl::emitNoteAnnotations()
4000 {
4001     // emit note annotations
4002     int nAnnots = m_aNotes.size();
4003     for( int i = 0; i < nAnnots; i++ )
4004     {
4005         const PDFNoteEntry& rNote       = m_aNotes[i];
4006         if( ! updateObject( rNote.m_nObject ) )
4007             return false;
4008 
4009         OStringBuffer aLine( 1024 );
4010         aLine.append( rNote.m_nObject );
4011         aLine.append( " 0 obj\n" );
4012 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
4013 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
4014         aLine.append( "<</Type/Annot" );
4015         if( m_bIsPDF_A1 )
4016             aLine.append( "/F 4" );
4017         aLine.append( "/Subtype/Text/Rect[" );
4018 
4019         appendFixedInt( rNote.m_aRect.Left(), aLine );
4020         aLine.append( ' ' );
4021         appendFixedInt( rNote.m_aRect.Top(), aLine );
4022         aLine.append( ' ' );
4023         appendFixedInt( rNote.m_aRect.Right(), aLine );
4024         aLine.append( ' ' );
4025         appendFixedInt( rNote.m_aRect.Bottom(), aLine );
4026         aLine.append( "]" );
4027 
4028         // contents of the note (type text string)
4029         aLine.append( "/Contents\n" );
4030         appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine );
4031         aLine.append( "\n" );
4032 
4033         // optional title
4034         if( !rNote.m_aContents.Title.isEmpty() )
4035         {
4036             aLine.append( "/T" );
4037             appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine );
4038             aLine.append( "\n" );
4039         }
4040 
4041         aLine.append( ">>\nendobj\n\n" );
4042         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4043     }
4044     return true;
4045 }
4046 
4047 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font&  rAppSetFont )
4048 {
4049     bool bAdjustSize = false;
4050 
4051     Font aFont( rControlFont );
4052     if( aFont.GetFamilyName().isEmpty() )
4053     {
4054         aFont = rAppSetFont;
4055         if( rControlFont.GetFontHeight() )
4056             aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
4057         else
4058             bAdjustSize = true;
4059         if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
4060             aFont.SetItalic( rControlFont.GetItalic() );
4061         if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
4062             aFont.SetWeight( rControlFont.GetWeight() );
4063     }
4064     else if( ! aFont.GetFontHeight() )
4065     {
4066         aFont.SetFontSize( rAppSetFont.GetFontSize() );
4067         bAdjustSize = true;
4068     }
4069     if( bAdjustSize )
4070     {
4071         Size aFontSize = aFont.GetFontSize();
4072         OutputDevice* pDefDev = Application::GetDefaultDevice();
4073         aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
4074         aFont.SetFontSize( aFontSize );
4075     }
4076     return aFont;
4077 }
4078 
4079 sal_Int32 PDFWriterImpl::getBestBuiltinFont( const vcl::Font& rFont )
4080 {
4081     sal_Int32 nBest = 4; // default to Helvetica
4082     OUString aFontName( rFont.GetFamilyName() );
4083     aFontName = aFontName.toAsciiLowerCase();
4084 
4085     if( aFontName.indexOf( "times" ) != -1 )
4086         nBest = 8;
4087     else if( aFontName.indexOf( "courier" ) != -1 )
4088         nBest = 0;
4089     else if( aFontName.indexOf( "dingbats" ) != -1 )
4090         nBest = 13;
4091     else if( aFontName.indexOf( "symbol" ) != -1 )
4092         nBest = 12;
4093     if( nBest < 12 )
4094     {
4095         if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
4096             nBest += 1;
4097         if( rFont.GetWeight() > WEIGHT_MEDIUM )
4098             nBest += 2;
4099     }
4100 
4101     if( m_aBuiltinFontToObjectMap.find( nBest ) == m_aBuiltinFontToObjectMap.end() )
4102         m_aBuiltinFontToObjectMap[ nBest ] = createObject();
4103 
4104     return nBest;
4105 }
4106 
4107 static inline const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
4108 {
4109     return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
4110 }
4111 
4112 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
4113 {
4114     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4115 
4116     // save graphics state
4117     push( PushFlags::ALL );
4118 
4119     // transform relative to control's coordinates since an
4120     // appearance stream is a form XObject
4121     // this relies on the m_aRect member of rButton NOT already being transformed
4122     // to default user space
4123     if( rWidget.Background || rWidget.Border )
4124     {
4125         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
4126         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
4127         drawRectangle( rWidget.Location );
4128     }
4129     // prepare font to use
4130     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
4131     setFont( aFont );
4132     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
4133 
4134     drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
4135 
4136     // create DA string while local mapmode is still in place
4137     // (that is before endRedirect())
4138     OStringBuffer aDA( 256 );
4139     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
4140     Font aDummyFont( "Helvetica", aFont.GetFontSize() );
4141     sal_Int32 nDummyBuiltin = getBestBuiltinFont( aDummyFont );
4142     aDA.append( ' ' );
4143     aDA.append( m_aBuiltinFonts[nDummyBuiltin].getNameObject() );
4144     aDA.append( ' ' );
4145     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4146     aDA.append( " Tf" );
4147     rButton.m_aDAString = aDA.makeStringAndClear();
4148 
4149     pop();
4150 
4151     rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
4152 
4153     /* seems like a bad hack but at least works in both AR5 and 6:
4154        we draw the button ourselves and tell AR
4155        the button would be totally transparent with no text
4156 
4157        One would expect that simply setting a normal appearance
4158        should suffice, but no, as soon as the user actually presses
4159        the button and an action is tied to it (gasp! a button that
4160        does something) the appearance gets replaced by some crap that AR
4161        creates on the fly even if no DA or MK is given. On AR6 at least
4162        the DA and MK work as expected, but on AR5 this creates a region
4163        filled with the background color but nor text. Urgh.
4164     */
4165     rButton.m_aMKDict = "/BC [] /BG [] /CA";
4166     rButton.m_aMKDictCAString = "";
4167 }
4168 
4169 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
4170                                      const PDFWriter::AnyWidget& rWidget,
4171                                      const StyleSettings& rSettings )
4172 {
4173     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
4174 
4175     if( rWidget.Background || rWidget.Border )
4176     {
4177         if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
4178         {
4179             sal_Int32 nDelta = getReferenceDevice()->GetDPIX() / 500;
4180             if( nDelta < 1 )
4181                 nDelta = 1;
4182             setLineColor( COL_TRANSPARENT );
4183             tools::Rectangle aRect = rIntern.m_aRect;
4184             setFillColor( rSettings.GetLightBorderColor() );
4185             drawRectangle( aRect );
4186             aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
4187             aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
4188             setFillColor( rSettings.GetFieldColor() );
4189             drawRectangle( aRect );
4190             setFillColor( rSettings.GetLightColor() );
4191             drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
4192             drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
4193             setFillColor( rSettings.GetDarkShadowColor() );
4194             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
4195             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
4196         }
4197         else
4198         {
4199             setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
4200             setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4201             drawRectangle( rIntern.m_aRect );
4202         }
4203 
4204         if( rWidget.Border )
4205         {
4206             // adjust edit area accounting for border
4207             sal_Int32 nDelta = aFont.GetFontHeight()/4;
4208             if( nDelta < 1 )
4209                 nDelta = 1;
4210             rIntern.m_aRect.AdjustLeft(nDelta );
4211             rIntern.m_aRect.AdjustTop(nDelta );
4212             rIntern.m_aRect.AdjustRight( -nDelta );
4213             rIntern.m_aRect.AdjustBottom( -nDelta );
4214         }
4215     }
4216     return aFont;
4217 }
4218 
4219 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
4220 {
4221     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4222     SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
4223 
4224     push( PushFlags::ALL );
4225 
4226     // prepare font to use, draw field border
4227     Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
4228     sal_Int32 nBest = getSystemFont( aFont );
4229 
4230     // prepare DA string
4231     OStringBuffer aDA( 32 );
4232     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4233     aDA.append( ' ' );
4234     aDA.append( "/F" );
4235     aDA.append( nBest );
4236 
4237     OStringBuffer aDR( 32 );
4238     aDR.append( "/Font " );
4239     aDR.append( getFontDictObject() );
4240     aDR.append( " 0 R" );
4241     rEdit.m_aDRDict = aDR.makeStringAndClear();
4242     aDA.append( ' ' );
4243     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4244     aDA.append( " Tf" );
4245 
4246     /*  create an empty appearance stream, let the viewer create
4247         the appearance at runtime. This is because AR5 seems to
4248         paint the widget appearance always, and a dynamically created
4249         appearance on top of it. AR6 is well behaved in that regard, so
4250         that behaviour seems to be a bug. Anyway this empty appearance
4251         relies on /NeedAppearances in the AcroForm dictionary set to "true"
4252      */
4253     beginRedirect( pEditStream, rEdit.m_aRect );
4254     OStringBuffer aAppearance( 32 );
4255     aAppearance.append( "/Tx BMC\nEMC\n" );
4256     writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
4257 
4258     endRedirect();
4259     pop();
4260 
4261     rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
4262 
4263     rEdit.m_aDAString = aDA.makeStringAndClear();
4264 }
4265 
4266 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
4267 {
4268     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4269     SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
4270 
4271     push( PushFlags::ALL );
4272 
4273     // prepare font to use, draw field border
4274     Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
4275     sal_Int32 nBest = getSystemFont( aFont );
4276 
4277     beginRedirect( pListBoxStream, rBox.m_aRect );
4278     OStringBuffer aAppearance( 64 );
4279 
4280     setLineColor( COL_TRANSPARENT );
4281     setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
4282     drawRectangle( rBox.m_aRect );
4283 
4284     // empty appearance, see createDefaultEditAppearance for reference
4285     aAppearance.append( "/Tx BMC\nEMC\n" );
4286     writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
4287 
4288     endRedirect();
4289     pop();
4290 
4291     rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
4292 
4293     // prepare DA string
4294     OStringBuffer aDA( 256 );
4295     // prepare DA string
4296     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4297     aDA.append( ' ' );
4298     aDA.append( "/F" );
4299     aDA.append( nBest );
4300 
4301     OStringBuffer aDR( 32 );
4302     aDR.append( "/Font " );
4303     aDR.append( getFontDictObject() );
4304     aDR.append( " 0 R" );
4305     rBox.m_aDRDict = aDR.makeStringAndClear();
4306     aDA.append( ' ' );
4307     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4308     aDA.append( " Tf" );
4309     rBox.m_aDAString = aDA.makeStringAndClear();
4310 }
4311 
4312 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
4313 {
4314     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4315 
4316     // save graphics state
4317     push( PushFlags::ALL );
4318 
4319     if( rWidget.Background || rWidget.Border )
4320     {
4321         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4322         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4323         drawRectangle( rBox.m_aRect );
4324     }
4325 
4326     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4327     setFont( aFont );
4328     Size aFontSize = aFont.GetFontSize();
4329     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4330         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4331     sal_Int32 nDelta = aFontSize.Height()/10;
4332     if( nDelta < 1 )
4333         nDelta = 1;
4334 
4335     tools::Rectangle aCheckRect, aTextRect;
4336     {
4337         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4338         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4339         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4340         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4341 
4342         // #i74206# handle small controls without text area
4343         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4344         {
4345             aCheckRect.AdjustRight( -nDelta );
4346             aCheckRect.AdjustTop(nDelta/2 );
4347             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4348         }
4349 
4350         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4351         aTextRect.SetTop( rBox.m_aRect.Top() );
4352         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4353         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4354     }
4355     setLineColor( COL_BLACK );
4356     setFillColor( COL_TRANSPARENT );
4357     OStringBuffer aLW( 32 );
4358     aLW.append( "q " );
4359     m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
4360     aLW.append( " w " );
4361     writeBuffer( aLW.getStr(), aLW.getLength() );
4362     drawRectangle( aCheckRect );
4363     writeBuffer( " Q\n", 3 );
4364     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4365     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4366 
4367     pop();
4368 
4369     OStringBuffer aDA( 256 );
4370     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4371     sal_Int32 nBest = getBestBuiltinFont( Font( "ZapfDingbats", aFont.GetFontSize() ) );
4372     aDA.append( ' ' );
4373     aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4374     aDA.append( " 0 Tf" );
4375     rBox.m_aDAString = aDA.makeStringAndClear();
4376     rBox.m_aMKDict = "/CA";
4377     rBox.m_aMKDictCAString = "8";
4378     rBox.m_aRect = aCheckRect;
4379 
4380     // create appearance streams
4381     sal_Char cMark = '8';
4382     sal_Int32 nCharXOffset = 1000-m_aBuiltinFonts[13].m_aWidths[sal_Int32(cMark)];
4383     nCharXOffset *= aCheckRect.GetHeight();
4384     nCharXOffset /= 2000;
4385     sal_Int32 nCharYOffset = 1000-
4386         (m_aBuiltinFonts[13].m_nAscent+m_aBuiltinFonts[13].m_nDescent); // descent is negative
4387     nCharYOffset *= aCheckRect.GetHeight();
4388     nCharYOffset /= 2000;
4389 
4390     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4391     beginRedirect( pCheckStream, aCheckRect );
4392     aDA.append( "/Tx BMC\nq BT\n" );
4393     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4394     aDA.append( ' ' );
4395     aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4396     aDA.append( ' ' );
4397     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4398     aDA.append( " Tf\n" );
4399     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
4400     aDA.append( " " );
4401     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
4402     aDA.append( " Td (" );
4403     aDA.append( cMark );
4404     aDA.append( ") Tj\nET\nQ\nEMC\n" );
4405     writeBuffer( aDA.getStr(), aDA.getLength() );
4406     endRedirect();
4407     rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4408 
4409     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4410     beginRedirect( pUncheckStream, aCheckRect );
4411     writeBuffer( "/Tx BMC\nEMC\n", 12 );
4412     endRedirect();
4413     rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4414 }
4415 
4416 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
4417 {
4418     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4419 
4420     // save graphics state
4421     push( PushFlags::ALL );
4422 
4423     if( rWidget.Background || rWidget.Border )
4424     {
4425         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4426         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4427         drawRectangle( rBox.m_aRect );
4428     }
4429 
4430     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4431     setFont( aFont );
4432     Size aFontSize = aFont.GetFontSize();
4433     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4434         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4435     sal_Int32 nDelta = aFontSize.Height()/10;
4436     if( nDelta < 1 )
4437         nDelta = 1;
4438 
4439     tools::Rectangle aCheckRect, aTextRect;
4440     {
4441         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4442         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4443         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4444         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4445 
4446         // #i74206# handle small controls without text area
4447         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4448         {
4449             aCheckRect.AdjustRight( -nDelta );
4450             aCheckRect.AdjustTop(nDelta/2 );
4451             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4452         }
4453 
4454         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4455         aTextRect.SetTop( rBox.m_aRect.Top() );
4456         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4457         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4458     }
4459     setLineColor( COL_BLACK );
4460     setFillColor( COL_TRANSPARENT );
4461     OStringBuffer aLW( 32 );
4462     aLW.append( "q " );
4463     m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4464     aLW.append( " w " );
4465     writeBuffer( aLW.getStr(), aLW.getLength() );
4466     drawEllipse( aCheckRect );
4467     writeBuffer( " Q\n", 3 );
4468     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4469     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4470 
4471     pop();
4472 
4473     OStringBuffer aDA( 256 );
4474     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4475     sal_Int32 nBest = getBestBuiltinFont( Font( "ZapfDingbats", aFont.GetFontSize() ) );
4476     aDA.append( ' ' );
4477     aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4478     aDA.append( " 0 Tf" );
4479     rBox.m_aDAString = aDA.makeStringAndClear();
4480     //to encrypt this (el)
4481     rBox.m_aMKDict = "/CA";
4482     //after this assignment, to m_aMKDic cannot be added anything
4483     rBox.m_aMKDictCAString = "l";
4484 
4485     rBox.m_aRect = aCheckRect;
4486 
4487     // create appearance streams
4488     push( PushFlags::ALL);
4489     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4490 
4491     beginRedirect( pCheckStream, aCheckRect );
4492     aDA.append( "/Tx BMC\nq BT\n" );
4493     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4494     aDA.append( ' ' );
4495     aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4496     aDA.append( ' ' );
4497     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4498     aDA.append( " Tf\n0 0 Td\nET\nQ\n" );
4499     writeBuffer( aDA.getStr(), aDA.getLength() );
4500     setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4501     setLineColor( COL_TRANSPARENT );
4502     aCheckRect.AdjustLeft(3*nDelta );
4503     aCheckRect.AdjustTop(3*nDelta );
4504     aCheckRect.AdjustBottom( -(3*nDelta) );
4505     aCheckRect.AdjustRight( -(3*nDelta) );
4506     drawEllipse( aCheckRect );
4507     writeBuffer( "\nEMC\n", 5 );
4508     endRedirect();
4509 
4510     pop();
4511     rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4512 
4513     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4514     beginRedirect( pUncheckStream, aCheckRect );
4515     writeBuffer( "/Tx BMC\nEMC\n", 12 );
4516     endRedirect();
4517     rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4518 }
4519 
4520 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4521 {
4522     // TODO: check and insert default streams
4523     OString aStandardAppearance;
4524     switch( rWidget.m_eType )
4525     {
4526         case PDFWriter::CheckBox:
4527             aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4528             break;
4529         default:
4530             break;
4531     }
4532 
4533     if( !rWidget.m_aAppearances.empty() )
4534     {
4535         rAnnotDict.append( "/AP<<\n" );
4536         for (auto & dict_item : rWidget.m_aAppearances)
4537         {
4538             rAnnotDict.append( "/" );
4539             rAnnotDict.append( dict_item.first );
4540             bool bUseSubDict = (dict_item.second.size() > 1);
4541             rAnnotDict.append( bUseSubDict ? "<<" : " " );
4542 
4543             for (auto const& stream_item : dict_item.second)
4544             {
4545                 SvMemoryStream* pApppearanceStream = stream_item.second;
4546                 dict_item.second[ stream_item.first ] = nullptr;
4547 
4548                 bool bDeflate = compressStream( pApppearanceStream );
4549 
4550                 pApppearanceStream->Seek( STREAM_SEEK_TO_END );
4551                 sal_Int64 nStreamLen = pApppearanceStream->Tell();
4552                 pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4553                 sal_Int32 nObject = createObject();
4554                 CHECK_RETURN( updateObject( nObject ) );
4555                 if (g_bDebugDisableCompression)
4556                 {
4557                     emitComment( "PDFWriterImpl::emitAppearances" );
4558                 }
4559                 OStringBuffer aLine;
4560                 aLine.append( nObject );
4561 
4562                 aLine.append( " 0 obj\n"
4563                               "<</Type/XObject\n"
4564                               "/Subtype/Form\n"
4565                               "/BBox[0 0 " );
4566                 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4567                 aLine.append( " " );
4568                 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4569                 aLine.append( "]\n"
4570                               "/Resources " );
4571                 aLine.append( getResourceDictObj() );
4572                 aLine.append( " 0 R\n"
4573                               "/Length " );
4574                 aLine.append( nStreamLen );
4575                 aLine.append( "\n" );
4576                 if( bDeflate )
4577                     aLine.append( "/Filter/FlateDecode\n" );
4578                 aLine.append( ">>\nstream\n" );
4579                 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4580                 checkAndEnableStreamEncryption( nObject );
4581                 CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
4582                 disableStreamEncryption();
4583                 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
4584 
4585                 if( bUseSubDict )
4586                 {
4587                     rAnnotDict.append( " /" );
4588                     rAnnotDict.append( stream_item.first );
4589                     rAnnotDict.append( " " );
4590                 }
4591                 rAnnotDict.append( nObject );
4592                 rAnnotDict.append( " 0 R" );
4593 
4594                 delete pApppearanceStream;
4595             }
4596 
4597             rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4598         }
4599         rAnnotDict.append( ">>\n" );
4600         if( !aStandardAppearance.isEmpty() )
4601         {
4602             rAnnotDict.append( "/AS /" );
4603             rAnnotDict.append( aStandardAppearance );
4604             rAnnotDict.append( "\n" );
4605         }
4606     }
4607 
4608     return true;
4609 }
4610 
4611 bool PDFWriterImpl::emitWidgetAnnotations()
4612 {
4613     ensureUniqueRadioOnValues();
4614 
4615     int nAnnots = m_aWidgets.size();
4616     for( int a = 0; a < nAnnots; a++ )
4617     {
4618         PDFWidget& rWidget = m_aWidgets[a];
4619 
4620         OStringBuffer aLine( 1024 );
4621         OStringBuffer aValue( 256 );
4622         aLine.append( rWidget.m_nObject );
4623         aLine.append( " 0 obj\n"
4624                       "<<" );
4625         if( rWidget.m_eType != PDFWriter::Hierarchy )
4626         {
4627             // emit widget annotation only for terminal fields
4628             if( rWidget.m_aKids.empty() )
4629             {
4630                 int iRectMargin;
4631 
4632                 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4633 
4634                 if (rWidget.m_eType == PDFWriter::Signature)
4635                 {
4636                     aLine.append( "132\n" ); // Print & Locked
4637                     iRectMargin = 0;
4638                 }
4639                 else
4640                 {
4641                     aLine.append( "4\n" );
4642                     iRectMargin = 1;
4643                 }
4644 
4645                 aLine.append("/Rect[" );
4646                 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4647                 aLine.append( ' ' );
4648                 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4649                 aLine.append( ' ' );
4650                 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4651                 aLine.append( ' ' );
4652                 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4653                 aLine.append( "]\n" );
4654             }
4655             aLine.append( "/FT/" );
4656             switch( rWidget.m_eType )
4657             {
4658                 case PDFWriter::RadioButton:
4659                 case PDFWriter::CheckBox:
4660                     // for radio buttons only the RadioButton field, not the
4661                     // CheckBox children should have a value, else acrobat reader
4662                     // does not always check the right button
4663                     // of course real check boxes (not belonging to a radio group)
4664                     // need their values, too
4665                     if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4666                     {
4667                         aValue.append( "/" );
4668                         // check for radio group with all buttons unpressed
4669                         if( rWidget.m_aValue.isEmpty() )
4670                             aValue.append( "Off" );
4671                         else
4672                             appendName( rWidget.m_aValue, aValue );
4673                     }
4674                     SAL_FALLTHROUGH;
4675                 case PDFWriter::PushButton:
4676                     aLine.append( "Btn" );
4677                     break;
4678                 case PDFWriter::ListBox:
4679                     if( rWidget.m_nFlags & 0x200000 ) // multiselect
4680                     {
4681                         aValue.append( "[" );
4682                         for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4683                         {
4684                             sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4685                             if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) )
4686                                 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4687                         }
4688                         aValue.append( "]" );
4689                     }
4690                     else if( rWidget.m_aSelectedEntries.size() > 0 &&
4691                              rWidget.m_aSelectedEntries[0] >= 0 &&
4692                              rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) )
4693                     {
4694                         appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4695                     }
4696                     else
4697                         appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4698                     aLine.append( "Ch" );
4699                     break;
4700                 case PDFWriter::ComboBox:
4701                     appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4702                     aLine.append( "Ch" );
4703                     break;
4704                 case PDFWriter::Edit:
4705                     aLine.append( "Tx" );
4706                     appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4707                     break;
4708                 case PDFWriter::Signature:
4709                     aLine.append( "Sig" );
4710                     aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4711                     break;
4712                 case PDFWriter::Hierarchy: // make the compiler happy
4713                     break;
4714             }
4715             aLine.append( "\n" );
4716             aLine.append( "/P " );
4717             aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4718             aLine.append( " 0 R\n" );
4719         }
4720         if( rWidget.m_nParent )
4721         {
4722             aLine.append( "/Parent " );
4723             aLine.append( rWidget.m_nParent );
4724             aLine.append( " 0 R\n" );
4725         }
4726         if( rWidget.m_aKids.size() )
4727         {
4728             aLine.append( "/Kids[" );
4729             for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4730             {
4731                 aLine.append( rWidget.m_aKids[i] );
4732                 aLine.append( " 0 R" );
4733                 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4734             }
4735             aLine.append( "]\n" );
4736         }
4737         if( !rWidget.m_aName.isEmpty() )
4738         {
4739             aLine.append( "/T" );
4740             appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4741             aLine.append( "\n" );
4742         }
4743         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
4744         {
4745             // the alternate field name should be unicode able since it is
4746             // supposed to be used in UI
4747             aLine.append( "/TU" );
4748             appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4749             aLine.append( "\n" );
4750         }
4751 
4752         if( rWidget.m_nFlags )
4753         {
4754             aLine.append( "/Ff " );
4755             aLine.append( rWidget.m_nFlags );
4756             aLine.append( "\n" );
4757         }
4758         if( !aValue.isEmpty() )
4759         {
4760             OString aVal = aValue.makeStringAndClear();
4761             aLine.append( "/V " );
4762             aLine.append( aVal );
4763             aLine.append( "\n"
4764                           "/DV " );
4765             aLine.append( aVal );
4766             aLine.append( "\n" );
4767         }
4768         if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4769         {
4770             sal_Int32 nTI = -1;
4771             aLine.append( "/Opt[\n" );
4772             sal_Int32 i = 0;
4773             for (auto const& entry : rWidget.m_aListEntries)
4774             {
4775                 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
4776                 aLine.append( "\n" );
4777                 if( entry == rWidget.m_aValue )
4778                     nTI = i;
4779                 ++i;
4780             }
4781             aLine.append( "]\n" );
4782             if( nTI > 0 )
4783             {
4784                 aLine.append( "/TI " );
4785                 aLine.append( nTI );
4786                 aLine.append( "\n" );
4787                 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4788                 {
4789                     aLine.append( "/I [" );
4790                     aLine.append( nTI );
4791                     aLine.append( "]\n" );
4792                 }
4793             }
4794         }
4795         if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
4796         {
4797             aLine.append( "/MaxLen " );
4798             aLine.append( rWidget.m_nMaxLen );
4799             aLine.append( "\n" );
4800         }
4801         if( rWidget.m_eType == PDFWriter::PushButton )
4802         {
4803             if(!m_bIsPDF_A1)
4804             {
4805                 OStringBuffer aDest;
4806                 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4807                 {
4808                     aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4809                     aLine.append( aDest.makeStringAndClear() );
4810                     aLine.append( ">>>>\n" );
4811                 }
4812                 else if( rWidget.m_aListEntries.empty() )
4813                 {
4814                     // create a reset form action
4815                     aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4816                 }
4817                 else if( rWidget.m_bSubmit )
4818                 {
4819                     // create a submit form action
4820                     aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4821                     appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4822                     aLine.append( "/Flags " );
4823 
4824                     sal_Int32 nFlags = 0;
4825                     switch( m_aContext.SubmitFormat )
4826                     {
4827                     case PDFWriter::HTML:
4828                         nFlags |= 4;
4829                         break;
4830                     case PDFWriter::XML:
4831                         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4832                             nFlags |= 32;
4833                         break;
4834                     case PDFWriter::PDF:
4835                         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4836                             nFlags |= 256;
4837                         break;
4838                     case PDFWriter::FDF:
4839                     default:
4840                         break;
4841                     }
4842                     if( rWidget.m_bSubmitGet )
4843                         nFlags |= 8;
4844                     aLine.append( nFlags );
4845                     aLine.append( ">>>>\n" );
4846                 }
4847                 else
4848                 {
4849                     // create a URI action
4850                     aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4851                     aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4852                     aLine.append( ")>>>>\n" );
4853                 }
4854             }
4855             else
4856                 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4857         }
4858         if( !rWidget.m_aDAString.isEmpty() )
4859         {
4860             if( !rWidget.m_aDRDict.isEmpty() )
4861             {
4862                 aLine.append( "/DR<<" );
4863                 aLine.append( rWidget.m_aDRDict );
4864                 aLine.append( ">>\n" );
4865             }
4866             else
4867             {
4868                 aLine.append( "/DR<</Font<<" );
4869                 appendBuiltinFontsToDict( aLine );
4870                 aLine.append( ">>>>\n" );
4871             }
4872             aLine.append( "/DA" );
4873             appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
4874             aLine.append( "\n" );
4875             if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4876                 aLine.append( "/Q 1\n" );
4877             else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4878                 aLine.append( "/Q 2\n" );
4879         }
4880         // appearance characteristics for terminal fields
4881         // which are supposed to have an appearance constructed
4882         // by the viewer application
4883         if( !rWidget.m_aMKDict.isEmpty() )
4884         {
4885             aLine.append( "/MK<<" );
4886             aLine.append( rWidget.m_aMKDict );
4887             //add the CA string, encrypting it
4888             appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
4889             aLine.append( ">>\n" );
4890         }
4891 
4892         CHECK_RETURN( emitAppearances( rWidget, aLine ) );
4893 
4894         aLine.append( ">>\n"
4895                       "endobj\n\n" );
4896         CHECK_RETURN( updateObject( rWidget.m_nObject ) );
4897         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4898     }
4899     return true;
4900 }
4901 
4902 bool PDFWriterImpl::emitAnnotations()
4903 {
4904     if( m_aPages.size() < 1 )
4905         return false;
4906 
4907     CHECK_RETURN( emitLinkAnnotations() );
4908     CHECK_RETURN(emitScreenAnnotations());
4909     CHECK_RETURN( emitNoteAnnotations() );
4910     CHECK_RETURN( emitWidgetAnnotations() );
4911 
4912     return true;
4913 }
4914 
4915 bool PDFWriterImpl::emitEmbeddedFiles()
4916 {
4917     for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4918     {
4919         if (!updateObject(rEmbeddedFile.m_nObject))
4920             continue;
4921 
4922         OStringBuffer aLine;
4923         aLine.append(rEmbeddedFile.m_nObject);
4924         aLine.append(" 0 obj\n");
4925         aLine.append("<< /Type /EmbeddedFile /Length ");
4926         aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_aData.getLength()));
4927         aLine.append(" >>\nstream\n");
4928         CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4929         aLine.setLength(0);
4930 
4931         CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aData.getArray(), rEmbeddedFile.m_aData.getLength()));
4932 
4933         aLine.append("\nendstream\nendobj\n\n");
4934         CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4935     }
4936     return true;
4937 }
4938 
4939 #undef CHECK_RETURN
4940 #define CHECK_RETURN( x ) if( !x ) return false
4941 
4942 bool PDFWriterImpl::emitCatalog()
4943 {
4944     // build page tree
4945     // currently there is only one node that contains all leaves
4946 
4947     // first create a page tree node id
4948     sal_Int32 nTreeNode = createObject();
4949 
4950     // emit global resource dictionary (page emit needs it)
4951     CHECK_RETURN( emitResources() );
4952 
4953     // emit all pages
4954     for (auto & page : m_aPages)
4955         if( ! page.emit( nTreeNode ) )
4956             return false;
4957 
4958     sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4959 
4960     sal_Int32 nOutlineDict = emitOutline();
4961 
4962     // emit Output intent
4963     sal_Int32 nOutputIntentObject = emitOutputIntent();
4964 
4965     // emit metadata
4966     sal_Int32 nMetadataObject = emitDocumentMetadata();
4967 
4968     sal_Int32 nStructureDict = 0;
4969     if(m_aStructure.size() > 1)
4970     {
4971         // check if dummy structure containers are needed
4972         addInternalStructureContainer(m_aStructure[0]);
4973         nStructureDict = m_aStructure[0].m_nObject = createObject();
4974         emitStructure( m_aStructure[ 0 ] );
4975     }
4976 
4977     // adjust tree node file offset
4978     if( ! updateObject( nTreeNode ) )
4979         return false;
4980 
4981     // emit tree node
4982     OStringBuffer aLine( 2048 );
4983     aLine.append( nTreeNode );
4984     aLine.append( " 0 obj\n" );
4985     aLine.append( "<</Type/Pages\n" );
4986     aLine.append( "/Resources " );
4987     aLine.append( getResourceDictObj() );
4988     aLine.append( " 0 R\n" );
4989 
4990     sal_Int32 nMediaBoxWidth = 0;
4991     sal_Int32 nMediaBoxHeight = 0;
4992     if( m_aPages.empty() ) // sanity check, this should not happen
4993     {
4994         nMediaBoxWidth = m_nInheritedPageWidth;
4995         nMediaBoxHeight = m_nInheritedPageHeight;
4996     }
4997     else
4998     {
4999         for (auto const& page : m_aPages)
5000         {
5001             if( page.m_nPageWidth > nMediaBoxWidth )
5002                 nMediaBoxWidth = page.m_nPageWidth;
5003             if( page.m_nPageHeight > nMediaBoxHeight )
5004                 nMediaBoxHeight = page.m_nPageHeight;
5005         }
5006     }
5007     aLine.append( "/MediaBox[ 0 0 " );
5008     aLine.append( nMediaBoxWidth );
5009     aLine.append( ' ' );
5010     aLine.append( nMediaBoxHeight );
5011     aLine.append( " ]\n"
5012                   "/Kids[ " );
5013     unsigned int i = 0;
5014     for (auto & page : m_aPages)
5015     {
5016         aLine.append( page.m_nPageObject );
5017         aLine.append( " 0 R" );
5018         aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
5019         ++i;
5020     }
5021     aLine.append( "]\n"
5022                   "/Count " );
5023     aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
5024     aLine.append( ">>\n"
5025                   "endobj\n\n" );
5026     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5027 
5028     // emit annotation objects
5029     CHECK_RETURN( emitAnnotations() );
5030     CHECK_RETURN( emitEmbeddedFiles() );
5031 
5032     // emit Catalog
5033     m_nCatalogObject = createObject();
5034     if( ! updateObject( m_nCatalogObject ) )
5035         return false;
5036     aLine.setLength( 0 );
5037     aLine.append( m_nCatalogObject );
5038     aLine.append( " 0 obj\n"
5039                   "<</Type/Catalog/Pages " );
5040     aLine.append( nTreeNode );
5041     aLine.append( " 0 R\n" );
5042 
5043     // check if there are named destinations to emit (root must be inside the catalog)
5044     if( nNamedDestinationsDictionary )
5045     {
5046         aLine.append("/Dests ");
5047         aLine.append( nNamedDestinationsDictionary );
5048         aLine.append( " 0 R\n" );
5049     }
5050 
5051     if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
5052         switch(  m_aContext.PageLayout )
5053         {
5054         default :
5055         case  PDFWriter::SinglePage :
5056             aLine.append( "/PageLayout/SinglePage\n" );
5057             break;
5058         case  PDFWriter::Continuous :
5059             aLine.append( "/PageLayout/OneColumn\n" );
5060             break;
5061         case  PDFWriter::ContinuousFacing :
5062             // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
5063             aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
5064             break;
5065         }
5066     if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
5067         switch(  m_aContext.PDFDocumentMode )
5068         {
5069         default :
5070             aLine.append( "/PageMode/UseNone\n" );
5071             break;
5072         case PDFWriter::UseOutlines :
5073             aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
5074             break;
5075         case PDFWriter::UseThumbs :
5076             aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
5077             break;
5078         }
5079     else if( m_aContext.OpenInFullScreenMode )
5080         aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
5081 
5082     OStringBuffer aInitPageRef;
5083     if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < static_cast<sal_Int32>(m_aPages.size()) )
5084     {
5085         aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
5086         aInitPageRef.append( " 0 R" );
5087     }
5088     else
5089         aInitPageRef.append( "0" );
5090 
5091     switch( m_aContext.PDFDocumentAction )
5092     {
5093     case PDFWriter::ActionDefault :     //do nothing, this is the Acrobat default
5094     default:
5095         if( aInitPageRef.getLength() > 1 )
5096         {
5097             aLine.append( "/OpenAction[" );
5098             aLine.append( aInitPageRef.makeStringAndClear() );
5099             aLine.append( " /XYZ null null 0]\n" );
5100         }
5101         break;
5102     case PDFWriter::FitInWindow :
5103         aLine.append( "/OpenAction[" );
5104         aLine.append( aInitPageRef.makeStringAndClear() );
5105         aLine.append( " /Fit]\n" ); //Open fit page
5106         break;
5107     case PDFWriter::FitWidth :
5108         aLine.append( "/OpenAction[" );
5109         aLine.append( aInitPageRef.makeStringAndClear() );
5110         aLine.append( " /FitH " );
5111         aLine.append( m_nInheritedPageHeight );//Open fit width
5112         aLine.append( "]\n" );
5113         break;
5114     case PDFWriter::FitVisible :
5115         aLine.append( "/OpenAction[" );
5116         aLine.append( aInitPageRef.makeStringAndClear() );
5117         aLine.append( " /FitBH " );
5118         aLine.append( m_nInheritedPageHeight );//Open fit visible
5119         aLine.append( "]\n" );
5120         break;
5121     case PDFWriter::ActionZoom :
5122         aLine.append( "/OpenAction[" );
5123         aLine.append( aInitPageRef.makeStringAndClear() );
5124         aLine.append( " /XYZ null null " );
5125         if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
5126             aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
5127         else
5128             aLine.append( "0" );
5129         aLine.append( "]\n" );
5130         break;
5131     }
5132 
5133     // viewer preferences, if we had some, then emit
5134     if( m_aContext.HideViewerToolbar ||
5135         ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
5136         m_aContext.HideViewerMenubar ||
5137         m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
5138         m_aContext.CenterWindow || (m_aContext.FirstPageLeft  &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
5139         m_aContext.OpenInFullScreenMode )
5140     {
5141         aLine.append( "/ViewerPreferences<<" );
5142         if( m_aContext.HideViewerToolbar )
5143             aLine.append( "/HideToolbar true\n" );
5144         if( m_aContext.HideViewerMenubar )
5145             aLine.append( "/HideMenubar true\n" );
5146         if( m_aContext.HideViewerWindowControls )
5147             aLine.append( "/HideWindowUI true\n" );
5148         if( m_aContext.FitWindow )
5149             aLine.append( "/FitWindow true\n" );
5150         if( m_aContext.CenterWindow )
5151             aLine.append( "/CenterWindow true\n" );
5152         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
5153             aLine.append( "/DisplayDocTitle true\n" );
5154         if( m_aContext.FirstPageLeft &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing )
5155             aLine.append( "/Direction/R2L\n" );
5156         if( m_aContext.OpenInFullScreenMode )
5157             switch( m_aContext.PDFDocumentMode )
5158             {
5159             default :
5160             case PDFWriter::ModeDefault :
5161                 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
5162                 break;
5163             case PDFWriter::UseOutlines :
5164                 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
5165                 break;
5166             case PDFWriter::UseThumbs :
5167                 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
5168                 break;
5169             }
5170         aLine.append( ">>\n" );
5171     }
5172 
5173     if( nOutlineDict )
5174     {
5175         aLine.append( "/Outlines " );
5176         aLine.append( nOutlineDict );
5177         aLine.append( " 0 R\n" );
5178     }
5179     if( nStructureDict )
5180     {
5181         aLine.append( "/StructTreeRoot " );
5182         aLine.append( nStructureDict );
5183         aLine.append( " 0 R\n" );
5184     }
5185     if( !m_aContext.DocumentLocale.Language.isEmpty() )
5186     {
5187         /* PDF allows only RFC 3066, see above in emitStructure(). */
5188         LanguageTag aLanguageTag( m_aContext.DocumentLocale);
5189         OUString aLanguage, aScript, aCountry;
5190         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
5191         if (!aLanguage.isEmpty())
5192         {
5193             OUStringBuffer aLocBuf( 16 );
5194             aLocBuf.append( aLanguage );
5195             if( !aCountry.isEmpty() )
5196             {
5197                 aLocBuf.append( '-' );
5198                 aLocBuf.append( aCountry );
5199             }
5200             aLine.append( "/Lang" );
5201             appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine );
5202             aLine.append( "\n" );
5203         }
5204     }
5205     if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
5206     {
5207         aLine.append( "/MarkInfo<</Marked true>>\n" );
5208     }
5209     if( m_aWidgets.size() > 0 )
5210     {
5211         aLine.append( "/AcroForm<</Fields[\n" );
5212         int nWidgets = m_aWidgets.size();
5213         int nOut = 0;
5214         for( int j = 0; j < nWidgets; j++ )
5215         {
5216             // output only root fields
5217             if( m_aWidgets[j].m_nParent < 1 )
5218             {
5219                 aLine.append( m_aWidgets[j].m_nObject );
5220                 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
5221             }
5222         }
5223         aLine.append( "\n]" );
5224 
5225 #if HAVE_FEATURE_NSS
5226         if (m_nSignatureObject != -1)
5227             aLine.append( "/SigFlags 3");
5228 #endif
5229 
5230         aLine.append( "/DR " );
5231         aLine.append( getResourceDictObj() );
5232         aLine.append( " 0 R" );
5233         // NeedAppearances must not be used if PDF is signed
5234         if( m_bIsPDF_A1
5235 #if HAVE_FEATURE_NSS
5236             || ( m_nSignatureObject != -1 )
5237 #endif
5238             )
5239             aLine.append( ">>\n" );
5240         else
5241             aLine.append( "/NeedAppearances true>>\n" );
5242     }
5243 
5244     //check if there is a Metadata object
5245     if( nOutputIntentObject )
5246     {
5247         aLine.append("/OutputIntents[");
5248         aLine.append( nOutputIntentObject );
5249         aLine.append( " 0 R]" );
5250     }
5251 
5252     if( nMetadataObject )
5253     {
5254         aLine.append("/Metadata ");
5255         aLine.append( nMetadataObject );
5256         aLine.append( " 0 R" );
5257     }
5258 
5259     aLine.append( ">>\n"
5260                   "endobj\n\n" );
5261     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5262 
5263     return true;
5264 }
5265 
5266 #if HAVE_FEATURE_NSS
5267 
5268 bool PDFWriterImpl::emitSignature()
5269 {
5270     if( !updateObject( m_nSignatureObject ) )
5271         return false;
5272 
5273     OStringBuffer aLine( 0x5000 );
5274     aLine.append( m_nSignatureObject );
5275     aLine.append( " 0 obj\n" );
5276     aLine.append("<</Contents <" );
5277 
5278     sal_uInt64 nOffset = ~0U;
5279     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
5280 
5281     m_nSignatureContentOffset = nOffset + aLine.getLength();
5282 
5283     // reserve some space for the PKCS#7 object
5284     OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
5285     comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
5286     aLine.append( aContentFiller.makeStringAndClear() );
5287     aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5288 
5289     if( !m_aContext.DocumentInfo.Author.isEmpty() )
5290     {
5291         aLine.append( "/Name" );
5292         appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
5293     }
5294 
5295     aLine.append( " /M ");
5296     appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
5297 
5298     aLine.append( " /ByteRange [ 0 ");
5299     aLine.append( m_nSignatureContentOffset - 1 );
5300     aLine.append( " " );
5301     aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5302     aLine.append( " " );
5303 
5304     m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5305 
5306     // mark the last ByteRange no and add some space. Now, we don't know
5307     // how many bytes we need for this ByteRange value
5308     // The real value will be overwritten in the finalizeSignature method
5309     OStringBuffer aByteRangeFiller( 100  );
5310     comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
5311     aLine.append( aByteRangeFiller.makeStringAndClear() );
5312     aLine.append("  /Filter/Adobe.PPKMS");
5313 
5314     //emit reason, location and contactinfo
5315     if ( !m_aContext.SignReason.isEmpty() )
5316     {
5317         aLine.append("/Reason");
5318         appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
5319     }
5320 
5321     if ( !m_aContext.SignLocation.isEmpty() )
5322     {
5323         aLine.append("/Location");
5324         appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
5325     }
5326 
5327     if ( !m_aContext.SignContact.isEmpty() )
5328     {
5329         aLine.append("/ContactInfo");
5330         appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
5331     }
5332 
5333     aLine.append(" >>\nendobj\n\n" );
5334 
5335     if (!writeBuffer( aLine.getStr(), aLine.getLength() ))
5336         return false;
5337 
5338     return true;
5339 }
5340 
5341 bool PDFWriterImpl::finalizeSignature()
5342 {
5343     if (!m_aContext.SignCertificate.is())
5344         return false;
5345 
5346     // 1- calculate last ByteRange value
5347     sal_uInt64 nOffset = ~0U;
5348     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
5349 
5350     sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
5351 
5352     // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
5353     sal_uInt64 nWritten = 0;
5354     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
5355     OStringBuffer aByteRangeNo( 256 );
5356     aByteRangeNo.append( nLastByteRangeNo );
5357     aByteRangeNo.append( " ]" );
5358 
5359     if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
5360     {
5361         CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
5362         return false;
5363     }
5364 
5365     // 3- create the PKCS#7 object using NSS
5366 
5367     // Prepare buffer and calculate PDF file digest
5368     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
5369 
5370     std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
5371     sal_uInt64 bytesRead1;
5372 
5373     //FIXME: Check if hash is calculated from the correct byterange
5374     if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
5375         bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
5376     {
5377         SAL_WARN("vcl.pdfwriter", "First buffer read failed");
5378         return false;
5379     }
5380 
5381     std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
5382     sal_uInt64 bytesRead2;
5383 
5384     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
5385         osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
5386         bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
5387     {
5388         SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
5389         return false;
5390     }
5391 
5392     OStringBuffer aCMSHexBuffer;
5393     svl::crypto::Signing aSigning(m_aContext.SignCertificate);
5394     aSigning.AddDataRange(buffer1.get(), bytesRead1);
5395     aSigning.AddDataRange(buffer2.get(), bytesRead2);
5396     aSigning.SetSignTSA(m_aContext.SignTSA);
5397     aSigning.SetSignPassword(m_aContext.SignPassword);
5398     if (!aSigning.Sign(aCMSHexBuffer))
5399     {
5400         SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
5401         return false;
5402     }
5403 
5404     assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
5405 
5406     // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
5407     nWritten = 0;
5408     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
5409     m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
5410 
5411     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
5412     return true;
5413 }
5414 
5415 #endif //HAVE_FEATURE_NSS
5416 
5417 sal_Int32 PDFWriterImpl::emitInfoDict( )
5418 {
5419     sal_Int32 nObject = createObject();
5420 
5421     if( updateObject( nObject ) )
5422     {
5423         OStringBuffer aLine( 1024 );
5424         aLine.append( nObject );
5425         aLine.append( " 0 obj\n"
5426                       "<<" );
5427         if( !m_aContext.DocumentInfo.Title.isEmpty() )
5428         {
5429             aLine.append( "/Title" );
5430             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
5431             aLine.append( "\n" );
5432         }
5433         if( !m_aContext.DocumentInfo.Author.isEmpty() )
5434         {
5435             aLine.append( "/Author" );
5436             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
5437             aLine.append( "\n" );
5438         }
5439         if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5440         {
5441             aLine.append( "/Subject" );
5442             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
5443             aLine.append( "\n" );
5444         }
5445         if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5446         {
5447             aLine.append( "/Keywords" );
5448             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
5449             aLine.append( "\n" );
5450         }
5451         if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5452         {
5453             aLine.append( "/Creator" );
5454             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
5455             aLine.append( "\n" );
5456         }
5457         if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5458         {
5459             aLine.append( "/Producer" );
5460             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
5461             aLine.append( "\n" );
5462         }
5463 
5464          aLine.append( "/CreationDate" );
5465          appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
5466         aLine.append( ">>\nendobj\n\n" );
5467         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5468             nObject = 0;
5469     }
5470     else
5471         nObject = 0;
5472 
5473     return nObject;
5474 }
5475 
5476 // Part of this function may be shared with method appendDest.
5477 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5478 {
5479     sal_Int32  nCount = m_aNamedDests.size();
5480     if( nCount <= 0 )
5481         return 0;//define internal error
5482 
5483     //get the object number for all the destinations
5484     sal_Int32 nObject = createObject();
5485 
5486     if( updateObject( nObject ) )
5487     {
5488         //emit the dictionary
5489         OStringBuffer aLine( 1024 );
5490         aLine.append( nObject );
5491         aLine.append( " 0 obj\n"
5492                       "<<" );
5493 
5494         sal_Int32  nDestID;
5495         for( nDestID = 0; nDestID < nCount; nDestID++ )
5496         {
5497             const PDFNamedDest& rDest   = m_aNamedDests[ nDestID ];
5498             // In order to correctly function both under an Internet browser and
5499             // directly with a reader (provided the reader has the feature) we
5500             // need to set the name of the destination the same way it will be encoded
5501             // in an Internet link
5502             INetURLObject aLocalURL( "http://ahost.ax" ); //dummy location, won't be used
5503             aLocalURL.SetMark( rDest.m_aDestName );
5504 
5505             const OUString aName   = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5506             // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5507             const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
5508 
5509             aLine.append( '/' );
5510             appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5511             aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5512                                  //maps the preceding character properly
5513             aLine.append( rDestPage.m_nPageObject );
5514             aLine.append( " 0 R" );
5515 
5516             switch( rDest.m_eType )
5517             {
5518             case PDFWriter::DestAreaType::XYZ:
5519             default:
5520                 aLine.append( "/XYZ " );
5521                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5522                 aLine.append( ' ' );
5523                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5524                 aLine.append( " 0" );
5525                 break;
5526             case PDFWriter::DestAreaType::FitRectangle:
5527                 aLine.append( "/FitR " );
5528                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5529                 aLine.append( ' ' );
5530                 appendFixedInt( rDest.m_aRect.Top(), aLine );
5531                 aLine.append( ' ' );
5532                 appendFixedInt( rDest.m_aRect.Right(), aLine );
5533                 aLine.append( ' ' );
5534                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5535                 break;
5536             }
5537             aLine.append( "]\n" );
5538         }
5539 
5540         //close
5541         aLine.append( ">>\nendobj\n\n" );
5542         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5543             nObject = 0;
5544     }
5545     else
5546         nObject = 0;
5547 
5548     return nObject;
5549 }
5550 
5551 // emits the output intent dictionary
5552 sal_Int32 PDFWriterImpl::emitOutputIntent()
5553 {
5554     if( !m_bIsPDF_A1 )
5555         return 0;
5556 
5557     //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5558 
5559     OStringBuffer aLine( 1024 );
5560     sal_Int32 nICCObject = createObject();
5561     sal_Int32 nStreamLengthObject = createObject();
5562 
5563     aLine.append( nICCObject );
5564 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5565     aLine.append( " 0 obj\n<</N 3/Length " );
5566     aLine.append( nStreamLengthObject );
5567     aLine.append( " 0 R" );
5568     if (!g_bDebugDisableCompression)
5569         aLine.append( "/Filter/FlateDecode" );
5570     aLine.append( ">>\nstream\n" );
5571     if ( !updateObject( nICCObject ) ) return 0;
5572     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5573     //get file position
5574     sal_uInt64 nBeginStreamPos = 0;
5575     m_aFile.getPos(nBeginStreamPos);
5576     beginCompression();
5577     checkAndEnableStreamEncryption( nICCObject );
5578     cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5579     //force ICC profile version 2.1
5580     cmsSetProfileVersion(hProfile, 2.1);
5581     cmsUInt32Number nBytesNeeded = 0;
5582     cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5583     if (!nBytesNeeded)
5584       return 0;
5585     std::vector<unsigned char> aBuffer(nBytesNeeded);
5586     cmsSaveProfileToMem(hProfile, &aBuffer[0], &nBytesNeeded);
5587     cmsCloseProfile(hProfile);
5588     bool written = writeBuffer( &aBuffer[0], static_cast<sal_Int32>(aBuffer.size()) );
5589     disableStreamEncryption();
5590     endCompression();
5591     sal_uInt64 nEndStreamPos = 0;
5592     m_aFile.getPos(nEndStreamPos);
5593 
5594     if( !written )
5595         return 0;
5596     if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5597         return 0 ;
5598     aLine.setLength( 0 );
5599 
5600     //emit the stream length   object
5601     if ( !updateObject( nStreamLengthObject ) ) return 0;
5602     aLine.setLength( 0 );
5603     aLine.append( nStreamLengthObject );
5604     aLine.append( " 0 obj\n" );
5605     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5606     aLine.append( "\nendobj\n\n" );
5607     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5608     aLine.setLength( 0 );
5609 
5610     //emit the OutputIntent dictionary
5611     sal_Int32 nOIObject = createObject();
5612     if ( !updateObject( nOIObject ) ) return 0;
5613     aLine.append( nOIObject );
5614     aLine.append( " 0 obj\n"
5615                   "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5616 
5617     OUString const aComment( "sRGB IEC61966-2.1"  );
5618     appendLiteralStringEncrypt( aComment ,nOIObject, aLine );
5619     aLine.append("/DestOutputProfile ");
5620     aLine.append( nICCObject );
5621     aLine.append( " 0 R>>\nendobj\n\n" );
5622     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5623 
5624     return nOIObject;
5625 }
5626 
5627 // formats the string for the XML stream
5628 static void escapeStringXML( const OUString& rStr, OUString &rValue)
5629 {
5630     const sal_Unicode* pUni = rStr.getStr();
5631     int nLen = rStr.getLength();
5632     for( ; nLen; nLen--, pUni++ )
5633     {
5634         switch( *pUni )
5635         {
5636         case u'&':
5637             rValue += "&amp;";
5638         break;
5639         case u'<':
5640             rValue += "&lt;";
5641         break;
5642         case u'>':
5643             rValue += "&gt;";
5644         break;
5645         case u'\'':
5646             rValue += "&apos;";
5647         break;
5648         case u'"':
5649             rValue += "&quot;";
5650         break;
5651         default:
5652             rValue += OUStringLiteral1( *pUni );
5653             break;
5654         }
5655     }
5656 }
5657 
5658 // emits the document metadata
5659 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5660 {
5661     if( !m_bIsPDF_A1 )
5662         return 0;
5663 
5664     //get the object number for all the destinations
5665     sal_Int32 nObject = createObject();
5666 
5667     if( updateObject( nObject ) )
5668     {
5669         // the following string are written in UTF-8 unicode
5670         OStringBuffer aMetadataStream( 8192 );
5671 
5672         aMetadataStream.append( "<?xpacket begin=\"" );
5673         // these lines write Unicode "zero width non-breaking space character" (U+FEFF)
5674         // (aka byte-order mark ) used as a byte-order marker.
5675         aMetadataStream.append( OUStringToOString( OUString( u'\xFEFF' ), RTL_TEXTENCODING_UTF8 ) );
5676         aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" );
5677         aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" );
5678         aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" );
5679         //PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
5680         aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
5681         aMetadataStream.append( "      xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" );
5682         aMetadataStream.append( "   <pdfaid:part>1</pdfaid:part>\n" );
5683         aMetadataStream.append( "   <pdfaid:conformance>A</pdfaid:conformance>\n" );
5684         aMetadataStream.append( "  </rdf:Description>\n" );
5685         //... Dublin Core properties go here
5686         if( !m_aContext.DocumentInfo.Title.isEmpty() ||
5687             !m_aContext.DocumentInfo.Author.isEmpty() ||
5688             !m_aContext.DocumentInfo.Subject.isEmpty() )
5689         {
5690             aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
5691             aMetadataStream.append( "      xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" );
5692             if( !m_aContext.DocumentInfo.Title.isEmpty() )
5693             {
5694                 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
5695                 aMetadataStream.append( "   <dc:title>\n" );
5696                 aMetadataStream.append( "    <rdf:Alt>\n" );
5697                 aMetadataStream.append( "     <rdf:li xml:lang=\"x-default\">" );
5698                 OUString aTitle;
5699                 escapeStringXML( m_aContext.DocumentInfo.Title, aTitle );
5700                 aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 )  );
5701                 aMetadataStream.append( "</rdf:li>\n" );
5702                 aMetadataStream.append( "    </rdf:Alt>\n" );
5703                 aMetadataStream.append( "   </dc:title>\n" );
5704             }
5705             if( !m_aContext.DocumentInfo.Author.isEmpty() )
5706             {
5707                 aMetadataStream.append( "   <dc:creator>\n" );
5708                 aMetadataStream.append( "    <rdf:Seq>\n" );
5709                 aMetadataStream.append( "     <rdf:li>" );
5710                 OUString aAuthor;
5711                 escapeStringXML( m_aContext.DocumentInfo.Author, aAuthor );
5712                 aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 )  );
5713                 aMetadataStream.append( "</rdf:li>\n" );
5714                 aMetadataStream.append( "    </rdf:Seq>\n" );
5715                 aMetadataStream.append( "   </dc:creator>\n" );
5716             }
5717             if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5718             {
5719                 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
5720                 aMetadataStream.append( "   <dc:description>\n" );
5721                 aMetadataStream.append( "    <rdf:Alt>\n" );
5722                 aMetadataStream.append( "     <rdf:li xml:lang=\"x-default\">" );
5723                 OUString aSubject;
5724                 escapeStringXML( m_aContext.DocumentInfo.Subject, aSubject );
5725                 aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 )  );
5726                 aMetadataStream.append( "</rdf:li>\n" );
5727                 aMetadataStream.append( "    </rdf:Alt>\n" );
5728                 aMetadataStream.append( "   </dc:description>\n" );
5729             }
5730             aMetadataStream.append( "  </rdf:Description>\n" );
5731         }
5732 
5733         //... PDF properties go here
5734         if( !m_aContext.DocumentInfo.Producer.isEmpty() ||
5735             !m_aContext.DocumentInfo.Keywords.isEmpty() )
5736         {
5737             aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
5738             aMetadataStream.append( "     xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" );
5739             if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5740             {
5741                 aMetadataStream.append( "   <pdf:Producer>" );
5742                 OUString aProducer;
5743                 escapeStringXML( m_aContext.DocumentInfo.Producer, aProducer );
5744                 aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 )  );
5745                 aMetadataStream.append( "</pdf:Producer>\n" );
5746             }
5747             if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5748             {
5749                 aMetadataStream.append( "   <pdf:Keywords>" );
5750                 OUString aKeywords;
5751                 escapeStringXML( m_aContext.DocumentInfo.Keywords, aKeywords );
5752                 aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 )  );
5753                 aMetadataStream.append( "</pdf:Keywords>\n" );
5754             }
5755             aMetadataStream.append( "  </rdf:Description>\n" );
5756         }
5757 
5758         aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
5759         aMetadataStream.append( "    xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" );
5760         if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5761         {
5762             aMetadataStream.append( "   <xmp:CreatorTool>" );
5763             OUString aCreator;
5764             escapeStringXML( m_aContext.DocumentInfo.Creator, aCreator );
5765             aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 )  );
5766             aMetadataStream.append( "</xmp:CreatorTool>\n" );
5767         }
5768         //creation date
5769         aMetadataStream.append( "   <xmp:CreateDate>" );
5770         aMetadataStream.append( m_aCreationMetaDateString );
5771         aMetadataStream.append( "</xmp:CreateDate>\n" );
5772 
5773         aMetadataStream.append( "  </rdf:Description>\n" );
5774         aMetadataStream.append( " </rdf:RDF>\n" );
5775         aMetadataStream.append( "</x:xmpmeta>\n" );
5776 
5777         //add the padding
5778         for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ )
5779         {
5780             aMetadataStream.append( " " );
5781             if( nSpaces % 100 == 0 )
5782                 aMetadataStream.append( "\n" );
5783         }
5784 
5785         aMetadataStream.append( "<?xpacket end=\"w\"?>\n" );
5786 
5787         OStringBuffer aMetadataObj( 1024 );
5788 
5789         aMetadataObj.append( nObject );
5790         aMetadataObj.append( " 0 obj\n" );
5791 
5792         aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
5793 
5794         aMetadataObj.append( aMetadataStream.getLength() );
5795         aMetadataObj.append( ">>\nstream\n" );
5796         if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5797             return 0;
5798         //emit the stream
5799         if ( !writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) )
5800             return 0;
5801 
5802         aMetadataObj.setLength( 0 );
5803         aMetadataObj.append( "\nendstream\nendobj\n\n" );
5804         if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5805             nObject = 0;
5806     }
5807     else
5808         nObject = 0;
5809 
5810     return nObject;
5811 }
5812 
5813 bool PDFWriterImpl::emitTrailer()
5814 {
5815     // emit doc info
5816     sal_Int32 nDocInfoObject = emitInfoDict( );
5817 
5818     sal_Int32 nSecObject = 0;
5819 
5820     if( m_aContext.Encryption.Encrypt() )
5821     {
5822         //emit the security information
5823         //must be emitted as indirect dictionary object, since
5824         //Acrobat Reader 5 works only with this kind of implementation
5825         nSecObject = createObject();
5826 
5827         if( updateObject( nSecObject ) )
5828         {
5829             OStringBuffer aLineS( 1024 );
5830             aLineS.append( nSecObject );
5831             aLineS.append( " 0 obj\n"
5832                            "<</Filter/Standard/V " );
5833             // check the version
5834             aLineS.append( "2/Length 128/R 3" );
5835 
5836             // emit the owner password, must not be encrypted
5837             aLineS.append( "/O(" );
5838             appendLiteralString( reinterpret_cast<char*>(&m_aContext.Encryption.OValue[0]), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
5839             aLineS.append( ")/U(" );
5840             appendLiteralString( reinterpret_cast<char*>(&m_aContext.Encryption.UValue[0]), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
5841             aLineS.append( ")/P " );// the permission set
5842             aLineS.append( m_nAccessPermissions );
5843             aLineS.append( ">>\nendobj\n\n" );
5844             if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
5845                 nSecObject = 0;
5846         }
5847         else
5848             nSecObject = 0;
5849     }
5850     // emit xref table
5851     // remember start
5852     sal_uInt64 nXRefOffset = 0;
5853     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
5854     CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
5855 
5856     sal_Int32 nObjects = m_aObjects.size();
5857     OStringBuffer aLine;
5858     aLine.append( "0 " );
5859     aLine.append( static_cast<sal_Int32>(nObjects+1) );
5860     aLine.append( "\n" );
5861     aLine.append( "0000000000 65535 f \n" );
5862     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5863 
5864     for( sal_Int32 i = 0; i < nObjects; i++ )
5865     {
5866         aLine.setLength( 0 );
5867         OString aOffset = OString::number( m_aObjects[i] );
5868         for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
5869             aLine.append( '0' );
5870         aLine.append( aOffset );
5871         aLine.append( " 00000 n \n" );
5872         SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
5873         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5874     }
5875 
5876     // prepare document checksum
5877     OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
5878     ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
5879     for (sal_uInt8 i : nMD5Sum)
5880         appendHex( i, aDocChecksum );
5881     // document id set in setDocInfo method
5882     // emit trailer
5883     aLine.setLength( 0 );
5884     aLine.append( "trailer\n"
5885                   "<</Size " );
5886     aLine.append( static_cast<sal_Int32>(nObjects+1) );
5887     aLine.append( "/Root " );
5888     aLine.append( m_nCatalogObject );
5889     aLine.append( " 0 R\n" );
5890     if( nSecObject )
5891     {
5892         aLine.append( "/Encrypt ");
5893         aLine.append( nSecObject );
5894         aLine.append( " 0 R\n" );
5895     }
5896     if( nDocInfoObject )
5897     {
5898         aLine.append( "/Info " );
5899         aLine.append( nDocInfoObject );
5900         aLine.append( " 0 R\n" );
5901     }
5902     if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
5903     {
5904         aLine.append( "/ID [ <" );
5905         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5906         {
5907             appendHex( sal_Int8(item), aLine );
5908         }
5909         aLine.append( ">\n"
5910                       "<" );
5911         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5912         {
5913             appendHex( sal_Int8(item), aLine );
5914         }
5915         aLine.append( "> ]\n" );
5916     }
5917     if( !aDocChecksum.isEmpty() )
5918     {
5919         aLine.append( "/DocChecksum /" );
5920         aLine.append( aDocChecksum.makeStringAndClear() );
5921         aLine.append( "\n" );
5922     }
5923     if( m_aAdditionalStreams.size() > 0 )
5924     {
5925         aLine.append( "/AdditionalStreams [" );
5926         for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
5927         {
5928             aLine.append( "/" );
5929             appendName( rAdditionalStream.m_aMimeType, aLine );
5930             aLine.append( " " );
5931             aLine.append( rAdditionalStream.m_nStreamObject );
5932             aLine.append( " 0 R\n" );
5933         }
5934         aLine.append( "]\n" );
5935     }
5936     aLine.append( ">>\n"
5937                   "startxref\n" );
5938     aLine.append( static_cast<sal_Int64>(nXRefOffset) );
5939     aLine.append( "\n"
5940                   "%%EOF\n" );
5941     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5942 
5943     return true;
5944 }
5945 
5946 struct AnnotationSortEntry
5947 {
5948     sal_Int32 nTabOrder;
5949     sal_Int32 nObject;
5950     sal_Int32 nWidgetIndex;
5951 
5952     AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
5953         nTabOrder( nTab ),
5954         nObject( nObj ),
5955         nWidgetIndex( nI )
5956     {}
5957 };
5958 
5959 struct AnnotSortContainer
5960 {
5961     std::set< sal_Int32 >               aObjects;
5962     std::vector< AnnotationSortEntry >    aSortedAnnots;
5963 };
5964 
5965 struct AnnotSorterLess
5966 {
5967     std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets;
5968 
5969     explicit AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {}
5970 
5971     bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
5972     {
5973         if( rLeft.nTabOrder < rRight.nTabOrder )
5974             return true;
5975         if( rRight.nTabOrder < rLeft.nTabOrder )
5976             return false;
5977         if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
5978             return false;
5979         if( rRight.nWidgetIndex < 0 )
5980             return true;
5981         if( rLeft.nWidgetIndex < 0 )
5982             return false;
5983         // remember: widget rects are in PDF coordinates, so they are ordered down up
5984         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
5985             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
5986             return true;
5987         if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
5988             m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
5989             return false;
5990         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
5991             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
5992             return true;
5993         return false;
5994     }
5995 };
5996 
5997 void PDFWriterImpl::sortWidgets()
5998 {
5999     // sort widget annotations on each page as per their
6000     // TabOrder attribute
6001     std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
6002     int nWidgets = m_aWidgets.size();
6003     for( int nW = 0; nW < nWidgets; nW++ )
6004     {
6005         const PDFWidget& rWidget = m_aWidgets[nW];
6006         if( rWidget.m_nPage >= 0 )
6007         {
6008             AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
6009             // optimize vector allocation
6010             if( rCont.aSortedAnnots.empty() )
6011                 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
6012             // insert widget to tab sorter
6013             // RadioButtons are not page annotations, only their individual check boxes are
6014             if( rWidget.m_eType != PDFWriter::RadioButton )
6015             {
6016                 rCont.aObjects.insert( rWidget.m_nObject );
6017                 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
6018             }
6019         }
6020     }
6021     for (auto & item : sorted)
6022     {
6023         // append entries for non widget annotations
6024         PDFPage& rPage = m_aPages[ item.first ];
6025         unsigned int nAnnots = rPage.m_aAnnotations.size();
6026         for( unsigned int nA = 0; nA < nAnnots; nA++ )
6027             if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
6028                 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
6029 
6030         AnnotSorterLess aLess( m_aWidgets );
6031         std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
6032         // sanity check
6033         if( item.second.aSortedAnnots.size() == nAnnots)
6034         {
6035             for( unsigned int nA = 0; nA < nAnnots; nA++ )
6036                 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
6037         }
6038         else
6039         {
6040             SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
6041             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
6042                      "on page nr " << item.first << ", " <<
6043                      static_cast<long int>(item.second.aSortedAnnots.size()) << " sorted and " <<
6044                      static_cast<long int>(nAnnots) << " unsorted");
6045         }
6046     }
6047 
6048     // FIXME: implement tab order in structure tree for PDF 1.5
6049 }
6050 
6051 namespace vcl {
6052 class PDFStreamIf :
6053         public cppu::WeakImplHelper< css::io::XOutputStream >
6054 {
6055     PDFWriterImpl*  m_pWriter;
6056     bool            m_bWrite;
6057     public:
6058     explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
6059 
6060     virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
6061     virtual void SAL_CALL flush() override;
6062     virtual void SAL_CALL closeOutput() override;
6063 };
6064 }
6065 
6066 void SAL_CALL  PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
6067 {
6068     if( m_bWrite && aData.getLength() )
6069     {
6070         sal_Int32 nBytes = aData.getLength();
6071         m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
6072     }
6073 }
6074 
6075 void SAL_CALL PDFStreamIf::flush()
6076 {
6077 }
6078 
6079 void SAL_CALL PDFStreamIf::closeOutput()
6080 {
6081     m_bWrite = false;
6082 }
6083 
6084 bool PDFWriterImpl::emitAdditionalStreams()
6085 {
6086     unsigned int nStreams = m_aAdditionalStreams.size();
6087     for( unsigned int i = 0; i < nStreams; i++ )
6088     {
6089         PDFAddStream& rStream = m_aAdditionalStreams[i];
6090         rStream.m_nStreamObject = createObject();
6091         sal_Int32 nSizeObject = createObject();
6092 
6093         if( ! updateObject( rStream.m_nStreamObject ) )
6094             return false;
6095 
6096         OStringBuffer aLine;
6097         aLine.append( rStream.m_nStreamObject );
6098         aLine.append( " 0 obj\n<</Length " );
6099         aLine.append( nSizeObject );
6100         aLine.append( " 0 R" );
6101         if( rStream.m_bCompress )
6102             aLine.append( "/Filter/FlateDecode" );
6103         aLine.append( ">>\nstream\n" );
6104         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
6105             return false;
6106         sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
6107         if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
6108         {
6109             m_aFile.close();
6110             m_bOpen = false;
6111         }
6112         if( rStream.m_bCompress )
6113             beginCompression();
6114 
6115         checkAndEnableStreamEncryption( rStream.m_nStreamObject );
6116         css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
6117         assert(rStream.m_pStream);
6118         if (!rStream.m_pStream)
6119             return false;
6120         rStream.m_pStream->write( xStream );
6121         xStream.clear();
6122         delete rStream.m_pStream;
6123         rStream.m_pStream = nullptr;
6124         disableStreamEncryption();
6125 
6126         if( rStream.m_bCompress )
6127             endCompression();
6128 
6129         if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
6130         {
6131             m_aFile.close();
6132             m_bOpen = false;
6133             return false;
6134         }
6135         if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
6136             return false ;
6137         // emit stream length object
6138         if( ! updateObject( nSizeObject ) )
6139             return false;
6140         aLine.setLength( 0 );
6141         aLine.append( nSizeObject );
6142         aLine.append( " 0 obj\n" );
6143         aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
6144         aLine.append( "\nendobj\n\n" );
6145         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
6146             return false;
6147     }
6148     return true;
6149 }
6150 
6151 bool PDFWriterImpl::emit()
6152 {
6153     endPage();
6154 
6155     // resort structure tree and annotations if necessary
6156     // needed for widget tab order
6157     sortWidgets();
6158 
6159 #if HAVE_FEATURE_NSS
6160     if( m_aContext.SignPDF )
6161     {
6162         // sign the document
6163         PDFWriter::SignatureWidget aSignature;
6164         aSignature.Name = "Signature1";
6165         createControl( aSignature, 0 );
6166     }
6167 #endif
6168 
6169     // emit additional streams
6170     CHECK_RETURN( emitAdditionalStreams() );
6171 
6172     // emit catalog
6173     CHECK_RETURN( emitCatalog() );
6174 
6175 #if HAVE_FEATURE_NSS
6176     if (m_nSignatureObject != -1) // if document is signed, emit sigdict
6177     {
6178         if( !emitSignature() )
6179         {
6180             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6181             return false;
6182         }
6183     }
6184 #endif
6185 
6186     // emit trailer
6187     CHECK_RETURN( emitTrailer() );
6188 
6189 #if HAVE_FEATURE_NSS
6190     if (m_nSignatureObject != -1) // finalize the signature
6191     {
6192         if( !finalizeSignature() )
6193         {
6194             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6195             return false;
6196         }
6197     }
6198 #endif
6199 
6200     m_aFile.close();
6201     m_bOpen = false;
6202 
6203     return true;
6204 }
6205 
6206 
6207 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
6208 {
6209     getReferenceDevice()->Push();
6210     getReferenceDevice()->SetFont( i_rFont );
6211     getReferenceDevice()->ImplNewFont();
6212 
6213     const PhysicalFontFace* pDevFont = m_pReferenceDevice->mpFontInstance->GetFontFace();
6214     sal_Int32 nFontID = 0;
6215     FontEmbedData::iterator it = m_aSystemFonts.find( pDevFont );
6216     if( it != m_aSystemFonts.end() )
6217         nFontID = it->second.m_nNormalFontID;
6218     else
6219     {
6220         nFontID = m_nNextFID++;
6221         m_aSystemFonts[ pDevFont ] = EmbedFont();
6222         m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
6223     }
6224 
6225     getReferenceDevice()->Pop();
6226     getReferenceDevice()->ImplNewFont();
6227 
6228     return nFontID;
6229 }
6230 
6231 void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
6232                                   const PhysicalFontFace* pFont,
6233                                   const std::vector<sal_Ucs>& rCodeUnits,
6234                                   sal_uInt8& nMappedGlyph,
6235                                   sal_Int32& nMappedFontObject)
6236 {
6237     const int nFontGlyphId = pGlyph->maGlyphId;
6238     FontSubset& rSubset = m_aSubsets[ pFont ];
6239     // search for font specific glyphID
6240     FontMapping::iterator it = rSubset.m_aMapping.find( nFontGlyphId );
6241     if( it != rSubset.m_aMapping.end() )
6242     {
6243         nMappedFontObject = it->second.m_nFontID;
6244         nMappedGlyph = it->second.m_nSubsetGlyphID;
6245     }
6246     else
6247     {
6248         // create new subset if necessary
6249         if( rSubset.m_aSubsets.empty()
6250         || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
6251         {
6252             rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
6253         }
6254 
6255         // copy font id
6256         nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6257         // create new glyph in subset
6258         sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
6259         nMappedGlyph = nNewId;
6260 
6261         // add new glyph to emitted font subset
6262         GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
6263         rNewGlyphEmit.setGlyphId( nNewId );
6264         for (const auto nCode : rCodeUnits)
6265             rNewGlyphEmit.addCode(nCode);
6266 
6267         // add new glyph to font mapping
6268         Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
6269         rNewGlyph.m_nFontID = nMappedFontObject;
6270         rNewGlyph.m_nSubsetGlyphID = nNewId;
6271     }
6272 }
6273 
6274 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6275 {
6276     push( PushFlags::ALL );
6277 
6278     FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
6279 
6280     Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
6281     Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6282     Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6283     Color aReliefColor( COL_LIGHTGRAY );
6284     if( aTextColor == COL_BLACK )
6285         aTextColor = COL_WHITE;
6286     if( aTextLineColor == COL_BLACK )
6287         aTextLineColor = COL_WHITE;
6288     if( aOverlineColor == COL_BLACK )
6289         aOverlineColor = COL_WHITE;
6290     if( aTextColor == COL_WHITE )
6291         aReliefColor = COL_BLACK;
6292 
6293     Font aSetFont = m_aCurrentPDFState.m_aFont;
6294     aSetFont.SetRelief( FontRelief::NONE );
6295     aSetFont.SetShadow( false );
6296 
6297     aSetFont.SetColor( aReliefColor );
6298     setTextLineColor( aReliefColor );
6299     setOverlineColor( aReliefColor );
6300     setFont( aSetFont );
6301     long nOff = 1 + getReferenceDevice()->mnDPIX/300;
6302     if( eRelief == FontRelief::Engraved )
6303         nOff = -nOff;
6304 
6305     rLayout.DrawOffset() += Point( nOff, nOff );
6306     updateGraphicsState();
6307     drawLayout( rLayout, rText, bTextLines );
6308 
6309     rLayout.DrawOffset() -= Point( nOff, nOff );
6310     setTextLineColor( aTextLineColor );
6311     setOverlineColor( aOverlineColor );
6312     aSetFont.SetColor( aTextColor );
6313     setFont( aSetFont );
6314     updateGraphicsState();
6315     drawLayout( rLayout, rText, bTextLines );
6316 
6317     // clean up the mess
6318     pop();
6319 }
6320 
6321 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6322 {
6323     Font aSaveFont = m_aCurrentPDFState.m_aFont;
6324     Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6325     Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6326 
6327     Font& rFont = m_aCurrentPDFState.m_aFont;
6328     if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
6329         rFont.SetColor( COL_LIGHTGRAY );
6330     else
6331         rFont.SetColor( COL_BLACK );
6332     rFont.SetShadow( false );
6333     rFont.SetOutline( false );
6334     setFont( rFont );
6335     setTextLineColor( rFont.GetColor() );
6336     setOverlineColor( rFont.GetColor() );
6337     updateGraphicsState();
6338 
6339     long nOff = 1 + ((m_pReferenceDevice->mpFontInstance->mnLineHeight-24)/24);
6340     if( rFont.IsOutline() )
6341         nOff++;
6342     rLayout.DrawBase() += Point( nOff, nOff );
6343     drawLayout( rLayout, rText, bTextLines );
6344     rLayout.DrawBase() -= Point( nOff, nOff );
6345 
6346     setFont( aSaveFont );
6347     setTextLineColor( aSaveTextLineColor );
6348     setOverlineColor( aSaveOverlineColor );
6349     updateGraphicsState();
6350 }
6351 
6352 void PDFWriterImpl::drawVerticalGlyphs(
6353         const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
6354         OStringBuffer& rLine,
6355         const Point& rAlignOffset,
6356         const Matrix3& rRotScale,
6357         double fAngle,
6358         double fXScale,
6359         double fSkew,
6360         sal_Int32 nFontHeight )
6361 {
6362     long nXOffset = 0;
6363     Point aCurPos( rGlyphs[0].m_aPos );
6364     aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos );
6365     aCurPos += rAlignOffset;
6366     for( size_t i = 0; i < rGlyphs.size(); i++ )
6367     {
6368         // have to emit each glyph on its own
6369         double fDeltaAngle = 0.0;
6370         double fYScale = 1.0;
6371         double fTempXScale = fXScale;
6372         double fSkewB = fSkew;
6373         double fSkewA = 0.0;
6374 
6375         Point aDeltaPos;
6376         if (rGlyphs[i].m_pGlyph->IsVertical())
6377         {
6378             fDeltaAngle = M_PI/2.0;
6379             aDeltaPos.setX( m_pReferenceDevice->GetFontMetric().GetAscent() );
6380             aDeltaPos.setY( static_cast<int>(static_cast<double>(m_pReferenceDevice->GetFontMetric().GetDescent()) * fXScale) );
6381             fYScale = fXScale;
6382             fTempXScale = 1.0;
6383             fSkewA = -fSkewB;
6384             fSkewB = 0.0;
6385         }
6386         aDeltaPos += (m_pReferenceDevice->PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - m_pReferenceDevice->PixelToLogic( Point() ) );
6387         if( i < rGlyphs.size()-1 )
6388         // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
6389         {
6390             long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
6391             long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
6392             nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)));
6393         }
6394         if( ! rGlyphs[i].m_pGlyph->maGlyphId )
6395             continue;
6396 
6397         aDeltaPos = rRotScale.transform( aDeltaPos );
6398 
6399         Matrix3 aMat;
6400         if( fSkewB != 0.0 || fSkewA != 0.0 )
6401             aMat.skew( fSkewA, fSkewB );
6402         aMat.scale( fTempXScale, fYScale );
6403         aMat.rotate( fAngle+fDeltaAngle );
6404         aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
6405         aMat.append( m_aPages.back(), rLine );
6406         rLine.append( " Tm" );
6407         if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
6408         {
6409             rLine.append( " /F" );
6410             rLine.append( rGlyphs[i].m_nMappedFontId );
6411             rLine.append( ' ' );
6412             m_aPages.back().appendMappedLength( nFontHeight, rLine );
6413             rLine.append( " Tf" );
6414         }
6415         rLine.append( "<" );
6416         appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
6417         rLine.append( ">Tj\n" );
6418     }
6419 }
6420 
6421 void PDFWriterImpl::drawHorizontalGlyphs(
6422         const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
6423         OStringBuffer& rLine,
6424         const Point& rAlignOffset,
6425         bool bFirst,
6426         double fAngle,
6427         double fXScale,
6428         double fSkew,
6429         sal_Int32 nFontHeight,
6430         sal_Int32 nPixelFontHeight
6431         )
6432 {
6433     // horizontal (= normal) case
6434 
6435     // fill in  run end indices
6436     // end is marked by index of the first glyph of the next run
6437     // a run is marked by same mapped font id and same Y position
6438     std::vector< sal_uInt32 > aRunEnds;
6439     aRunEnds.reserve( rGlyphs.size() );
6440     for( size_t i = 1; i < rGlyphs.size(); i++ )
6441     {
6442         if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6443             rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
6444         {
6445             aRunEnds.push_back(i);
6446         }
6447     }
6448     // last run ends at last glyph
6449     aRunEnds.push_back( rGlyphs.size() );
6450 
6451     // loop over runs of the same font
6452     sal_uInt32 nBeginRun = 0;
6453     for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6454     {
6455         // setup text matrix
6456         Point aCurPos = rGlyphs[nBeginRun].m_aPos;
6457         // back transformation to current coordinate system
6458         aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos );
6459         aCurPos += rAlignOffset;
6460         // the first run can be set with "Td" operator
6461         // subsequent use of that operator would move
6462         // the textline matrix relative to what was set before
6463         // making use of that would drive us into rounding issues
6464         Matrix3 aMat;
6465         if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6466         {
6467             m_aPages.back().appendPoint( aCurPos, rLine );
6468             rLine.append( " Td " );
6469         }
6470         else
6471         {
6472             if( fSkew != 0.0 )
6473                 aMat.skew( 0.0, fSkew );
6474             aMat.scale( fXScale, 1.0 );
6475             aMat.rotate( fAngle );
6476             aMat.translate( aCurPos.X(), aCurPos.Y() );
6477             aMat.append( m_aPages.back(), rLine );
6478             rLine.append( " Tm\n" );
6479         }
6480         // set up correct font
6481         rLine.append( "/F" );
6482         rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6483         rLine.append( ' ' );
6484         m_aPages.back().appendMappedLength( nFontHeight, rLine );
6485         rLine.append( " Tf" );
6486 
6487         // output glyphs using Tj or TJ
6488         OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6489         aKernedLine.append( "[<" );
6490         aUnkernedLine.append( '<' );
6491         appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6492         appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6493 
6494         aMat.invert();
6495         bool bNeedKern = false;
6496         for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6497         {
6498             appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6499             // check if default glyph positioning is sufficient
6500             const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6501             const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6502             double fAdvance = aThisPos.X() - aPrevPos.X();
6503             fAdvance *= 1000.0 / nPixelFontHeight;
6504             const sal_Int32 nAdjustment = static_cast<sal_Int32>(rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5);
6505             if( nAdjustment != 0 )
6506             {
6507                 // apply individual glyph positioning
6508                 bNeedKern = true;
6509                 aKernedLine.append( ">" );
6510                 aKernedLine.append( nAdjustment );
6511                 aKernedLine.append( "<" );
6512             }
6513             appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6514         }
6515         aKernedLine.append( ">]TJ\n" );
6516         aUnkernedLine.append( ">Tj\n" );
6517         rLine.append(
6518             (bNeedKern ? aKernedLine : aUnkernedLine).makeStringAndClear() );
6519 
6520         // set beginning of next run
6521         nBeginRun = aRunEnds[nRun];
6522     }
6523 }
6524 
6525 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6526 {
6527     // relief takes precedence over shadow (see outdev3.cxx)
6528     if(  m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6529     {
6530         drawRelief( rLayout, rText, bTextLines );
6531         return;
6532     }
6533     else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6534         drawShadow( rLayout, rText, bTextLines );
6535 
6536     OStringBuffer aLine( 512 );
6537 
6538     const int nMaxGlyphs = 256;
6539 
6540     const GlyphItem* pGlyph = nullptr;
6541     const PhysicalFontFace* pFallbackFont = nullptr;
6542     std::vector<sal_Ucs> aCodeUnits;
6543     bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6544     int nIndex = 0;
6545     double fXScale = 1.0;
6546     double fSkew = 0.0;
6547     sal_Int32 nPixelFontHeight = m_pReferenceDevice->mpFontInstance->GetFontSelectPattern().mnHeight;
6548     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6549 
6550     // transform font height back to current units
6551     // note: the layout calculates in outdevs device pixel !!
6552     sal_Int32 nFontHeight = m_pReferenceDevice->ImplDevicePixelToLogicHeight( nPixelFontHeight );
6553     if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6554     {
6555         Font aFont( m_aCurrentPDFState.m_aFont );
6556         aFont.SetAverageFontWidth( 0 );
6557         FontMetric aMetric = m_pReferenceDevice->GetFontMetric( aFont );
6558         if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6559         {
6560             fXScale =
6561                 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6562                 static_cast<double>(aMetric.GetAverageFontWidth());
6563         }
6564         // force state before GetFontMetric
6565         m_pReferenceDevice->ImplNewFont();
6566     }
6567 
6568     // perform artificial italics if necessary
6569     if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
6570           m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
6571         !( m_pReferenceDevice->mpFontInstance->GetFontFace()->GetItalic() == ITALIC_NORMAL ||
6572            m_pReferenceDevice->mpFontInstance->GetFontFace()->GetItalic() == ITALIC_OBLIQUE )
6573         )
6574     {
6575         fSkew = M_PI/12.0;
6576     }
6577 
6578     // if the mapmode is distorted we need to adjust for that also
6579     if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6580     {
6581         fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6582     }
6583 
6584     int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6585     // normalize angles
6586     while( nAngle < 0 )
6587         nAngle += 3600;
6588     nAngle = nAngle % 3600;
6589     double fAngle = static_cast<double>(nAngle) * M_PI / 1800.0;
6590 
6591     Matrix3 aRotScale;
6592     aRotScale.scale( fXScale, 1.0 );
6593     if( fAngle != 0.0 )
6594         aRotScale.rotate( -fAngle );
6595 
6596     bool bPop = false;
6597     bool bABold = false;
6598     // artificial bold necessary ?
6599     if( m_pReferenceDevice->mpFontInstance->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
6600         m_pReferenceDevice->mpFontInstance->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
6601     {
6602         if( ! bPop )
6603             aLine.append( "q " );
6604         bPop = true;
6605         bABold = true;
6606     }
6607     // setup text colors (if necessary)
6608     Color aStrokeColor( COL_TRANSPARENT );
6609     Color aNonStrokeColor( COL_TRANSPARENT );
6610 
6611     if( m_aCurrentPDFState.m_aFont.IsOutline() )
6612     {
6613         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6614         aNonStrokeColor = COL_WHITE;
6615     }
6616     else
6617         aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6618     if( bABold )
6619         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6620 
6621     if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6622     {
6623         if( ! bPop )
6624             aLine.append( "q " );
6625         bPop = true;
6626         appendStrokingColor( aStrokeColor, aLine );
6627         aLine.append( "\n" );
6628     }
6629     if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6630     {
6631         if( ! bPop )
6632             aLine.append( "q " );
6633         bPop = true;
6634         appendNonStrokingColor( aNonStrokeColor, aLine );
6635         aLine.append( "\n" );
6636     }
6637 
6638     // begin text object
6639     aLine.append( "BT\n" );
6640     // outline attribute ?
6641     if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6642     {
6643         // set correct text mode, set stroke width
6644         aLine.append( "2 Tr " ); // fill, then stroke
6645 
6646         if( m_aCurrentPDFState.m_aFont.IsOutline() )
6647         {
6648             // unclear what to do in case of outline and artificial bold
6649             // for the time being outline wins
6650             aLine.append( "0.25 w \n" );
6651         }
6652         else
6653         {
6654             double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6655             m_aPages.back().appendMappedLength( fW, aLine );
6656             aLine.append ( " w\n" );
6657         }
6658     }
6659 
6660     FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric();
6661     const PhysicalFontFace* pDevFont = m_pReferenceDevice->mpFontInstance->GetFontFace();
6662 
6663     // collect the glyphs into a single array
6664     std::vector< PDFGlyph > aGlyphs;
6665     aGlyphs.reserve( nMaxGlyphs );
6666     // first get all the glyphs and register them; coordinates still in Pixel
6667     Point aPos;
6668     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pFallbackFont))
6669     {
6670         const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
6671 
6672         aCodeUnits.clear();
6673 
6674         // tdf#66597, tdf#115117
6675         //
6676         // Here is how we embed textual content in PDF files, to allow for
6677         // better text extraction for complex and typography-rich text.
6678         //
6679         // * If there is many to one or many to many mapping, use an
6680         //   ActualText span embedding the original string, since ToUnicode
6681         //   can’t handle these.
6682         // * If the one glyph is used for several Unicode code points, also
6683         //   use ActualText since ToUnicode can map each glyph in the font
6684         //   only once.
6685         // * Limit ActualText to single cluster at a time, since using it
6686         //   for whole words or sentences breaks text selection and
6687         //   highlighting in PDF viewers (there will be no way to tell
6688         //   which glyphs belong to which characters).
6689         // * Keep generating (now) redundant ToUnicode entries for
6690         //   compatibility with old tools not supporting ActualText.
6691 
6692         assert(pGlyph->mnCharCount >= 0);
6693         for (int n = 0; n < pGlyph->mnCharCount; n++)
6694             aCodeUnits.push_back(rText[pGlyph->mnCharPos + n]);
6695 
6696         bool bUseActualText = false;
6697 
6698         // If this is a start of complex cluster, use ActualText.
6699         if (pGlyph->IsClusterStart())
6700             bUseActualText = true;
6701 
6702         // Or part of a complex cluster, will be handled by the ActualText
6703         // of its cluster start.
6704         if (pGlyph->IsInCluster())
6705             assert(aCodeUnits.empty());
6706 
6707         // A glyph can’t have more than one ToUnicode entry, use ActualText
6708         // instead.
6709         if (!aCodeUnits.empty() && !bUseActualText)
6710         {
6711             for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
6712             {
6713                 const auto& it = rSubset.m_aMapping.find(pGlyph->maGlyphId);
6714                 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6715                 {
6716                     bUseActualText = true;
6717                     aCodeUnits.clear();
6718                 }
6719             }
6720         }
6721 
6722         assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
6723 
6724         sal_uInt8 nMappedGlyph;
6725         sal_Int32 nMappedFontObject;
6726         registerGlyph(pGlyph, pFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
6727 
6728         sal_Int32 nGlyphWidth = 0;
6729         if (m_pReferenceDevice->AcquireGraphics())
6730         {
6731             SalGraphics *pGraphics = m_pReferenceDevice->GetGraphics();
6732             if (pGraphics)
6733                 nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
6734                                                          pGlyph->maGlyphId,
6735                                                          pGlyph->IsVertical(),
6736                                                          pGraphics);
6737         }
6738 
6739         int nCharPos = -1;
6740         if (bUseActualText || pGlyph->IsInCluster())
6741             nCharPos = pGlyph->mnCharPos;
6742 
6743         aGlyphs.emplace_back(aPos,
6744                              pGlyph,
6745                              nGlyphWidth,
6746                              nMappedFontObject,
6747                              nMappedGlyph,
6748                              nCharPos);
6749     }
6750 
6751     // Avoid fill color when map mode is in pixels, the below code assumes
6752     // logic map mode.
6753     bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6754     if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6755     {
6756         // PDF doesn't have a text fill color, so draw a rectangle before
6757         // drawing the actual text.
6758         push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6759         setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6760         // Avoid border around the rectangle for Writer shape text.
6761         setLineColor(COL_TRANSPARENT);
6762 
6763         // The rectangle is the bounding box of the text, but also includes
6764         // ascent / descent to match the on-screen rendering.
6765         tools::Rectangle aRectangle;
6766         // This is the top left of the text without ascent / descent.
6767         aRectangle.SetPos(m_pReferenceDevice->PixelToLogic(rLayout.GetDrawPosition()));
6768         aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
6769         aRectangle.SetSize(m_pReferenceDevice->PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
6770         // This includes ascent / descent.
6771         aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6772 
6773         LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance.get();
6774         if (pFontInstance->mnOrientation)
6775         {
6776             // Adapt rectangle for rotated text.
6777             tools::Polygon aPolygon(aRectangle);
6778             aPolygon.Rotate(m_pReferenceDevice->PixelToLogic(rLayout.GetDrawPosition()), pFontInstance->mnOrientation);
6779             drawPolygon(aPolygon);
6780         }
6781         else
6782             drawRectangle(aRectangle);
6783 
6784         pop();
6785     }
6786 
6787     Point aAlignOffset;
6788     if ( eAlign == ALIGN_BOTTOM )
6789         aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6790     else if ( eAlign == ALIGN_TOP )
6791         aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6792     if( aAlignOffset.X() || aAlignOffset.Y() )
6793         aAlignOffset = aRotScale.transform( aAlignOffset );
6794 
6795     /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6796        string contained only on of the UTF16 BOMs
6797     */
6798     if( ! aGlyphs.empty() )
6799     {
6800         size_t nStart = 0;
6801         size_t nEnd = 0;
6802         while (nStart < aGlyphs.size())
6803         {
6804             while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6805                 nEnd++;
6806 
6807             std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6808 
6809             int nCharPos, nCharCount;
6810             if (!aRun.front().m_pGlyph->IsRTLGlyph())
6811             {
6812                 nCharPos = aRun.front().m_nCharPos;
6813                 nCharCount = aRun.front().m_pGlyph->mnCharCount;
6814             }
6815             else
6816             {
6817                 nCharPos = aRun.back().m_nCharPos;
6818                 nCharCount = aRun.back().m_pGlyph->mnCharCount;
6819             }
6820 
6821             if (nCharPos >= 0 && nCharCount)
6822             {
6823                 aLine.append("/Span<</ActualText<FEFF");
6824                 for (int i = 0; i < nCharCount; i++)
6825                 {
6826                     sal_Unicode aChar = rText[nCharPos + i];
6827                     appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6828                     appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6829                 }
6830                 aLine.append( ">>>\nBDC\n" );
6831             }
6832 
6833             if (bVertical)
6834                 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight);
6835             else
6836                 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight);
6837 
6838             if (nCharPos >= 0 && nCharCount)
6839                 aLine.append( "EMC\n" );
6840 
6841             nStart = nEnd;
6842         }
6843     }
6844 
6845     // end textobject
6846     aLine.append( "ET\n" );
6847     if( bPop )
6848         aLine.append( "Q\n" );
6849 
6850     writeBuffer( aLine.getStr(), aLine.getLength() );
6851 
6852     // draw eventual textlines
6853     FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6854     FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6855     FontLineStyle eOverline  = m_aCurrentPDFState.m_aFont.GetOverline();
6856     if( bTextLines &&
6857         (
6858          ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6859          ( eOverline  != LINESTYLE_NONE && eOverline  != LINESTYLE_DONTKNOW ) ||
6860          ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6861          )
6862         )
6863     {
6864         bool bUnderlineAbove = OutputDevice::ImplIsUnderlineAbove( m_aCurrentPDFState.m_aFont );
6865         if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6866         {
6867             Point aStartPt;
6868             sal_Int32 nWidth = 0;
6869             nIndex = 0;
6870             while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6871             {
6872                 if (!pGlyph->IsSpacing())
6873                 {
6874                     if( !nWidth )
6875                         aStartPt = aPos;
6876 
6877                     nWidth += pGlyph->mnNewWidth;
6878                 }
6879                 else if( nWidth > 0 )
6880                 {
6881                     drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
6882                                   m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
6883                                   eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6884                     nWidth = 0;
6885                 }
6886             }
6887 
6888             if( nWidth > 0 )
6889             {
6890                 drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
6891                               m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
6892                               eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6893             }
6894         }
6895         else
6896         {
6897             Point aStartPt = rLayout.GetDrawPosition();
6898             int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
6899             drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
6900                           m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
6901                           eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6902         }
6903     }
6904 
6905     // write eventual emphasis marks
6906     if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
6907         return;
6908 
6909     tools::PolyPolygon             aEmphPoly;
6910     tools::Rectangle               aEmphRect1;
6911     tools::Rectangle               aEmphRect2;
6912     long                    nEmphYOff;
6913     long                    nEmphWidth;
6914     long                    nEmphHeight;
6915     bool                    bEmphPolyLine;
6916     FontEmphasisMark        nEmphMark;
6917 
6918     push( PushFlags::ALL );
6919 
6920     aLine.setLength( 0 );
6921     aLine.append( "q\n" );
6922 
6923     nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
6924     if ( nEmphMark & FontEmphasisMark::PosBelow )
6925         nEmphHeight = m_pReferenceDevice->mnEmphasisDescent;
6926     else
6927         nEmphHeight = m_pReferenceDevice->mnEmphasisAscent;
6928     m_pReferenceDevice->ImplGetEmphasisMark( aEmphPoly,
6929                                              bEmphPolyLine,
6930                                              aEmphRect1,
6931                                              aEmphRect2,
6932                                              nEmphYOff,
6933                                              nEmphWidth,
6934                                              nEmphMark,
6935                                              m_pReferenceDevice->ImplDevicePixelToLogicWidth(nEmphHeight) );
6936     if ( bEmphPolyLine )
6937     {
6938         setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
6939         setFillColor( COL_TRANSPARENT );
6940     }
6941     else
6942     {
6943         setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
6944         setLineColor( COL_TRANSPARENT );
6945     }
6946     writeBuffer( aLine.getStr(), aLine.getLength() );
6947 
6948     Point aOffset = Point(0,0);
6949 
6950     if ( nEmphMark & FontEmphasisMark::PosBelow )
6951         aOffset.AdjustY(m_pReferenceDevice->mpFontInstance->mxFontMetric->GetDescent() + nEmphYOff );
6952     else
6953         aOffset.AdjustY( -(m_pReferenceDevice->mpFontInstance->mxFontMetric->GetAscent() + nEmphYOff) );
6954 
6955     long nEmphWidth2     = nEmphWidth / 2;
6956     long nEmphHeight2    = nEmphHeight / 2;
6957     aOffset += Point( nEmphWidth2, nEmphHeight2 );
6958 
6959     if ( eAlign == ALIGN_BOTTOM )
6960         aOffset.AdjustY( -(m_pReferenceDevice->mpFontInstance->mxFontMetric->GetDescent()) );
6961     else if ( eAlign == ALIGN_TOP )
6962         aOffset.AdjustY(m_pReferenceDevice->mpFontInstance->mxFontMetric->GetAscent() );
6963 
6964     nIndex = 0;
6965     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6966     {
6967         if (pGlyph->IsSpacing())
6968         {
6969             Point aAdjOffset = aOffset;
6970             aAdjOffset.AdjustX((pGlyph->mnNewWidth - nEmphWidth) / 2 );
6971             aAdjOffset = aRotScale.transform( aAdjOffset );
6972 
6973             aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
6974 
6975             aPos += aAdjOffset;
6976             aPos = m_pReferenceDevice->PixelToLogic( aPos );
6977             drawEmphasisMark( aPos.X(), aPos.Y(),
6978                               aEmphPoly, bEmphPolyLine,
6979                               aEmphRect1, aEmphRect2 );
6980         }
6981     }
6982 
6983     writeBuffer( "Q\n", 2 );
6984     pop();
6985 
6986 }
6987 
6988 void PDFWriterImpl::drawEmphasisMark( long nX, long nY,
6989                                       const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
6990                                       const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
6991 {
6992     // TODO: pass nWidth as width of this mark
6993     // long nWidth = 0;
6994 
6995     if ( rPolyPoly.Count() )
6996     {
6997         if ( bPolyLine )
6998         {
6999             tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
7000             aPoly.Move( nX, nY );
7001             drawPolyLine( aPoly );
7002         }
7003         else
7004         {
7005             tools::PolyPolygon aPolyPoly = rPolyPoly;
7006             aPolyPoly.Move( nX, nY );
7007             drawPolyPolygon( aPolyPoly );
7008         }
7009     }
7010 
7011     if ( !rRect1.IsEmpty() )
7012     {
7013         tools::Rectangle aRect( Point( nX+rRect1.Left(),
7014                                 nY+rRect1.Top() ), rRect1.GetSize() );
7015         drawRectangle( aRect );
7016     }
7017 
7018     if ( !rRect2.IsEmpty() )
7019     {
7020         tools::Rectangle aRect( Point( nX+rRect2.Left(),
7021                                 nY+rRect2.Top() ), rRect2.GetSize() );
7022 
7023         drawRectangle( aRect );
7024     }
7025 }
7026 
7027 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
7028 {
7029     MARK( "drawText" );
7030 
7031     updateGraphicsState();
7032 
7033     // get a layout from the OutputDevice's SalGraphics
7034     // this also enforces font substitution and sets the font on SalGraphics
7035     std::unique_ptr<SalLayout> pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos );
7036     if( pLayout )
7037     {
7038         drawLayout( *pLayout, rText, bTextLines );
7039     }
7040 }
7041 
7042 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
7043 {
7044     MARK( "drawText with array" );
7045 
7046     updateGraphicsState();
7047 
7048     // get a layout from the OutputDevice's SalGraphics
7049     // this also enforces font substitution and sets the font on SalGraphics
7050     std::unique_ptr<SalLayout> pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
7051     if( pLayout )
7052     {
7053         drawLayout( *pLayout, rText, true );
7054     }
7055 }
7056 
7057 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
7058 {
7059     MARK( "drawStretchText" );
7060 
7061     updateGraphicsState();
7062 
7063     // get a layout from the OutputDevice's SalGraphics
7064     // this also enforces font substitution and sets the font on SalGraphics
7065     std::unique_ptr<SalLayout> pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, nWidth );
7066     if( pLayout )
7067     {
7068         drawLayout( *pLayout, rText, true );
7069     }
7070 }
7071 
7072 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
7073 {
7074     long        nWidth          = rRect.GetWidth();
7075     long        nHeight         = rRect.GetHeight();
7076 
7077     if ( nWidth <= 0 || nHeight <= 0 )
7078         return;
7079 
7080     MARK( "drawText with rectangle" );
7081 
7082     updateGraphicsState();
7083 
7084     // clip with rectangle
7085     OStringBuffer aLine;
7086     aLine.append( "q " );
7087     m_aPages.back().appendRect( rRect, aLine );
7088     aLine.append( " W* n\n" );
7089     writeBuffer( aLine.getStr(), aLine.getLength() );
7090 
7091     // if disabled text is needed, put in here
7092 
7093     Point       aPos            = rRect.TopLeft();
7094 
7095     long        nTextHeight     = m_pReferenceDevice->GetTextHeight();
7096     sal_Int32   nMnemonicPos    = -1;
7097 
7098     OUString aStr = rOrigStr;
7099     if ( nStyle & DrawTextFlags::Mnemonic )
7100         aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
7101 
7102     // multiline text
7103     if ( nStyle & DrawTextFlags::MultiLine )
7104     {
7105         OUString           aLastLine;
7106         ImplMultiTextLineInfo   aMultiLineInfo;
7107         ImplTextLineInfo*       pLineInfo;
7108         sal_Int32               i;
7109         sal_Int32               nLines;
7110         sal_Int32               nFormatLines;
7111 
7112         if ( nTextHeight )
7113         {
7114             vcl::DefaultTextLayout aLayout( *m_pReferenceDevice );
7115             OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
7116             nLines = nHeight/nTextHeight;
7117             nFormatLines = aMultiLineInfo.Count();
7118             if ( !nLines )
7119                 nLines = 1;
7120             if ( nFormatLines > nLines )
7121             {
7122                 if ( nStyle & DrawTextFlags::EndEllipsis )
7123                 {
7124                     // handle last line
7125                     nFormatLines = nLines-1;
7126 
7127                     pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
7128                     aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF);
7129                     // replace line feed by space
7130                     aLastLine = aLastLine.replace('\n', ' ');
7131                     aLastLine = m_pReferenceDevice->GetEllipsisString( aLastLine, nWidth, nStyle );
7132                     nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
7133                     nStyle |= DrawTextFlags::Top;
7134                 }
7135             }
7136 
7137             // vertical alignment
7138             if ( nStyle & DrawTextFlags::Bottom )
7139                 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
7140             else if ( nStyle & DrawTextFlags::VCenter )
7141                 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
7142 
7143             // draw all lines excluding the last
7144             for ( i = 0; i < nFormatLines; i++ )
7145             {
7146                 pLineInfo = aMultiLineInfo.GetLine( i );
7147                 if ( nStyle & DrawTextFlags::Right )
7148                     aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
7149                 else if ( nStyle & DrawTextFlags::Center )
7150                     aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
7151                 sal_Int32 nIndex = pLineInfo->GetIndex();
7152                 sal_Int32 nLineLen = pLineInfo->GetLen();
7153                 drawText( aPos, aStr, nIndex, nLineLen );
7154                 // mnemonics should not appear in documents,
7155                 // if the need arises, put them in here
7156                 aPos.AdjustY(nTextHeight );
7157                 aPos.setX( rRect.Left() );
7158             }
7159 
7160             // output last line left adjusted since it was shortened
7161             if (!aLastLine.isEmpty())
7162                 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
7163         }
7164     }
7165     else
7166     {
7167         long nTextWidth = m_pReferenceDevice->GetTextWidth( aStr );
7168 
7169         // Evt. Text kuerzen
7170         if ( nTextWidth > nWidth )
7171         {
7172             if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
7173             {
7174                 aStr = m_pReferenceDevice->GetEllipsisString( aStr, nWidth, nStyle );
7175                 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
7176                 nStyle |= DrawTextFlags::Left;
7177                 nTextWidth = m_pReferenceDevice->GetTextWidth( aStr );
7178             }
7179         }
7180 
7181         // vertical alignment
7182         if ( nStyle & DrawTextFlags::Right )
7183             aPos.AdjustX(nWidth-nTextWidth );
7184         else if ( nStyle & DrawTextFlags::Center )
7185             aPos.AdjustX((nWidth-nTextWidth)/2 );
7186 
7187         if ( nStyle & DrawTextFlags::Bottom )
7188             aPos.AdjustY(nHeight-nTextHeight );
7189         else if ( nStyle & DrawTextFlags::VCenter )
7190             aPos.AdjustY((nHeight-nTextHeight)/2 );
7191 
7192         // mnemonics should be inserted here if the need arises
7193 
7194         // draw the actual text
7195         drawText( aPos, aStr, 0, aStr.getLength() );
7196     }
7197 
7198     // reset clip region to original value
7199     aLine.setLength( 0 );
7200     aLine.append( "Q\n" );
7201     writeBuffer( aLine.getStr(), aLine.getLength() );
7202 }
7203 
7204 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
7205 {
7206     MARK( "drawLine" );
7207 
7208     updateGraphicsState();
7209 
7210     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7211         return;
7212 
7213     OStringBuffer aLine;
7214     m_aPages.back().appendPoint( rStart, aLine );
7215     aLine.append( " m " );
7216     m_aPages.back().appendPoint( rStop, aLine );
7217     aLine.append( " l S\n" );
7218 
7219     writeBuffer( aLine.getStr(), aLine.getLength() );
7220 }
7221 
7222 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
7223 {
7224     MARK( "drawLine with LineInfo" );
7225     updateGraphicsState();
7226 
7227     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7228         return;
7229 
7230     if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
7231     {
7232         drawLine( rStart, rStop );
7233         return;
7234     }
7235 
7236     OStringBuffer aLine;
7237 
7238     aLine.append( "q " );
7239     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7240     {
7241         m_aPages.back().appendPoint( rStart, aLine );
7242         aLine.append( " m " );
7243         m_aPages.back().appendPoint( rStop, aLine );
7244         aLine.append( " l S Q\n" );
7245 
7246         writeBuffer( aLine.getStr(), aLine.getLength() );
7247     }
7248     else
7249     {
7250         PDFWriter::ExtLineInfo aInfo;
7251         convertLineInfoToExtLineInfo( rInfo, aInfo );
7252         Point aPolyPoints[2] = { rStart, rStop };
7253         tools::Polygon aPoly( 2, aPolyPoints );
7254         drawPolyLine( aPoly, aInfo );
7255     }
7256 }
7257 
7258 #define HCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicHeight( x )
7259 
7260 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7261 {
7262     // note: units in pFontInstance are ref device pixel
7263     LogicalFontInstance*  pFontInstance = m_pReferenceDevice->mpFontInstance.get();
7264     long            nLineHeight = 0;
7265     long            nLinePos = 0;
7266 
7267     appendStrokingColor( aColor, aLine );
7268     aLine.append( "\n" );
7269 
7270     if ( bIsAbove )
7271     {
7272         if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
7273             m_pReferenceDevice->ImplInitAboveTextLineSize();
7274         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
7275         nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
7276     }
7277     else
7278     {
7279         if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
7280             m_pReferenceDevice->ImplInitTextLineSize();
7281         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
7282         nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
7283     }
7284     if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
7285         nLineHeight = 3;
7286 
7287     long nLineWidth = getReferenceDevice()->mnDPIX/450;
7288     if ( ! nLineWidth )
7289         nLineWidth = 1;
7290 
7291     if ( eTextLine == LINESTYLE_BOLDWAVE )
7292         nLineWidth = 3*nLineWidth;
7293 
7294     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
7295     aLine.append( " w " );
7296 
7297     if ( eTextLine == LINESTYLE_DOUBLEWAVE )
7298     {
7299         long nOrgLineHeight = nLineHeight;
7300         nLineHeight /= 3;
7301         if ( nLineHeight < 2 )
7302         {
7303             if ( nOrgLineHeight > 1 )
7304                 nLineHeight = 2;
7305             else
7306                 nLineHeight = 1;
7307         }
7308         long nLineDY = nOrgLineHeight-(nLineHeight*2);
7309         if ( nLineDY < nLineWidth )
7310             nLineDY = nLineWidth;
7311         long nLineDY2 = nLineDY/2;
7312         if ( !nLineDY2 )
7313             nLineDY2 = 1;
7314 
7315         nLinePos -= nLineWidth-nLineDY2;
7316 
7317         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7318 
7319         nLinePos += nLineWidth+nLineDY;
7320         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7321     }
7322     else
7323     {
7324         if ( eTextLine != LINESTYLE_BOLDWAVE )
7325             nLinePos -= nLineWidth/2;
7326         m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
7327     }
7328 }
7329 
7330 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7331 {
7332     // note: units in pFontInstance are ref device pixel
7333     LogicalFontInstance*  pFontInstance = m_pReferenceDevice->mpFontInstance.get();
7334     long            nLineHeight = 0;
7335     long            nLinePos  = 0;
7336     long            nLinePos2 = 0;
7337 
7338     if ( eTextLine > LINESTYLE_BOLDWAVE )
7339         eTextLine = LINESTYLE_SINGLE;
7340 
7341     switch ( eTextLine )
7342     {
7343         case LINESTYLE_SINGLE:
7344         case LINESTYLE_DOTTED:
7345         case LINESTYLE_DASH:
7346         case LINESTYLE_LONGDASH:
7347         case LINESTYLE_DASHDOT:
7348         case LINESTYLE_DASHDOTDOT:
7349             if ( bIsAbove )
7350             {
7351                 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
7352                     m_pReferenceDevice->ImplInitAboveTextLineSize();
7353                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
7354                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
7355             }
7356             else
7357             {
7358                 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
7359                     m_pReferenceDevice->ImplInitTextLineSize();
7360                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
7361                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
7362             }
7363             break;
7364         case LINESTYLE_BOLD:
7365         case LINESTYLE_BOLDDOTTED:
7366         case LINESTYLE_BOLDDASH:
7367         case LINESTYLE_BOLDLONGDASH:
7368         case LINESTYLE_BOLDDASHDOT:
7369         case LINESTYLE_BOLDDASHDOTDOT:
7370             if ( bIsAbove )
7371             {
7372                 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
7373                     m_pReferenceDevice->ImplInitAboveTextLineSize();
7374                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
7375                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
7376             }
7377             else
7378             {
7379                 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
7380                     m_pReferenceDevice->ImplInitTextLineSize();
7381                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
7382                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
7383                 nLinePos += nLineHeight/2;
7384             }
7385             break;
7386         case LINESTYLE_DOUBLE:
7387             if ( bIsAbove )
7388             {
7389                 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
7390                     m_pReferenceDevice->ImplInitAboveTextLineSize();
7391                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
7392                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
7393                 nLinePos2   = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
7394             }
7395             else
7396             {
7397                 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
7398                     m_pReferenceDevice->ImplInitTextLineSize();
7399                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
7400                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
7401                 nLinePos2   = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
7402             }
7403             break;
7404         default:
7405             break;
7406     }
7407 
7408     if ( !nLineHeight )
7409         return;
7410 
7411     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7412     aLine.append( " w " );
7413     appendStrokingColor( aColor, aLine );
7414     aLine.append( "\n" );
7415 
7416     switch ( eTextLine )
7417     {
7418         case LINESTYLE_DOTTED:
7419         case LINESTYLE_BOLDDOTTED:
7420             aLine.append( "[ " );
7421             m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7422             aLine.append( " ] 0 d\n" );
7423             break;
7424         case LINESTYLE_DASH:
7425         case LINESTYLE_LONGDASH:
7426         case LINESTYLE_BOLDDASH:
7427         case LINESTYLE_BOLDLONGDASH:
7428             {
7429                 sal_Int32 nDashLength = 4*nLineHeight;
7430                 sal_Int32 nVoidLength = 2*nLineHeight;
7431                 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7432                     nDashLength = 8*nLineHeight;
7433 
7434                 aLine.append( "[ " );
7435                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7436                 aLine.append( ' ' );
7437                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7438                 aLine.append( " ] 0 d\n" );
7439             }
7440             break;
7441         case LINESTYLE_DASHDOT:
7442         case LINESTYLE_BOLDDASHDOT:
7443             {
7444                 sal_Int32 nDashLength = 4*nLineHeight;
7445                 sal_Int32 nVoidLength = 2*nLineHeight;
7446                 aLine.append( "[ " );
7447                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7448                 aLine.append( ' ' );
7449                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7450                 aLine.append( ' ' );
7451                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7452                 aLine.append( ' ' );
7453                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7454                 aLine.append( " ] 0 d\n" );
7455             }
7456             break;
7457         case LINESTYLE_DASHDOTDOT:
7458         case LINESTYLE_BOLDDASHDOTDOT:
7459             {
7460                 sal_Int32 nDashLength = 4*nLineHeight;
7461                 sal_Int32 nVoidLength = 2*nLineHeight;
7462                 aLine.append( "[ " );
7463                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7464                 aLine.append( ' ' );
7465                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7466                 aLine.append( ' ' );
7467                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7468                 aLine.append( ' ' );
7469                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7470                 aLine.append( ' ' );
7471                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7472                 aLine.append( ' ' );
7473                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7474                 aLine.append( " ] 0 d\n" );
7475             }
7476             break;
7477         default:
7478             break;
7479     }
7480 
7481     aLine.append( "0 " );
7482     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7483     aLine.append( " m " );
7484     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7485     aLine.append( ' ' );
7486     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7487     aLine.append( " l S\n" );
7488     if ( eTextLine == LINESTYLE_DOUBLE )
7489     {
7490         aLine.append( "0 " );
7491         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7492         aLine.append( " m " );
7493         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7494         aLine.append( ' ' );
7495         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7496         aLine.append( " l S\n" );
7497     }
7498 
7499 }
7500 
7501 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor )
7502 {
7503     // note: units in pFontInstance are ref device pixel
7504     LogicalFontInstance*  pFontInstance = m_pReferenceDevice->mpFontInstance.get();
7505     long            nLineHeight = 0;
7506     long            nLinePos  = 0;
7507     long            nLinePos2 = 0;
7508 
7509     if ( eStrikeout > STRIKEOUT_X )
7510         eStrikeout = STRIKEOUT_SINGLE;
7511 
7512     switch ( eStrikeout )
7513     {
7514         case STRIKEOUT_SINGLE:
7515             if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7516                 m_pReferenceDevice->ImplInitTextLineSize();
7517             nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
7518             nLinePos    = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
7519             break;
7520         case STRIKEOUT_BOLD:
7521             if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7522                 m_pReferenceDevice->ImplInitTextLineSize();
7523             nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
7524             nLinePos    = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
7525             break;
7526         case STRIKEOUT_DOUBLE:
7527             if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7528                 m_pReferenceDevice->ImplInitTextLineSize();
7529             nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
7530             nLinePos    = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
7531             nLinePos2   = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
7532             break;
7533         default:
7534             break;
7535     }
7536 
7537     if ( !nLineHeight )
7538         return;
7539 
7540     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7541     aLine.append( " w " );
7542     appendStrokingColor( aColor, aLine );
7543     aLine.append( "\n" );
7544 
7545     aLine.append( "0 " );
7546     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7547     aLine.append( " m " );
7548     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7549     aLine.append( ' ' );
7550     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7551     aLine.append( " l S\n" );
7552 
7553     if ( eStrikeout == STRIKEOUT_DOUBLE )
7554     {
7555         aLine.append( "0 " );
7556         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7557         aLine.append( " m " );
7558         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7559         aLine.append( ' ' );
7560         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7561         aLine.append( " l S\n" );
7562     }
7563 
7564 }
7565 
7566 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout )
7567 {
7568     //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7569     //to tweak this
7570 
7571     OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
7572     OUString aStrikeout = aStrikeoutChar;
7573     while( m_pReferenceDevice->GetTextWidth( aStrikeout ) < nWidth )
7574         aStrikeout += aStrikeout;
7575 
7576     // do not get broader than nWidth modulo 1 character
7577     while( m_pReferenceDevice->GetTextWidth( aStrikeout ) >= nWidth )
7578         aStrikeout = aStrikeout.replaceAt( 0, 1, "" );
7579     aStrikeout += aStrikeoutChar;
7580     bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7581     if ( bShadow )
7582     {
7583         Font aFont = m_aCurrentPDFState.m_aFont;
7584         aFont.SetShadow( false );
7585         setFont( aFont );
7586         updateGraphicsState();
7587     }
7588 
7589     // strikeout string is left aligned non-CTL text
7590     ComplexTextLayoutFlags nOrigTLM = m_pReferenceDevice->GetLayoutMode();
7591     m_pReferenceDevice->SetLayoutMode(ComplexTextLayoutFlags::BiDiStrong);
7592 
7593     push( PushFlags::CLIPREGION );
7594     FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric();
7595     tools::Rectangle aRect;
7596     aRect.SetLeft( rPos.X() );
7597     aRect.SetRight( aRect.Left()+nWidth );
7598     aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7599     aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7600 
7601     LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance.get();
7602     if (pFontInstance->mnOrientation)
7603     {
7604         tools::Polygon aPoly( aRect );
7605         aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7606         aRect = aPoly.GetBoundRect();
7607     }
7608 
7609     intersectClipRegion( aRect );
7610     drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7611     pop();
7612 
7613     m_pReferenceDevice->SetLayoutMode( nOrigTLM );
7614 
7615     if ( bShadow )
7616     {
7617         Font aFont = m_aCurrentPDFState.m_aFont;
7618         aFont.SetShadow( true );
7619         setFont( aFont );
7620         updateGraphicsState();
7621     }
7622 }
7623 
7624 void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7625 {
7626     if ( !nWidth ||
7627          ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7628            ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7629            ((eOverline  == LINESTYLE_NONE)||(eOverline  == LINESTYLE_DONTKNOW)) ) )
7630         return;
7631 
7632     MARK( "drawTextLine" );
7633     updateGraphicsState();
7634 
7635     // note: units in pFontInstance are ref device pixel
7636     LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance.get();
7637     Color           aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7638     Color           aOverlineColor  = m_aCurrentPDFState.m_aOverlineColor;
7639     Color           aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7640     bool            bStrikeoutDone = false;
7641     bool            bUnderlineDone = false;
7642     bool            bOverlineDone  = false;
7643 
7644     if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7645     {
7646         drawStrikeoutChar( rPos, nWidth, eStrikeout );
7647         bStrikeoutDone = true;
7648     }
7649 
7650     Point aPos( rPos );
7651     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7652     if( eAlign == ALIGN_TOP )
7653         aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7654     else if( eAlign == ALIGN_BOTTOM )
7655         aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7656 
7657     OStringBuffer aLine( 512 );
7658     // save GS
7659     aLine.append( "q " );
7660 
7661     // rotate and translate matrix
7662     double fAngle = static_cast<double>(m_aCurrentPDFState.m_aFont.GetOrientation()) * M_PI / 1800.0;
7663     Matrix3 aMat;
7664     aMat.rotate( fAngle );
7665     aMat.translate( aPos.X(), aPos.Y() );
7666     aMat.append( m_aPages.back(), aLine );
7667     aLine.append( " cm\n" );
7668 
7669     if ( aUnderlineColor.GetTransparency() != 0 )
7670         aUnderlineColor = aStrikeoutColor;
7671 
7672     if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7673          (eUnderline == LINESTYLE_WAVE) ||
7674          (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7675          (eUnderline == LINESTYLE_BOLDWAVE) )
7676     {
7677         drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7678         bUnderlineDone = true;
7679     }
7680 
7681     if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7682          (eOverline == LINESTYLE_WAVE) ||
7683          (eOverline == LINESTYLE_DOUBLEWAVE) ||
7684          (eOverline == LINESTYLE_BOLDWAVE) )
7685     {
7686         drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7687         bOverlineDone = true;
7688     }
7689 
7690     if ( !bUnderlineDone )
7691     {
7692         drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7693     }
7694 
7695     if ( !bOverlineDone )
7696     {
7697         drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7698     }
7699 
7700     if ( !bStrikeoutDone )
7701     {
7702         drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7703     }
7704 
7705     aLine.append( "Q\n" );
7706     writeBuffer( aLine.getStr(), aLine.getLength() );
7707 }
7708 
7709 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7710 {
7711     MARK( "drawPolygon" );
7712 
7713     updateGraphicsState();
7714 
7715     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7716         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7717         return;
7718 
7719     int nPoints = rPoly.GetSize();
7720     OStringBuffer aLine( 20 * nPoints );
7721     m_aPages.back().appendPolygon( rPoly, aLine );
7722     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7723         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7724         aLine.append( "B*\n" );
7725     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7726         aLine.append( "S\n" );
7727     else
7728         aLine.append( "f*\n" );
7729 
7730     writeBuffer( aLine.getStr(), aLine.getLength() );
7731 }
7732 
7733 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7734 {
7735     MARK( "drawPolyPolygon" );
7736 
7737     updateGraphicsState();
7738 
7739     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7740         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7741         return;
7742 
7743     int nPolygons = rPolyPoly.Count();
7744 
7745     OStringBuffer aLine( 40 * nPolygons );
7746     m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7747     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7748         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7749         aLine.append( "B*\n" );
7750     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7751         aLine.append( "S\n" );
7752     else
7753         aLine.append( "f*\n" );
7754 
7755     writeBuffer( aLine.getStr(), aLine.getLength() );
7756 }
7757 
7758 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7759 {
7760     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7761     nTransparentPercent = nTransparentPercent % 100;
7762 
7763     MARK( "drawTransparent" );
7764 
7765     updateGraphicsState();
7766 
7767     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7768         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7769         return;
7770 
7771     if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7772     {
7773         m_aErrors.insert( m_bIsPDF_A1 ?
7774                           PDFWriter::Warning_Transparency_Omitted_PDFA :
7775                           PDFWriter::Warning_Transparency_Omitted_PDF13 );
7776 
7777         drawPolyPolygon( rPolyPoly );
7778         return;
7779     }
7780 
7781     // create XObject
7782     m_aTransparentObjects.emplace_back( );
7783     // FIXME: polygons with beziers may yield incorrect bound rect
7784     m_aTransparentObjects.back().m_aBoundRect     = rPolyPoly.GetBoundRect();
7785     // convert rectangle to default user space
7786     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7787     m_aTransparentObjects.back().m_nObject          = createObject();
7788     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7789     m_aTransparentObjects.back().m_fAlpha           = static_cast<double>(100-nTransparentPercent) / 100.0;
7790     m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7791     // create XObject's content stream
7792     OStringBuffer aContent( 256 );
7793     m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7794     if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7795         m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7796         aContent.append( " B*\n" );
7797     else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7798         aContent.append( " S\n" );
7799     else
7800         aContent.append( " f*\n" );
7801     m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7802         aContent.getStr(), aContent.getLength() );
7803 
7804     OStringBuffer aObjName( 16 );
7805     aObjName.append( "Tr" );
7806     aObjName.append( m_aTransparentObjects.back().m_nObject );
7807     OString aTrName( aObjName.makeStringAndClear() );
7808     aObjName.append( "EGS" );
7809     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7810     OString aExtName( aObjName.makeStringAndClear() );
7811 
7812     OStringBuffer aLine( 80 );
7813     // insert XObject
7814     aLine.append( "q /" );
7815     aLine.append( aExtName );
7816     aLine.append( " gs /" );
7817     aLine.append( aTrName );
7818     aLine.append( " Do Q\n" );
7819     writeBuffer( aLine.getStr(), aLine.getLength() );
7820 
7821     pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
7822     pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7823 }
7824 
7825 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
7826 {
7827     if( nObject >= 0 )
7828     {
7829         switch( eKind )
7830         {
7831             case ResXObject:
7832                 m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
7833                 if( ! m_aOutputStreams.empty() )
7834                     m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
7835                 break;
7836             case ResExtGState:
7837                 m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
7838                 if( ! m_aOutputStreams.empty() )
7839                     m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
7840                 break;
7841             case ResShading:
7842                 m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
7843                 if( ! m_aOutputStreams.empty() )
7844                     m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
7845                 break;
7846             case ResPattern:
7847                 m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
7848                 if( ! m_aOutputStreams.empty() )
7849                     m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
7850                 break;
7851         }
7852     }
7853 }
7854 
7855 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
7856 {
7857     push( PushFlags::ALL );
7858 
7859     // force reemitting clip region inside the new stream, and
7860     // prevent emitting an unbalanced "Q" at the start
7861     clearClipRegion();
7862     // this is needed to point m_aCurrentPDFState at the pushed state
7863     // ... but it's pointless to actually write into the "outer" stream here!
7864     updateGraphicsState(NOWRITE);
7865 
7866     m_aOutputStreams.push_front( StreamRedirect() );
7867     m_aOutputStreams.front().m_pStream = pStream;
7868     m_aOutputStreams.front().m_aMapMode = m_aMapMode;
7869 
7870     if( !rTargetRect.IsEmpty() )
7871     {
7872         m_aOutputStreams.front().m_aTargetRect =
7873             lcl_convert( m_aGraphicsStack.front().m_aMapMode,
7874                          m_aMapMode,
7875                          getReferenceDevice(),
7876                          rTargetRect );
7877         Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
7878         long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
7879         aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
7880         m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
7881     }
7882 
7883     // setup graphics state for independent object stream
7884 
7885     // force reemitting colors
7886     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7887     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7888 }
7889 
7890 SvStream* PDFWriterImpl::endRedirect()
7891 {
7892     SvStream* pStream = nullptr;
7893     if( ! m_aOutputStreams.empty() )
7894     {
7895         pStream     = m_aOutputStreams.front().m_pStream;
7896         m_aMapMode  = m_aOutputStreams.front().m_aMapMode;
7897         m_aOutputStreams.pop_front();
7898     }
7899 
7900     pop();
7901 
7902     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7903     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7904 
7905     // needed after pop() to set m_aCurrentPDFState
7906     updateGraphicsState(NOWRITE);
7907 
7908     return pStream;
7909 }
7910 
7911 void PDFWriterImpl::beginTransparencyGroup()
7912 {
7913     updateGraphicsState();
7914     if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
7915         beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
7916 }
7917 
7918 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
7919 {
7920     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7921     nTransparentPercent = nTransparentPercent % 100;
7922 
7923     if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7924         return;
7925 
7926     // create XObject
7927     m_aTransparentObjects.emplace_back( );
7928     m_aTransparentObjects.back().m_aBoundRect   = rBoundingBox;
7929     // convert rectangle to default user space
7930     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7931     m_aTransparentObjects.back().m_nObject      = createObject();
7932     m_aTransparentObjects.back().m_fAlpha       = static_cast<double>(100-nTransparentPercent) / 100.0;
7933     // get XObject's content stream
7934     m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
7935     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7936 
7937     OStringBuffer aObjName( 16 );
7938     aObjName.append( "Tr" );
7939     aObjName.append( m_aTransparentObjects.back().m_nObject );
7940     OString aTrName( aObjName.makeStringAndClear() );
7941     aObjName.append( "EGS" );
7942     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7943     OString aExtName( aObjName.makeStringAndClear() );
7944 
7945     OStringBuffer aLine( 80 );
7946     // insert XObject
7947     aLine.append( "q /" );
7948     aLine.append( aExtName );
7949     aLine.append( " gs /" );
7950     aLine.append( aTrName );
7951     aLine.append( " Do Q\n" );
7952     writeBuffer( aLine.getStr(), aLine.getLength() );
7953 
7954     pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
7955     pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7956 
7957 }
7958 
7959 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
7960 {
7961     MARK( "drawRectangle" );
7962 
7963     updateGraphicsState();
7964 
7965     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7966         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7967         return;
7968 
7969     OStringBuffer aLine( 40 );
7970     m_aPages.back().appendRect( rRect, aLine );
7971 
7972     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7973         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7974         aLine.append( " B*\n" );
7975     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7976         aLine.append( " S\n" );
7977     else
7978         aLine.append( " f*\n" );
7979 
7980     writeBuffer( aLine.getStr(), aLine.getLength() );
7981 }
7982 
7983 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
7984 {
7985     MARK( "drawRectangle with rounded edges" );
7986 
7987     if( !nHorzRound && !nVertRound )
7988         drawRectangle( rRect );
7989 
7990     updateGraphicsState();
7991 
7992     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7993         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7994         return;
7995 
7996     if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
7997         nHorzRound = rRect.GetWidth()/2;
7998     if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
7999         nVertRound = rRect.GetHeight()/2;
8000 
8001     Point aPoints[16];
8002     const double kappa = 0.5522847498;
8003     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
8004     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
8005 
8006     aPoints[1]  = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
8007     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8008     aPoints[2]  = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
8009     aPoints[3]  = Point( aPoints[2].X()+kx, aPoints[2].Y() );
8010 
8011     aPoints[5]  = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
8012     aPoints[4]  = Point( aPoints[5].X(), aPoints[5].Y()-ky );
8013     aPoints[6]  = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
8014     aPoints[7]  = Point( aPoints[6].X(), aPoints[6].Y()+ky );
8015 
8016     aPoints[9]  = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
8017     aPoints[8]  = Point( aPoints[9].X()+kx, aPoints[9].Y() );
8018     aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
8019     aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
8020 
8021     aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
8022     aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
8023     aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
8024     aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
8025 
8026     OStringBuffer aLine( 80 );
8027     m_aPages.back().appendPoint( aPoints[1], aLine );
8028     aLine.append( " m " );
8029     m_aPages.back().appendPoint( aPoints[2], aLine );
8030     aLine.append( " l " );
8031     m_aPages.back().appendPoint( aPoints[3], aLine );
8032     aLine.append( ' ' );
8033     m_aPages.back().appendPoint( aPoints[4], aLine );
8034     aLine.append( ' ' );
8035     m_aPages.back().appendPoint( aPoints[5], aLine );
8036     aLine.append( " c\n" );
8037     m_aPages.back().appendPoint( aPoints[6], aLine );
8038     aLine.append( " l " );
8039     m_aPages.back().appendPoint( aPoints[7], aLine );
8040     aLine.append( ' ' );
8041     m_aPages.back().appendPoint( aPoints[8], aLine );
8042     aLine.append( ' ' );
8043     m_aPages.back().appendPoint( aPoints[9], aLine );
8044     aLine.append( " c\n" );
8045     m_aPages.back().appendPoint( aPoints[10], aLine );
8046     aLine.append( " l " );
8047     m_aPages.back().appendPoint( aPoints[11], aLine );
8048     aLine.append( ' ' );
8049     m_aPages.back().appendPoint( aPoints[12], aLine );
8050     aLine.append( ' ' );
8051     m_aPages.back().appendPoint( aPoints[13], aLine );
8052     aLine.append( " c\n" );
8053     m_aPages.back().appendPoint( aPoints[14], aLine );
8054     aLine.append( " l " );
8055     m_aPages.back().appendPoint( aPoints[15], aLine );
8056     aLine.append( ' ' );
8057     m_aPages.back().appendPoint( aPoints[0], aLine );
8058     aLine.append( ' ' );
8059     m_aPages.back().appendPoint( aPoints[1], aLine );
8060     aLine.append( " c " );
8061 
8062     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8063         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8064         aLine.append( "b*\n" );
8065     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8066         aLine.append( "s\n" );
8067     else
8068         aLine.append( "f*\n" );
8069 
8070     writeBuffer( aLine.getStr(), aLine.getLength() );
8071 }
8072 
8073 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
8074 {
8075     MARK( "drawEllipse" );
8076 
8077     updateGraphicsState();
8078 
8079     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8080         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8081         return;
8082 
8083     Point aPoints[12];
8084     const double kappa = 0.5522847498;
8085     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
8086     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
8087 
8088     aPoints[1]  = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
8089     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8090     aPoints[2]  = Point( aPoints[1].X() + kx, aPoints[1].Y() );
8091 
8092     aPoints[4]  = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 );
8093     aPoints[3]  = Point( aPoints[4].X(), aPoints[4].Y() - ky );
8094     aPoints[5]  = Point( aPoints[4].X(), aPoints[4].Y() + ky );
8095 
8096     aPoints[7]  = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 );
8097     aPoints[6]  = Point( aPoints[7].X() + kx, aPoints[7].Y() );
8098     aPoints[8]  = Point( aPoints[7].X() - kx, aPoints[7].Y() );
8099 
8100     aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
8101     aPoints[9]  = Point( aPoints[10].X(), aPoints[10].Y() + ky );
8102     aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
8103 
8104     OStringBuffer aLine( 80 );
8105     m_aPages.back().appendPoint( aPoints[1], aLine );
8106     aLine.append( " m " );
8107     m_aPages.back().appendPoint( aPoints[2], aLine );
8108     aLine.append( ' ' );
8109     m_aPages.back().appendPoint( aPoints[3], aLine );
8110     aLine.append( ' ' );
8111     m_aPages.back().appendPoint( aPoints[4], aLine );
8112     aLine.append( " c\n" );
8113     m_aPages.back().appendPoint( aPoints[5], aLine );
8114     aLine.append( ' ' );
8115     m_aPages.back().appendPoint( aPoints[6], aLine );
8116     aLine.append( ' ' );
8117     m_aPages.back().appendPoint( aPoints[7], aLine );
8118     aLine.append( " c\n" );
8119     m_aPages.back().appendPoint( aPoints[8], aLine );
8120     aLine.append( ' ' );
8121     m_aPages.back().appendPoint( aPoints[9], aLine );
8122     aLine.append( ' ' );
8123     m_aPages.back().appendPoint( aPoints[10], aLine );
8124     aLine.append( " c\n" );
8125     m_aPages.back().appendPoint( aPoints[11], aLine );
8126     aLine.append( ' ' );
8127     m_aPages.back().appendPoint( aPoints[0], aLine );
8128     aLine.append( ' ' );
8129     m_aPages.back().appendPoint( aPoints[1], aLine );
8130     aLine.append( " c " );
8131 
8132     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8133         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8134         aLine.append( "b*\n" );
8135     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8136         aLine.append( "s\n" );
8137     else
8138         aLine.append( "f*\n" );
8139 
8140     writeBuffer( aLine.getStr(), aLine.getLength() );
8141 }
8142 
8143 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
8144 {
8145     Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
8146                   (rRect.Top()+rRect.Bottom()+1)/2);
8147     Point aPoint = rPoint - aOrigin;
8148 
8149     double fX = static_cast<double>(aPoint.X());
8150     double fY = static_cast<double>(-aPoint.Y());
8151 
8152     if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
8153         throw o3tl::divide_by_zero();
8154 
8155     if( rRect.GetWidth() > rRect.GetHeight() )
8156         fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
8157     else if( rRect.GetHeight() > rRect.GetWidth() )
8158         fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
8159     return atan2( fY, fX );
8160 }
8161 
8162 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
8163 {
8164     MARK( "drawArc" );
8165 
8166     updateGraphicsState();
8167 
8168     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8169         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8170         return;
8171 
8172     // calculate start and stop angles
8173     const double fStartAngle = calcAngle( rRect, rStart );
8174     double fStopAngle  = calcAngle( rRect, rStop );
8175     while( fStopAngle < fStartAngle )
8176         fStopAngle += 2.0*M_PI;
8177     const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
8178     const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
8179     const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
8180     const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
8181     const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
8182 
8183     const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
8184                          (rRect.Top()+rRect.Bottom()+1)/2 );
8185 
8186     OStringBuffer aLine( 30*nFragments );
8187     Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
8188                   -static_cast<int>(halfHeight * sin(fStartAngle) ) );
8189     aPoint += aCenter;
8190     m_aPages.back().appendPoint( aPoint, aLine );
8191     aLine.append( " m " );
8192     if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
8193     {
8194         for( int i = 0; i < nFragments; i++ )
8195         {
8196             const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
8197             const double fStopFragment = fStartFragment + fFragmentDelta;
8198             aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
8199                             -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
8200             aPoint += aCenter;
8201             m_aPages.back().appendPoint( aPoint, aLine );
8202             aLine.append( ' ' );
8203 
8204             aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
8205                             -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
8206             aPoint += aCenter;
8207             m_aPages.back().appendPoint( aPoint, aLine );
8208             aLine.append( ' ' );
8209 
8210             aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
8211                             -static_cast<int>(halfHeight * sin(fStopFragment) ) );
8212             aPoint += aCenter;
8213             m_aPages.back().appendPoint( aPoint, aLine );
8214             aLine.append( " c\n" );
8215         }
8216     }
8217     if( bWithChord || bWithPie )
8218     {
8219         if( bWithPie )
8220         {
8221             m_aPages.back().appendPoint( aCenter, aLine );
8222             aLine.append( " l " );
8223         }
8224         aLine.append( "h " );
8225     }
8226     if( ! bWithChord && ! bWithPie )
8227         aLine.append( "S\n" );
8228     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8229         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8230         aLine.append( "B*\n" );
8231     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8232         aLine.append( "S\n" );
8233     else
8234         aLine.append( "f*\n" );
8235 
8236     writeBuffer( aLine.getStr(), aLine.getLength() );
8237 }
8238 
8239 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
8240 {
8241     MARK( "drawPolyLine" );
8242 
8243     sal_uInt16 nPoints = rPoly.GetSize();
8244     if( nPoints < 2 )
8245         return;
8246 
8247     updateGraphicsState();
8248 
8249     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8250         return;
8251 
8252     OStringBuffer aLine( 20 * nPoints );
8253     m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
8254     aLine.append( "S\n" );
8255 
8256     writeBuffer( aLine.getStr(), aLine.getLength() );
8257 }
8258 
8259 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
8260 {
8261     MARK( "drawPolyLine with LineInfo" );
8262 
8263     updateGraphicsState();
8264 
8265     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8266         return;
8267 
8268     OStringBuffer aLine;
8269     aLine.append( "q " );
8270     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
8271     {
8272         writeBuffer( aLine.getStr(), aLine.getLength() );
8273         drawPolyLine( rPoly );
8274         writeBuffer( "Q\n", 2 );
8275     }
8276     else
8277     {
8278         PDFWriter::ExtLineInfo aInfo;
8279         convertLineInfoToExtLineInfo( rInfo, aInfo );
8280         drawPolyLine( rPoly, aInfo );
8281     }
8282 }
8283 
8284 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
8285 {
8286     SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
8287     rOut.m_fLineWidth           = rIn.GetWidth();
8288     rOut.m_fTransparency        = 0.0;
8289     rOut.m_eCap                 = PDFWriter::capButt;
8290     rOut.m_eJoin                = PDFWriter::joinMiter;
8291     rOut.m_fMiterLimit          = 10;
8292     rOut.m_aDashArray.clear();
8293 
8294     // add DashDot to DashArray
8295     const int nDashes   = rIn.GetDashCount();
8296     const int nDashLen  = rIn.GetDashLen();
8297     const int nDistance = rIn.GetDistance();
8298 
8299     for( int n  = 0; n < nDashes; n++ )
8300     {
8301         rOut.m_aDashArray.push_back( nDashLen );
8302         rOut.m_aDashArray.push_back( nDistance );
8303     }
8304     const int nDots   = rIn.GetDotCount();
8305     const int nDotLen = rIn.GetDotLen();
8306 
8307     for( int n  = 0; n < nDots; n++ )
8308     {
8309         rOut.m_aDashArray.push_back( nDotLen );
8310         rOut.m_aDashArray.push_back( nDistance );
8311     }
8312 
8313     // add LineJoin
8314     switch(rIn.GetLineJoin())
8315     {
8316         case basegfx::B2DLineJoin::Bevel :
8317         {
8318             rOut.m_eJoin = PDFWriter::joinBevel;
8319             break;
8320         }
8321         // Pdf has no 'none' lineJoin, default is miter
8322         case basegfx::B2DLineJoin::NONE :
8323         case basegfx::B2DLineJoin::Miter :
8324         {
8325             rOut.m_eJoin = PDFWriter::joinMiter;
8326             break;
8327         }
8328         case basegfx::B2DLineJoin::Round :
8329         {
8330             rOut.m_eJoin = PDFWriter::joinRound;
8331             break;
8332         }
8333     }
8334 
8335     // add LineCap
8336     switch(rIn.GetLineCap())
8337     {
8338         default: /* css::drawing::LineCap_BUTT */
8339         {
8340             rOut.m_eCap = PDFWriter::capButt;
8341             break;
8342         }
8343         case css::drawing::LineCap_ROUND:
8344         {
8345             rOut.m_eCap = PDFWriter::capRound;
8346             break;
8347         }
8348         case css::drawing::LineCap_SQUARE:
8349         {
8350             rOut.m_eCap = PDFWriter::capSquare;
8351             break;
8352         }
8353     }
8354 }
8355 
8356 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
8357 {
8358     MARK( "drawPolyLine with ExtLineInfo" );
8359 
8360     updateGraphicsState();
8361 
8362     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8363         return;
8364 
8365     if( rInfo.m_fTransparency >= 1.0 )
8366         return;
8367 
8368     if( rInfo.m_fTransparency != 0.0 )
8369         beginTransparencyGroup();
8370 
8371     OStringBuffer aLine;
8372     aLine.append( "q " );
8373     m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
8374     aLine.append( " w" );
8375     if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
8376     {
8377         switch( rInfo.m_eCap )
8378         {
8379             default:
8380             case PDFWriter::capButt:   aLine.append( " 0 J" );break;
8381             case PDFWriter::capRound:  aLine.append( " 1 J" );break;
8382             case PDFWriter::capSquare: aLine.append( " 2 J" );break;
8383         }
8384         switch( rInfo.m_eJoin )
8385         {
8386             default:
8387             case PDFWriter::joinMiter:
8388             {
8389                 double fLimit = rInfo.m_fMiterLimit;
8390                 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
8391                     fLimit = fLimit / rInfo.m_fLineWidth;
8392                 if( fLimit < 1.0 )
8393                     fLimit = 1.0;
8394                 aLine.append( " 0 j " );
8395                 appendDouble( fLimit, aLine );
8396                 aLine.append( " M" );
8397             }
8398             break;
8399             case PDFWriter::joinRound:  aLine.append( " 1 j" );break;
8400             case PDFWriter::joinBevel:  aLine.append( " 2 j" );break;
8401         }
8402         if( rInfo.m_aDashArray.size() > 0 )
8403         {
8404             aLine.append( " [ " );
8405             for (auto const& dash : rInfo.m_aDashArray)
8406             {
8407                 m_aPages.back().appendMappedLength( dash, aLine );
8408                 aLine.append( ' ' );
8409             }
8410             aLine.append( "] 0 d" );
8411         }
8412         aLine.append( "\n" );
8413         writeBuffer( aLine.getStr(), aLine.getLength() );
8414         drawPolyLine( rPoly );
8415     }
8416     else
8417     {
8418         basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8419         basegfx::B2DPolyPolygon aPolyPoly;
8420 
8421         basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8422 
8423         // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8424         // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8425         // this line needs to be removed and the loop below adapted accordingly
8426         aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8427 
8428         const sal_uInt32 nPolygonCount(aPolyPoly.count());
8429 
8430         for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8431         {
8432             aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8433             aPoly = aPolyPoly.getB2DPolygon( nPoly );
8434             const sal_uInt32 nPointCount(aPoly.count());
8435 
8436             if(nPointCount)
8437             {
8438                 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8439                 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8440 
8441                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
8442                 {
8443                     if( a > 0 )
8444                         aLine.append( " " );
8445                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8446                     const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8447 
8448                     m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
8449                                                         FRound(aCurrent.getY()) ),
8450                                                  aLine );
8451                     aLine.append( " m " );
8452                     m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
8453                                                         FRound(aNext.getY()) ),
8454                                                  aLine );
8455                     aLine.append( " l" );
8456 
8457                     // prepare next edge
8458                     aCurrent = aNext;
8459                 }
8460             }
8461         }
8462         aLine.append( " S " );
8463         writeBuffer( aLine.getStr(), aLine.getLength() );
8464     }
8465     writeBuffer( "Q\n", 2 );
8466 
8467     if( rInfo.m_fTransparency != 0.0 )
8468     {
8469         // FIXME: actually this may be incorrect with bezier polygons
8470         tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8471         // avoid clipping with thick lines
8472         if( rInfo.m_fLineWidth > 0.0 )
8473         {
8474             sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8475             aBoundRect.AdjustTop( -nLW );
8476             aBoundRect.AdjustLeft( -nLW );
8477             aBoundRect.AdjustRight(nLW );
8478             aBoundRect.AdjustBottom(nLW );
8479         }
8480         endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8481     }
8482 }
8483 
8484 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8485 {
8486     MARK( "drawPixel" );
8487 
8488     Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8489 
8490     if( aColor == COL_TRANSPARENT )
8491         return;
8492 
8493     // pixels are drawn in line color, so have to set
8494     // the nonstroking color to line color
8495     Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8496     setFillColor( aColor );
8497 
8498     updateGraphicsState();
8499 
8500     OStringBuffer aLine( 20 );
8501     m_aPages.back().appendPoint( rPoint, aLine );
8502     aLine.append( ' ' );
8503     appendDouble( 1.0/double(getReferenceDevice()->GetDPIX()), aLine );
8504     aLine.append( ' ' );
8505     appendDouble( 1.0/double(getReferenceDevice()->GetDPIY()), aLine );
8506     aLine.append( " re f\n" );
8507     writeBuffer( aLine.getStr(), aLine.getLength() );
8508 
8509     setFillColor( aOldFillColor );
8510 }
8511 
8512 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8513 {
8514     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8515 
8516     bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8517     rObject.m_pContentStream->Seek( STREAM_SEEK_TO_END );
8518     sal_uLong nSize = rObject.m_pContentStream->Tell();
8519     rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8520     if (g_bDebugDisableCompression)
8521     {
8522         emitComment( "PDFWriterImpl::writeTransparentObject" );
8523     }
8524     OStringBuffer aLine( 512 );
8525     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8526     aLine.append( rObject.m_nObject );
8527     aLine.append( " 0 obj\n"
8528                   "<</Type/XObject\n"
8529                   "/Subtype/Form\n"
8530                   "/BBox[ " );
8531     appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8532     aLine.append( ' ' );
8533     appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8534     aLine.append( ' ' );
8535     appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8536     aLine.append( ' ' );
8537     appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8538     aLine.append( " ]\n" );
8539     if( ! rObject.m_pSoftMaskStream )
8540     {
8541         if( ! m_bIsPDF_A1 )
8542         {
8543             aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8544         }
8545     }
8546     /* #i42884# the PDF reference recommends that each Form XObject
8547     *  should have a resource dict; alas if that is the same object
8548     *  as the one of the page it triggers an endless recursion in
8549     *  acroread 5 (6 and up have that fixed). Since we have only one
8550     *  resource dict anyway, let's use the one from the page by NOT
8551     *  emitting a Resources entry.
8552     */
8553 
8554     aLine.append( "/Length " );
8555     aLine.append( static_cast<sal_Int32>(nSize) );
8556     aLine.append( "\n" );
8557     if( bFlateFilter )
8558         aLine.append( "/Filter/FlateDecode\n" );
8559     aLine.append( ">>\n"
8560                   "stream\n" );
8561     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8562     checkAndEnableStreamEncryption( rObject.m_nObject );
8563     CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
8564     disableStreamEncryption();
8565     aLine.setLength( 0 );
8566     aLine.append( "\n"
8567                   "endstream\n"
8568                   "endobj\n\n" );
8569     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8570 
8571     // write ExtGState dict for this XObject
8572     aLine.setLength( 0 );
8573     aLine.append( rObject.m_nExtGStateObject );
8574     aLine.append( " 0 obj\n"
8575                   "<<" );
8576     if( ! rObject.m_pSoftMaskStream )
8577     {
8578         if( m_bIsPDF_A1 )
8579         {
8580             aLine.append( "/CA 1.0/ca 1.0" );
8581             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8582         }
8583         else
8584         {
8585             aLine.append(  "/CA " );
8586             appendDouble( rObject.m_fAlpha, aLine );
8587             aLine.append( "\n"
8588                           "   /ca " );
8589             appendDouble( rObject.m_fAlpha, aLine );
8590         }
8591         aLine.append( "\n" );
8592     }
8593     else
8594     {
8595         if( m_bIsPDF_A1 )
8596         {
8597             aLine.append( "/SMask/None" );
8598             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8599         }
8600         else
8601         {
8602             rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_END );
8603             sal_Int32 nMaskSize = static_cast<sal_Int32>(rObject.m_pSoftMaskStream->Tell());
8604             rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
8605             sal_Int32 nMaskObject = createObject();
8606             aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
8607             aLine.append( nMaskObject );
8608             aLine.append( " 0 R>>\n" );
8609 
8610             OStringBuffer aMask;
8611             aMask.append( nMaskObject );
8612             aMask.append( " 0 obj\n"
8613                           "<</Type/XObject\n"
8614                           "/Subtype/Form\n"
8615                           "/BBox[" );
8616             appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
8617             aMask.append( ' ' );
8618             appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
8619             aMask.append( ' ' );
8620             appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
8621             aMask.append( ' ' );
8622             appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
8623             aMask.append( "]\n" );
8624 
8625             /* #i42884# see above */
8626             aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
8627             aMask.append( "/Length " );
8628             aMask.append( nMaskSize );
8629             aMask.append( ">>\n"
8630                           "stream\n" );
8631             CHECK_RETURN2( updateObject( nMaskObject ) );
8632             checkAndEnableStreamEncryption(  nMaskObject );
8633             CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8634             CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
8635             disableStreamEncryption();
8636             aMask.setLength( 0 );
8637             aMask.append( "\nendstream\n"
8638                           "endobj\n\n" );
8639             CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8640         }
8641     }
8642     aLine.append( ">>\n"
8643                   "endobj\n\n" );
8644     CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
8645     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8646 }
8647 
8648 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8649 {
8650     // LO internal gradient -> PDF shading type:
8651     //  * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
8652     //                          [t=0:colorStart, t=1:colorEnd]
8653     //  * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
8654     //                          [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8655     //  * other styles: function shading with aSize.Width() * aSize.Height() samples
8656     sal_Int32 nFunctionObject = createObject();
8657     CHECK_RETURN( updateObject( nFunctionObject ) );
8658 
8659     ScopedVclPtrInstance< VirtualDevice > aDev;
8660     aDev->SetOutputSizePixel( rObject.m_aSize );
8661     aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8662     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8663         aDev->SetDrawMode( aDev->GetDrawMode() |
8664                           ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8665                             DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8666     aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8667 
8668     Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8669     Bitmap::ScopedReadAccess pAccess(aSample);
8670 
8671     Size aSize = aSample.GetSizePixel();
8672 
8673     sal_Int32 nStreamLengthObject = createObject();
8674     if (g_bDebugDisableCompression)
8675     {
8676         emitComment( "PDFWriterImpl::writeGradientFunction" );
8677     }
8678     OStringBuffer aLine( 120 );
8679     aLine.append( nFunctionObject );
8680     aLine.append( " 0 obj\n"
8681                   "<</FunctionType 0\n");
8682     switch (rObject.m_aGradient.GetStyle())
8683     {
8684         case GradientStyle::Linear:
8685         case GradientStyle::Axial:
8686             aLine.append("/Domain[ 0 1]\n");
8687             break;
8688         default:
8689             aLine.append("/Domain[ 0 1 0 1]\n");
8690     }
8691     aLine.append("/Size[ " );
8692     switch (rObject.m_aGradient.GetStyle())
8693     {
8694         case GradientStyle::Linear:
8695             aLine.append('2');
8696             break;
8697         case GradientStyle::Axial:
8698             aLine.append('3');
8699             break;
8700         default:
8701             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8702             aLine.append( ' ' );
8703             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8704     }
8705     aLine.append( " ]\n"
8706                   "/BitsPerSample 8\n"
8707                   "/Range[ 0 1 0 1 0 1 ]\n"
8708                   "/Order 3\n"
8709                   "/Length " );
8710     aLine.append( nStreamLengthObject );
8711     if (!g_bDebugDisableCompression)
8712         aLine.append( " 0 R\n"
8713                       "/Filter/FlateDecode"
8714                       ">>\n"
8715                       "stream\n" );
8716     else
8717         aLine.append( " 0 R\n"
8718                       ">>\n"
8719                       "stream\n" );
8720     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8721 
8722     sal_uInt64 nStartStreamPos = 0;
8723     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
8724 
8725     checkAndEnableStreamEncryption( nFunctionObject );
8726     beginCompression();
8727     sal_uInt8 aCol[3];
8728     switch (rObject.m_aGradient.GetStyle())
8729     {
8730         case GradientStyle::Axial:
8731             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8732             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8733             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8734             CHECK_RETURN( writeBuffer( aCol, 3 ) );
8735             SAL_FALLTHROUGH;
8736         case GradientStyle::Linear:
8737         {
8738             aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8739             aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8740             aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8741             CHECK_RETURN( writeBuffer( aCol, 3 ) );
8742 
8743             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8744             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8745             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8746             CHECK_RETURN( writeBuffer( aCol, 3 ) );
8747             break;
8748         }
8749         default:
8750             for( int y = aSize.Height()-1; y >= 0; y-- )
8751             {
8752                 for( long x = 0; x < aSize.Width(); x++ )
8753                 {
8754                     BitmapColor aColor = pAccess->GetColor( y, x );
8755                     aCol[0] = aColor.GetRed();
8756                     aCol[1] = aColor.GetGreen();
8757                     aCol[2] = aColor.GetBlue();
8758                     CHECK_RETURN( writeBuffer( aCol, 3 ) );
8759                 }
8760             }
8761     }
8762     endCompression();
8763     disableStreamEncryption();
8764 
8765     sal_uInt64 nEndStreamPos = 0;
8766     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
8767 
8768     aLine.setLength( 0 );
8769     aLine.append( "\nendstream\nendobj\n\n" );
8770     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8771 
8772     // write stream length
8773     CHECK_RETURN( updateObject( nStreamLengthObject ) );
8774     aLine.setLength( 0 );
8775     aLine.append( nStreamLengthObject );
8776     aLine.append( " 0 obj\n" );
8777     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8778     aLine.append( "\nendobj\n\n" );
8779     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8780 
8781     CHECK_RETURN( updateObject( rObject.m_nObject ) );
8782     aLine.setLength( 0 );
8783     aLine.append( rObject.m_nObject );
8784     aLine.append( " 0 obj\n");
8785     switch (rObject.m_aGradient.GetStyle())
8786     {
8787         case GradientStyle::Linear:
8788         case GradientStyle::Axial:
8789             aLine.append("<</ShadingType 2\n");
8790             break;
8791         default:
8792             aLine.append("<</ShadingType 1\n");
8793     }
8794     aLine.append("/ColorSpace/DeviceRGB\n"
8795                   "/AntiAlias true\n");
8796 
8797     // Determination of shading axis
8798     // See: OutputDevice::ImplDrawLinearGradient for reference
8799     tools::Rectangle aRect;
8800     aRect.SetLeft(0);
8801     aRect.SetTop(0);
8802     aRect.SetRight( aSize.Width() );
8803     aRect.SetBottom( aSize.Height() );
8804 
8805     tools::Rectangle aBoundRect;
8806     Point     aCenter;
8807     sal_uInt16    nAngle = rObject.m_aGradient.GetAngle() % 3600;
8808     rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8809 
8810     const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
8811     double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8812     if ( !bLinear )
8813     {
8814         fBorder /= 2.0;
8815     }
8816 
8817     aBoundRect.AdjustBottom( -fBorder );
8818     if (!bLinear)
8819     {
8820         aBoundRect.AdjustTop(fBorder );
8821     }
8822 
8823     switch (rObject.m_aGradient.GetStyle())
8824     {
8825         case GradientStyle::Linear:
8826         case GradientStyle::Axial:
8827         {
8828             aLine.append("/Domain[ 0 1 ]\n"
8829                     "/Coords[ " );
8830             tools::Polygon aPoly( 2 );
8831             aPoly[0] = aBoundRect.BottomCenter();
8832             aPoly[1] = aBoundRect.TopCenter();
8833             aPoly.Rotate( aCenter, 3600 - nAngle );
8834 
8835             aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8836             aLine.append( " " );
8837             aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8838             aLine.append( " " );
8839             aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8840             aLine.append( " ");
8841             aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8842             aLine.append( " ]\n");
8843             aLine.append("/Extend [true true]\n");
8844             break;
8845         }
8846         default:
8847             aLine.append("/Domain[ 0 1 0 1 ]\n"
8848                     "/Matrix[ " );
8849             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8850             aLine.append( " 0 0 " );
8851             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8852             aLine.append( " 0 0 ]\n");
8853     }
8854     aLine.append("/Function " );
8855     aLine.append( nFunctionObject );
8856     aLine.append( " 0 R\n"
8857                   ">>\n"
8858                   "endobj\n\n" );
8859     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8860 
8861     return true;
8862 }
8863 
8864 void PDFWriterImpl::writeJPG( JPGEmit& rObject )
8865 {
8866     if (rObject.m_aReferenceXObject.m_aPDFData.hasElements() && !m_aContext.UseReferenceXObject)
8867     {
8868         writeReferenceXObject(rObject.m_aReferenceXObject);
8869         return;
8870     }
8871 
8872     CHECK_RETURN2( rObject.m_pStream );
8873     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8874 
8875     sal_Int32 nLength = 0;
8876     rObject.m_pStream->Seek( STREAM_SEEK_TO_END );
8877     nLength = rObject.m_pStream->Tell();
8878     rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8879 
8880     sal_Int32 nMaskObject = 0;
8881     if( !!rObject.m_aMask )
8882     {
8883         if( rObject.m_aMask.GetBitCount() == 1 ||
8884             ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && !m_bIsPDF_A1 )
8885             )
8886         {
8887             nMaskObject = createObject();
8888         }
8889         else if( m_bIsPDF_A1 )
8890             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8891         else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8892             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8893 
8894     }
8895     if (g_bDebugDisableCompression)
8896     {
8897         emitComment( "PDFWriterImpl::writeJPG" );
8898     }
8899 
8900     OStringBuffer aLine(200);
8901     aLine.append( rObject.m_nObject );
8902     aLine.append( " 0 obj\n"
8903                   "<</Type/XObject/Subtype/Image/Width " );
8904     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
8905     aLine.append( " /Height " );
8906     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
8907     aLine.append( " /BitsPerComponent 8 " );
8908     if( rObject.m_bTrueColor )
8909         aLine.append( "/ColorSpace/DeviceRGB" );
8910     else
8911         aLine.append( "/ColorSpace/DeviceGray" );
8912     aLine.append( "/Filter/DCTDecode/Length " );
8913     aLine.append( nLength );
8914     if( nMaskObject )
8915     {
8916         aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
8917         aLine.append( nMaskObject );
8918         aLine.append( " 0 R " );
8919     }
8920     aLine.append( ">>\nstream\n" );
8921     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8922 
8923     checkAndEnableStreamEncryption( rObject.m_nObject );
8924     CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
8925     disableStreamEncryption();
8926 
8927     aLine.setLength( 0 );
8928     aLine.append( "\nendstream\nendobj\n\n" );
8929     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8930 
8931     if( nMaskObject )
8932     {
8933         BitmapEmit aEmit;
8934         aEmit.m_nObject = nMaskObject;
8935         if( rObject.m_aMask.GetBitCount() == 1 )
8936             aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
8937         else if( rObject.m_aMask.GetBitCount() == 8 )
8938             aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
8939         writeBitmapObject( aEmit, true );
8940     }
8941 
8942     writeReferenceXObject(rObject.m_aReferenceXObject);
8943 }
8944 
8945 sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources)
8946 {
8947     auto it = rCopiedResources.find(rObject.GetObjectValue());
8948     if (it != rCopiedResources.end())
8949         // This resource was already copied once, nothing to do.
8950         return it->second;
8951 
8952     sal_Int32 nObject = createObject();
8953     // Remember what is the ID of this object in our output.
8954     rCopiedResources[rObject.GetObjectValue()] = nObject;
8955     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject);
8956 
8957     OStringBuffer aLine;
8958     aLine.append(nObject);
8959     aLine.append(" 0 obj\n");
8960     if (rObject.GetDictionary())
8961     {
8962         aLine.append("<<");
8963 
8964         // Complex case: can't copy the dictionary byte array as is, as it may contain references.
8965         bool bDone = false;
8966         sal_uInt64 nCopyStart = 0;
8967         for (auto pReference : rObject.GetDictionaryReferences())
8968         {
8969             if (pReference)
8970             {
8971                 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8972                 if (pReferenced)
8973                 {
8974                     // Copy the referenced object.
8975                     sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
8976 
8977                     sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
8978                     sal_uInt64 nReferenceEnd = pReference->GetOffset();
8979                     sal_uInt64 nOffset = 0;
8980                     if (nCopyStart == 0)
8981                         // Dict start -> reference start.
8982                         nOffset = rObject.GetDictionaryOffset();
8983                     else
8984                         // Previous reference end -> reference start.
8985                         nOffset = nCopyStart;
8986                     aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
8987                     // Write the updated reference.
8988                     aLine.append(" ");
8989                     aLine.append(nRef);
8990                     aLine.append(" 0 R");
8991                     // Start copying here next time.
8992                     nCopyStart = nReferenceEnd;
8993 
8994                     bDone = true;
8995                 }
8996             }
8997         }
8998 
8999         if (bDone)
9000         {
9001             // Copy the last part here, in the complex case.
9002             sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength();
9003             aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nDictEnd - nCopyStart);
9004         }
9005         else
9006             // Can copy it as-is.
9007             aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength());
9008 
9009         aLine.append(">>\n");
9010     }
9011 
9012     if (filter::PDFStreamElement* pStream = rObject.GetStream())
9013     {
9014         aLine.append("stream\n");
9015         SvMemoryStream& rStream = pStream->GetMemory();
9016         aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize());
9017         aLine.append("\nendstream\n");
9018     }
9019 
9020     if (filter::PDFArrayElement* pArray = rObject.GetArray())
9021     {
9022         aLine.append("[");
9023 
9024         const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
9025         bool bDone = false;
9026         // Complex case: can't copy the array byte array as is, as it may contain references.
9027         sal_uInt64 nCopyStart = 0;
9028         for (const auto pElement : rElements)
9029         {
9030             auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9031             if (pReference)
9032             {
9033                 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
9034                 if (pReferenced)
9035                 {
9036                     // Copy the referenced object.
9037                     sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
9038 
9039                     sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
9040                     sal_uInt64 nReferenceEnd = pReference->GetOffset();
9041                     sal_uInt64 nOffset = 0;
9042                     if (nCopyStart == 0)
9043                         // Array start -> reference start.
9044                         nOffset = rObject.GetArrayOffset();
9045                     else
9046                         // Previous reference end -> reference start.
9047                         nOffset = nCopyStart;
9048                     aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
9049 
9050                     // Write the updated reference.
9051                     aLine.append(" ");
9052                     aLine.append(nRef);
9053                     aLine.append(" 0 R");
9054                     // Start copying here next time.
9055                     nCopyStart = nReferenceEnd;
9056 
9057                     bDone = true;
9058                 }
9059             }
9060         }
9061 
9062         if (bDone)
9063         {
9064             // Copy the last part here, in the complex case.
9065             sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength();
9066             aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nArrEnd - nCopyStart);
9067         }
9068         else
9069             // Can copy it as-is.
9070             aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength());
9071 
9072         aLine.append("]\n");
9073     }
9074 
9075     // If the object has a number element outside a dictionary or array, copy that.
9076     if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
9077     {
9078         aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + pNumber->GetLocation(), pNumber->GetLength());
9079         aLine.append("\n");
9080     }
9081 
9082 
9083     aLine.append("endobj\n\n");
9084 
9085     // We have the whole object, now write it to the output.
9086     if (!updateObject(nObject))
9087         return -1;
9088     if (!writeBuffer(aLine.getStr(), aLine.getLength()))
9089         return -1;
9090 
9091     return nObject;
9092 }
9093 
9094 OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources)
9095 {
9096     // A name - object ID map, IDs as they appear in our output, not the
9097     // original ones.
9098     std::map<OString, sal_Int32> aRet;
9099 
9100     // Get the rKind subset of the resource dictionary.
9101     std::map<OString, filter::PDFElement*> aItems;
9102     if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
9103     {
9104         // Resources is a direct dictionary.
9105         if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pResources->LookupElement(rKind)))
9106             aItems = pDictionary->GetItems();
9107     }
9108     else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
9109     {
9110         // Resources is an indirect object.
9111         filter::PDFElement* pValue = pPageResources->Lookup(rKind);
9112         if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
9113             // Kind is a direct dictionary.
9114             aItems = pDictionary->GetItems();
9115         else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
9116             // Kind is an indirect object.
9117             aItems = pObject->GetDictionaryItems();
9118     }
9119     if (aItems.empty())
9120         return OString();
9121 
9122     SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
9123 
9124     for (const auto& rItem : aItems)
9125     {
9126         // For each item copy it over to our output then insert it into aRet.
9127         auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
9128         if (!pReference)
9129             continue;
9130 
9131         filter::PDFObjectElement* pValue = pReference->LookupObject();
9132         if (!pValue)
9133             continue;
9134 
9135         // Then copying over an object copy its dictionary and its stream.
9136         sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
9137         aRet[rItem.first] = nObject;
9138     }
9139 
9140     // Build the dictionary entry string.
9141     OString sRet = "/" + rKind + "<<";
9142     for (const auto& rPair : aRet)
9143     {
9144         sRet += "/" + rPair.first + " " + OString::number(rPair.second) + " 0 R";
9145     }
9146     sRet += ">>";
9147 
9148     return sRet;
9149 }
9150 
9151 void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
9152 {
9153     if (rEmit.m_nFormObject <= 0)
9154         return;
9155 
9156     // Count /Matrix and /BBox.
9157     // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
9158     sal_Int32 nOldDPIX = getReferenceDevice()->GetDPIX();
9159     getReferenceDevice()->SetDPIX(96);
9160     sal_Int32 nOldDPIY = getReferenceDevice()->GetDPIY();
9161     getReferenceDevice()->SetDPIY(96);
9162     Size aSize = getReferenceDevice()->PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
9163     getReferenceDevice()->SetDPIX(nOldDPIX);
9164     getReferenceDevice()->SetDPIY(nOldDPIY);
9165     double fScaleX = 1.0 / aSize.Width();
9166     double fScaleY = 1.0 / aSize.Height();
9167 
9168     sal_Int32 nWrappedFormObject = 0;
9169     if (!m_aContext.UseReferenceXObject)
9170     {
9171         // Parse the PDF data, we need that to write the PDF dictionary of our
9172         // object.
9173         SvMemoryStream aPDFStream;
9174         aPDFStream.WriteBytes(rEmit.m_aPDFData.getArray(), rEmit.m_aPDFData.getLength());
9175         aPDFStream.Seek(0);
9176         filter::PDFDocument aPDFDocument;
9177         if (!aPDFDocument.Read(aPDFStream))
9178         {
9179             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
9180             return;
9181         }
9182         std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
9183         if (aPages.empty())
9184         {
9185             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
9186             return;
9187         }
9188 
9189         filter::PDFObjectElement* pPage = aPages[0];
9190         if (!pPage)
9191         {
9192             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
9193             return;
9194         }
9195 
9196         std::vector<filter::PDFObjectElement*> aContentStreams;
9197         if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
9198             aContentStreams.push_back(pContentStream);
9199         else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
9200         {
9201             for (const auto pElement : pArray->GetElements())
9202             {
9203                 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9204                 if (!pReference)
9205                     continue;
9206 
9207                 filter::PDFObjectElement* pObject = pReference->LookupObject();
9208                 if (!pObject)
9209                     continue;
9210 
9211                 aContentStreams.push_back(pObject);
9212             }
9213         }
9214 
9215         if (aContentStreams.empty())
9216         {
9217             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
9218             return;
9219         }
9220 
9221         // Maps from source object id (PDF image) to target object id (export result).
9222         std::map<sal_Int32, sal_Int32> aCopiedResources;
9223 
9224         nWrappedFormObject = createObject();
9225         // Write the form XObject wrapped below. This is a separate object from
9226         // the wrapper, this way there is no need to alter the stream contents.
9227 
9228         OStringBuffer aLine;
9229         aLine.append(nWrappedFormObject);
9230         aLine.append(" 0 obj\n");
9231         aLine.append("<< /Type /XObject");
9232         aLine.append(" /Subtype /Form");
9233         aLine.append(" /Resources <<");
9234         static const std::initializer_list<OString> aKeys =
9235         {
9236             "ColorSpace",
9237             "ExtGState",
9238             "Font",
9239             "XObject",
9240             "Shading"
9241         };
9242         for (const auto& rKey : aKeys)
9243             aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
9244         aLine.append(">>");
9245         aLine.append(" /BBox [ 0 0 ");
9246         aLine.append(aSize.Width());
9247         aLine.append(" ");
9248         aLine.append(aSize.Height());
9249         aLine.append(" ]");
9250 
9251         if (!g_bDebugDisableCompression)
9252             aLine.append(" /Filter/FlateDecode");
9253         aLine.append(" /Length ");
9254 
9255         SvMemoryStream aStream;
9256         for (auto pContent : aContentStreams)
9257         {
9258             filter::PDFStreamElement* pPageStream = pContent->GetStream();
9259             if (!pPageStream)
9260             {
9261                 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
9262                 continue;
9263             }
9264 
9265             SvMemoryStream& rPageStream = pPageStream->GetMemory();
9266 
9267             auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
9268             if (pFilter)
9269             {
9270                 if (pFilter->GetValue() != "FlateDecode")
9271                     continue;
9272 
9273                 SvMemoryStream aMemoryStream;
9274                 ZCodec aZCodec;
9275                 rPageStream.Seek(0);
9276                 aZCodec.BeginCompression();
9277                 aZCodec.Decompress(rPageStream, aMemoryStream);
9278                 if (!aZCodec.EndCompression())
9279                 {
9280                     SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
9281                     continue;
9282                 }
9283 
9284                 aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
9285             }
9286             else
9287                 aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
9288         }
9289 
9290         compressStream(&aStream);
9291         sal_Int32 nLength = aStream.Tell();
9292         aLine.append(nLength);
9293 
9294         aLine.append(">>\nstream\n");
9295         // Copy the original page streams to the form XObject stream.
9296         aLine.append(static_cast<const sal_Char*>(aStream.GetData()), aStream.GetSize());
9297         aLine.append("\nendstream\nendobj\n\n");
9298         if (!updateObject(nWrappedFormObject))
9299             return;
9300         if (!writeBuffer(aLine.getStr(), aLine.getLength()))
9301             return;
9302     }
9303 
9304     OStringBuffer aLine;
9305     if (!updateObject(rEmit.m_nFormObject))
9306         return;
9307 
9308     // Now have all the info to write the form XObject.
9309     aLine.append(rEmit.m_nFormObject);
9310     aLine.append(" 0 obj\n");
9311     aLine.append("<< /Type /XObject");
9312     aLine.append(" /Subtype /Form");
9313     aLine.append(" /Resources << /XObject<<");
9314 
9315     sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
9316     aLine.append(" /Im");
9317     aLine.append(nObject);
9318     aLine.append(" ");
9319     aLine.append(nObject);
9320     aLine.append(" 0 R");
9321 
9322     aLine.append(">> >>");
9323     aLine.append(" /Matrix [ ");
9324     appendDouble(fScaleX, aLine);
9325     aLine.append(" 0 0 ");
9326     appendDouble(fScaleY, aLine);
9327     aLine.append(" 0 0 ]");
9328     aLine.append(" /BBox [ 0 0 ");
9329     aLine.append(aSize.Width());
9330     aLine.append(" ");
9331     aLine.append(aSize.Height());
9332     aLine.append(" ]\n");
9333 
9334     if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
9335     {
9336         // Write the reference dictionary.
9337         aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
9338         aLine.append(rEmit.m_nEmbeddedObject);
9339         aLine.append(" 0 R >> >> /Page 0 >>\n");
9340     }
9341 
9342     aLine.append("/Length ");
9343 
9344     OStringBuffer aStream;
9345     aStream.append("q ");
9346     if (m_aContext.UseReferenceXObject)
9347     {
9348         // Reference XObject markup is used, just refer to the fallback bitmap
9349         // here.
9350         aStream.append(aSize.Width());
9351         aStream.append(" 0 0 ");
9352         aStream.append(aSize.Height());
9353         aStream.append(" 0 0 cm\n");
9354         aStream.append("/Im");
9355         aStream.append(rEmit.m_nBitmapObject);
9356         aStream.append(" Do\n");
9357     }
9358     else
9359     {
9360         // Reset line width to the default.
9361         aStream.append(" 1 w\n");
9362 
9363         // No reference XObject, draw the form XObject containing the original
9364         // page streams.
9365         aStream.append("/Im");
9366         aStream.append(nWrappedFormObject);
9367         aStream.append(" Do\n");
9368     }
9369     aStream.append("Q");
9370     aLine.append(aStream.getLength());
9371 
9372     aLine.append(">>\nstream\n");
9373     aLine.append(aStream.getStr());
9374     aLine.append("\nendstream\nendobj\n\n");
9375     CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
9376 }
9377 
9378 namespace
9379 {
9380     unsigned char reverseByte(unsigned char b)
9381     {
9382         b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
9383         b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
9384         b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
9385         return b;
9386     }
9387 
9388     //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
9389     Bitmap getExportBitmap(const Bitmap &rBitmap)
9390     {
9391         Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
9392         const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
9393         if (eFormat != ScanlineFormat::N1BitLsbPal)
9394             return rBitmap;
9395         Bitmap aNewBmp(rBitmap);
9396         BitmapScopedWriteAccess xWriteAcc(aNewBmp);
9397         const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
9398         for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
9399         {
9400             Scanline pBitSwap = xWriteAcc->GetScanline(nY);
9401             for (int x = 0; x < nScanLineBytes; ++x)
9402                 pBitSwap[x] = reverseByte(pBitSwap[x]);
9403         }
9404         return aNewBmp;
9405     }
9406 }
9407 
9408 bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
9409 {
9410     if (rObject.m_aReferenceXObject.m_aPDFData.hasElements() && !m_aContext.UseReferenceXObject)
9411     {
9412         writeReferenceXObject(rObject.m_aReferenceXObject);
9413         return true;
9414     }
9415 
9416     CHECK_RETURN( updateObject( rObject.m_nObject ) );
9417 
9418     Bitmap  aBitmap;
9419     Color   aTransparentColor( COL_TRANSPARENT );
9420     bool    bWriteMask = false;
9421     if( ! bMask )
9422     {
9423         aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
9424         if( rObject.m_aBitmap.IsAlpha() )
9425         {
9426             if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9427                 bWriteMask = true;
9428             // else draw without alpha channel
9429         }
9430         else
9431         {
9432             switch( rObject.m_aBitmap.GetTransparentType() )
9433             {
9434                 case TransparentType::NONE:
9435                     break;
9436                 case TransparentType::Color:
9437                     aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
9438                     break;
9439                 case TransparentType::Bitmap:
9440                     bWriteMask = true;
9441                     break;
9442             }
9443         }
9444     }
9445     else
9446     {
9447         if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
9448         {
9449             aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
9450             aBitmap.Convert( BmpConversion::N1BitThreshold );
9451             SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
9452         }
9453         else if( aBitmap.GetBitCount() != 8 )
9454         {
9455             aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
9456             aBitmap.Convert( BmpConversion::N8BitGreys );
9457             SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
9458         }
9459     }
9460 
9461     Bitmap::ScopedReadAccess pAccess(aBitmap);
9462 
9463     bool bTrueColor;
9464     sal_Int32 nBitsPerComponent;
9465     switch( aBitmap.GetBitCount() )
9466     {
9467         case 1:
9468         case 2:
9469         case 4:
9470         case 8:
9471             bTrueColor = false;
9472             nBitsPerComponent = aBitmap.GetBitCount();
9473             break;
9474         default:
9475             bTrueColor = true;
9476             nBitsPerComponent = 8;
9477             break;
9478     }
9479 
9480     sal_Int32 nStreamLengthObject   = createObject();
9481     sal_Int32 nMaskObject           = 0;
9482 
9483     if (g_bDebugDisableCompression)
9484     {
9485         emitComment( "PDFWriterImpl::writeBitmapObject" );
9486     }
9487     OStringBuffer aLine(1024);
9488     aLine.append( rObject.m_nObject );
9489     aLine.append( " 0 obj\n"
9490                   "<</Type/XObject/Subtype/Image/Width " );
9491     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9492     aLine.append( "/Height " );
9493     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9494     aLine.append( "/BitsPerComponent " );
9495     aLine.append( nBitsPerComponent );
9496     aLine.append( "/Length " );
9497     aLine.append( nStreamLengthObject );
9498     aLine.append( " 0 R\n" );
9499     if (!g_bDebugDisableCompression)
9500     {
9501         if( nBitsPerComponent != 1 )
9502         {
9503             aLine.append( "/Filter/FlateDecode" );
9504         }
9505         else
9506         {
9507             aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9508             aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9509             aLine.append( ">>\n" );
9510         }
9511     }
9512     if( ! bMask )
9513     {
9514         aLine.append( "/ColorSpace" );
9515         if( bTrueColor )
9516             aLine.append( "/DeviceRGB\n" );
9517         else if( aBitmap.HasGreyPalette() )
9518         {
9519             aLine.append( "/DeviceGray\n" );
9520             if( aBitmap.GetBitCount() == 1 )
9521             {
9522                 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
9523                 sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9524                 assert( nBlackIndex == 0 || nBlackIndex == 1);
9525                 sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
9526                 if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
9527                     pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
9528                 {
9529                     // It is black and white
9530                     if( nBlackIndex == 1 )
9531                         aLine.append( "/Decode[1 0]\n" );
9532                 }
9533                 else
9534                 {
9535                     // It is two levels of grey
9536                     aLine.append( "/Decode[" );
9537                     assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
9538                             pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
9539                             pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
9540                             pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
9541                     aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
9542                     aLine.append( " " );
9543                     aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
9544                     aLine.append( "]\n" );
9545                 }
9546             }
9547         }
9548         else
9549         {
9550             aLine.append( "[ /Indexed/DeviceRGB " );
9551             aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9552             aLine.append( "\n<" );
9553             if( m_aContext.Encryption.Encrypt() )
9554             {
9555                 enableStringEncryption( rObject.m_nObject );
9556                 //check encryption buffer size
9557                 if( checkEncryptionBufferSize( pAccess->GetPaletteEntryCount()*3 ) )
9558                 {
9559                     int nChar = 0;
9560                     //fill the encryption buffer
9561                     for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9562                     {
9563                         const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9564                         m_pEncryptionBuffer[nChar++] = rColor.GetRed();
9565                         m_pEncryptionBuffer[nChar++] = rColor.GetGreen();
9566                         m_pEncryptionBuffer[nChar++] = rColor.GetBlue();
9567                     }
9568                     //encrypt the colorspace lookup table
9569                     rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChar, m_pEncryptionBuffer, nChar );
9570                     //now queue the data for output
9571                     nChar = 0;
9572                     for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9573                     {
9574                         appendHex(m_pEncryptionBuffer[nChar++], aLine );
9575                         appendHex(m_pEncryptionBuffer[nChar++], aLine );
9576                         appendHex(m_pEncryptionBuffer[nChar++], aLine );
9577                     }
9578                 }
9579             }
9580             else //no encryption requested (PDF/A-1a program flow drops here)
9581             {
9582                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9583                 {
9584                     const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9585                     appendHex( rColor.GetRed(), aLine );
9586                     appendHex( rColor.GetGreen(), aLine );
9587                     appendHex( rColor.GetBlue(), aLine );
9588                 }
9589             }
9590             aLine.append( ">\n]\n" );
9591         }
9592     }
9593     else
9594     {
9595         if( aBitmap.GetBitCount() == 1 )
9596         {
9597             aLine.append( "/ImageMask true\n" );
9598             sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9599             SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
9600             if( nBlackIndex )
9601                 aLine.append( "/Decode[ 1 0 ]\n" );
9602             else
9603                 aLine.append( "/Decode[ 0 1 ]\n" );
9604         }
9605         else if( aBitmap.GetBitCount() == 8 )
9606         {
9607             aLine.append( "/ColorSpace/DeviceGray\n"
9608                           "/Decode [ 1 0 ]\n" );
9609         }
9610     }
9611 
9612     if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
9613     {
9614         if( bWriteMask )
9615         {
9616             nMaskObject = createObject();
9617             if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
9618                 aLine.append( "/SMask " );
9619             else
9620                 aLine.append( "/Mask " );
9621             aLine.append( nMaskObject );
9622             aLine.append( " 0 R\n" );
9623         }
9624         else if( aTransparentColor != COL_TRANSPARENT )
9625         {
9626             aLine.append( "/Mask[ " );
9627             if( bTrueColor )
9628             {
9629                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
9630                 aLine.append( ' ' );
9631                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
9632                 aLine.append( ' ' );
9633                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
9634                 aLine.append( ' ' );
9635                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
9636                 aLine.append( ' ' );
9637                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
9638                 aLine.append( ' ' );
9639                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
9640             }
9641             else
9642             {
9643                 sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
9644                 aLine.append( nIndex );
9645             }
9646             aLine.append( " ]\n" );
9647         }
9648     }
9649     else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != COL_TRANSPARENT) )
9650         m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9651 
9652     aLine.append( ">>\n"
9653                   "stream\n" );
9654     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9655     sal_uInt64 nStartPos = 0;
9656     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
9657 
9658     checkAndEnableStreamEncryption( rObject.m_nObject );
9659     if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
9660     {
9661         writeG4Stream(pAccess.get());
9662     }
9663     else
9664     {
9665         beginCompression();
9666         if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9667         {
9668             //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9669             const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9670 
9671             for( long i = 0; i < pAccess->Height(); i++ )
9672             {
9673                 CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
9674             }
9675         }
9676         else
9677         {
9678             const int nScanLineBytes = pAccess->Width()*3;
9679             std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9680             for( long y = 0; y < pAccess->Height(); y++ )
9681             {
9682                 for( long x = 0; x < pAccess->Width(); x++ )
9683                 {
9684                     BitmapColor aColor = pAccess->GetColor( y, x );
9685                     xCol[3*x+0] = aColor.GetRed();
9686                     xCol[3*x+1] = aColor.GetGreen();
9687                     xCol[3*x+2] = aColor.GetBlue();
9688                 }
9689                 CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
9690             }
9691         }
9692         endCompression();
9693     }
9694     disableStreamEncryption();
9695 
9696     sal_uInt64 nEndPos = 0;
9697     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
9698     aLine.setLength( 0 );
9699     aLine.append( "\nendstream\nendobj\n\n" );
9700     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9701     CHECK_RETURN( updateObject( nStreamLengthObject ) );
9702     aLine.setLength( 0 );
9703     aLine.append( nStreamLengthObject );
9704     aLine.append( " 0 obj\n" );
9705     aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9706     aLine.append( "\nendobj\n\n" );
9707     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9708 
9709     if( nMaskObject )
9710     {
9711         BitmapEmit aEmit;
9712         aEmit.m_nObject             = nMaskObject;
9713         aEmit.m_aBitmap             = rObject.m_aBitmap;
9714         return writeBitmapObject( aEmit, true );
9715     }
9716 
9717     writeReferenceXObject(rObject.m_aReferenceXObject);
9718 
9719     return true;
9720 }
9721 
9722 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9723 {
9724     // The bitmap object is always a valid identifier, even if the graphic has
9725     // no pdf data.
9726     rEmit.m_nBitmapObject = nBitmapObject;
9727 
9728     if (!rGraphic.hasPdfData())
9729         return;
9730 
9731     if (m_aContext.UseReferenceXObject)
9732     {
9733         // Store the original PDF data as an embedded file.
9734         m_aEmbeddedFiles.emplace_back();
9735         m_aEmbeddedFiles.back().m_nObject = createObject();
9736         m_aEmbeddedFiles.back().m_aData = *rGraphic.getPdfData();
9737 
9738         rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
9739     }
9740     else
9741         rEmit.m_aPDFData = *rGraphic.getPdfData();
9742 
9743     rEmit.m_nFormObject = createObject();
9744     rEmit.m_aPixelSize = rGraphic.GetPrefSize();
9745 }
9746 
9747 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, const Graphic& rGraphic )
9748 {
9749     MARK( "drawJPGBitmap" );
9750 
9751     OStringBuffer aLine( 80 );
9752     updateGraphicsState();
9753 
9754     // #i40055# sanity check
9755     if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9756         return;
9757     if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9758         return;
9759 
9760     rDCTData.Seek( 0 );
9761     if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9762     {
9763         // need to convert to grayscale;
9764         // load stream to bitmap and draw the bitmap instead
9765         Graphic aGraphic;
9766         GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9767         if( !!rMask && rMask.GetSizePixel() == aGraphic.GetSizePixel() )
9768         {
9769             Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
9770             BitmapEx aBmpEx( aBmp, rMask );
9771             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
9772         }
9773         else
9774             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
9775         return;
9776     }
9777 
9778     SvMemoryStream* pStream = new SvMemoryStream;
9779     pStream->WriteStream( rDCTData );
9780     pStream->Seek( STREAM_SEEK_TO_END );
9781 
9782     BitmapID aID;
9783     aID.m_aPixelSize    = rSizePixel;
9784     aID.m_nSize         = pStream->Tell();
9785     pStream->Seek( STREAM_SEEK_TO_BEGIN );
9786     aID.m_nChecksum     = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
9787     if( ! rMask.IsEmpty() )
9788         aID.m_nMaskChecksum = rMask.GetChecksum();
9789 
9790     std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9791                                              [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9792     if( it == m_aJPGs.end() )
9793     {
9794         m_aJPGs.emplace( m_aJPGs.begin() );
9795         JPGEmit& rEmit = m_aJPGs.front();
9796         if (!rGraphic.hasPdfData() || m_aContext.UseReferenceXObject)
9797             rEmit.m_nObject = createObject();
9798         rEmit.m_aID         = aID;
9799         rEmit.m_pStream.reset( pStream );
9800         rEmit.m_bTrueColor  = bIsTrueColor;
9801         if( !! rMask && rMask.GetSizePixel() == rSizePixel )
9802             rEmit.m_aMask   = rMask;
9803         createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9804 
9805         it = m_aJPGs.begin();
9806     }
9807     else
9808         delete pStream;
9809 
9810     aLine.append( "q " );
9811     sal_Int32 nCheckWidth = 0;
9812     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9813     aLine.append( " 0 0 " );
9814     sal_Int32 nCheckHeight = 0;
9815     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9816     aLine.append( ' ' );
9817     m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9818     aLine.append( " cm\n/Im" );
9819     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9820     aLine.append(nObject);
9821     aLine.append( " Do Q\n" );
9822     if( nCheckWidth == 0 || nCheckHeight == 0 )
9823     {
9824         // #i97512# avoid invalid current matrix
9825         aLine.setLength( 0 );
9826         aLine.append( "\n%jpeg image /Im" );
9827         aLine.append( it->m_nObject );
9828         aLine.append( " scaled to zero size, omitted\n" );
9829     }
9830     writeBuffer( aLine.getStr(), aLine.getLength() );
9831 
9832     OStringBuffer aObjName( 16 );
9833     aObjName.append( "Im" );
9834     aObjName.append(nObject);
9835     pushResource( ResXObject, aObjName.makeStringAndClear(), nObject );
9836 
9837 }
9838 
9839 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9840 {
9841     OStringBuffer aLine( 80 );
9842     updateGraphicsState();
9843 
9844     aLine.append( "q " );
9845     if( rFillColor != COL_TRANSPARENT )
9846     {
9847         appendNonStrokingColor( rFillColor, aLine );
9848         aLine.append( ' ' );
9849     }
9850     sal_Int32 nCheckWidth = 0;
9851     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
9852     aLine.append( " 0 0 " );
9853     sal_Int32 nCheckHeight = 0;
9854     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
9855     aLine.append( ' ' );
9856     m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
9857     aLine.append( " cm\n/Im" );
9858     sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9859     aLine.append(nObject);
9860     aLine.append( " Do Q\n" );
9861     if( nCheckWidth == 0 || nCheckHeight == 0 )
9862     {
9863         // #i97512# avoid invalid current matrix
9864         aLine.setLength( 0 );
9865         aLine.append( "\n%bitmap image /Im" );
9866         aLine.append( rBitmap.m_nObject );
9867         aLine.append( " scaled to zero size, omitted\n" );
9868     }
9869     writeBuffer( aLine.getStr(), aLine.getLength() );
9870 }
9871 
9872 const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
9873 {
9874     BitmapEx aBitmap( i_rBitmap );
9875     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9876     {
9877         BmpConversion eConv = BmpConversion::N8BitGreys;
9878         int nDepth = aBitmap.GetBitmap().GetBitCount();
9879         if( nDepth <= 4 )
9880             eConv = BmpConversion::N4BitGreys;
9881         if( nDepth > 1 )
9882             aBitmap.Convert( eConv );
9883     }
9884     BitmapID aID;
9885     aID.m_aPixelSize        = aBitmap.GetSizePixel();
9886     aID.m_nSize             = aBitmap.GetBitCount();
9887     aID.m_nChecksum         = aBitmap.GetBitmap().GetChecksum();
9888     aID.m_nMaskChecksum     = 0;
9889     if( aBitmap.IsAlpha() )
9890         aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
9891     else
9892     {
9893         Bitmap aMask = aBitmap.GetMask();
9894         if( ! aMask.IsEmpty() )
9895             aID.m_nMaskChecksum = aMask.GetChecksum();
9896     }
9897     std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
9898                                              [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9899     if( it == m_aBitmaps.end() )
9900     {
9901         m_aBitmaps.push_front( BitmapEmit() );
9902         m_aBitmaps.front().m_aID        = aID;
9903         m_aBitmaps.front().m_aBitmap    = aBitmap;
9904         if (!rGraphic.hasPdfData() || m_aContext.UseReferenceXObject)
9905             m_aBitmaps.front().m_nObject = createObject();
9906         createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
9907         it = m_aBitmaps.begin();
9908     }
9909 
9910     OStringBuffer aObjName( 16 );
9911     aObjName.append( "Im" );
9912     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9913     aObjName.append(nObject);
9914     pushResource( ResXObject, aObjName.makeStringAndClear(), nObject );
9915 
9916     return *it;
9917 }
9918 
9919 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9920 {
9921     MARK( "drawBitmap (Bitmap)" );
9922 
9923     // #i40055# sanity check
9924     if( ! (rDestSize.Width() && rDestSize.Height()) )
9925         return;
9926 
9927     const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
9928     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9929 }
9930 
9931 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
9932 {
9933     MARK( "drawBitmap (BitmapEx)" );
9934 
9935     // #i40055# sanity check
9936     if( ! (rDestSize.Width() && rDestSize.Height()) )
9937         return;
9938 
9939     const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9940     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9941 }
9942 
9943 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9944 {
9945     Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
9946                                MapMode( MapUnit::MapPoint ),
9947                                getReferenceDevice(),
9948                                rSize ) );
9949     // check if we already have this gradient
9950     // rounding to point will generally lose some pixels
9951     // round up to point boundary
9952     aPtSize.AdjustWidth( 1 );
9953     aPtSize.AdjustHeight( 1 );
9954     std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9955                                              [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9956 
9957     if( it == m_aGradients.end() )
9958     {
9959         m_aGradients.push_front( GradientEmit() );
9960         m_aGradients.front().m_aGradient    = rGradient;
9961         m_aGradients.front().m_nObject      = createObject();
9962         m_aGradients.front().m_aSize        = aPtSize;
9963         it = m_aGradients.begin();
9964     }
9965 
9966     OStringBuffer aObjName( 16 );
9967     aObjName.append( 'P' );
9968     aObjName.append( it->m_nObject );
9969     pushResource( ResShading, aObjName.makeStringAndClear(), it->m_nObject );
9970 
9971     return it->m_nObject;
9972 }
9973 
9974 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9975 {
9976     MARK( "drawGradient (Rectangle)" );
9977 
9978     if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
9979     {
9980         drawRectangle( rRect );
9981         return;
9982     }
9983 
9984     sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9985 
9986     Point aTranslate( rRect.BottomLeft() );
9987     aTranslate += Point( 0, 1 );
9988 
9989     updateGraphicsState();
9990 
9991     OStringBuffer aLine( 80 );
9992     aLine.append( "q 1 0 0 1 " );
9993     m_aPages.back().appendPoint( aTranslate, aLine );
9994     aLine.append( " cm " );
9995     // if a stroke is appended reset the clip region before stroke
9996     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9997         aLine.append( "q " );
9998     aLine.append( "0 0 " );
9999     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
10000     aLine.append( ' ' );
10001     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
10002     aLine.append( " re W n\n" );
10003 
10004     aLine.append( "/P" );
10005     aLine.append( nGradient );
10006     aLine.append( " sh " );
10007     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
10008     {
10009         aLine.append( "Q 0 0 " );
10010         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
10011         aLine.append( ' ' );
10012         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
10013         aLine.append( " re S " );
10014     }
10015     aLine.append( "Q\n" );
10016     writeBuffer( aLine.getStr(), aLine.getLength() );
10017 }
10018 
10019 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
10020 {
10021     MARK( "drawHatch" );
10022 
10023     updateGraphicsState();
10024 
10025     if( rPolyPoly.Count() )
10026     {
10027         tools::PolyPolygon     aPolyPoly( rPolyPoly );
10028 
10029         aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
10030         push( PushFlags::LINECOLOR );
10031         setLineColor( rHatch.GetColor() );
10032         getReferenceDevice()->DrawHatch( aPolyPoly, rHatch, false );
10033         pop();
10034     }
10035 }
10036 
10037 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
10038 {
10039     MARK( "drawWallpaper" );
10040 
10041     bool bDrawColor         = false;
10042     bool bDrawGradient      = false;
10043     bool bDrawBitmap        = false;
10044 
10045     BitmapEx aBitmap;
10046     Point aBmpPos = rRect.TopLeft();
10047     Size aBmpSize;
10048     if( rWall.IsBitmap() )
10049     {
10050         aBitmap = rWall.GetBitmap();
10051         aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
10052                                 getMapMode(),
10053                                 getReferenceDevice(),
10054                                 aBitmap.GetPrefSize() );
10055         tools::Rectangle aRect( rRect );
10056         if( rWall.IsRect() )
10057         {
10058             aRect = rWall.GetRect();
10059             aBmpPos = aRect.TopLeft();
10060             aBmpSize = aRect.GetSize();
10061         }
10062         if( rWall.GetStyle() != WallpaperStyle::Scale )
10063         {
10064             if( rWall.GetStyle() != WallpaperStyle::Tile )
10065             {
10066                 bDrawBitmap     = true;
10067                 if( rWall.IsGradient() )
10068                     bDrawGradient = true;
10069                 else
10070                     bDrawColor = true;
10071                 switch( rWall.GetStyle() )
10072                 {
10073                     case WallpaperStyle::TopLeft:
10074                         break;
10075                     case WallpaperStyle::Top:
10076                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10077                         break;
10078                     case WallpaperStyle::Left:
10079                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10080                         break;
10081                     case WallpaperStyle::TopRight:
10082                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10083                         break;
10084                     case WallpaperStyle::Center:
10085                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10086                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10087                         break;
10088                     case WallpaperStyle::Right:
10089                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10090                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10091                         break;
10092                     case WallpaperStyle::BottomLeft:
10093                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10094                         break;
10095                     case WallpaperStyle::Bottom:
10096                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10097                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10098                         break;
10099                     case WallpaperStyle::BottomRight:
10100                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10101                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10102                         break;
10103                     default: ;
10104                 }
10105             }
10106             else
10107             {
10108                 // push the bitmap
10109                 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
10110 
10111                 // convert to page coordinates; this needs to be done here
10112                 // since the emit does not know the page anymore
10113                 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
10114                 m_aPages.back().convertRect( aConvertRect );
10115 
10116                 OStringBuffer aNameBuf(16);
10117                 aNameBuf.append( "Im" );
10118                 aNameBuf.append( rEmit.m_nObject );
10119                 OString aImageName( aNameBuf.makeStringAndClear() );
10120 
10121                 // push the pattern
10122                 OStringBuffer aTilingStream( 32 );
10123                 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
10124                 aTilingStream.append( " 0 0 " );
10125                 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
10126                 aTilingStream.append( " 0 0 cm\n/" );
10127                 aTilingStream.append( aImageName );
10128                 aTilingStream.append( " Do\n" );
10129 
10130                 m_aTilings.emplace_back( );
10131                 m_aTilings.back().m_nObject         = createObject();
10132                 m_aTilings.back().m_aRectangle      = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
10133                 m_aTilings.back().m_pTilingStream   = new SvMemoryStream();
10134                 m_aTilings.back().m_pTilingStream->WriteBytes(
10135                     aTilingStream.getStr(), aTilingStream.getLength() );
10136                 // phase the tiling so wallpaper begins on upper left
10137                 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
10138                     throw o3tl::divide_by_zero();
10139                 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
10140                 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
10141                 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
10142 
10143                 updateGraphicsState();
10144 
10145                 OStringBuffer aObjName( 16 );
10146                 aObjName.append( 'P' );
10147                 aObjName.append( m_aTilings.back().m_nObject );
10148                 OString aPatternName( aObjName.makeStringAndClear() );
10149                 pushResource( ResPattern, aPatternName, m_aTilings.back().m_nObject );
10150 
10151                 // fill a rRect with the pattern
10152                 OStringBuffer aLine( 100 );
10153                 aLine.append( "q /Pattern cs /" );
10154                 aLine.append( aPatternName );
10155                 aLine.append( " scn " );
10156                 m_aPages.back().appendRect( rRect, aLine );
10157                 aLine.append( " f Q\n" );
10158                 writeBuffer( aLine.getStr(), aLine.getLength() );
10159             }
10160         }
10161         else
10162         {
10163             aBmpPos     = aRect.TopLeft();
10164             aBmpSize    = aRect.GetSize();
10165             bDrawBitmap = true;
10166         }
10167 
10168         if( aBitmap.IsTransparent() )
10169         {
10170             if( rWall.IsGradient() )
10171                 bDrawGradient = true;
10172             else
10173                 bDrawColor = true;
10174         }
10175     }
10176     else if( rWall.IsGradient() )
10177         bDrawGradient = true;
10178     else
10179         bDrawColor = true;
10180 
10181     if( bDrawGradient )
10182     {
10183         drawGradient( rRect, rWall.GetGradient() );
10184     }
10185     if( bDrawColor )
10186     {
10187         Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
10188         Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
10189         setLineColor( COL_TRANSPARENT );
10190         setFillColor( rWall.GetColor() );
10191         drawRectangle( rRect );
10192         setLineColor( aOldLineColor );
10193         setFillColor( aOldFillColor );
10194     }
10195     if( bDrawBitmap )
10196     {
10197         // set temporary clip region since aBmpPos and aBmpSize
10198         // may be outside rRect
10199         OStringBuffer aLine( 20 );
10200         aLine.append( "q " );
10201         m_aPages.back().appendRect( rRect, aLine );
10202         aLine.append( " W n\n" );
10203         writeBuffer( aLine.getStr(), aLine.getLength() );
10204         drawBitmap( aBmpPos, aBmpSize, aBitmap );
10205         writeBuffer( "Q\n", 2 );
10206     }
10207 }
10208 
10209 void PDFWriterImpl::updateGraphicsState(Mode const mode)
10210 {
10211     OStringBuffer aLine( 256 );
10212     GraphicsState& rNewState = m_aGraphicsStack.front();
10213     // first set clip region since it might invalidate everything else
10214 
10215     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
10216     {
10217         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
10218 
10219         if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
10220             ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
10221         {
10222             if( m_aCurrentPDFState.m_bClipRegion )
10223             {
10224                 aLine.append( "Q " );
10225                 // invalidate everything but the clip region
10226                 m_aCurrentPDFState = GraphicsState();
10227                 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
10228             }
10229             if( rNewState.m_bClipRegion )
10230             {
10231                 // clip region is always stored in private PDF mapmode
10232                 MapMode aNewMapMode = rNewState.m_aMapMode;
10233                 rNewState.m_aMapMode = m_aMapMode;
10234                 getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
10235                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10236 
10237                 aLine.append("q ");
10238                 if ( rNewState.m_aClipRegion.count() )
10239                 {
10240                     m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
10241                     aLine.append( "W* n\n" );
10242                 }
10243 
10244                 rNewState.m_aMapMode = aNewMapMode;
10245                 getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
10246                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10247             }
10248         }
10249     }
10250 
10251     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
10252     {
10253         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
10254         getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
10255     }
10256 
10257     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
10258     {
10259         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
10260         getReferenceDevice()->SetFont( rNewState.m_aFont );
10261         getReferenceDevice()->ImplNewFont();
10262     }
10263 
10264     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
10265     {
10266         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
10267         getReferenceDevice()->SetLayoutMode( rNewState.m_nLayoutMode );
10268     }
10269 
10270     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
10271     {
10272         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
10273         getReferenceDevice()->SetDigitLanguage( rNewState.m_aDigitLanguage );
10274     }
10275 
10276     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
10277     {
10278         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
10279         if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
10280             rNewState.m_aLineColor != COL_TRANSPARENT )
10281         {
10282             appendStrokingColor( rNewState.m_aLineColor, aLine );
10283             aLine.append( "\n" );
10284         }
10285     }
10286 
10287     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
10288     {
10289         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
10290         if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
10291             rNewState.m_aFillColor != COL_TRANSPARENT )
10292         {
10293             appendNonStrokingColor( rNewState.m_aFillColor, aLine );
10294             aLine.append( "\n" );
10295         }
10296     }
10297 
10298     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
10299     {
10300         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
10301         if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
10302         {
10303             // TODO: switch extended graphicsstate
10304         }
10305     }
10306 
10307     // everything is up to date now
10308     m_aCurrentPDFState = m_aGraphicsStack.front();
10309     if ((mode != NOWRITE) &&  !aLine.isEmpty())
10310         writeBuffer( aLine.getStr(), aLine.getLength() );
10311 }
10312 
10313 /* #i47544# imitate OutputDevice behaviour:
10314 *  if a font with a nontransparent color is set, it overwrites the current
10315 *  text color. OTOH setting the text color will overwrite the color of the font.
10316 */
10317 void PDFWriterImpl::setFont( const vcl::Font& rFont )
10318 {
10319     Color aColor = rFont.GetColor();
10320     if( aColor == COL_TRANSPARENT )
10321         aColor = m_aGraphicsStack.front().m_aFont.GetColor();
10322     m_aGraphicsStack.front().m_aFont = rFont;
10323     m_aGraphicsStack.front().m_aFont.SetColor( aColor );
10324     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
10325 }
10326 
10327 void PDFWriterImpl::push( PushFlags nFlags )
10328 {
10329     OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
10330     m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
10331     m_aGraphicsStack.front().m_nFlags = nFlags;
10332 }
10333 
10334 void PDFWriterImpl::pop()
10335 {
10336     OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
10337     if( m_aGraphicsStack.size() < 2 )
10338         return;
10339 
10340     GraphicsState aState = m_aGraphicsStack.front();
10341     m_aGraphicsStack.pop_front();
10342     GraphicsState& rOld = m_aGraphicsStack.front();
10343 
10344     // move those parameters back that were not pushed
10345     // in the first place
10346     if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
10347         setLineColor( aState.m_aLineColor );
10348     if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
10349         setFillColor( aState.m_aFillColor );
10350     if( ! (aState.m_nFlags & PushFlags::FONT) )
10351         setFont( aState.m_aFont );
10352     if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
10353         setTextColor( aState.m_aFont.GetColor() );
10354     if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
10355         setMapMode( aState.m_aMapMode );
10356     if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
10357     {
10358         // do not use setClipRegion here
10359         // it would convert again assuming the current mapmode
10360         rOld.m_aClipRegion = aState.m_aClipRegion;
10361         rOld.m_bClipRegion = aState.m_bClipRegion;
10362     }
10363     if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
10364         setTextLineColor( aState.m_aTextLineColor );
10365     if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
10366         setOverlineColor( aState.m_aOverlineColor );
10367     if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
10368         setTextAlign( aState.m_aFont.GetAlignment() );
10369     if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
10370         setTextFillColor( aState.m_aFont.GetFillColor() );
10371     if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10372     {
10373         // what ?
10374     }
10375     // invalidate graphics state
10376     m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10377 }
10378 
10379 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10380 {
10381     m_aGraphicsStack.front().m_aMapMode = rMapMode;
10382     getReferenceDevice()->SetMapMode( rMapMode );
10383     m_aCurrentPDFState.m_aMapMode = rMapMode;
10384 }
10385 
10386 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10387 {
10388     basegfx::B2DPolyPolygon aRegion = getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode );
10389     aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode );
10390     m_aGraphicsStack.front().m_aClipRegion = aRegion;
10391     m_aGraphicsStack.front().m_bClipRegion = true;
10392     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10393 }
10394 
10395 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10396 {
10397     if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() )
10398     {
10399         Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10400                                    m_aMapMode,
10401                                    getReferenceDevice(),
10402                                    Point( nX, nY ) ) );
10403         aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10404                                m_aMapMode,
10405                                getReferenceDevice(),
10406                                Point() );
10407         basegfx::B2DHomMatrix aMat;
10408         aMat.translate( aPoint.X(), aPoint.Y() );
10409         m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10410         m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10411     }
10412 }
10413 
10414 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10415 {
10416     basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10417         basegfx::B2DRectangle( rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() ) ) );
10418     intersectClipRegion( aRect );
10419 }
10420 
10421 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10422 {
10423     basegfx::B2DPolyPolygon aRegion( getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ) );
10424     aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode );
10425     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10426     if( m_aGraphicsStack.front().m_bClipRegion )
10427     {
10428         basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10429         aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10430         m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10431     }
10432     else
10433     {
10434         m_aGraphicsStack.front().m_aClipRegion = aRegion;
10435         m_aGraphicsStack.front().m_bClipRegion = true;
10436     }
10437 }
10438 
10439 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
10440 {
10441     if( nPageNr < 0 )
10442         nPageNr = m_nCurrentPage;
10443 
10444     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10445         return;
10446 
10447     m_aNotes.emplace_back( );
10448     m_aNotes.back().m_nObject       = createObject();
10449     m_aNotes.back().m_aContents     = rNote;
10450     m_aNotes.back().m_aRect         = rRect;
10451     // convert to default user space now, since the mapmode may change
10452     m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
10453 
10454     // insert note to page's annotation list
10455     m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
10456 }
10457 
10458 sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
10459 {
10460     if( nPageNr < 0 )
10461         nPageNr = m_nCurrentPage;
10462 
10463     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10464         return -1;
10465 
10466     sal_Int32 nRet = m_aLinks.size();
10467 
10468     m_aLinks.emplace_back( );
10469     m_aLinks.back().m_nObject   = createObject();
10470     m_aLinks.back().m_nPage     = nPageNr;
10471     m_aLinks.back().m_aRect     = rRect;
10472     // convert to default user space now, since the mapmode may change
10473     m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10474 
10475     // insert link to page's annotation list
10476     m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10477 
10478     return nRet;
10479 }
10480 
10481 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
10482 {
10483     if (nPageNr < 0)
10484         nPageNr = m_nCurrentPage;
10485 
10486     if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
10487         return -1;
10488 
10489     sal_Int32 nRet = m_aScreens.size();
10490 
10491     m_aScreens.emplace_back();
10492     m_aScreens.back().m_nObject = createObject();
10493     m_aScreens.back().m_nPage = nPageNr;
10494     m_aScreens.back().m_aRect = rRect;
10495     // Convert to default user space now, since the mapmode may change.
10496     m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10497 
10498     // Insert link to page's annotation list.
10499     m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10500 
10501     return nRet;
10502 }
10503 
10504 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10505 {
10506     if( nPageNr < 0 )
10507         nPageNr = m_nCurrentPage;
10508 
10509     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10510         return -1;
10511 
10512     sal_Int32 nRet = m_aNamedDests.size();
10513 
10514     m_aNamedDests.emplace_back( );
10515     m_aNamedDests.back().m_aDestName = sDestName;
10516     m_aNamedDests.back().m_nPage = nPageNr;
10517     m_aNamedDests.back().m_eType = eType;
10518     m_aNamedDests.back().m_aRect = rRect;
10519     // convert to default user space now, since the mapmode may change
10520     m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10521 
10522     return nRet;
10523 }
10524 
10525 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10526 {
10527     if( nPageNr < 0 )
10528         nPageNr = m_nCurrentPage;
10529 
10530     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10531         return -1;
10532 
10533     sal_Int32 nRet = m_aDests.size();
10534 
10535     m_aDests.emplace_back( );
10536     m_aDests.back().m_nPage = nPageNr;
10537     m_aDests.back().m_eType = eType;
10538     m_aDests.back().m_aRect = rRect;
10539     // convert to default user space now, since the mapmode may change
10540     m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10541 
10542     return nRet;
10543 }
10544 
10545 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10546 {
10547     return m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10548 }
10549 
10550 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10551 {
10552     if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
10553         return;
10554     if( nDestId < 0 || nDestId >= static_cast<sal_Int32>(m_aDests.size()) )
10555         return;
10556 
10557     m_aLinks[ nLinkId ].m_nDest = nDestId;
10558 }
10559 
10560 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10561 {
10562     if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
10563         return;
10564 
10565     m_aLinks[ nLinkId ].m_nDest = -1;
10566 
10567     using namespace ::com::sun::star;
10568 
10569     if (!m_xTrans.is())
10570     {
10571         uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
10572         m_xTrans = util::URLTransformer::create(xContext);
10573     }
10574 
10575     util::URL aURL;
10576     aURL.Complete = rURL;
10577 
10578     m_xTrans->parseStrict( aURL );
10579 
10580     m_aLinks[ nLinkId ].m_aURL  = aURL.Complete;
10581 }
10582 
10583 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10584 {
10585     if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
10586         return;
10587 
10588     m_aScreens[nScreenId].m_aURL = rURL;
10589 }
10590 
10591 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10592 {
10593     if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
10594         return;
10595 
10596     m_aScreens[nScreenId].m_aTempFileURL = rURL;
10597     m_aScreens[nScreenId].m_nTempFileObject = createObject();
10598 }
10599 
10600 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10601 {
10602     m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10603 }
10604 
10605 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
10606 {
10607     // create new item
10608     sal_Int32 nNewItem = m_aOutline.size();
10609     m_aOutline.emplace_back( );
10610 
10611     // set item attributes
10612     setOutlineItemParent( nNewItem, nParent );
10613     setOutlineItemText( nNewItem, rText );
10614     setOutlineItemDest( nNewItem, nDestID );
10615 
10616     return nNewItem;
10617 }
10618 
10619 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10620 {
10621     if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
10622         return;
10623 
10624     if( nNewParent < 0 || nNewParent >= static_cast<sal_Int32>(m_aOutline.size()) || nNewParent == nItem )
10625     {
10626         nNewParent = 0;
10627     }
10628     // insert item to new parent's list of children
10629     m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10630 }
10631 
10632 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText )
10633 {
10634     if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
10635         return;
10636 
10637     m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10638 }
10639 
10640 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10641 {
10642     if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) ) // item does not exist
10643         return;
10644     if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) ) // dest does not exist
10645         return;
10646     m_aOutline[nItem].m_nDestID = nDestID;
10647 }
10648 
10649 const sal_Char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
10650 {
10651     static std::map< PDFWriter::StructElement, const char* > aTagStrings;
10652     if( aTagStrings.empty() )
10653     {
10654         aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
10655         aTagStrings[ PDFWriter::Document ]      = "Document";
10656         aTagStrings[ PDFWriter::Part ]          = "Part";
10657         aTagStrings[ PDFWriter::Article ]       = "Art";
10658         aTagStrings[ PDFWriter::Section ]       = "Sect";
10659         aTagStrings[ PDFWriter::Division ]      = "Div";
10660         aTagStrings[ PDFWriter::BlockQuote ]    = "BlockQuote";
10661         aTagStrings[ PDFWriter::Caption ]       = "Caption";
10662         aTagStrings[ PDFWriter::TOC ]           = "TOC";
10663         aTagStrings[ PDFWriter::TOCI ]          = "TOCI";
10664         aTagStrings[ PDFWriter::Index ]         = "Index";
10665         aTagStrings[ PDFWriter::Paragraph ]     = "P";
10666         aTagStrings[ PDFWriter::Heading ]       = "H";
10667         aTagStrings[ PDFWriter::H1 ]            = "H1";
10668         aTagStrings[ PDFWriter::H2 ]            = "H2";
10669         aTagStrings[ PDFWriter::H3 ]            = "H3";
10670         aTagStrings[ PDFWriter::H4 ]            = "H4";
10671         aTagStrings[ PDFWriter::H5 ]            = "H5";
10672         aTagStrings[ PDFWriter::H6 ]            = "H6";
10673         aTagStrings[ PDFWriter::List ]          = "L";
10674         aTagStrings[ PDFWriter::ListItem ]      = "LI";
10675         aTagStrings[ PDFWriter::LILabel ]       = "Lbl";
10676         aTagStrings[ PDFWriter::LIBody ]        = "LBody";
10677         aTagStrings[ PDFWriter::Table ]         = "Table";
10678         aTagStrings[ PDFWriter::TableRow ]      = "TR";
10679         aTagStrings[ PDFWriter::TableHeader ]   = "TH";
10680         aTagStrings[ PDFWriter::TableData ]     = "TD";
10681         aTagStrings[ PDFWriter::Span ]          = "Span";
10682         aTagStrings[ PDFWriter::Quote ]         = "Quote";
10683         aTagStrings[ PDFWriter::Note ]          = "Note";
10684         aTagStrings[ PDFWriter::Reference ]     = "Reference";
10685         aTagStrings[ PDFWriter::BibEntry ]      = "BibEntry";
10686         aTagStrings[ PDFWriter::Code ]          = "Code";
10687         aTagStrings[ PDFWriter::Link ]          = "Link";
10688         aTagStrings[ PDFWriter::Figure ]        = "Figure";
10689         aTagStrings[ PDFWriter::Formula ]       = "Formula";
10690         aTagStrings[ PDFWriter::Form ]          = "Form";
10691     }
10692 
10693     std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
10694 
10695     return it != aTagStrings.end() ? it->second : "Div";
10696 }
10697 
10698 void PDFWriterImpl::beginStructureElementMCSeq()
10699 {
10700     if( m_bEmitStructure &&
10701         m_nCurrentStructElement > 0 && // StructTreeRoot
10702         ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10703         )
10704     {
10705         PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10706         OStringBuffer aLine( 128 );
10707         sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10708         aLine.append( "/" );
10709         if( !rEle.m_aAlias.isEmpty() )
10710             aLine.append( rEle.m_aAlias );
10711         else
10712             aLine.append( getStructureTag( rEle.m_eType ) );
10713         aLine.append( "<</MCID " );
10714         aLine.append( nMCID );
10715         aLine.append( ">>BDC\n" );
10716         writeBuffer( aLine.getStr(), aLine.getLength() );
10717 
10718         // update the element's content list
10719         SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10720                  << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10721                  << rEle.m_nFirstPageObject);
10722         rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
10723         // update the page's mcid parent list
10724         m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10725         // mark element MC sequence as open
10726         rEle.m_bOpenMCSeq = true;
10727     }
10728     // handle artifacts
10729     else if( ! m_bEmitStructure && m_aContext.Tagged &&
10730                m_nCurrentStructElement > 0 &&
10731                m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
10732              ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10733              )
10734     {
10735         OStringBuffer aLine( 128 );
10736         aLine.append( "/Artifact BMC\n" );
10737         writeBuffer( aLine.getStr(), aLine.getLength() );
10738         // mark element MC sequence as open
10739         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10740     }
10741 }
10742 
10743 void PDFWriterImpl::endStructureElementMCSeq()
10744 {
10745     if( m_nCurrentStructElement > 0 && // StructTreeRoot
10746         ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
10747         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
10748         )
10749     {
10750         writeBuffer( "EMC\n", 4 );
10751         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10752     }
10753 }
10754 
10755 bool PDFWriterImpl::checkEmitStructure()
10756 {
10757     bool bEmit = false;
10758     if( m_aContext.Tagged )
10759     {
10760         bEmit = true;
10761         sal_Int32 nEle = m_nCurrentStructElement;
10762         while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
10763         {
10764             if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
10765             {
10766                 bEmit = false;
10767                 break;
10768             }
10769             nEle = m_aStructure[ nEle ].m_nParentElement;
10770         }
10771     }
10772     return bEmit;
10773 }
10774 
10775 sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
10776 {
10777     if( m_nCurrentPage < 0 )
10778         return -1;
10779 
10780     if( ! m_aContext.Tagged )
10781         return -1;
10782 
10783     // close eventual current MC sequence
10784     endStructureElementMCSeq();
10785 
10786     if( m_nCurrentStructElement == 0 &&
10787         eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
10788     {
10789         // struct tree root hit, but not beginning document
10790         // this might happen with setCurrentStructureElement
10791         // silently insert structure into document again if one properly exists
10792         if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10793         {
10794             PDFWriter::StructElement childType = PDFWriter::NonStructElement;
10795             sal_Int32 nNewCurElement = 0;
10796             const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10797             for( std::list< sal_Int32 >::const_iterator it = rRootChildren.begin();
10798                  childType != PDFWriter::Document && it != rRootChildren.end(); ++it )
10799             {
10800                 nNewCurElement = *it;
10801                 childType = m_aStructure[ nNewCurElement ].m_eType;
10802             }
10803             if( childType == PDFWriter::Document )
10804             {
10805                 m_nCurrentStructElement = nNewCurElement;
10806                 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10807             }
10808             else {
10809                 OSL_FAIL( "document structure in disorder !" );
10810             }
10811         }
10812         else {
10813             OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10814         }
10815     }
10816 
10817     sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10818     m_aStructure.emplace_back( );
10819     PDFStructureElement& rEle = m_aStructure.back();
10820     rEle.m_eType            = eType;
10821     rEle.m_nOwnElement      = nNewId;
10822     rEle.m_nParentElement   = m_nCurrentStructElement;
10823     rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10824     m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10825     m_nCurrentStructElement = nNewId;
10826 
10827     // handle alias names
10828     if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
10829     {
10830         OStringBuffer aNameBuf( rAlias.getLength() );
10831         appendName( rAlias, aNameBuf );
10832         OString aAliasName( aNameBuf.makeStringAndClear() );
10833         rEle.m_aAlias = aAliasName;
10834         m_aRoleMap[ aAliasName ] = getStructureTag( eType );
10835     }
10836 
10837     if (g_bDebugDisableCompression)
10838     {
10839         OStringBuffer aLine( "beginStructureElement " );
10840         aLine.append( m_nCurrentStructElement );
10841         aLine.append( ": " );
10842         aLine.append( getStructureTag( eType ) );
10843         if( !rEle.m_aAlias.isEmpty() )
10844         {
10845             aLine.append( " aliased as \"" );
10846             aLine.append( rEle.m_aAlias );
10847             aLine.append( '\"' );
10848         }
10849         emitComment( aLine.getStr() );
10850     }
10851 
10852     // check whether to emit structure henceforth
10853     m_bEmitStructure = checkEmitStructure();
10854 
10855     if( m_bEmitStructure ) // don't create nonexistent objects
10856     {
10857         rEle.m_nObject      = createObject();
10858         // update parent's kids list
10859         m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
10860     }
10861     return nNewId;
10862 }
10863 
10864 void PDFWriterImpl::endStructureElement()
10865 {
10866     if( m_nCurrentPage < 0 )
10867         return;
10868 
10869     if( ! m_aContext.Tagged )
10870         return;
10871 
10872     if( m_nCurrentStructElement == 0 )
10873     {
10874         // hit the struct tree root, that means there is an endStructureElement
10875         // without corresponding beginStructureElement
10876         return;
10877     }
10878 
10879     // end the marked content sequence
10880     endStructureElementMCSeq();
10881 
10882     OStringBuffer aLine;
10883     if (g_bDebugDisableCompression)
10884     {
10885         aLine.append( "endStructureElement " );
10886         aLine.append( m_nCurrentStructElement );
10887         aLine.append( ": " );
10888         aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10889         if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10890         {
10891             aLine.append( " aliased as \"" );
10892             aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10893             aLine.append( '\"' );
10894         }
10895     }
10896 
10897     // "end" the structure element, the parent becomes current element
10898     m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
10899 
10900     // check whether to emit structure henceforth
10901     m_bEmitStructure = checkEmitStructure();
10902 
10903     if (g_bDebugDisableCompression && m_bEmitStructure)
10904     {
10905         emitComment( aLine.getStr() );
10906     }
10907 }
10908 
10909 /*
10910  * This function adds an internal structure list container to overcome the 8191 elements array limitation
10911  * in kids element emission.
10912  * Recursive function
10913  *
10914  */
10915 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
10916 {
10917     if( rEle.m_eType == PDFWriter::NonStructElement &&
10918         rEle.m_nOwnElement != rEle.m_nParentElement )
10919         return;
10920 
10921     for (auto const& child : rEle.m_aChildren)
10922     {
10923         if( child > 0 && child < sal_Int32(m_aStructure.size()) )
10924         {
10925             PDFStructureElement& rChild = m_aStructure[ child ];
10926             if( rChild.m_eType != PDFWriter::NonStructElement )
10927             {
10928                 //triggered when a child of the rEle element is found
10929                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
10930                     addInternalStructureContainer( rChild );//examine the child
10931                 else
10932                 {
10933                     OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
10934                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
10935                 }
10936             }
10937         }
10938         else
10939         {
10940             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
10941             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
10942         }
10943     }
10944 
10945     if( rEle.m_nOwnElement != rEle.m_nParentElement )
10946     {
10947         if( !rEle.m_aKids.empty() )
10948         {
10949             if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
10950                 //then we need to add the containers for the kids elements
10951                 // a list to be used for the new kid element
10952                 std::list< PDFStructureElementKid > aNewKids;
10953                 std::list< sal_Int32 > aNewChildren;
10954 
10955                 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
10956                 OString aAliasName( "Div" );
10957                 m_aRoleMap[ aAliasName ] = getStructureTag( PDFWriter::Division );
10958 
10959                 while( rEle.m_aKids.size() > ncMaxPDFArraySize )
10960                 {
10961                     sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
10962                     sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10963                     m_aStructure.emplace_back( );
10964                     PDFStructureElement& rEleNew = m_aStructure.back();
10965                     rEleNew.m_aAlias            = aAliasName;
10966                     rEleNew.m_eType             = PDFWriter::Division; // a new Div type container
10967                     rEleNew.m_nOwnElement       = nNewId;
10968                     rEleNew.m_nParentElement    = nCurrentStructElement;
10969                     //inherit the same page as the first child to be reparented
10970                     rEleNew.m_nFirstPageObject  = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
10971                     rEleNew.m_nObject           = createObject();//assign a PDF object number
10972                     //add the object to the kid list of the parent
10973                     aNewKids.emplace_back( rEleNew.m_nObject );
10974                     aNewChildren.push_back( nNewId );
10975 
10976                     std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
10977                     std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
10978                     advance( aChildEndIt, ncMaxPDFArraySize );
10979                     advance( aKidEndIt, ncMaxPDFArraySize );
10980 
10981                     rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
10982                                             rEle.m_aKids,
10983                                             rEle.m_aKids.begin(),
10984                                             aKidEndIt );
10985                     rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
10986                                                 rEle.m_aChildren,
10987                                                 rEle.m_aChildren.begin(),
10988                                                 aChildEndIt );
10989                     // set the kid's new parent
10990                     for (auto const& child : rEleNew.m_aChildren)
10991                     {
10992                         m_aStructure[ child ].m_nParentElement = nNewId;
10993                     }
10994                 }
10995                 //finally add the new kids resulting from the container added
10996                 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
10997                 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
10998             }
10999         }
11000     }
11001 }
11002 
11003 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
11004 {
11005     bool bSuccess = false;
11006 
11007     if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
11008     {
11009         // end eventual previous marked content sequence
11010         endStructureElementMCSeq();
11011 
11012         m_nCurrentStructElement = nEle;
11013         m_bEmitStructure = checkEmitStructure();
11014         if (g_bDebugDisableCompression)
11015         {
11016             OStringBuffer aLine( "setCurrentStructureElement " );
11017             aLine.append( m_nCurrentStructElement );
11018             aLine.append( ": " );
11019             aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
11020             if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
11021             {
11022                 aLine.append( " aliased as \"" );
11023                 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
11024                 aLine.append( '\"' );
11025             }
11026             if( ! m_bEmitStructure )
11027                 aLine.append( " (inside NonStruct)" );
11028             emitComment( aLine.getStr() );
11029         }
11030         bSuccess = true;
11031     }
11032 
11033     return bSuccess;
11034 }
11035 
11036 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
11037 {
11038     if( !m_aContext.Tagged )
11039         return false;
11040 
11041     bool bInsert = false;
11042     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11043     {
11044         PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
11045         switch( eAttr )
11046         {
11047             case PDFWriter::Placement:
11048                 if( eVal == PDFWriter::Block        ||
11049                     eVal == PDFWriter::Inline       ||
11050                     eVal == PDFWriter::Before       ||
11051                     eVal == PDFWriter::Start        ||
11052                     eVal == PDFWriter::End )
11053                     bInsert = true;
11054                 break;
11055             case PDFWriter::WritingMode:
11056                 if( eVal == PDFWriter::LrTb         ||
11057                     eVal == PDFWriter::RlTb         ||
11058                     eVal == PDFWriter::TbRl )
11059                 {
11060                     bInsert = true;
11061                 }
11062                 break;
11063             case PDFWriter::TextAlign:
11064                 if( eVal == PDFWriter::Start        ||
11065                     eVal == PDFWriter::Center       ||
11066                     eVal == PDFWriter::End          ||
11067                     eVal == PDFWriter::Justify )
11068                 {
11069                     if( eType == PDFWriter::Paragraph   ||
11070                         eType == PDFWriter::Heading     ||
11071                         eType == PDFWriter::H1          ||
11072                         eType == PDFWriter::H2          ||
11073                         eType == PDFWriter::H3          ||
11074                         eType == PDFWriter::H4          ||
11075                         eType == PDFWriter::H5          ||
11076                         eType == PDFWriter::H6          ||
11077                         eType == PDFWriter::List        ||
11078                         eType == PDFWriter::ListItem    ||
11079                         eType == PDFWriter::LILabel     ||
11080                         eType == PDFWriter::LIBody      ||
11081                         eType == PDFWriter::Table       ||
11082                         eType == PDFWriter::TableRow    ||
11083                         eType == PDFWriter::TableHeader ||
11084                         eType == PDFWriter::TableData )
11085                     {
11086                         bInsert = true;
11087                     }
11088                 }
11089                 break;
11090             case PDFWriter::Width:
11091             case PDFWriter::Height:
11092                 if( eVal == PDFWriter::Auto )
11093                 {
11094                     if( eType == PDFWriter::Figure      ||
11095                         eType == PDFWriter::Formula     ||
11096                         eType == PDFWriter::Form        ||
11097                         eType == PDFWriter::Table       ||
11098                         eType == PDFWriter::TableHeader ||
11099                         eType == PDFWriter::TableData )
11100                     {
11101                         bInsert = true;
11102                     }
11103                 }
11104                 break;
11105             case PDFWriter::BlockAlign:
11106                 if( eVal == PDFWriter::Before       ||
11107                     eVal == PDFWriter::Middle       ||
11108                     eVal == PDFWriter::After        ||
11109                     eVal == PDFWriter::Justify )
11110                 {
11111                     if( eType == PDFWriter::TableHeader ||
11112                         eType == PDFWriter::TableData )
11113                     {
11114                         bInsert = true;
11115                     }
11116                 }
11117                 break;
11118             case PDFWriter::InlineAlign:
11119                 if( eVal == PDFWriter::Start        ||
11120                     eVal == PDFWriter::Center       ||
11121                     eVal == PDFWriter::End )
11122                 {
11123                     if( eType == PDFWriter::TableHeader ||
11124                         eType == PDFWriter::TableData )
11125                     {
11126                         bInsert = true;
11127                     }
11128                 }
11129                 break;
11130             case PDFWriter::LineHeight:
11131                 if( eVal == PDFWriter::Normal       ||
11132                     eVal == PDFWriter::Auto )
11133                 {
11134                     // only for ILSE and BLSE
11135                     if( eType == PDFWriter::Paragraph   ||
11136                         eType == PDFWriter::Heading     ||
11137                         eType == PDFWriter::H1          ||
11138                         eType == PDFWriter::H2          ||
11139                         eType == PDFWriter::H3          ||
11140                         eType == PDFWriter::H4          ||
11141                         eType == PDFWriter::H5          ||
11142                         eType == PDFWriter::H6          ||
11143                         eType == PDFWriter::List        ||
11144                         eType == PDFWriter::ListItem    ||
11145                         eType == PDFWriter::LILabel     ||
11146                         eType == PDFWriter::LIBody      ||
11147                         eType == PDFWriter::Table       ||
11148                         eType == PDFWriter::TableRow    ||
11149                         eType == PDFWriter::TableHeader ||
11150                         eType == PDFWriter::TableData   ||
11151                         eType == PDFWriter::Span        ||
11152                         eType == PDFWriter::Quote       ||
11153                         eType == PDFWriter::Note        ||
11154                         eType == PDFWriter::Reference   ||
11155                         eType == PDFWriter::BibEntry    ||
11156                         eType == PDFWriter::Code        ||
11157                         eType == PDFWriter::Link )
11158                     {
11159                         bInsert = true;
11160                     }
11161                 }
11162                 break;
11163             case PDFWriter::TextDecorationType:
11164                 if( eVal == PDFWriter::NONE         ||
11165                     eVal == PDFWriter::Underline    ||
11166                     eVal == PDFWriter::Overline     ||
11167                     eVal == PDFWriter::LineThrough )
11168                 {
11169                     // only for ILSE and BLSE
11170                     if( eType == PDFWriter::Paragraph   ||
11171                         eType == PDFWriter::Heading     ||
11172                         eType == PDFWriter::H1          ||
11173                         eType == PDFWriter::H2          ||
11174                         eType == PDFWriter::H3          ||
11175                         eType == PDFWriter::H4          ||
11176                         eType == PDFWriter::H5          ||
11177                         eType == PDFWriter::H6          ||
11178                         eType == PDFWriter::List        ||
11179                         eType == PDFWriter::ListItem    ||
11180                         eType == PDFWriter::LILabel     ||
11181                         eType == PDFWriter::LIBody      ||
11182                         eType == PDFWriter::Table       ||
11183                         eType == PDFWriter::TableRow    ||
11184                         eType == PDFWriter::TableHeader ||
11185                         eType == PDFWriter::TableData   ||
11186                         eType == PDFWriter::Span        ||
11187                         eType == PDFWriter::Quote       ||
11188                         eType == PDFWriter::Note        ||
11189                         eType == PDFWriter::Reference   ||
11190                         eType == PDFWriter::BibEntry    ||
11191                         eType == PDFWriter::Code        ||
11192                         eType == PDFWriter::Link )
11193                     {
11194                         bInsert = true;
11195                     }
11196                 }
11197                 break;
11198             case PDFWriter::ListNumbering:
11199                 if( eVal == PDFWriter::NONE         ||
11200                     eVal == PDFWriter::Disc         ||
11201                     eVal == PDFWriter::Circle       ||
11202                     eVal == PDFWriter::Square       ||
11203                     eVal == PDFWriter::Decimal      ||
11204                     eVal == PDFWriter::UpperRoman   ||
11205                     eVal == PDFWriter::LowerRoman   ||
11206                     eVal == PDFWriter::UpperAlpha   ||
11207                     eVal == PDFWriter::LowerAlpha )
11208                 {
11209                     if( eType == PDFWriter::List )
11210                         bInsert = true;
11211                 }
11212                 break;
11213             default: break;
11214         }
11215     }
11216 
11217     if( bInsert )
11218         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
11219     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11220         SAL_INFO("vcl.pdfwriter",
11221                  "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
11222                  << ", " << getAttributeValueTag( eVal )
11223                  << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
11224                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr()
11225                  << ") element");
11226 
11227     return bInsert;
11228 }
11229 
11230 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
11231 {
11232     if( ! m_aContext.Tagged )
11233         return false;
11234 
11235     bool bInsert = false;
11236     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11237     {
11238         if( eAttr == PDFWriter::Language )
11239         {
11240             m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
11241             return true;
11242         }
11243 
11244         PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
11245         switch( eAttr )
11246         {
11247             case PDFWriter::SpaceBefore:
11248             case PDFWriter::SpaceAfter:
11249             case PDFWriter::StartIndent:
11250             case PDFWriter::EndIndent:
11251                 // just for BLSE
11252                 if( eType == PDFWriter::Paragraph   ||
11253                     eType == PDFWriter::Heading     ||
11254                     eType == PDFWriter::H1          ||
11255                     eType == PDFWriter::H2          ||
11256                     eType == PDFWriter::H3          ||
11257                     eType == PDFWriter::H4          ||
11258                     eType == PDFWriter::H5          ||
11259                     eType == PDFWriter::H6          ||
11260                     eType == PDFWriter::List        ||
11261                     eType == PDFWriter::ListItem    ||
11262                     eType == PDFWriter::LILabel     ||
11263                     eType == PDFWriter::LIBody      ||
11264                     eType == PDFWriter::Table       ||
11265                     eType == PDFWriter::TableRow    ||
11266                     eType == PDFWriter::TableHeader ||
11267                     eType == PDFWriter::TableData )
11268                 {
11269                     bInsert = true;
11270                 }
11271                 break;
11272             case PDFWriter::TextIndent:
11273                 // paragraph like BLSE and additional elements
11274                 if( eType == PDFWriter::Paragraph   ||
11275                     eType == PDFWriter::Heading     ||
11276                     eType == PDFWriter::H1          ||
11277                     eType == PDFWriter::H2          ||
11278                     eType == PDFWriter::H3          ||
11279                     eType == PDFWriter::H4          ||
11280                     eType == PDFWriter::H5          ||
11281                     eType == PDFWriter::H6          ||
11282                     eType == PDFWriter::LILabel     ||
11283                     eType == PDFWriter::LIBody      ||
11284                     eType == PDFWriter::TableHeader ||
11285                     eType == PDFWriter::TableData )
11286                 {
11287                     bInsert = true;
11288                 }
11289                 break;
11290             case PDFWriter::Width:
11291             case PDFWriter::Height:
11292                 if( eType == PDFWriter::Figure      ||
11293                     eType == PDFWriter::Formula     ||
11294                     eType == PDFWriter::Form        ||
11295                     eType == PDFWriter::Table       ||
11296                     eType == PDFWriter::TableHeader ||
11297                     eType == PDFWriter::TableData )
11298                 {
11299                     bInsert = true;
11300                 }
11301                 break;
11302             case PDFWriter::LineHeight:
11303             case PDFWriter::BaselineShift:
11304                 // only for ILSE and BLSE
11305                 if( eType == PDFWriter::Paragraph   ||
11306                     eType == PDFWriter::Heading     ||
11307                     eType == PDFWriter::H1          ||
11308                     eType == PDFWriter::H2          ||
11309                     eType == PDFWriter::H3          ||
11310                     eType == PDFWriter::H4          ||
11311                     eType == PDFWriter::H5          ||
11312                     eType == PDFWriter::H6          ||
11313                     eType == PDFWriter::List        ||
11314                     eType == PDFWriter::ListItem    ||
11315                     eType == PDFWriter::LILabel     ||
11316                     eType == PDFWriter::LIBody      ||
11317                     eType == PDFWriter::Table       ||
11318                     eType == PDFWriter::TableRow    ||
11319                     eType == PDFWriter::TableHeader ||
11320                     eType == PDFWriter::TableData   ||
11321                     eType == PDFWriter::Span        ||
11322                     eType == PDFWriter::Quote       ||
11323                     eType == PDFWriter::Note        ||
11324                     eType == PDFWriter::Reference   ||
11325                     eType == PDFWriter::BibEntry    ||
11326                     eType == PDFWriter::Code        ||
11327                     eType == PDFWriter::Link )
11328                 {
11329                         bInsert = true;
11330                 }
11331                 break;
11332             case PDFWriter::RowSpan:
11333             case PDFWriter::ColSpan:
11334                 // only for table cells
11335                 if( eType == PDFWriter::TableHeader ||
11336                     eType == PDFWriter::TableData )
11337                 {
11338                     bInsert = true;
11339                 }
11340                 break;
11341             case PDFWriter::LinkAnnotation:
11342                 if( eType == PDFWriter::Link )
11343                     bInsert = true;
11344                 break;
11345             default: break;
11346         }
11347     }
11348 
11349     if( bInsert )
11350         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
11351     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11352         SAL_INFO("vcl.pdfwriter",
11353                  "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
11354                  << ", " << static_cast<int>(nValue)
11355                  << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
11356                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr()
11357                  << ") element");
11358 
11359     return bInsert;
11360 }
11361 
11362 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
11363 {
11364     sal_Int32 nPageNr = m_nCurrentPage;
11365     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) || !m_aContext.Tagged )
11366         return;
11367 
11368     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11369     {
11370         PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
11371         if( eType == PDFWriter::Figure      ||
11372             eType == PDFWriter::Formula     ||
11373             eType == PDFWriter::Form        ||
11374             eType == PDFWriter::Table )
11375         {
11376             m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
11377             // convert to default user space now, since the mapmode may change
11378             m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11379         }
11380     }
11381 }
11382 
11383 void PDFWriterImpl::setActualText( const OUString& rText )
11384 {
11385     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11386     {
11387         m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11388     }
11389 }
11390 
11391 void PDFWriterImpl::setAlternateText( const OUString& rText )
11392 {
11393     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11394     {
11395         m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11396     }
11397 }
11398 
11399 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11400 {
11401     if( nPageNr < 0 )
11402         nPageNr = m_nCurrentPage;
11403 
11404     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
11405         return;
11406 
11407     m_aPages[ nPageNr ].m_eTransition   = eType;
11408     m_aPages[ nPageNr ].m_nTransTime    = nMilliSec;
11409 }
11410 
11411 void PDFWriterImpl::ensureUniqueRadioOnValues()
11412 {
11413     // loop over radio groups
11414     for (auto const& group : m_aRadioGroupWidgets)
11415     {
11416         PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11417         // check whether all kids have a unique OnValue
11418         std::unordered_map< OUString, sal_Int32 > aOnValues;
11419         bool bIsUnique = true;
11420         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11421         {
11422             const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11423             SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11424             if( aOnValues.find( rVal ) == aOnValues.end() )
11425             {
11426                 aOnValues[ rVal ] = 1;
11427             }
11428             else
11429             {
11430                 bIsUnique = false;
11431                 break;
11432             }
11433         }
11434         if( ! bIsUnique )
11435         {
11436             SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11437             // make unique by using ascending OnValues
11438             int nKid = 0;
11439             for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11440             {
11441                 PDFWidget& rKid = m_aWidgets[nKidIndex];
11442                 rKid.m_aOnValue = OUString::number( nKid+1 );
11443                 if( rKid.m_aValue != "Off" )
11444                     rKid.m_aValue = rKid.m_aOnValue;
11445                 ++nKid;
11446             }
11447         }
11448         // finally move the "Yes" appearance to the OnValue appearance
11449         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11450         {
11451             PDFWidget& rKid = m_aWidgets[nKidIndex];
11452             PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" );
11453             if( app_it != rKid.m_aAppearances.end() )
11454             {
11455                 PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" );
11456                 if( stream_it != app_it->second.end() )
11457                 {
11458                     SvMemoryStream* pStream = stream_it->second;
11459                     app_it->second.erase( stream_it );
11460                     OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11461                     appendName( rKid.m_aOnValue, aBuf );
11462                     (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11463                 }
11464                 else
11465                     SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11466             }
11467             // update selected radio button
11468             if( rKid.m_aValue != "Off" )
11469             {
11470                 rGroupWidget.m_aValue = rKid.m_aValue;
11471             }
11472         }
11473     }
11474 }
11475 
11476 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11477 {
11478     sal_Int32 nRadioGroupWidget = -1;
11479 
11480     std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11481 
11482     if( it == m_aRadioGroupWidgets.end() )
11483     {
11484         m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11485             sal_Int32(m_aWidgets.size());
11486 
11487         // new group, insert the radiobutton
11488         m_aWidgets.emplace_back( );
11489         m_aWidgets.back().m_nObject     = createObject();
11490         m_aWidgets.back().m_nPage       = m_nCurrentPage;
11491         m_aWidgets.back().m_eType       = PDFWriter::RadioButton;
11492         m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11493         m_aWidgets.back().m_nFlags |= 0x0000C000;   // NoToggleToOff and Radio bits
11494 
11495         createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11496     }
11497     else
11498         nRadioGroupWidget = it->second;
11499 
11500     return nRadioGroupWidget;
11501 }
11502 
11503 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11504 {
11505     if( nPageNr < 0 )
11506         nPageNr = m_nCurrentPage;
11507 
11508     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
11509         return -1;
11510 
11511     bool sigHidden(true);
11512     sal_Int32 nNewWidget = m_aWidgets.size();
11513     m_aWidgets.emplace_back( );
11514 
11515     m_aWidgets.back().m_nObject         = createObject();
11516     m_aWidgets.back().m_aRect           = rControl.Location;
11517     m_aWidgets.back().m_nPage           = nPageNr;
11518     m_aWidgets.back().m_eType           = rControl.getType();
11519 
11520     sal_Int32 nRadioGroupWidget = -1;
11521     // for unknown reasons the radio buttons of a radio group must not have a
11522     // field name, else the buttons are in fact check boxes -
11523     // that is multiple buttons of the radio group can be selected
11524     if( rControl.getType() == PDFWriter::RadioButton )
11525         nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11526     else
11527     {
11528         createWidgetFieldName( nNewWidget, rControl );
11529     }
11530 
11531     // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11532     PDFWidget& rNewWidget           = m_aWidgets[nNewWidget];
11533     rNewWidget.m_aDescription       = rControl.Description;
11534     rNewWidget.m_aText              = rControl.Text;
11535     rNewWidget.m_nTextStyle         = rControl.TextStyle &
11536         (  DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11537            DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11538            DrawTextFlags::MultiLine | DrawTextFlags::WordBreak  );
11539     rNewWidget.m_nTabOrder          = rControl.TabOrder;
11540 
11541     // various properties are set via the flags (/Ff) property of the field dict
11542     if( rControl.ReadOnly )
11543         rNewWidget.m_nFlags |= 1;
11544     if( rControl.getType() == PDFWriter::PushButton )
11545     {
11546         const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11547         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11548             rNewWidget.m_nTextStyle =
11549                 DrawTextFlags::Center | DrawTextFlags::VCenter |
11550                 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11551 
11552         rNewWidget.m_nFlags |= 0x00010000;
11553         if( !rBtn.URL.isEmpty() )
11554             rNewWidget.m_aListEntries.push_back( rBtn.URL );
11555         rNewWidget.m_bSubmit    = rBtn.Submit;
11556         rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
11557         rNewWidget.m_nDest      = rBtn.Dest;
11558         createDefaultPushButtonAppearance( rNewWidget, rBtn );
11559     }
11560     else if( rControl.getType() == PDFWriter::RadioButton )
11561     {
11562         const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
11563         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11564             rNewWidget.m_nTextStyle =
11565                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11566         /*  PDF sees a RadioButton group as one radio button with
11567          *  children which are in turn check boxes
11568          *
11569          *  so we need to create a radio button on demand for a new group
11570          *  and insert a checkbox for each RadioButtonWidget as its child
11571          */
11572         rNewWidget.m_eType          = PDFWriter::CheckBox;
11573         rNewWidget.m_nRadioGroup    = rBtn.RadioGroup;
11574 
11575         SAL_WARN_IF( nRadioGroupWidget < 0 || nRadioGroupWidget >= static_cast<sal_Int32>(m_aWidgets.size()), "vcl.pdfwriter", "no radio group parent" );
11576 
11577         PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
11578         rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
11579         rRadioButton.m_aKidsIndex.push_back( nNewWidget );
11580         rNewWidget.m_nParent = rRadioButton.m_nObject;
11581 
11582         rNewWidget.m_aValue     = "Off";
11583         rNewWidget.m_aOnValue   = rBtn.OnValue;
11584         if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
11585         {
11586             rNewWidget.m_aValue     = rNewWidget.m_aOnValue;
11587             rRadioButton.m_aValue   = rNewWidget.m_aOnValue;
11588         }
11589         createDefaultRadioButtonAppearance( rNewWidget, rBtn );
11590 
11591         // union rect of radio group
11592         tools::Rectangle aRect = rNewWidget.m_aRect;
11593         m_aPages[ nPageNr ].convertRect( aRect );
11594         rRadioButton.m_aRect.Union( aRect );
11595     }
11596     else if( rControl.getType() == PDFWriter::CheckBox )
11597     {
11598         const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
11599         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11600             rNewWidget.m_nTextStyle =
11601                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11602 
11603         rNewWidget.m_aValue = rBox.Checked ? OUString("Yes") : OUString("Off" );
11604         // create default appearance before m_aRect gets transformed
11605         createDefaultCheckBoxAppearance( rNewWidget, rBox );
11606     }
11607     else if( rControl.getType() == PDFWriter::ListBox )
11608     {
11609         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11610             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11611 
11612         const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
11613         rNewWidget.m_aListEntries     = rLstBox.Entries;
11614         rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
11615         rNewWidget.m_aValue           = rLstBox.Text;
11616         if( rLstBox.DropDown )
11617             rNewWidget.m_nFlags |= 0x00020000;
11618         if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11619             rNewWidget.m_nFlags |= 0x00200000;
11620 
11621         createDefaultListBoxAppearance( rNewWidget, rLstBox );
11622     }
11623     else if( rControl.getType() == PDFWriter::ComboBox )
11624     {
11625         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11626             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11627 
11628         const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
11629         rNewWidget.m_aValue         = rBox.Text;
11630         rNewWidget.m_aListEntries   = rBox.Entries;
11631         rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
11632 
11633         PDFWriter::ListBoxWidget aLBox;
11634         aLBox.Name              = rBox.Name;
11635         aLBox.Description       = rBox.Description;
11636         aLBox.Text              = rBox.Text;
11637         aLBox.TextStyle         = rBox.TextStyle;
11638         aLBox.ReadOnly          = rBox.ReadOnly;
11639         aLBox.Border            = rBox.Border;
11640         aLBox.BorderColor       = rBox.BorderColor;
11641         aLBox.Background        = rBox.Background;
11642         aLBox.BackgroundColor   = rBox.BackgroundColor;
11643         aLBox.TextFont          = rBox.TextFont;
11644         aLBox.TextColor         = rBox.TextColor;
11645         aLBox.DropDown          = true;
11646         aLBox.MultiSelect       = false;
11647         aLBox.Entries           = rBox.Entries;
11648 
11649         createDefaultListBoxAppearance( rNewWidget, aLBox );
11650     }
11651     else if( rControl.getType() == PDFWriter::Edit )
11652     {
11653         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11654             rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
11655 
11656         const PDFWriter::EditWidget& rEdit = static_cast<const  PDFWriter::EditWidget&>(rControl);
11657         if( rEdit.MultiLine )
11658         {
11659             rNewWidget.m_nFlags |= 0x00001000;
11660             rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11661         }
11662         if( rEdit.Password )
11663             rNewWidget.m_nFlags |= 0x00002000;
11664         if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11665             rNewWidget.m_nFlags |= 0x00100000;
11666         rNewWidget.m_nMaxLen = rEdit.MaxLen;
11667         rNewWidget.m_aValue = rEdit.Text;
11668 
11669         createDefaultEditAppearance( rNewWidget, rEdit );
11670     }
11671 #if HAVE_FEATURE_NSS
11672     else if( rControl.getType() == PDFWriter::Signature)
11673     {
11674         sigHidden = true;
11675 
11676         rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11677 
11678         m_nSignatureObject = createObject();
11679         rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11680         rNewWidget.m_aValue += " 0 R";
11681         // let's add a fake appearance
11682         rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
11683     }
11684 #endif
11685 
11686     // if control is a hidden signature, do not convert coordinates since we
11687     // need /Rect [ 0 0 0 0 ]
11688     if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
11689     {
11690         // convert to default user space now, since the mapmode may change
11691         // note: create default appearances before m_aRect gets transformed
11692         m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11693     }
11694 
11695     // insert widget to page's annotation list
11696     m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11697 
11698     return nNewWidget;
11699 }
11700 
11701 void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
11702 {
11703     if( pStream )
11704     {
11705         m_aAdditionalStreams.emplace_back( );
11706         PDFAddStream& rStream = m_aAdditionalStreams.back();
11707         rStream.m_aMimeType = !rMimeType.isEmpty()
11708                               ? rMimeType
11709                               : OUString( "application/octet-stream"  );
11710         rStream.m_pStream = pStream;
11711         rStream.m_bCompress = false;
11712     }
11713 }
11714 
11715 void PDFWriterImpl::MARK( const char* pString )
11716 {
11717     beginStructureElementMCSeq();
11718     if (g_bDebugDisableCompression)
11719         emitComment( pString );
11720 }
11721 
11722 sal_Int32 PDFWriterImpl::ReferenceXObjectEmit::getObject() const
11723 {
11724     if (m_nFormObject > 0)
11725         return m_nFormObject;
11726     else
11727         return m_nBitmapObject;
11728 }
11729 
11730 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
11731