Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / source / gdi / pdfwriter_impl.cxx
blobc0ebe40f2c8889ba0500edfc0d2c6969379eaee4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_features.h>
22 #include <sal/types.h>
24 #include <math.h>
25 #include <algorithm>
27 #if defined __GNUC__ && __cplusplus > 201402L
28 #pragma GCC diagnostic push
29 #pragma GCC diagnostic ignored "-Wregister"
30 #endif
31 #include <lcms2.h>
32 #if defined __GNUC__ && __cplusplus > 201402L
33 #pragma GCC diagnostic pop
34 #endif
36 #include <basegfx/matrix/b2dhommatrix.hxx>
37 #include <basegfx/polygon/b2dpolygon.hxx>
38 #include <basegfx/polygon/b2dpolygontools.hxx>
39 #include <basegfx/polygon/b2dpolypolygon.hxx>
40 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
41 #include <basegfx/polygon/b2dpolypolygontools.hxx>
42 #include <memory>
43 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
44 #include <com/sun/star/util/URL.hpp>
45 #include <com/sun/star/util/URLTransformer.hpp>
46 #include <comphelper/processfactory.hxx>
47 #include <comphelper/random.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 <vcl/bitmapex.hxx>
65 #include <vcl/bitmapaccess.hxx>
66 #include <vcl/cvtgrf.hxx>
67 #include <vcl/image.hxx>
68 #include <vcl/lineinfo.hxx>
69 #include <vcl/metric.hxx>
70 #include <vcl/settings.hxx>
71 #include <vcl/strhelper.hxx>
72 #include <vcl/svapp.hxx>
73 #include <vcl/virdev.hxx>
74 #include <vcl/filter/pdfdocument.hxx>
76 #include "fontsubset.hxx"
77 #include "outdev.h"
78 #include "PhysicalFontFace.hxx"
79 #include "salgdi.hxx"
80 #include "sallayout.hxx"
81 #include "textlayout.hxx"
82 #include "textlineinfo.hxx"
84 #include "pdfwriter_impl.hxx"
86 #if HAVE_FEATURE_NSS && !defined(_WIN32)
87 // NSS headers for PDF signing
88 #include "nss.h"
89 #include "cert.h"
90 #include "hasht.h"
91 #include "secerr.h"
92 #include "sechash.h"
93 #include "cms.h"
94 #include "cmst.h"
96 // We use curl for RFC3161 time stamp requests
97 #include <curl/curl.h>
98 #endif
100 #ifdef _WIN32
101 // WinCrypt headers for PDF signing
102 // Note: this uses Windows 7 APIs and requires the relevant data types;
103 // the functions that don't exist in WinXP must be looked up at runtime!
104 #undef _WIN32_WINNT
105 #define _WIN32_WINNT _WIN32_WINNT_WIN7
106 #include <prewin.h>
107 #include <wincrypt.h>
108 #include <postwin.h>
109 #include <comphelper/windowserrorstring.hxx>
110 #endif
112 #include <config_eot.h>
114 #if ENABLE_EOT
115 #include <libeot/libeot.h>
116 #endif
118 using namespace vcl;
119 using namespace::com::sun::star;
121 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
123 #if HAVE_FEATURE_NSS
124 // Is this length truly the maximum possible, or just a number that
125 // seemed large enough when the author tested this (with some type of
126 // certificates)? I suspect the latter.
128 // Used to be 0x4000 = 16384, but a sample signed PDF (produced by
129 // some other software) provided by the customer has a signature
130 // content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
131 // Adobe has one that is 21942 bytes. So let's be careful. Pity this
132 // can't be dynamic, at least not without restructuring the code. Also
133 // note that the checks in the code for this being too small
134 // apparently are broken, if this overflows you end up with an invalid
135 // PDF. Need to fix that.
137 #define MAX_SIGNATURE_CONTENT_LENGTH 50000
138 #endif
140 #ifdef DO_TEST_PDF
141 class PDFTestOutputStream : public PDFOutputStream
143 public:
144 virtual ~PDFTestOutputStream();
145 virtual void write( const css::uno::Reference< css::io::XOutputStream >& xStream );
148 PDFTestOutputStream::~PDFTestOutputStream()
152 void PDFTestOutputStream::write( const css::uno::Reference< css::io::XOutputStream >& xStream )
154 OString aStr( "lalala\ntest\ntest\ntest" );
155 css::uno::Sequence< sal_Int8 > aData( aStr.getLength() );
156 memcpy( aData.getArray(), aStr.getStr(), aStr.getLength() );
157 xStream->writeBytes( aData );
160 // this test code cannot be used to test PDF/A-1 because it forces
161 // control item (widgets) to bypass the structure controlling
162 // the embedding of such elements in actual run
163 void doTestCode()
165 static const char* pHome = getenv( "HOME" );
166 OUString aTestFile( "file://" );
167 aTestFile += OUString( pHome, strlen( pHome ), RTL_TEXTENCODING_MS_1252 );
168 aTestFile += "/pdf_export_test.pdf";
170 PDFWriter::PDFWriterContext aContext;
171 aContext.URL = aTestFile;
172 aContext.Version = PDFWriter::PDF_1_4;
173 aContext.Tagged = true;
174 aContext.InitialPage = 2;
175 aContext.DocumentInfo.Title = "PDF export test document";
176 aContext.DocumentInfo.Producer = "VCL";
178 aContext.SignPDF = true;
179 aContext.SignLocation = "Burdur";
180 aContext.SignReason = "Some valid reason to sign";
181 aContext.SignContact = "signer@example.com";
183 css::uno::Reference< css::beans::XMaterialHolder > xEnc;
184 PDFWriter aWriter( aContext, xEnc );
185 aWriter.NewPage( 595, 842 );
186 aWriter.BeginStructureElement( PDFWriter::Document );
187 // set duration of 3 sec for first page
188 aWriter.SetAutoAdvanceTime( 3 );
189 aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
191 aWriter.SetFillColor( Color( COL_LIGHTRED ) );
192 aWriter.SetLineColor( Color( COL_LIGHTGREEN ) );
193 aWriter.DrawRect( Rectangle( Point( 2000, 200 ), Size( 8000, 3000 ) ), 5000, 2000 );
195 aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
196 aWriter.SetTextColor( Color( COL_BLACK ) );
197 aWriter.SetLineColor( Color( COL_BLACK ) );
198 aWriter.SetFillColor( Color( COL_LIGHTBLUE ) );
200 Rectangle aRect( Point( 5000, 5000 ), Size( 6000, 3000 ) );
201 aWriter.DrawRect( aRect );
202 aWriter.DrawText( aRect, OUString( "Link annot 1" ) );
203 sal_Int32 nFirstLink = aWriter.CreateLink( aRect );
204 PDFNote aNote;
205 aNote.Title = "A small test note";
206 aNote.Contents = "There is no business like show business like no business i know. Everything about it is appealing.";
207 aWriter.CreateNote( Rectangle( Point( aRect.Right(), aRect.Top() ), Size( 6000, 3000 ) ), aNote );
209 Rectangle aTargetRect( Point( 3000, 23000 ), Size( 12000, 6000 ) );
210 aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
211 aWriter.DrawRect( aTargetRect );
212 aWriter.DrawText( aTargetRect, "Dest second link" );
213 sal_Int32 nSecondDest = aWriter.CreateDest( aTargetRect );
215 aWriter.BeginStructureElement( PDFWriter::Section );
216 aWriter.BeginStructureElement( PDFWriter::Heading );
217 aWriter.DrawText( Point(4500, 9000), "A small structure test" );
218 aWriter.EndStructureElement();
219 aWriter.BeginStructureElement( PDFWriter::Paragraph );
220 aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
221 aWriter.SetStructureAttribute( PDFWriter::TextDecorationType, PDFWriter::Underline );
222 aWriter.DrawText( Rectangle( Point( 4500, 10000 ), Size( 12000, 6000 ) ),
223 "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.",
224 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
226 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." );
227 aWriter.SetAlternateText( "This paragraph contains some lengthy nonsense to test structural element emission of PDFWriter." );
228 aWriter.EndStructureElement();
229 aWriter.BeginStructureElement( PDFWriter::Paragraph );
230 aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
231 aWriter.DrawText( Rectangle( Point( 4500, 19000 ), Size( 12000, 1000 ) ),
232 "This paragraph is nothing special either but ends on the next page structurewise",
233 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
236 aWriter.NewPage( 595, 842 );
237 // test AddStream interface
238 aWriter.AddStream( "text/plain", new PDFTestOutputStream(), true );
239 // set transitional mode
240 aWriter.SetPageTransition( PDFWriter::WipeRightToLeft, 1500 );
241 aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
242 aWriter.SetTextColor( Color( COL_BLACK ) );
243 aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
244 aWriter.DrawText( Rectangle( Point( 4500, 1500 ), Size( 12000, 3000 ) ),
245 "Here's where all things come to an end ... well at least the paragraph from the last page.",
246 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
248 aWriter.EndStructureElement();
250 aWriter.SetFillColor( Color( COL_LIGHTBLUE ) );
251 // disable structure
252 aWriter.BeginStructureElement( PDFWriter::NonStructElement );
253 aWriter.DrawRect( aRect );
254 aWriter.BeginStructureElement( PDFWriter::Paragraph );
255 aWriter.DrawText( aRect, "Link annot 2" );
256 sal_Int32 nSecondLink = aWriter.CreateLink( aRect );
258 aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
259 aWriter.BeginStructureElement( PDFWriter::ListItem );
260 aWriter.DrawRect( aTargetRect );
261 aWriter.DrawText( aTargetRect, "Dest first link" );
262 sal_Int32 nFirstDest = aWriter.CreateDest( aTargetRect );
263 // enable structure
264 aWriter.EndStructureElement();
266 aWriter.EndStructureElement();
267 aWriter.EndStructureElement();
268 aWriter.BeginStructureElement( PDFWriter::Figure );
269 aWriter.BeginStructureElement( PDFWriter::Caption );
270 aWriter.DrawText( Point( 4500, 9000 ), "Some drawing stuff inside the structure" );
271 aWriter.EndStructureElement();
273 // test clipping
274 basegfx::B2DPolyPolygon aClip;
275 basegfx::B2DPolygon aClipPoly;
276 aClipPoly.append( basegfx::B2DPoint( 8250, 9600 ) );
277 aClipPoly.append( basegfx::B2DPoint( 16500, 11100 ) );
278 aClipPoly.append( basegfx::B2DPoint( 8250, 12600 ) );
279 aClipPoly.append( basegfx::B2DPoint( 4500, 11100 ) );
280 aClipPoly.setClosed( true );
281 aClip.append( aClipPoly );
283 aWriter.Push( PushFlags::CLIPREGION | PushFlags::FILLCOLOR );
284 aWriter.SetClipRegion( aClip );
285 aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
286 aWriter.MoveClipRegion( 1000, 500 );
287 aWriter.SetFillColor( Color( COL_RED ) );
288 aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
289 aWriter.Pop();
290 // test transparency
291 // draw background
292 Rectangle aTranspRect( Point( 7500, 13500 ), Size( 9000, 6000 ) );
293 aWriter.SetFillColor( Color( COL_LIGHTRED ) );
294 aWriter.DrawRect( aTranspRect );
295 aWriter.BeginTransparencyGroup();
297 aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
298 aWriter.DrawEllipse( aTranspRect );
299 aWriter.SetTextColor( Color( COL_LIGHTBLUE ) );
300 aWriter.DrawText( aTranspRect,
301 "Some transparent text",
302 DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
304 aWriter.EndTransparencyGroup( aTranspRect, 50 );
306 // prepare an alpha mask
307 Bitmap aTransMask( Size( 256, 256 ), 8, &Bitmap::GetGreyPalette( 256 ) );
308 Bitmap::ScopedWriteAccess pAcc(aTransMask);
309 for( int nX = 0; nX < 256; nX++ )
310 for( int nY = 0; nY < 256; nY++ )
311 pAcc->SetPixel( nX, nY, BitmapColor( (sal_uInt8)((nX+nY)/2) ) );
312 pAcc.reset();
313 aTransMask.SetPrefMapMode( MapUnit::MapMM );
314 aTransMask.SetPrefSize( Size( 10, 10 ) );
316 aWriter.DrawBitmap( Point( 600, 13500 ), Size( 3000, 3000 ), aTransMask );
318 aTranspRect = Rectangle( Point( 4200, 13500 ), Size( 3000, 3000 ) );
319 aWriter.SetFillColor( Color( COL_LIGHTRED ) );
320 aWriter.DrawRect( aTranspRect );
321 aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
322 aWriter.DrawEllipse( aTranspRect );
323 aWriter.SetTextColor( Color( COL_LIGHTBLUE ) );
324 aWriter.DrawText( aTranspRect,
325 "Some transparent text",
326 DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
327 aTranspRect = Rectangle( Point( 1500, 16500 ), Size( 4800, 3000 ) );
328 aWriter.SetFillColor( Color( COL_LIGHTRED ) );
329 aWriter.DrawRect( aTranspRect );
331 Bitmap aImageBmp( Size( 256, 256 ), 24 );
332 pAcc = Bitmap::ScopedWriteAccess(aImageBmp);
333 pAcc->SetFillColor( Color( 0xff, 0, 0xff ) );
334 pAcc->FillRect( Rectangle( Point( 0, 0 ), Size( 256, 256 ) ) );
335 pAcc.reset();
336 BitmapEx aBmpEx( aImageBmp, AlphaMask( aTransMask ) );
337 aWriter.DrawBitmapEx( Point( 1500, 19500 ), Size( 4800, 3000 ), aBmpEx );
339 aWriter.EndStructureElement();
340 aWriter.EndStructureElement();
342 LineInfo aLI( LineStyle::Dash, 3 );
343 aLI.SetDashCount( 2 );
344 aLI.SetDashLen( 50 );
345 aLI.SetDotCount( 2 );
346 aLI.SetDotLen( 25 );
347 aLI.SetDistance( 15 );
348 Point aLIPoints[] = { Point( 4000, 10000 ),
349 Point( 8000, 12000 ),
350 Point( 3000, 19000 ) };
351 tools::Polygon aLIPoly( 3, aLIPoints );
352 aWriter.SetLineColor( Color( COL_BLUE ) );
353 aWriter.SetFillColor();
354 aWriter.DrawPolyLine( aLIPoly, aLI );
356 aLI.SetDashCount( 4 );
357 aLIPoly.Move( 1000, 1000 );
358 aWriter.DrawPolyLine( aLIPoly, aLI );
360 aWriter.NewPage( 595, 842 );
361 aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
362 Wallpaper aWall( aTransMask );
363 aWall.SetStyle( WallpaperStyle::Tile );
364 aWriter.DrawWallpaper( Rectangle( Point( 4400, 4200 ), Size( 10200, 6300 ) ), aWall );
366 aWriter.NewPage( 595, 842 );
367 aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
368 aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
369 aWriter.SetTextColor( Color( COL_BLACK ) );
370 aRect = Rectangle( Point( 4500, 6000 ), Size( 6000, 1500 ) );
371 aWriter.DrawRect( aRect );
372 aWriter.DrawText( aRect, "www.heise.de" );
373 sal_Int32 nURILink = aWriter.CreateLink( aRect );
374 aWriter.SetLinkURL( nURILink, OUString( "http://www.heise.de" ) );
376 aWriter.SetLinkDest( nFirstLink, nFirstDest );
377 aWriter.SetLinkDest( nSecondLink, nSecondDest );
379 // include a button
380 PDFWriter::PushButtonWidget aBtn;
381 aBtn.Name = "testButton";
382 aBtn.Description = "A test button";
383 aBtn.Text = "hit me";
384 aBtn.Location = Rectangle( Point( 4500, 9000 ), Size( 4500, 3000 ) );
385 aBtn.Border = aBtn.Background = true;
386 aWriter.CreateControl( aBtn );
388 // include a uri button
389 PDFWriter::PushButtonWidget aUriBtn;
390 aUriBtn.Name = "wwwButton";
391 aUriBtn.Description = "A URI button";
392 aUriBtn.Text = "to www";
393 aUriBtn.Location = Rectangle( Point( 9500, 9000 ), Size( 4500, 3000 ) );
394 aUriBtn.Border = aUriBtn.Background = true;
395 aUriBtn.URL = "http://www.heise.de";
396 aWriter.CreateControl( aUriBtn );
398 // include a dest button
399 PDFWriter::PushButtonWidget aDstBtn;
400 aDstBtn.Name = "destButton";
401 aDstBtn.Description = "A Dest button";
402 aDstBtn.Text = "to paragraph";
403 aDstBtn.Location = Rectangle( Point( 14500, 9000 ), Size( 4500, 3000 ) );
404 aDstBtn.Border = aDstBtn.Background = true;
405 aDstBtn.Dest = nFirstDest;
406 aWriter.CreateControl( aDstBtn );
408 PDFWriter::CheckBoxWidget aCBox;
409 aCBox.Name = "textCheckBox";
410 aCBox.Description = "A test check box";
411 aCBox.Text = "check me";
412 aCBox.Location = Rectangle( Point( 4500, 13500 ), Size( 3000, 750 ) );
413 aCBox.Checked = true;
414 aCBox.Border = aCBox.Background = false;
415 aWriter.CreateControl( aCBox );
417 PDFWriter::CheckBoxWidget aCBox2;
418 aCBox2.Name = "textCheckBox2";
419 aCBox2.Description = "Another test check box";
420 aCBox2.Text = "check me right";
421 aCBox2.Location = Rectangle( Point( 4500, 14250 ), Size( 3000, 750 ) );
422 aCBox2.Checked = true;
423 aCBox2.Border = aCBox2.Background = false;
424 aCBox2.ButtonIsLeft = false;
425 aWriter.CreateControl( aCBox2 );
427 PDFWriter::RadioButtonWidget aRB1;
428 aRB1.Name = "rb1_1";
429 aRB1.Description = "radio 1 button 1";
430 aRB1.Text = "Despair";
431 aRB1.Location = Rectangle( Point( 4500, 15000 ), Size( 6000, 1000 ) );
432 aRB1.Selected = true;
433 aRB1.RadioGroup = 1;
434 aRB1.Border = aRB1.Background = true;
435 aRB1.ButtonIsLeft = false;
436 aRB1.BorderColor = Color( COL_LIGHTGREEN );
437 aRB1.BackgroundColor = Color( COL_LIGHTBLUE );
438 aRB1.TextColor = Color( COL_LIGHTRED );
439 aRB1.TextFont = Font( OUString( "Courier" ), Size( 0, 800 ) );
440 aWriter.CreateControl( aRB1 );
442 PDFWriter::RadioButtonWidget aRB2;
443 aRB2.Name = "rb2_1";
444 aRB2.Description = "radio 2 button 1";
445 aRB2.Text = "Joy";
446 aRB2.Location = Rectangle( Point( 10500, 15000 ), Size( 3000, 1000 ) );
447 aRB2.Selected = true;
448 aRB2.RadioGroup = 2;
449 aWriter.CreateControl( aRB2 );
451 PDFWriter::RadioButtonWidget aRB3;
452 aRB3.Name = "rb1_2";
453 aRB3.Description = "radio 1 button 2";
454 aRB3.Text = "Desperation";
455 aRB3.Location = Rectangle( Point( 4500, 16000 ), Size( 3000, 1000 ) );
456 aRB3.Selected = true;
457 aRB3.RadioGroup = 1;
458 aWriter.CreateControl( aRB3 );
460 PDFWriter::EditWidget aEditBox;
461 aEditBox.Name = "testEdit";
462 aEditBox.Description = "A test edit field";
463 aEditBox.Text = "A little test text";
464 aEditBox.TextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
465 aEditBox.Location = Rectangle( Point( 10000, 18000 ), Size( 5000, 1500 ) );
466 aEditBox.MaxLen = 100;
467 aEditBox.Border = aEditBox.Background = true;
468 aEditBox.BorderColor = Color( COL_BLACK );
469 aWriter.CreateControl( aEditBox );
471 // normal list box
472 PDFWriter::ListBoxWidget aLstBox;
473 aLstBox.Name = "testListBox";
474 aLstBox.Text = "One";
475 aLstBox.Description = "select me";
476 aLstBox.Location = Rectangle( Point( 4500, 18000 ), Size( 3000, 1500 ) );
477 aLstBox.Sort = true;
478 aLstBox.MultiSelect = true;
479 aLstBox.Border = aLstBox.Background = true;
480 aLstBox.BorderColor = Color( COL_BLACK );
481 aLstBox.Entries.push_back( OUString( "One" ) );
482 aLstBox.Entries.push_back( OUString( "Two" ) );
483 aLstBox.Entries.push_back( OUString( "Three" ) );
484 aLstBox.Entries.push_back( OUString( "Four" ) );
485 aLstBox.SelectedEntries.push_back( 1 );
486 aLstBox.SelectedEntries.push_back( 2 );
487 aWriter.CreateControl( aLstBox );
489 // dropdown list box
490 aLstBox.Name = "testDropDownListBox";
491 aLstBox.DropDown = true;
492 aLstBox.Location = Rectangle( Point( 4500, 19500 ), Size( 3000, 500 ) );
493 aWriter.CreateControl( aLstBox );
495 // combo box
496 PDFWriter::ComboBoxWidget aComboBox;
497 aComboBox.Name = "testComboBox";
498 aComboBox.Text = "test a combobox";
499 aComboBox.Entries.push_back( OUString( "Larry" ) );
500 aComboBox.Entries.push_back( OUString( "Curly" ) );
501 aComboBox.Entries.push_back( OUString( "Moe" ) );
502 aComboBox.Location = Rectangle( Point( 4500, 20000 ), Size( 3000, 500 ) );
503 aWriter.CreateControl( aComboBox );
505 // test outlines
506 sal_Int32 nPage1OL = aWriter.CreateOutlineItem();
507 aWriter.SetOutlineItemText( nPage1OL, OUString( "Page 1" ) );
508 aWriter.SetOutlineItemDest( nPage1OL, nSecondDest );
509 aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2" ), nSecondDest );
510 aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2 revisited" ), nSecondDest );
511 aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2 again" ), nSecondDest );
512 sal_Int32 nPage2OL = aWriter.CreateOutlineItem();
513 aWriter.SetOutlineItemText( nPage2OL, OUString( "Page 2" ) );
514 aWriter.CreateOutlineItem( nPage2OL, OUString( "Dest 1" ), nFirstDest );
516 aWriter.EndStructureElement(); // close document
518 aWriter.Emit();
520 #endif
522 static const sal_Int32 nLog10Divisor = 1;
523 static const double fDivisor = 10.0;
525 static inline double pixelToPoint( double px ) { return px/fDivisor; }
526 static inline sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
528 const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
530 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
531 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
534 static void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer )
536 static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
537 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
538 rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
539 rBuffer.append( pHexDigits[ nInt & 15 ] );
542 static void appendName( const OUString& rStr, OStringBuffer& rBuffer )
544 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
545 // I guess than when reading the #xx sequence it will count for a single character.
546 OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
547 const sal_Char* pStr = aStr.getStr();
548 int nLen = aStr.getLength();
549 for( int i = 0; i < nLen; i++ )
551 /* #i16920# PDF recommendation: output UTF8, any byte
552 * outside the interval [33(=ASCII'!');126(=ASCII'~')]
553 * should be escaped hexadecimal
554 * for the sake of ghostscript which also reads PDF
555 * but has a narrower acceptance rate we only pass
556 * alphanumerics and '-' literally.
558 if( (pStr[i] >= 'A' && pStr[i] <= 'Z' ) ||
559 (pStr[i] >= 'a' && pStr[i] <= 'z' ) ||
560 (pStr[i] >= '0' && pStr[i] <= '9' ) ||
561 pStr[i] == '-' )
563 rBuffer.append( pStr[i] );
565 else
567 rBuffer.append( '#' );
568 appendHex( (sal_Int8)pStr[i], rBuffer );
573 static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer )
575 // FIXME i59651 see above
576 while( pStr && *pStr )
578 if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
579 (*pStr >= 'a' && *pStr <= 'z' ) ||
580 (*pStr >= '0' && *pStr <= '9' ) ||
581 *pStr == '-' )
583 rBuffer.append( *pStr );
585 else
587 rBuffer.append( '#' );
588 appendHex( (sal_Int8)*pStr, rBuffer );
590 pStr++;
594 //used only to emit encoded passwords
595 static void appendLiteralString( const sal_Char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
597 while( nLength )
599 switch( *pStr )
601 case '\n' :
602 rBuffer.append( "\\n" );
603 break;
604 case '\r' :
605 rBuffer.append( "\\r" );
606 break;
607 case '\t' :
608 rBuffer.append( "\\t" );
609 break;
610 case '\b' :
611 rBuffer.append( "\\b" );
612 break;
613 case '\f' :
614 rBuffer.append( "\\f" );
615 break;
616 case '(' :
617 case ')' :
618 case '\\' :
619 rBuffer.append( "\\" );
620 rBuffer.append( (sal_Char) *pStr );
621 break;
622 default:
623 rBuffer.append( (sal_Char) *pStr );
624 break;
626 pStr++;
627 nLength--;
632 * Convert a string before using it.
634 * This string conversion function is needed because the destination name
635 * in a PDF file seen through an Internet browser should be
636 * specially crafted, in order to be used directly by the browser.
637 * In this way the fragment part of a hyperlink to a PDF file (e.g. something
638 * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
639 * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
640 * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
641 * and go to named destination thefragment using default zoom'.
642 * The conversion is needed because in case of a fragment in the form: Slide%201
643 * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
644 * using this conversion, in both the generated named destinations, fragment and GoToR
645 * destination.
647 * The names for destinations are name objects and so they don't need to be encrypted
648 * even though they expose the content of PDF file (e.g. guessing the PDF content from the
649 * destination name).
651 * Further limitation: it is advisable to use standard ASCII characters for
652 * OOo bookmarks.
654 static void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
656 const sal_Unicode* pStr = rString.getStr();
657 sal_Int32 nLen = rString.getLength();
658 for( int i = 0; i < nLen; i++ )
660 sal_Unicode aChar = pStr[i];
661 if( (aChar >= '0' && aChar <= '9' ) ||
662 (aChar >= 'a' && aChar <= 'z' ) ||
663 (aChar >= 'A' && aChar <= 'Z' ) ||
664 aChar == '-' )
666 rBuffer.append((sal_Char)aChar);
668 else
670 sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
671 if(aValueHigh > 0)
672 appendHex( aValueHigh, rBuffer );
673 appendHex( (sal_Int8)(aChar & 255 ), rBuffer );
678 void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
680 rBuffer.append( "FEFF" );
681 const sal_Unicode* pStr = rString.getStr();
682 sal_Int32 nLen = rString.getLength();
683 for( int i = 0; i < nLen; i++ )
685 sal_Unicode aChar = pStr[i];
686 appendHex( (sal_Int8)(aChar >> 8), rBuffer );
687 appendHex( (sal_Int8)(aChar & 255 ), rBuffer );
691 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
693 /* #i80258# previously we use appendName here
694 however we need a slightly different coding scheme than the normal
695 name encoding for field names
697 const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
698 OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
699 const sal_Char* pStr = aStr.getStr();
700 int nLen = aStr.getLength();
702 OStringBuffer aBuffer( rName.getLength()+64 );
703 for( int i = 0; i < nLen; i++ )
705 /* #i16920# PDF recommendation: output UTF8, any byte
706 * outside the interval [32(=ASCII' ');126(=ASCII'~')]
707 * should be escaped hexadecimal
709 if( (pStr[i] >= 32 && pStr[i] <= 126 ) )
710 aBuffer.append( pStr[i] );
711 else
713 aBuffer.append( '#' );
714 appendHex( (sal_Int8)pStr[i], aBuffer );
718 OString aFullName( aBuffer.makeStringAndClear() );
720 /* #i82785# create hierarchical fields down to the for each dot in i_rName */
721 sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
722 OString aPartialName;
723 OString aDomain;
726 nLastTokenIndex = nTokenIndex;
727 aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
728 if( nTokenIndex != -1 )
730 // find or create a hierarchical field
731 // first find the fully qualified name up to this field
732 aDomain = aFullName.copy( 0, nTokenIndex-1 );
733 std::unordered_map< OString, sal_Int32, OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain );
734 if( it == m_aFieldNameMap.end() )
736 // create new hierarchy field
737 sal_Int32 nNewWidget = m_aWidgets.size();
738 m_aWidgets.push_back( PDFWidget() );
739 m_aWidgets[nNewWidget].m_nObject = createObject();
740 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
741 m_aWidgets[nNewWidget].m_aName = aPartialName;
742 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
743 m_aFieldNameMap[aDomain] = nNewWidget;
744 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
745 if( nLastTokenIndex > 0 )
747 // this field is not a root field and
748 // needs to be inserted to its parent
749 OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
750 it = m_aFieldNameMap.find( aParentDomain );
751 OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
752 if( it != m_aFieldNameMap.end() )
754 OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
755 if( it->second < sal_Int32(m_aWidgets.size()) )
757 PDFWidget& rParentField( m_aWidgets[it->second] );
758 rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
759 rParentField.m_aKidsIndex.push_back( nNewWidget );
760 m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
765 else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
767 // this is invalid, someone tries to have a terminal field as parent
768 // example: a button with the name foo.bar exists and
769 // another button is named foo.bar.no
770 // workaround: put the second terminal field as much up in the hierarchy as
771 // necessary to have a non-terminal field as parent (or none at all)
772 // since it->second already is terminal, we just need to use its parent
773 aDomain.clear();
774 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
775 if( nLastTokenIndex > 0 )
777 aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
778 OStringBuffer aBuf( aDomain.getLength() + 1 + aPartialName.getLength() );
779 aBuf.append( aDomain );
780 aBuf.append( '.' );
781 aBuf.append( aPartialName );
782 aFullName = aBuf.makeStringAndClear();
784 else
785 aFullName = aPartialName;
786 break;
789 } while( nTokenIndex != -1 );
791 // insert widget into its hierarchy field
792 if( !aDomain.isEmpty() )
794 std::unordered_map< OString, sal_Int32, OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain );
795 if( it != m_aFieldNameMap.end() )
797 OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
798 if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) )
800 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
801 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
802 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
807 if( aPartialName.isEmpty() )
809 // how funny, an empty field name
810 if( i_rControl.getType() == PDFWriter::RadioButton )
812 aPartialName = "RadioGroup";
813 aPartialName += OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
815 else
816 aPartialName = OString( "Widget" );
819 if( ! m_aContext.AllowDuplicateFieldNames )
821 std::unordered_map<OString, sal_Int32, OStringHash>::iterator it = m_aFieldNameMap.find( aFullName );
823 if( it != m_aFieldNameMap.end() ) // not unique
825 std::unordered_map< OString, sal_Int32, OStringHash >::const_iterator check_it;
826 OString aTry;
827 sal_Int32 nTry = 2;
830 OStringBuffer aUnique( aFullName.getLength() + 16 );
831 aUnique.append( aFullName );
832 aUnique.append( '_' );
833 aUnique.append( nTry++ );
834 aTry = aUnique.makeStringAndClear();
835 check_it = m_aFieldNameMap.find( aTry );
836 } while( check_it != m_aFieldNameMap.end() );
837 aFullName = aTry;
838 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
839 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
841 else
842 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
845 // finally
846 m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
849 static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
851 if( nValue < 0 )
853 rBuffer.append( '-' );
854 nValue = -nValue;
856 const sal_Int32 nFactor = 10;
857 const sal_Int32 nInt = nValue / nFactor;
858 rBuffer.append( nInt );
859 sal_Int32 nDecimal = nValue % nFactor;
860 if (nDecimal)
862 rBuffer.append('.');
863 rBuffer.append(nDecimal);
867 // appends a double. PDF does not accept exponential format, only fixed point
868 static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
870 bool bNeg = false;
871 if( fValue < 0.0 )
873 bNeg = true;
874 fValue=-fValue;
877 sal_Int64 nInt = (sal_Int64)fValue;
878 fValue -= (double)nInt;
879 // optimizing hardware may lead to a value of 1.0 after the subtraction
880 if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
882 nInt++;
883 fValue = 0.0;
885 sal_Int64 nFrac = 0;
886 if( fValue )
888 fValue *= pow( 10.0, (double)nPrecision );
889 nFrac = (sal_Int64)fValue;
891 if( bNeg && ( nInt || nFrac ) )
892 rBuffer.append( '-' );
893 rBuffer.append( nInt );
894 if( nFrac )
896 int i;
897 rBuffer.append( '.' );
898 sal_Int64 nBound = (sal_Int64)(pow( 10.0, nPrecision - 1.0 )+0.5);
899 for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
901 sal_Int64 nNumb = nFrac / nBound;
902 nFrac -= nNumb * nBound;
903 rBuffer.append( nNumb );
904 nBound /= 10;
909 static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
912 if( rColor != Color( COL_TRANSPARENT ) )
914 if( bConvertToGrey )
916 sal_uInt8 cByte = rColor.GetLuminance();
917 appendDouble( (double)cByte / 255.0, rBuffer );
919 else
921 appendDouble( (double)rColor.GetRed() / 255.0, rBuffer );
922 rBuffer.append( ' ' );
923 appendDouble( (double)rColor.GetGreen() / 255.0, rBuffer );
924 rBuffer.append( ' ' );
925 appendDouble( (double)rColor.GetBlue() / 255.0, rBuffer );
930 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
932 if( rColor != Color( COL_TRANSPARENT ) )
934 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
935 appendColor( rColor, rBuffer, bGrey );
936 rBuffer.append( bGrey ? " G" : " RG" );
940 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
942 if( rColor != Color( COL_TRANSPARENT ) )
944 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
945 appendColor( rColor, rBuffer, bGrey );
946 rBuffer.append( bGrey ? " g" : " rg" );
950 // matrix helper class
951 // TODO: use basegfx matrix class instead or derive from it
952 namespace vcl // TODO: use anonymous namespace to keep this class local
954 /* for sparse matrices of the form (2D linear transformations)
955 * f[0] f[1] 0
956 * f[2] f[3] 0
957 * f[4] f[5] 1
959 class Matrix3
961 double f[6];
963 void set( double *pn ) { for( int i = 0 ; i < 6; i++ ) f[i] = pn[i]; }
964 public:
965 Matrix3();
967 void skew( double alpha, double beta );
968 void scale( double sx, double sy );
969 void rotate( double angle );
970 void translate( double tx, double ty );
971 void invert();
973 void append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer );
975 Point transform( const Point& rPoint ) const;
979 Matrix3::Matrix3()
981 // initialize to unity
982 f[0] = 1.0;
983 f[1] = 0.0;
984 f[2] = 0.0;
985 f[3] = 1.0;
986 f[4] = 0.0;
987 f[5] = 0.0;
990 Point Matrix3::transform( const Point& rOrig ) const
992 double x = (double)rOrig.X(), y = (double)rOrig.Y();
993 return Point( (int)(x*f[0] + y*f[2] + f[4]), (int)(x*f[1] + y*f[3] + f[5]) );
996 void Matrix3::skew( double alpha, double beta )
998 double fn[6];
999 double tb = tan( beta );
1000 fn[0] = f[0] + f[2]*tb;
1001 fn[1] = f[1];
1002 fn[2] = f[2] + f[3]*tb;
1003 fn[3] = f[3];
1004 fn[4] = f[4] + f[5]*tb;
1005 fn[5] = f[5];
1006 if( alpha != 0.0 )
1008 double ta = tan( alpha );
1009 fn[1] += f[0]*ta;
1010 fn[3] += f[2]*ta;
1011 fn[5] += f[4]*ta;
1013 set( fn );
1016 void Matrix3::scale( double sx, double sy )
1018 double fn[6];
1019 fn[0] = sx*f[0];
1020 fn[1] = sy*f[1];
1021 fn[2] = sx*f[2];
1022 fn[3] = sy*f[3];
1023 fn[4] = sx*f[4];
1024 fn[5] = sy*f[5];
1025 set( fn );
1028 void Matrix3::rotate( double angle )
1030 double fn[6];
1031 double fSin = sin(angle);
1032 double fCos = cos(angle);
1033 fn[0] = f[0]*fCos - f[1]*fSin;
1034 fn[1] = f[0]*fSin + f[1]*fCos;
1035 fn[2] = f[2]*fCos - f[3]*fSin;
1036 fn[3] = f[2]*fSin + f[3]*fCos;
1037 fn[4] = f[4]*fCos - f[5]*fSin;
1038 fn[5] = f[4]*fSin + f[5]*fCos;
1039 set( fn );
1042 void Matrix3::translate( double tx, double ty )
1044 f[4] += tx;
1045 f[5] += ty;
1048 void Matrix3::invert()
1050 // short circuit trivial cases
1051 if( f[1]==f[2] && f[1]==0.0 && f[0]==f[3] && f[0]==1.0 )
1053 f[4] = -f[4];
1054 f[5] = -f[5];
1055 return;
1058 // check determinant
1059 const double fDet = f[0]*f[3]-f[1]*f[2];
1060 if( fDet == 0.0 )
1061 return;
1063 // invert the matrix
1064 double fn[6];
1065 fn[0] = +f[3] / fDet;
1066 fn[1] = -f[1] / fDet;
1067 fn[2] = -f[2] / fDet;
1068 fn[3] = +f[0] / fDet;
1070 // apply inversion to translation
1071 fn[4] = -(f[4]*fn[0] + f[5]*fn[2]);
1072 fn[5] = -(f[4]*fn[1] + f[5]*fn[3]);
1074 set( fn );
1077 void Matrix3::append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer )
1079 appendDouble( f[0], rBuffer );
1080 rBuffer.append( ' ' );
1081 appendDouble( f[1], rBuffer );
1082 rBuffer.append( ' ' );
1083 appendDouble( f[2], rBuffer );
1084 rBuffer.append( ' ' );
1085 appendDouble( f[3], rBuffer );
1086 rBuffer.append( ' ' );
1087 rPage.appendPoint( Point( (long)f[4], (long)f[5] ), rBuffer );
1090 static void appendResourceMap( OStringBuffer& rBuf, const char* pPrefix, const PDFWriterImpl::ResourceMap& rList )
1092 if( rList.empty() )
1093 return;
1094 rBuf.append( '/' );
1095 rBuf.append( pPrefix );
1096 rBuf.append( "<<" );
1097 int ni = 0;
1098 for( PDFWriterImpl::ResourceMap::const_iterator it = rList.begin(); it != rList.end(); ++it )
1100 if( !it->first.isEmpty() && it->second > 0 )
1102 rBuf.append( '/' );
1103 rBuf.append( it->first );
1104 rBuf.append( ' ' );
1105 rBuf.append( it->second );
1106 rBuf.append( " 0 R" );
1107 if( ((++ni) & 7) == 0 )
1108 rBuf.append( '\n' );
1111 rBuf.append( ">>\n" );
1114 void PDFWriterImpl::ResourceDict::append( OStringBuffer& rBuf, sal_Int32 nFontDictObject )
1116 rBuf.append( "<</Font " );
1117 rBuf.append( nFontDictObject );
1118 rBuf.append( " 0 R\n" );
1119 appendResourceMap( rBuf, "XObject", m_aXObjects );
1120 appendResourceMap( rBuf, "ExtGState", m_aExtGStates );
1121 appendResourceMap( rBuf, "Shading", m_aShadings );
1122 appendResourceMap( rBuf, "Pattern", m_aPatterns );
1123 rBuf.append( "/ProcSet[/PDF/Text" );
1124 if( !m_aXObjects.empty() )
1125 rBuf.append( "/ImageC/ImageI/ImageB" );
1126 rBuf.append( "]\n>>\n" );
1129 PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1131 m_pWriter( pWriter ),
1132 m_nPageWidth( nPageWidth ),
1133 m_nPageHeight( nPageHeight ),
1134 m_eOrientation( eOrientation ),
1135 m_nPageObject( 0 ), // invalid object number
1136 m_nPageIndex( -1 ), // invalid index
1137 m_nStreamLengthObject( 0 ),
1138 m_nBeginStreamPos( 0 ),
1139 m_eTransition( PDFWriter::PageTransition::Regular ),
1140 m_nTransTime( 0 ),
1141 m_nDuration( 0 ),
1142 m_bHasWidgets( false )
1144 // object ref must be only ever updated in emit()
1145 m_nPageObject = m_pWriter->createObject();
1148 PDFWriterImpl::PDFPage::~PDFPage()
1152 void PDFWriterImpl::PDFPage::beginStream()
1154 #if OSL_DEBUG_LEVEL > 1
1156 OStringBuffer aLine( "PDFWriterImpl::PDFPage::beginStream, +" );
1157 m_pWriter->emitComment( aLine.getStr() );
1159 #endif
1160 m_aStreamObjects.push_back(m_pWriter->createObject());
1161 if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
1162 return;
1164 m_nStreamLengthObject = m_pWriter->createObject();
1165 // write content stream header
1166 OStringBuffer aLine;
1167 aLine.append( m_aStreamObjects.back() );
1168 aLine.append( " 0 obj\n<</Length " );
1169 aLine.append( m_nStreamLengthObject );
1170 aLine.append( " 0 R" );
1171 if (!g_bDebugDisableCompression)
1172 aLine.append( "/Filter/FlateDecode" );
1173 aLine.append( ">>\nstream\n" );
1174 if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
1175 return;
1176 if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
1178 m_pWriter->m_aFile.close();
1179 m_pWriter->m_bOpen = false;
1181 if (!g_bDebugDisableCompression)
1182 m_pWriter->beginCompression();
1183 m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
1186 void PDFWriterImpl::PDFPage::endStream()
1188 if (!g_bDebugDisableCompression)
1189 m_pWriter->endCompression();
1190 sal_uInt64 nEndStreamPos;
1191 if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
1193 m_pWriter->m_aFile.close();
1194 m_pWriter->m_bOpen = false;
1195 return;
1197 m_pWriter->disableStreamEncryption();
1198 if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
1199 return;
1200 // emit stream length object
1201 if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
1202 return;
1203 OStringBuffer aLine;
1204 aLine.append( m_nStreamLengthObject );
1205 aLine.append( " 0 obj\n" );
1206 aLine.append( (sal_Int64)(nEndStreamPos-m_nBeginStreamPos) );
1207 aLine.append( "\nendobj\n\n" );
1208 m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
1211 bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject )
1213 // emit page object
1214 if( ! m_pWriter->updateObject( m_nPageObject ) )
1215 return false;
1216 OStringBuffer aLine;
1218 aLine.append( m_nPageObject );
1219 aLine.append( " 0 obj\n"
1220 "<</Type/Page/Parent " );
1221 aLine.append( nParentObject );
1222 aLine.append( " 0 R" );
1223 aLine.append( "/Resources " );
1224 aLine.append( m_pWriter->getResourceDictObj() );
1225 aLine.append( " 0 R" );
1226 if( m_nPageWidth && m_nPageHeight )
1228 aLine.append( "/MediaBox[0 0 " );
1229 aLine.append( m_nPageWidth );
1230 aLine.append( ' ' );
1231 aLine.append( m_nPageHeight );
1232 aLine.append( "]" );
1234 switch( m_eOrientation )
1236 case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
1237 case PDFWriter::Orientation::Inherit: break;
1239 int nAnnots = m_aAnnotations.size();
1240 if( nAnnots > 0 )
1242 aLine.append( "/Annots[\n" );
1243 for( int i = 0; i < nAnnots; i++ )
1245 aLine.append( m_aAnnotations[i] );
1246 aLine.append( " 0 R" );
1247 aLine.append( ((i+1)%15) ? " " : "\n" );
1249 aLine.append( "]\n" );
1251 if( m_aMCIDParents.size() > 0 )
1253 OStringBuffer aStructParents( 1024 );
1254 aStructParents.append( "[ " );
1255 int nParents = m_aMCIDParents.size();
1256 for( int i = 0; i < nParents; i++ )
1258 aStructParents.append( m_aMCIDParents[i] );
1259 aStructParents.append( " 0 R" );
1260 aStructParents.append( ((i%10) == 9) ? "\n" : " " );
1262 aStructParents.append( "]" );
1263 m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
1265 aLine.append( "/StructParents " );
1266 aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
1267 aLine.append( "\n" );
1269 if( m_nDuration > 0 )
1271 aLine.append( "/Dur " );
1272 aLine.append( (sal_Int32)m_nDuration );
1273 aLine.append( "\n" );
1275 if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
1277 // transition duration
1278 aLine.append( "/Trans<</D " );
1279 appendDouble( (double)m_nTransTime/1000.0, aLine, 3 );
1280 aLine.append( "\n" );
1281 const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
1282 switch( m_eTransition )
1284 case PDFWriter::PageTransition::SplitHorizontalInward:
1285 pStyle = "Split"; pDm = "H"; pM = "I"; break;
1286 case PDFWriter::PageTransition::SplitHorizontalOutward:
1287 pStyle = "Split"; pDm = "H"; pM = "O"; break;
1288 case PDFWriter::PageTransition::SplitVerticalInward:
1289 pStyle = "Split"; pDm = "V"; pM = "I"; break;
1290 case PDFWriter::PageTransition::SplitVerticalOutward:
1291 pStyle = "Split"; pDm = "V"; pM = "O"; break;
1292 case PDFWriter::PageTransition::BlindsHorizontal:
1293 pStyle = "Blinds"; pDm = "H"; break;
1294 case PDFWriter::PageTransition::BlindsVertical:
1295 pStyle = "Blinds"; pDm = "V"; break;
1296 case PDFWriter::PageTransition::BoxInward:
1297 pStyle = "Box"; pM = "I"; break;
1298 case PDFWriter::PageTransition::BoxOutward:
1299 pStyle = "Box"; pM = "O"; break;
1300 case PDFWriter::PageTransition::WipeLeftToRight:
1301 pStyle = "Wipe"; pDi = "0"; break;
1302 case PDFWriter::PageTransition::WipeBottomToTop:
1303 pStyle = "Wipe"; pDi = "90"; break;
1304 case PDFWriter::PageTransition::WipeRightToLeft:
1305 pStyle = "Wipe"; pDi = "180"; break;
1306 case PDFWriter::PageTransition::WipeTopToBottom:
1307 pStyle = "Wipe"; pDi = "270"; break;
1308 case PDFWriter::PageTransition::Dissolve:
1309 pStyle = "Dissolve"; break;
1310 case PDFWriter::PageTransition::Regular:
1311 break;
1313 // transition style
1314 if( pStyle )
1316 aLine.append( "/S/" );
1317 aLine.append( pStyle );
1318 aLine.append( "\n" );
1320 if( pDm )
1322 aLine.append( "/Dm/" );
1323 aLine.append( pDm );
1324 aLine.append( "\n" );
1326 if( pM )
1328 aLine.append( "/M/" );
1329 aLine.append( pM );
1330 aLine.append( "\n" );
1332 if( pDi )
1334 aLine.append( "/Di " );
1335 aLine.append( pDi );
1336 aLine.append( "\n" );
1338 aLine.append( ">>\n" );
1340 if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
1342 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
1344 aLine.append( "/Contents" );
1345 unsigned int nStreamObjects = m_aStreamObjects.size();
1346 if( nStreamObjects > 1 )
1347 aLine.append( '[' );
1348 for(sal_Int32 i : m_aStreamObjects)
1350 aLine.append( ' ' );
1351 aLine.append( i );
1352 aLine.append( " 0 R" );
1354 if( nStreamObjects > 1 )
1355 aLine.append( ']' );
1356 aLine.append( ">>\nendobj\n\n" );
1357 return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
1360 namespace vcl
1362 template < class GEOMETRY >
1363 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
1365 GEOMETRY aPoint;
1366 if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
1368 aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
1370 else
1372 aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
1374 return aPoint;
1378 void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
1380 Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1381 m_pWriter->m_aMapMode,
1382 m_pWriter->getReferenceDevice(),
1383 rPoint ) );
1385 sal_Int32 nValue = aPoint.X();
1387 appendFixedInt( nValue, rBuffer );
1389 rBuffer.append( ' ' );
1391 nValue = pointToPixel(getHeight()) - aPoint.Y();
1393 appendFixedInt( nValue, rBuffer );
1396 void PDFWriterImpl::PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
1398 double fValue = pixelToPoint(rPoint.getX());
1400 appendDouble( fValue, rBuffer, nLog10Divisor );
1401 rBuffer.append( ' ' );
1402 fValue = double(getHeight()) - pixelToPoint(rPoint.getY());
1403 appendDouble( fValue, rBuffer, nLog10Divisor );
1406 void PDFWriterImpl::PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
1408 appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
1409 rBuffer.append( ' ' );
1410 appendMappedLength( (sal_Int32)rRect.GetWidth(), rBuffer, false );
1411 rBuffer.append( ' ' );
1412 appendMappedLength( (sal_Int32)rRect.GetHeight(), rBuffer );
1413 rBuffer.append( " re" );
1416 void PDFWriterImpl::PDFPage::convertRect( tools::Rectangle& rRect ) const
1418 Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1419 m_pWriter->m_aMapMode,
1420 m_pWriter->getReferenceDevice(),
1421 rRect.BottomLeft() + Point( 0, 1 )
1423 Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1424 m_pWriter->m_aMapMode,
1425 m_pWriter->getReferenceDevice(),
1426 rRect.GetSize() );
1427 rRect.Left() = aLL.X();
1428 rRect.Right() = aLL.X() + aSize.Width();
1429 rRect.Top() = pointToPixel(getHeight()) - aLL.Y();
1430 rRect.Bottom() = rRect.Top() + aSize.Height();
1433 void PDFWriterImpl::PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
1435 sal_uInt16 nPoints = rPoly.GetSize();
1437 * #108582# applications do weird things
1439 sal_uInt32 nBufLen = rBuffer.getLength();
1440 if( nPoints > 0 )
1442 const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
1443 appendPoint( rPoly[0], rBuffer );
1444 rBuffer.append( " m\n" );
1445 for( sal_uInt16 i = 1; i < nPoints; i++ )
1447 if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
1449 // bezier
1450 SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
1451 appendPoint( rPoly[i], rBuffer );
1452 rBuffer.append( " " );
1453 appendPoint( rPoly[i+1], rBuffer );
1454 rBuffer.append( " " );
1455 appendPoint( rPoly[i+2], rBuffer );
1456 rBuffer.append( " c" );
1457 i += 2; // add additionally consumed points
1459 else
1461 // line
1462 appendPoint( rPoly[i], rBuffer );
1463 rBuffer.append( " l" );
1465 if( (rBuffer.getLength() - nBufLen) > 65 )
1467 rBuffer.append( "\n" );
1468 nBufLen = rBuffer.getLength();
1470 else
1471 rBuffer.append( " " );
1473 if( bClose )
1474 rBuffer.append( "h\n" );
1478 void PDFWriterImpl::PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
1480 basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1481 m_pWriter->m_aMapMode,
1482 m_pWriter->getReferenceDevice(),
1483 rPoly ) );
1485 if( basegfx::tools::isRectangle( aPoly ) )
1487 basegfx::B2DRange aRange( aPoly.getB2DRange() );
1488 basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
1489 appendPixelPoint( aBL, rBuffer );
1490 rBuffer.append( ' ' );
1491 appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
1492 rBuffer.append( ' ' );
1493 appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
1494 rBuffer.append( " re\n" );
1495 return;
1497 sal_uInt32 nPoints = aPoly.count();
1498 if( nPoints > 0 )
1500 sal_uInt32 nBufLen = rBuffer.getLength();
1501 basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
1502 appendPixelPoint( aLastPoint, rBuffer );
1503 rBuffer.append( " m\n" );
1504 for( sal_uInt32 i = 1; i <= nPoints; i++ )
1506 if( i != nPoints || aPoly.isClosed() )
1508 sal_uInt32 nCurPoint = i % nPoints;
1509 sal_uInt32 nLastPoint = i-1;
1510 basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
1511 if( aPoly.isNextControlPointUsed( nLastPoint ) &&
1512 aPoly.isPrevControlPointUsed( nCurPoint ) )
1514 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1515 rBuffer.append( ' ' );
1516 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1517 rBuffer.append( ' ' );
1518 appendPixelPoint( aPoint, rBuffer );
1519 rBuffer.append( " c" );
1521 else if( aPoly.isNextControlPointUsed( nLastPoint ) )
1523 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1524 rBuffer.append( ' ' );
1525 appendPixelPoint( aPoint, rBuffer );
1526 rBuffer.append( " y" );
1528 else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
1530 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1531 rBuffer.append( ' ' );
1532 appendPixelPoint( aPoint, rBuffer );
1533 rBuffer.append( " v" );
1535 else
1537 appendPixelPoint( aPoint, rBuffer );
1538 rBuffer.append( " l" );
1540 if( (rBuffer.getLength() - nBufLen) > 65 )
1542 rBuffer.append( "\n" );
1543 nBufLen = rBuffer.getLength();
1545 else
1546 rBuffer.append( " " );
1549 rBuffer.append( "h\n" );
1553 void PDFWriterImpl::PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1555 sal_uInt16 nPolygons = rPolyPoly.Count();
1556 for( sal_uInt16 n = 0; n < nPolygons; n++ )
1557 appendPolygon( rPolyPoly[n], rBuffer );
1560 void PDFWriterImpl::PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1562 sal_uInt32 nPolygons = rPolyPoly.count();
1563 for( sal_uInt32 n = 0; n < nPolygons; n++ )
1564 appendPolygon( rPolyPoly.getB2DPolygon( n ), rBuffer );
1567 void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1569 sal_Int32 nValue = nLength;
1570 if ( nLength < 0 )
1572 rBuffer.append( '-' );
1573 nValue = -nLength;
1575 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1576 m_pWriter->m_aMapMode,
1577 m_pWriter->getReferenceDevice(),
1578 Size( nValue, nValue ) ) );
1579 nValue = bVertical ? aSize.Height() : aSize.Width();
1580 if( pOutLength )
1581 *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1583 appendFixedInt( nValue, rBuffer );
1586 void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1588 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1589 m_pWriter->m_aMapMode,
1590 m_pWriter->getReferenceDevice(),
1591 Size( 1000, 1000 ) ) );
1592 fLength *= pixelToPoint((double)(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1593 appendDouble( fLength, rBuffer, nPrecision );
1596 bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1598 if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1600 // dashed and non-degraded case, check for implementation limits of dash array
1601 // in PDF reader apps (e.g. acroread)
1602 if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1604 return false;
1608 if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1610 // LineJoin used, ExtLineInfo required
1611 return false;
1614 if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1616 // LineCap used, ExtLineInfo required
1617 return false;
1620 if( rInfo.GetStyle() == LineStyle::Dash )
1622 rBuffer.append( "[ " );
1623 if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1625 appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer );
1626 rBuffer.append( ' ' );
1627 appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer );
1628 rBuffer.append( ' ' );
1630 else
1632 for( int n = 0; n < rInfo.GetDashCount(); n++ )
1634 appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer );
1635 rBuffer.append( ' ' );
1636 appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer );
1637 rBuffer.append( ' ' );
1639 for( int m = 0; m < rInfo.GetDotCount(); m++ )
1641 appendMappedLength( (sal_Int32)rInfo.GetDotLen(), rBuffer );
1642 rBuffer.append( ' ' );
1643 appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer );
1644 rBuffer.append( ' ' );
1647 rBuffer.append( "] 0 d\n" );
1650 if( rInfo.GetWidth() > 1 )
1652 appendMappedLength( (sal_Int32)rInfo.GetWidth(), rBuffer );
1653 rBuffer.append( " w\n" );
1655 else if( rInfo.GetWidth() == 0 )
1657 // "pixel" line
1658 appendDouble( 72.0/double(m_pWriter->getReferenceDevice()->GetDPIX()), rBuffer );
1659 rBuffer.append( " w\n" );
1662 return true;
1665 void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1667 if( nWidth <= 0 )
1668 return;
1669 if( nDelta < 1 )
1670 nDelta = 1;
1672 rBuffer.append( "0 " );
1673 appendMappedLength( nY, rBuffer );
1674 rBuffer.append( " m\n" );
1675 for( sal_Int32 n = 0; n < nWidth; )
1677 n += nDelta;
1678 appendMappedLength( n, rBuffer, false );
1679 rBuffer.append( ' ' );
1680 appendMappedLength( nDelta+nY, rBuffer );
1681 rBuffer.append( ' ' );
1682 n += nDelta;
1683 appendMappedLength( n, rBuffer, false );
1684 rBuffer.append( ' ' );
1685 appendMappedLength( nY, rBuffer );
1686 rBuffer.append( " v " );
1687 if( n < nWidth )
1689 n += nDelta;
1690 appendMappedLength( n, rBuffer, false );
1691 rBuffer.append( ' ' );
1692 appendMappedLength( nY-nDelta, rBuffer );
1693 rBuffer.append( ' ' );
1694 n += nDelta;
1695 appendMappedLength( n, rBuffer, false );
1696 rBuffer.append( ' ' );
1697 appendMappedLength( nY, rBuffer );
1698 rBuffer.append( " v\n" );
1701 rBuffer.append( "S\n" );
1704 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1705 const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
1706 PDFWriter& i_rOuterFace)
1708 m_pReferenceDevice( nullptr ),
1709 m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1710 m_nCurrentStructElement( 0 ),
1711 m_bEmitStructure( true ),
1712 m_nNextFID( 1 ),
1713 m_nInheritedPageWidth( 595 ), // default A4
1714 m_nInheritedPageHeight( 842 ), // default A4
1715 m_nCurrentPage( -1 ),
1716 m_nCatalogObject(0),
1717 m_nSignatureObject( -1 ),
1718 m_nSignatureContentOffset( 0 ),
1719 m_nSignatureLastByteRangeNoOffset( 0 ),
1720 m_nResourceDict( -1 ),
1721 m_nFontDictObject( -1 ),
1722 m_aContext(rContext),
1723 m_aFile(m_aContext.URL),
1724 m_bOpen(false),
1725 m_aDocDigest( rtl_digest_createMD5() ),
1726 m_aCipher( nullptr ),
1727 m_aDigest( nullptr ),
1728 m_nKeyLength(0),
1729 m_nRC4KeyLength(0),
1730 m_bEncryptThisStream( false ),
1731 m_nAccessPermissions(0),
1732 m_pEncryptionBuffer( nullptr ),
1733 m_nEncryptionBufferSize( 0 ),
1734 m_bIsPDF_A1( false ),
1735 m_rOuterFace( i_rOuterFace )
1737 #ifdef DO_TEST_PDF
1738 static bool bOnce = true;
1739 if( bOnce )
1741 bOnce = false;
1742 doTestCode();
1744 #endif
1745 m_aStructure.push_back( PDFStructureElement() );
1746 m_aStructure[0].m_nOwnElement = 0;
1747 m_aStructure[0].m_nParentElement = 0;
1749 Font aFont;
1750 aFont.SetFamilyName( "Times" );
1751 aFont.SetFontSize( Size( 0, 12 ) );
1753 GraphicsState aState;
1754 aState.m_aMapMode = m_aMapMode;
1755 aState.m_aFont = aFont;
1756 m_aGraphicsStack.push_front( aState );
1758 osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1759 if (aError != osl::File::E_None)
1761 if (aError == osl::File::E_EXIST)
1763 aError = m_aFile.open(osl_File_OpenFlag_Write);
1764 if (aError == osl::File::E_None)
1765 aError = m_aFile.setSize(0);
1768 if (aError != osl::File::E_None)
1769 return;
1771 m_bOpen = true;
1773 // setup DocInfo
1774 setupDocInfo();
1776 /* prepare the cypher engine, can be done in CTOR, free in DTOR */
1777 m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
1778 m_aDigest = rtl_digest_createMD5();
1780 /* the size of the Codec default maximum */
1781 /* is this 0x4000 required to be the same as MAX_SIGNATURE_CONTENT_LENGTH or just coincidentally the same at the moment? */
1782 if (!checkEncryptionBufferSize(0x4000))
1784 m_aFile.close();
1785 m_bOpen = false;
1786 return;
1789 if( xEnc.is() )
1790 prepareEncryption( xEnc );
1792 if( m_aContext.Encryption.Encrypt() )
1794 // sanity check
1795 if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
1796 m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
1797 m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
1800 // the field lengths are invalid ? This was not setup by initEncryption.
1801 // do not encrypt after all
1802 m_aContext.Encryption.OValue.clear();
1803 m_aContext.Encryption.UValue.clear();
1804 OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
1806 else // setup key lengths
1807 m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
1810 // write header
1811 OStringBuffer aBuffer( 20 );
1812 aBuffer.append( "%PDF-" );
1813 switch( m_aContext.Version )
1815 case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
1816 case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
1817 case PDFWriter::PDFVersion::PDF_A_1:
1818 default:
1819 case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
1820 case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
1822 // append something binary as comment (suggested in PDF Reference)
1823 aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1824 if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
1826 m_aFile.close();
1827 m_bOpen = false;
1828 return;
1831 // insert outline root
1832 m_aOutline.push_back( PDFOutlineEntry() );
1834 m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
1835 if( m_bIsPDF_A1 )
1836 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1839 PDFWriterImpl::~PDFWriterImpl()
1841 if( m_aDocDigest )
1842 rtl_digest_destroyMD5( m_aDocDigest );
1843 m_pReferenceDevice.disposeAndClear();
1845 if( m_aCipher )
1846 rtl_cipher_destroyARCFOUR( m_aCipher );
1847 if( m_aDigest )
1848 rtl_digest_destroyMD5( m_aDigest );
1850 rtl_freeMemory( m_pEncryptionBuffer );
1853 void PDFWriterImpl::setupDocInfo()
1855 std::vector< sal_uInt8 > aId;
1856 m_aCreationDateString = PDFWriter::GetDateTime();
1857 computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
1858 if( m_aContext.Encryption.DocumentIdentifier.empty() )
1859 m_aContext.Encryption.DocumentIdentifier = aId;
1862 OString PDFWriter::GetDateTime()
1864 OStringBuffer aRet;
1866 TimeValue aTVal, aGMT;
1867 oslDateTime aDT;
1868 osl_getSystemTime(&aGMT);
1869 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1870 osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1871 aRet.append("D:");
1872 aRet.append((sal_Char)('0' + ((aDT.Year / 1000) % 10)));
1873 aRet.append((sal_Char)('0' + ((aDT.Year / 100) % 10)));
1874 aRet.append((sal_Char)('0' + ((aDT.Year / 10) % 10)));
1875 aRet.append((sal_Char)('0' + (aDT.Year % 10)));
1876 aRet.append((sal_Char)('0' + ((aDT.Month / 10) % 10)));
1877 aRet.append((sal_Char)('0' + (aDT.Month % 10)));
1878 aRet.append((sal_Char)('0' + ((aDT.Day / 10) % 10)));
1879 aRet.append((sal_Char)('0' + (aDT.Day % 10)));
1880 aRet.append((sal_Char)('0' + ((aDT.Hours / 10) % 10)));
1881 aRet.append((sal_Char)('0' + (aDT.Hours % 10)));
1882 aRet.append((sal_Char)('0' + ((aDT.Minutes / 10) % 10)));
1883 aRet.append((sal_Char)('0' + (aDT.Minutes % 10)));
1884 aRet.append((sal_Char)('0' + ((aDT.Seconds / 10) % 10)));
1885 aRet.append((sal_Char)('0' + (aDT.Seconds % 10)));
1887 sal_uInt32 nDelta = 0;
1888 if (aGMT.Seconds > aTVal.Seconds)
1890 aRet.append("-");
1891 nDelta = aGMT.Seconds-aTVal.Seconds;
1893 else if (aGMT.Seconds < aTVal.Seconds)
1895 aRet.append("+");
1896 nDelta = aTVal.Seconds-aGMT.Seconds;
1898 else
1899 aRet.append("Z");
1901 if (nDelta)
1903 aRet.append((sal_Char)('0' + ((nDelta / 36000) % 10)));
1904 aRet.append((sal_Char)('0' + ((nDelta / 3600) % 10)));
1905 aRet.append("'");
1906 aRet.append((sal_Char)('0' + ((nDelta / 600) % 6)));
1907 aRet.append((sal_Char)('0' + ((nDelta / 60) % 10)));
1909 aRet.append( "'" );
1911 return aRet.makeStringAndClear();
1914 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1915 const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1916 const OString& i_rCString1,
1917 OString& o_rCString2
1920 o_rIdentifier.clear();
1922 //build the document id
1923 OString aInfoValuesOut;
1924 OStringBuffer aID( 1024 );
1925 if( !i_rDocInfo.Title.isEmpty() )
1926 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
1927 if( !i_rDocInfo.Author.isEmpty() )
1928 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
1929 if( !i_rDocInfo.Subject.isEmpty() )
1930 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
1931 if( !i_rDocInfo.Keywords.isEmpty() )
1932 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
1933 if( !i_rDocInfo.Creator.isEmpty() )
1934 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
1935 if( !i_rDocInfo.Producer.isEmpty() )
1936 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
1938 TimeValue aTVal, aGMT;
1939 oslDateTime aDT;
1940 osl_getSystemTime( &aGMT );
1941 osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1942 osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1943 OStringBuffer aCreationMetaDateString(64);
1945 // i59651: we fill the Metadata date string as well, if PDF/A is requested
1946 // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1947 // local time zone offset UTC only, whereas Acrobat 8 seems
1948 // to use the localtime notation only
1949 // according to a recommendation in XMP Specification (Jan 2004, page 75)
1950 // the Acrobat way seems the right approach
1951 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) );
1952 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) );
1953 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) );
1954 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) );
1955 aCreationMetaDateString.append( "-" );
1956 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) );
1957 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) );
1958 aCreationMetaDateString.append( "-" );
1959 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) );
1960 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) );
1961 aCreationMetaDateString.append( "T" );
1962 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) );
1963 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) );
1964 aCreationMetaDateString.append( ":" );
1965 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) );
1966 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) );
1967 aCreationMetaDateString.append( ":" );
1968 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) );
1969 aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) );
1971 sal_uInt32 nDelta = 0;
1972 if( aGMT.Seconds > aTVal.Seconds )
1974 nDelta = aGMT.Seconds-aTVal.Seconds;
1975 aCreationMetaDateString.append( "-" );
1977 else if( aGMT.Seconds < aTVal.Seconds )
1979 nDelta = aTVal.Seconds-aGMT.Seconds;
1980 aCreationMetaDateString.append( "+" );
1982 else
1984 aCreationMetaDateString.append( "Z" );
1987 if( nDelta )
1989 aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) );
1990 aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) );
1991 aCreationMetaDateString.append( ":" );
1992 aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) );
1993 aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) );
1995 aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1997 aInfoValuesOut = aID.makeStringAndClear();
1998 o_rCString2 = aCreationMetaDateString.makeStringAndClear();
2000 rtlDigest aDigest = rtl_digest_createMD5();
2001 OSL_ENSURE( aDigest != nullptr, "PDFWriterImpl::computeDocumentIdentifier: cannot obtain a digest object !" );
2002 if( aDigest )
2004 rtlDigestError nError = rtl_digest_updateMD5( aDigest, &aGMT, sizeof( aGMT ) );
2005 if( nError == rtl_Digest_E_None )
2006 nError = rtl_digest_updateMD5( aDigest, aInfoValuesOut.getStr(), aInfoValuesOut.getLength() );
2007 if( nError == rtl_Digest_E_None )
2009 o_rIdentifier = std::vector< sal_uInt8 >( 16, 0 );
2010 //the binary form of the doc id is needed for encryption stuff
2011 rtl_digest_getMD5( aDigest, &o_rIdentifier[0], 16 );
2013 rtl_digest_destroyMD5(aDigest);
2017 /* i12626 methods */
2019 check if the Unicode string must be encrypted or not, perform the requested task,
2020 append the string as unicode hex, encrypted if needed
2022 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
2024 rOutBuffer.append( "<" );
2025 if( m_aContext.Encryption.Encrypt() )
2027 const sal_Unicode* pStr = rInString.getStr();
2028 sal_Int32 nLen = rInString.getLength();
2029 //prepare a unicode string, encrypt it
2030 if( checkEncryptionBufferSize( nLen*2 ) )
2032 enableStringEncryption( nInObjectNumber );
2033 sal_uInt8 *pCopy = m_pEncryptionBuffer;
2034 sal_Int32 nChars = 2;
2035 *pCopy++ = 0xFE;
2036 *pCopy++ = 0xFF;
2037 // we need to prepare a byte stream from the unicode string buffer
2038 for( int i = 0; i < nLen; i++ )
2040 sal_Unicode aUnChar = pStr[i];
2041 *pCopy++ = (sal_uInt8)( aUnChar >> 8 );
2042 *pCopy++ = (sal_uInt8)( aUnChar & 255 );
2043 nChars += 2;
2045 //encrypt in place
2046 rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChars, m_pEncryptionBuffer, nChars );
2047 //now append, hexadecimal (appendHex), the encrypted result
2048 for(int i = 0; i < nChars; i++)
2049 appendHex( m_pEncryptionBuffer[i], rOutBuffer );
2052 else
2053 PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
2054 rOutBuffer.append( ">" );
2057 inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
2059 rOutBuffer.append( "(" );
2060 sal_Int32 nChars = rInString.getLength();
2061 //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
2062 if( m_aContext.Encryption.Encrypt() && checkEncryptionBufferSize( nChars ) )
2064 //encrypt the string in a buffer, then append it
2065 enableStringEncryption( nInObjectNumber );
2066 rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_pEncryptionBuffer, nChars );
2067 appendLiteralString( reinterpret_cast<sal_Char*>(m_pEncryptionBuffer), nChars, rOutBuffer );
2069 else
2070 appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
2071 rOutBuffer.append( ")" );
2074 inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
2076 OStringBuffer aBufferString( rInString );
2077 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
2080 void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
2082 OString aBufferString( OUStringToOString( rInString, nEnc ) );
2083 sal_Int32 nLen = aBufferString.getLength();
2084 OStringBuffer aBuf( nLen );
2085 const sal_Char* pT = aBufferString.getStr();
2087 for( sal_Int32 i = 0; i < nLen; i++, pT++ )
2089 if( (*pT & 0x80) == 0 )
2090 aBuf.append( *pT );
2091 else
2093 aBuf.append( '<' );
2094 appendHex( *pT, aBuf );
2095 aBuf.append( '>' );
2098 aBufferString = aBuf.makeStringAndClear();
2099 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
2102 /* end i12626 methods */
2104 void PDFWriterImpl::emitComment( const char* pComment )
2106 OStringBuffer aLine( 64 );
2107 aLine.append( "% " );
2108 aLine.append( pComment );
2109 aLine.append( "\n" );
2110 writeBuffer( aLine.getStr(), aLine.getLength() );
2113 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
2115 if (!g_bDebugDisableCompression)
2117 pStream->Seek( STREAM_SEEK_TO_END );
2118 sal_uLong nEndPos = pStream->Tell();
2119 pStream->Seek( STREAM_SEEK_TO_BEGIN );
2120 ZCodec aCodec( 0x4000, 0x4000 );
2121 SvMemoryStream aStream;
2122 aCodec.BeginCompression();
2123 aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
2124 aCodec.EndCompression();
2125 nEndPos = aStream.Tell();
2126 pStream->Seek( STREAM_SEEK_TO_BEGIN );
2127 aStream.Seek( STREAM_SEEK_TO_BEGIN );
2128 pStream->SetStreamSize( nEndPos );
2129 pStream->WriteBytes( aStream.GetData(), nEndPos );
2130 return true;
2132 else
2133 return false;
2136 void PDFWriterImpl::beginCompression()
2138 if (!g_bDebugDisableCompression)
2140 m_pCodec = o3tl::make_unique<ZCodec>( 0x4000, 0x4000 );
2141 m_pMemStream = o3tl::make_unique<SvMemoryStream>();
2142 m_pCodec->BeginCompression();
2146 void PDFWriterImpl::endCompression()
2148 if (!g_bDebugDisableCompression && m_pCodec)
2150 m_pCodec->EndCompression();
2151 m_pCodec.reset();
2152 sal_uInt64 nLen = m_pMemStream->Tell();
2153 m_pMemStream->Seek( 0 );
2154 writeBuffer( m_pMemStream->GetData(), nLen );
2155 m_pMemStream.reset();
2159 bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
2161 if( ! m_bOpen ) // we are already down the drain
2162 return false;
2164 if( ! nBytes ) // huh ?
2165 return true;
2167 if( !m_aOutputStreams.empty() )
2169 m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
2170 m_aOutputStreams.front().m_pStream->WriteBytes(
2171 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
2172 return true;
2175 sal_uInt64 nWritten;
2176 if( m_pCodec )
2178 m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), (sal_uLong)nBytes );
2179 nWritten = nBytes;
2181 else
2183 bool buffOK = true;
2184 if( m_bEncryptThisStream )
2186 /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
2187 buffOK = checkEncryptionBufferSize( static_cast<sal_Int32>(nBytes) );
2188 if( buffOK )
2189 rtl_cipher_encodeARCFOUR( m_aCipher,
2190 pBuffer, static_cast<sal_Size>(nBytes),
2191 m_pEncryptionBuffer, static_cast<sal_Size>(nBytes) );
2194 const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_pEncryptionBuffer : pBuffer;
2195 if( m_aDocDigest )
2196 rtl_digest_updateMD5( m_aDocDigest, pWriteBuffer, static_cast<sal_uInt32>(nBytes) );
2198 if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
2199 nWritten = 0;
2201 if( nWritten != nBytes )
2203 m_aFile.close();
2204 m_bOpen = false;
2208 return nWritten == nBytes;
2211 OutputDevice* PDFWriterImpl::getReferenceDevice()
2213 if( ! m_pReferenceDevice )
2215 VclPtrInstance<VirtualDevice> pVDev(DeviceFormat::DEFAULT);
2217 m_pReferenceDevice = pVDev;
2219 if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
2220 pVDev->SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
2221 else
2222 pVDev->SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
2224 pVDev->SetOutputSizePixel( Size( 640, 480 ) );
2225 pVDev->SetMapMode( MapUnit::MapMM );
2227 m_pReferenceDevice->mpPDFWriter = this;
2228 m_pReferenceDevice->ImplUpdateFontData();
2230 return m_pReferenceDevice;
2233 static FontAttributes GetDevFontAttributes( const PDFWriterImpl::BuiltinFont& rBuiltin )
2235 FontAttributes aDFA;
2236 aDFA.SetFamilyName( OUString::createFromAscii( rBuiltin.m_pName ) );
2237 aDFA.SetStyleName( OUString::createFromAscii( rBuiltin.m_pStyleName ) );
2238 aDFA.SetFamilyType( rBuiltin.m_eFamily );
2239 aDFA.SetSymbolFlag( rBuiltin.m_eCharSet != RTL_TEXTENCODING_MS_1252 );
2240 aDFA.SetPitch( rBuiltin.m_ePitch );
2241 aDFA.SetWeight( rBuiltin.m_eWeight );
2242 aDFA.SetItalic( rBuiltin.m_eItalic );
2243 aDFA.SetWidthType( rBuiltin.m_eWidthType );
2245 aDFA.SetQuality( 50000 );
2246 return aDFA;
2249 PdfBuiltinFontFace::PdfBuiltinFontFace( const PDFWriterImpl::BuiltinFont& rBuiltin )
2250 : PhysicalFontFace( GetDevFontAttributes(rBuiltin) ),
2251 mrBuiltin( rBuiltin )
2254 LogicalFontInstance* PdfBuiltinFontFace::CreateFontInstance( FontSelectPattern& rFSD ) const
2256 LogicalFontInstance* pEntry = new LogicalFontInstance( rFSD );
2257 return pEntry;
2261 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
2263 endPage();
2264 m_nCurrentPage = m_aPages.size();
2265 m_aPages.push_back( PDFPage(this, nPageWidth, nPageHeight, eOrientation ) );
2266 m_aPages.back().m_nPageIndex = m_nCurrentPage;
2267 m_aPages.back().beginStream();
2269 // setup global graphics state
2270 // linewidth is "1 pixel" by default
2271 OStringBuffer aBuf( 16 );
2272 appendDouble( 72.0/double(getReferenceDevice()->GetDPIX()), aBuf );
2273 aBuf.append( " w\n" );
2274 writeBuffer( aBuf.getStr(), aBuf.getLength() );
2277 void PDFWriterImpl::endPage()
2279 if( !m_aPages.empty() )
2281 // close eventual MC sequence
2282 endStructureElementMCSeq();
2284 // sanity check
2285 if( !m_aOutputStreams.empty() )
2287 OSL_FAIL( "redirection across pages !!!" );
2288 m_aOutputStreams.clear(); // leak !
2289 m_aMapMode.SetOrigin( Point() );
2292 m_aGraphicsStack.clear();
2293 m_aGraphicsStack.push_back( GraphicsState() );
2295 // this should pop the PDF graphics stack if necessary
2296 updateGraphicsState();
2298 m_aPages.back().endStream();
2300 // reset the default font
2301 Font aFont;
2302 aFont.SetFamilyName( "Times" );
2303 aFont.SetFontSize( Size( 0, 12 ) );
2305 m_aCurrentPDFState = m_aGraphicsStack.front();
2306 m_aGraphicsStack.front().m_aFont = aFont;
2308 for( std::list<BitmapEmit>::iterator it = m_aBitmaps.begin();
2309 it != m_aBitmaps.end(); ++it )
2311 if( ! it->m_aBitmap.IsEmpty() )
2313 writeBitmapObject( *it );
2314 it->m_aBitmap = BitmapEx();
2317 for( std::list<JPGEmit>::iterator jpeg = m_aJPGs.begin(); jpeg != m_aJPGs.end(); ++jpeg )
2319 if( jpeg->m_pStream )
2321 writeJPG( *jpeg );
2322 jpeg->m_pStream.reset();
2323 jpeg->m_aMask = Bitmap();
2326 for( std::list<TransparencyEmit>::iterator t = m_aTransparentObjects.begin();
2327 t != m_aTransparentObjects.end(); ++t )
2329 if( t->m_pContentStream )
2331 writeTransparentObject( *t );
2332 delete t->m_pContentStream;
2333 t->m_pContentStream = nullptr;
2339 sal_Int32 PDFWriterImpl::createObject()
2341 m_aObjects.push_back( ~0U );
2342 return m_aObjects.size();
2345 bool PDFWriterImpl::updateObject( sal_Int32 n )
2347 if( ! m_bOpen )
2348 return false;
2350 sal_uInt64 nOffset = ~0U;
2351 osl::File::RC aError = m_aFile.getPos(nOffset);
2352 SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
2353 if (aError != osl::File::E_None)
2355 m_aFile.close();
2356 m_bOpen = false;
2358 m_aObjects[ n-1 ] = nOffset;
2359 return aError == osl::File::E_None;
2362 #define CHECK_RETURN( x ) if( !(x) ) return 0
2363 #define CHECK_RETURN2( x ) if( !(x) ) return
2365 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
2367 if( nObject > 0 )
2369 OStringBuffer aLine( 1024 );
2371 aLine.append( nObject );
2372 aLine.append( " 0 obj\n"
2373 "<</Nums[\n" );
2374 sal_Int32 nTreeItems = m_aStructParentTree.size();
2375 for( sal_Int32 n = 0; n < nTreeItems; n++ )
2377 aLine.append( n );
2378 aLine.append( ' ' );
2379 aLine.append( m_aStructParentTree[n] );
2380 aLine.append( "\n" );
2382 aLine.append( "]>>\nendobj\n\n" );
2383 CHECK_RETURN( updateObject( nObject ) );
2384 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2386 return nObject;
2389 const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
2391 static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
2392 // fill maps once
2393 if( aAttributeStrings.empty() )
2395 aAttributeStrings[ PDFWriter::Placement ] = "Placement";
2396 aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
2397 aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
2398 aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
2399 aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
2400 aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
2401 aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
2402 aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
2403 aAttributeStrings[ PDFWriter::Width ] = "Width";
2404 aAttributeStrings[ PDFWriter::Height ] = "Height";
2405 aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
2406 aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
2407 aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
2408 aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
2409 aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
2410 aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
2411 aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
2412 aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
2413 aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
2416 std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
2417 aAttributeStrings.find( eAttr );
2419 #if OSL_DEBUG_LEVEL > 1
2420 if( it == aAttributeStrings.end() )
2421 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
2422 #endif
2424 return it != aAttributeStrings.end() ? it->second : "";
2427 const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
2429 static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
2431 if( aValueStrings.empty() )
2433 aValueStrings[ PDFWriter::NONE ] = "None";
2434 aValueStrings[ PDFWriter::Block ] = "Block";
2435 aValueStrings[ PDFWriter::Inline ] = "Inline";
2436 aValueStrings[ PDFWriter::Before ] = "Before";
2437 aValueStrings[ PDFWriter::After ] = "After";
2438 aValueStrings[ PDFWriter::Start ] = "Start";
2439 aValueStrings[ PDFWriter::End ] = "End";
2440 aValueStrings[ PDFWriter::LrTb ] = "LrTb";
2441 aValueStrings[ PDFWriter::RlTb ] = "RlTb";
2442 aValueStrings[ PDFWriter::TbRl ] = "TbRl";
2443 aValueStrings[ PDFWriter::Center ] = "Center";
2444 aValueStrings[ PDFWriter::Justify ] = "Justify";
2445 aValueStrings[ PDFWriter::Auto ] = "Auto";
2446 aValueStrings[ PDFWriter::Middle ] = "Middle";
2447 aValueStrings[ PDFWriter::Normal ] = "Normal";
2448 aValueStrings[ PDFWriter::Underline ] = "Underline";
2449 aValueStrings[ PDFWriter::Overline ] = "Overline";
2450 aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
2451 aValueStrings[ PDFWriter::Disc ] = "Disc";
2452 aValueStrings[ PDFWriter::Circle ] = "Circle";
2453 aValueStrings[ PDFWriter::Square ] = "Square";
2454 aValueStrings[ PDFWriter::Decimal ] = "Decimal";
2455 aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
2456 aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
2457 aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
2458 aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
2461 std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
2462 aValueStrings.find( eVal );
2464 #if OSL_DEBUG_LEVEL > 1
2465 if( it == aValueStrings.end() )
2466 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
2467 #endif
2469 return it != aValueStrings.end() ? it->second : "";
2472 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
2474 o_rLine.append( "/" );
2475 o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
2477 if( i_rVal.eValue != PDFWriter::Invalid )
2479 o_rLine.append( "/" );
2480 o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
2482 else
2484 // numerical value
2485 o_rLine.append( " " );
2486 if( i_bIsFixedInt )
2487 appendFixedInt( i_rVal.nValue, o_rLine );
2488 else
2489 o_rLine.append( i_rVal.nValue );
2491 o_rLine.append( "\n" );
2494 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
2496 // create layout, list and table attribute sets
2497 OStringBuffer aLayout(256), aList(64), aTable(64);
2498 for( PDFStructAttributes::const_iterator it = i_rEle.m_aAttributes.begin();
2499 it != i_rEle.m_aAttributes.end(); ++it )
2501 if( it->first == PDFWriter::ListNumbering )
2502 appendStructureAttributeLine( it->first, it->second, aList, true );
2503 else if( it->first == PDFWriter::RowSpan ||
2504 it->first == PDFWriter::ColSpan )
2505 appendStructureAttributeLine( it->first, it->second, aTable, false );
2506 else if( it->first == PDFWriter::LinkAnnotation )
2508 sal_Int32 nLink = it->second.nValue;
2509 std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
2510 m_aLinkPropertyMap.find( nLink );
2511 if( link_it != m_aLinkPropertyMap.end() )
2512 nLink = link_it->second;
2513 if( nLink >= 0 && nLink < (sal_Int32)m_aLinks.size() )
2515 // update struct parent of link
2516 OStringBuffer aStructParentEntry( 32 );
2517 aStructParentEntry.append( i_rEle.m_nObject );
2518 aStructParentEntry.append( " 0 R" );
2519 m_aStructParentTree.push_back( aStructParentEntry.makeStringAndClear() );
2520 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
2522 sal_Int32 nRefObject = createObject();
2523 OStringBuffer aRef( 256 );
2524 aRef.append( nRefObject );
2525 aRef.append( " 0 obj\n"
2526 "<</Type/OBJR/Obj " );
2527 aRef.append( m_aLinks[ nLink ].m_nObject );
2528 aRef.append( " 0 R>>\n"
2529 "endobj\n\n"
2531 if (updateObject(nRefObject))
2533 writeBuffer( aRef.getStr(), aRef.getLength() );
2536 i_rEle.m_aKids.push_back( PDFStructureElementKid( nRefObject ) );
2538 else
2540 OSL_FAIL( "unresolved link id for Link structure" );
2541 #if OSL_DEBUG_LEVEL > 1
2542 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
2544 OStringBuffer aLine( "unresolved link id " );
2545 aLine.append( nLink );
2546 aLine.append( " for Link structure" );
2547 emitComment( aLine.getStr() );
2549 #endif
2552 else
2553 appendStructureAttributeLine( it->first, it->second, aLayout, true );
2555 if( ! i_rEle.m_aBBox.IsEmpty() )
2557 aLayout.append( "/BBox[" );
2558 appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
2559 aLayout.append( " " );
2560 appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
2561 aLayout.append( " " );
2562 appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
2563 aLayout.append( " " );
2564 appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
2565 aLayout.append( "]\n" );
2568 std::vector< sal_Int32 > aAttribObjects;
2569 if( !aLayout.isEmpty() )
2571 aAttribObjects.push_back( createObject() );
2572 if (updateObject( aAttribObjects.back() ))
2574 OStringBuffer aObj( 64 );
2575 aObj.append( aAttribObjects.back() );
2576 aObj.append( " 0 obj\n"
2577 "<</O/Layout\n" );
2578 aLayout.append( ">>\nendobj\n\n" );
2579 writeBuffer( aObj.getStr(), aObj.getLength() );
2580 writeBuffer( aLayout.getStr(), aLayout.getLength() );
2583 if( !aList.isEmpty() )
2585 aAttribObjects.push_back( createObject() );
2586 if (updateObject( aAttribObjects.back() ))
2588 OStringBuffer aObj( 64 );
2589 aObj.append( aAttribObjects.back() );
2590 aObj.append( " 0 obj\n"
2591 "<</O/List\n" );
2592 aList.append( ">>\nendobj\n\n" );
2593 writeBuffer( aObj.getStr(), aObj.getLength() );
2594 writeBuffer( aList.getStr(), aList.getLength() );
2597 if( !aTable.isEmpty() )
2599 aAttribObjects.push_back( createObject() );
2600 if (updateObject( aAttribObjects.back() ))
2602 OStringBuffer aObj( 64 );
2603 aObj.append( aAttribObjects.back() );
2604 aObj.append( " 0 obj\n"
2605 "<</O/Table\n" );
2606 aTable.append( ">>\nendobj\n\n" );
2607 writeBuffer( aObj.getStr(), aObj.getLength() );
2608 writeBuffer( aTable.getStr(), aTable.getLength() );
2612 OStringBuffer aRet( 64 );
2613 if( aAttribObjects.size() > 1 )
2614 aRet.append( " [" );
2615 for( std::vector< sal_Int32 >::const_iterator at_it = aAttribObjects.begin();
2616 at_it != aAttribObjects.end(); ++at_it )
2618 aRet.append( " " );
2619 aRet.append( *at_it );
2620 aRet.append( " 0 R" );
2622 if( aAttribObjects.size() > 1 )
2623 aRet.append( " ]" );
2624 return aRet.makeStringAndClear();
2627 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
2630 // do not emit NonStruct and its children
2631 rEle.m_eType == PDFWriter::NonStructElement &&
2632 rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
2634 return 0;
2636 for( std::list< sal_Int32 >::const_iterator it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ++it )
2638 if( *it > 0 && *it < sal_Int32(m_aStructure.size()) )
2640 PDFStructureElement& rChild = m_aStructure[ *it ];
2641 if( rChild.m_eType != PDFWriter::NonStructElement )
2643 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2644 emitStructure( rChild );
2645 else
2647 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2648 #if OSL_DEBUG_LEVEL > 1
2649 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure elemnt with id " << *it);
2650 #endif
2654 else
2656 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2657 #if OSL_DEBUG_LEVEL > 1
2658 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << *it);
2659 #endif
2663 OStringBuffer aLine( 512 );
2664 aLine.append( rEle.m_nObject );
2665 aLine.append( " 0 obj\n"
2666 "<</Type" );
2667 sal_Int32 nParentTree = -1;
2668 if( rEle.m_nOwnElement == rEle.m_nParentElement )
2670 nParentTree = createObject();
2671 CHECK_RETURN( nParentTree );
2672 aLine.append( "/StructTreeRoot\n" );
2673 aLine.append( "/ParentTree " );
2674 aLine.append( nParentTree );
2675 aLine.append( " 0 R\n" );
2676 if( ! m_aRoleMap.empty() )
2678 aLine.append( "/RoleMap<<" );
2679 for( std::unordered_map<OString,OString,OStringHash>::const_iterator
2680 it = m_aRoleMap.begin(); it != m_aRoleMap.end(); ++it )
2682 aLine.append( '/' );
2683 aLine.append(it->first);
2684 aLine.append( '/' );
2685 aLine.append( it->second );
2686 aLine.append( '\n' );
2688 aLine.append( ">>\n" );
2691 else
2693 aLine.append( "/StructElem\n"
2694 "/S/" );
2695 if( !rEle.m_aAlias.isEmpty() )
2696 aLine.append( rEle.m_aAlias );
2697 else
2698 aLine.append( getStructureTag( rEle.m_eType ) );
2699 aLine.append( "\n"
2700 "/P " );
2701 aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
2702 aLine.append( " 0 R\n"
2703 "/Pg " );
2704 aLine.append( rEle.m_nFirstPageObject );
2705 aLine.append( " 0 R\n" );
2706 if( !rEle.m_aActualText.isEmpty() )
2708 aLine.append( "/ActualText" );
2709 appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
2710 aLine.append( "\n" );
2712 if( !rEle.m_aAltText.isEmpty() )
2714 aLine.append( "/Alt" );
2715 appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
2716 aLine.append( "\n" );
2719 if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2721 OString aAttribs = emitStructureAttributes( rEle );
2722 if( !aAttribs.isEmpty() )
2724 aLine.append( "/A" );
2725 aLine.append( aAttribs );
2726 aLine.append( "\n" );
2729 if( !rEle.m_aLocale.Language.isEmpty() )
2731 /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2732 * include script tags and others.
2733 * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2734 * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2735 * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2736 * */
2737 LanguageTag aLanguageTag( rEle.m_aLocale);
2738 OUString aLanguage, aScript, aCountry;
2739 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2740 if (!aLanguage.isEmpty())
2742 OUStringBuffer aLocBuf( 16 );
2743 aLocBuf.append( aLanguage );
2744 if( !aCountry.isEmpty() )
2746 aLocBuf.append( '-' );
2747 aLocBuf.append( aCountry );
2749 aLine.append( "/Lang" );
2750 appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
2751 aLine.append( "\n" );
2754 if( ! rEle.m_aKids.empty() )
2756 unsigned int i = 0;
2757 aLine.append( "/K[" );
2758 for( std::list< PDFStructureElementKid >::const_iterator it =
2759 rEle.m_aKids.begin(); it != rEle.m_aKids.end(); ++it, i++ )
2761 if( it->nMCID == -1 )
2763 aLine.append( it->nObject );
2764 aLine.append( " 0 R" );
2765 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2767 else
2769 if( it->nObject == rEle.m_nFirstPageObject )
2771 aLine.append( it->nMCID );
2772 aLine.append( " " );
2774 else
2776 aLine.append( "<</Type/MCR/Pg " );
2777 aLine.append( it->nObject );
2778 aLine.append( " 0 R /MCID " );
2779 aLine.append( it->nMCID );
2780 aLine.append( ">>\n" );
2784 aLine.append( "]\n" );
2786 aLine.append( ">>\nendobj\n\n" );
2788 CHECK_RETURN( updateObject( rEle.m_nObject ) );
2789 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2791 CHECK_RETURN( emitStructParentTree( nParentTree ) );
2793 return rEle.m_nObject;
2796 bool PDFWriterImpl::emitGradients()
2798 for( std::list<GradientEmit>::iterator it = m_aGradients.begin();
2799 it != m_aGradients.end(); ++it )
2801 if ( !writeGradientFunction( *it ) ) return false;
2803 return true;
2806 bool PDFWriterImpl::emitTilings()
2808 OStringBuffer aTilingObj( 1024 );
2810 for( std::vector<TilingEmit>::iterator it = m_aTilings.begin(); it != m_aTilings.end(); ++it )
2812 SAL_WARN_IF( !it->m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2813 if( ! it->m_pTilingStream )
2814 continue;
2816 aTilingObj.setLength( 0 );
2818 #if OSL_DEBUG_LEVEL > 1
2819 emitComment( "PDFWriterImpl::emitTilings" );
2820 #endif
2822 sal_Int32 nX = (sal_Int32)it->m_aRectangle.Left();
2823 sal_Int32 nY = (sal_Int32)it->m_aRectangle.Top();
2824 sal_Int32 nW = (sal_Int32)it->m_aRectangle.GetWidth();
2825 sal_Int32 nH = (sal_Int32)it->m_aRectangle.GetHeight();
2826 if( it->m_aCellSize.Width() == 0 )
2827 it->m_aCellSize.Width() = nW;
2828 if( it->m_aCellSize.Height() == 0 )
2829 it->m_aCellSize.Height() = nH;
2831 bool bDeflate = compressStream( it->m_pTilingStream );
2832 it->m_pTilingStream->Seek( STREAM_SEEK_TO_END );
2833 sal_uInt64 const nTilingStreamSize = it->m_pTilingStream->Tell();
2834 it->m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2836 // write pattern object
2837 aTilingObj.append( it->m_nObject );
2838 aTilingObj.append( " 0 obj\n" );
2839 aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
2840 "/PaintType 1\n"
2841 "/TilingType 2\n"
2842 "/BBox[" );
2843 appendFixedInt( nX, aTilingObj );
2844 aTilingObj.append( ' ' );
2845 appendFixedInt( nY, aTilingObj );
2846 aTilingObj.append( ' ' );
2847 appendFixedInt( nX+nW, aTilingObj );
2848 aTilingObj.append( ' ' );
2849 appendFixedInt( nY+nH, aTilingObj );
2850 aTilingObj.append( "]\n"
2851 "/XStep " );
2852 appendFixedInt( it->m_aCellSize.Width(), aTilingObj );
2853 aTilingObj.append( "\n"
2854 "/YStep " );
2855 appendFixedInt( it->m_aCellSize.Height(), aTilingObj );
2856 aTilingObj.append( "\n" );
2857 if( it->m_aTransform.matrix[0] != 1.0 ||
2858 it->m_aTransform.matrix[1] != 0.0 ||
2859 it->m_aTransform.matrix[3] != 0.0 ||
2860 it->m_aTransform.matrix[4] != 1.0 ||
2861 it->m_aTransform.matrix[2] != 0.0 ||
2862 it->m_aTransform.matrix[5] != 0.0 )
2864 aTilingObj.append( "/Matrix [" );
2865 // TODO: scaling, mirroring on y, etc
2866 appendDouble( it->m_aTransform.matrix[0], aTilingObj );
2867 aTilingObj.append( ' ' );
2868 appendDouble( it->m_aTransform.matrix[1], aTilingObj );
2869 aTilingObj.append( ' ' );
2870 appendDouble( it->m_aTransform.matrix[3], aTilingObj );
2871 aTilingObj.append( ' ' );
2872 appendDouble( it->m_aTransform.matrix[4], aTilingObj );
2873 aTilingObj.append( ' ' );
2874 appendDouble( it->m_aTransform.matrix[2], aTilingObj );
2875 aTilingObj.append( ' ' );
2876 appendDouble( it->m_aTransform.matrix[5], aTilingObj );
2877 aTilingObj.append( "]\n" );
2879 aTilingObj.append( "/Resources" );
2880 it->m_aResources.append( aTilingObj, getFontDictObject() );
2881 if( bDeflate )
2882 aTilingObj.append( "/Filter/FlateDecode" );
2883 aTilingObj.append( "/Length " );
2884 aTilingObj.append( (sal_Int32)nTilingStreamSize );
2885 aTilingObj.append( ">>\nstream\n" );
2886 if ( !updateObject( it->m_nObject ) ) return false;
2887 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2888 checkAndEnableStreamEncryption( it->m_nObject );
2889 bool written = writeBuffer( it->m_pTilingStream->GetData(), nTilingStreamSize );
2890 delete it->m_pTilingStream;
2891 it->m_pTilingStream = nullptr;
2892 if( !written )
2893 return false;
2894 disableStreamEncryption();
2895 aTilingObj.setLength( 0 );
2896 aTilingObj.append( "\nendstream\nendobj\n\n" );
2897 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2899 return true;
2902 sal_Int32 PDFWriterImpl::emitBuiltinFont( const PdfBuiltinFontFace* pFD, sal_Int32 nFontObject )
2904 if( !pFD )
2905 return 0;
2906 const BuiltinFont& rBuiltinFont = pFD->GetBuiltinFont();
2908 OStringBuffer aLine( 1024 );
2910 if( nFontObject <= 0 )
2911 nFontObject = createObject();
2912 CHECK_RETURN( updateObject( nFontObject ) );
2913 aLine.append( nFontObject );
2914 aLine.append( " 0 obj\n"
2915 "<</Type/Font/Subtype/Type1/BaseFont/" );
2916 appendName( rBuiltinFont.m_pPSName, aLine );
2917 aLine.append( "\n" );
2918 if( rBuiltinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2919 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2920 aLine.append( ">>\nendobj\n\n" );
2921 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2922 return nFontObject;
2925 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont& rEmbed )
2927 std::map< sal_Int32, sal_Int32 > aRet;
2929 sal_Int32 nFontDescriptor = 0;
2930 OString aSubType( "/Type1" );
2931 FontSubsetInfo aInfo;
2932 // fill in dummy values
2933 aInfo.m_nAscent = 1000;
2934 aInfo.m_nDescent = 200;
2935 aInfo.m_nCapHeight = 1000;
2936 aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2937 aInfo.m_aPSName = pFont->GetFamilyName();
2938 sal_Int32 pWidths[256];
2939 memset( pWidths, 0, sizeof(pWidths) );
2941 SalGraphics *pGraphics = m_pReferenceDevice->GetGraphics();
2943 assert(pGraphics);
2945 aSubType = OString( "/TrueType" );
2946 std::vector< sal_Int32 > aGlyphWidths;
2947 Ucs2UIntMap aUnicodeMap;
2948 pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
2950 OUString aTmpName;
2951 osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2952 sal_GlyphId aGlyphIds[ 256 ];
2953 sal_uInt8 pEncoding[ 256 ];
2954 sal_Int32 pDuWidths[ 256 ];
2956 memset( aGlyphIds, 0, sizeof( aGlyphIds ) );
2957 memset( pEncoding, 0, sizeof( pEncoding ) );
2958 memset( pDuWidths, 0, sizeof( pDuWidths ) );
2960 for( sal_Ucs c = 32; c < 256; c++ )
2962 pEncoding[c] = c;
2963 aGlyphIds[c] = 0;
2964 if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
2965 pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
2967 //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
2968 //had the right glyphids here then I imagine we could replace pDuWidths with
2969 //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
2970 //and map those to unicode rather than try and reverse map them ?
2971 pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
2972 osl_removeFile( aTmpName.pData );
2974 // write font descriptor
2975 nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
2976 if( nFontDescriptor )
2978 // write font object
2979 sal_Int32 nObject = createObject();
2980 if( updateObject( nObject ) )
2982 OStringBuffer aLine( 1024 );
2983 aLine.append( nObject );
2984 aLine.append( " 0 obj\n"
2985 "<</Type/Font/Subtype" );
2986 aLine.append( aSubType );
2987 aLine.append( "/BaseFont/" );
2988 appendName( aInfo.m_aPSName, aLine );
2989 aLine.append( "\n" );
2990 if( !pFont->IsSymbolFont() )
2991 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2992 aLine.append( "/FirstChar 32 /LastChar 255\n"
2993 "/Widths[" );
2994 for( int i = 32; i < 256; i++ )
2996 aLine.append( pWidths[i] );
2997 aLine.append( ((i&15) == 15) ? "\n" : " " );
2999 aLine.append( "]\n"
3000 "/FontDescriptor " );
3001 aLine.append( nFontDescriptor );
3002 aLine.append( " 0 R>>\n"
3003 "endobj\n\n" );
3004 writeBuffer( aLine.getStr(), aLine.getLength() );
3006 aRet[ rEmbed.m_nNormalFontID ] = nObject;
3010 return aRet;
3013 typedef int ThreeInts[3];
3014 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
3015 ThreeInts& rSegmentLengths )
3017 if( !pFontBytes || (nByteLen < 0) )
3018 return false;
3019 const unsigned char* pPtr = pFontBytes;
3020 const unsigned char* pEnd = pFontBytes + nByteLen;
3022 for(int & rSegmentLength : rSegmentLengths) {
3023 // read segment1 header
3024 if( pPtr+6 >= pEnd )
3025 return false;
3026 if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
3027 return false;
3028 const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
3029 if( nLen <= 0)
3030 return false;
3031 rSegmentLength = nLen;
3032 pPtr += nLen + 6;
3035 // read segment-end header
3036 if( pPtr+2 >= pEnd )
3037 return false;
3038 if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
3039 return false;
3041 return true;
3044 static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
3046 if( nSubsetID )
3048 for( int i = 0; i < 6; i++ )
3050 int nOffset = (nSubsetID % 26);
3051 nSubsetID /= 26;
3052 rBuffer.append( (sal_Char)('A'+nOffset) );
3054 rBuffer.append( '+' );
3056 appendName( rPSName, rBuffer );
3059 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8* pEncoding,
3060 sal_Ucs* pCodeUnits,
3061 sal_Int32* pCodeUnitsPerGlyph,
3062 sal_Int32* pEncToUnicodeIndex,
3063 int nGlyphs )
3065 int nMapped = 0;
3066 for (int n = 0; n < nGlyphs; ++n)
3067 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
3068 nMapped++;
3070 if( nMapped == 0 )
3071 return 0;
3073 sal_Int32 nStream = createObject();
3074 CHECK_RETURN( updateObject( nStream ) );
3076 OStringBuffer aContents( 1024 );
3077 aContents.append(
3078 "/CIDInit/ProcSet findresource begin\n"
3079 "12 dict begin\n"
3080 "begincmap\n"
3081 "/CIDSystemInfo<<\n"
3082 "/Registry (Adobe)\n"
3083 "/Ordering (UCS)\n"
3084 "/Supplement 0\n"
3085 ">> def\n"
3086 "/CMapName/Adobe-Identity-UCS def\n"
3087 "/CMapType 2 def\n"
3088 "1 begincodespacerange\n"
3089 "<00> <FF>\n"
3090 "endcodespacerange\n"
3092 int nCount = 0;
3093 for (int n = 0; n < nGlyphs; ++n)
3095 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
3097 if( (nCount % 100) == 0 )
3099 if( nCount )
3100 aContents.append( "endbfchar\n" );
3101 aContents.append( (sal_Int32)((nMapped-nCount > 100) ? 100 : nMapped-nCount ) );
3102 aContents.append( " beginbfchar\n" );
3104 aContents.append( '<' );
3105 appendHex( (sal_Int8)pEncoding[n], aContents );
3106 aContents.append( "> <" );
3107 // TODO: handle code points>U+FFFF
3108 sal_Int32 nIndex = pEncToUnicodeIndex[n];
3109 for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
3111 appendHex( (sal_Int8)(pCodeUnits[nIndex + j] / 256), aContents );
3112 appendHex( (sal_Int8)(pCodeUnits[nIndex + j] & 255), aContents );
3114 aContents.append( ">\n" );
3115 nCount++;
3118 aContents.append( "endbfchar\n"
3119 "endcmap\n"
3120 "CMapName currentdict /CMap define resource pop\n"
3121 "end\n"
3122 "end\n" );
3123 SvMemoryStream aStream;
3124 if (!g_bDebugDisableCompression)
3126 ZCodec aCodec( 0x4000, 0x4000 );
3127 aCodec.BeginCompression();
3128 aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
3129 aCodec.EndCompression();
3132 #if OSL_DEBUG_LEVEL > 1
3133 emitComment( "PDFWriterImpl::createToUnicodeCMap" );
3134 #endif
3135 OStringBuffer aLine( 40 );
3137 aLine.append( nStream );
3138 aLine.append( " 0 obj\n<</Length " );
3139 sal_Int32 nLen = 0;
3140 if (!g_bDebugDisableCompression)
3142 nLen = (sal_Int32)aStream.Tell();
3143 aStream.Seek( 0 );
3144 aLine.append( nLen );
3145 aLine.append( "/Filter/FlateDecode" );
3147 else
3148 aLine.append( aContents.getLength() );
3149 aLine.append( ">>\nstream\n" );
3150 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3151 checkAndEnableStreamEncryption( nStream );
3152 if (!g_bDebugDisableCompression)
3154 CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
3156 else
3158 CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
3160 disableStreamEncryption();
3161 aLine.setLength( 0 );
3162 aLine.append( "\nendstream\n"
3163 "endobj\n\n" );
3164 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3165 return nStream;
3168 sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, FontSubsetInfo& rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
3170 OStringBuffer aLine( 1024 );
3171 // get font flags, see PDF reference 1.4 p. 358
3172 // possibly characters outside Adobe standard encoding
3173 // so set Symbolic flag
3174 sal_Int32 nFontFlags = (1<<2);
3175 if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
3176 nFontFlags |= (1 << 6);
3177 if( pFont->GetPitch() == PITCH_FIXED )
3178 nFontFlags |= 1;
3179 if( pFont->GetFamilyType() == FAMILY_SCRIPT )
3180 nFontFlags |= (1 << 3);
3181 else if( pFont->GetFamilyType() == FAMILY_ROMAN )
3182 nFontFlags |= (1 << 1);
3184 sal_Int32 nFontDescriptor = createObject();
3185 CHECK_RETURN( updateObject( nFontDescriptor ) );
3186 aLine.setLength( 0 );
3187 aLine.append( nFontDescriptor );
3188 aLine.append( " 0 obj\n"
3189 "<</Type/FontDescriptor/FontName/" );
3190 appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
3191 aLine.append( "\n"
3192 "/Flags " );
3193 aLine.append( nFontFlags );
3194 aLine.append( "\n"
3195 "/FontBBox[" );
3196 // note: Top and Bottom are reversed in VCL and PDF rectangles
3197 aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().X() );
3198 aLine.append( ' ' );
3199 aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().Y() );
3200 aLine.append( ' ' );
3201 aLine.append( (sal_Int32)rInfo.m_aFontBBox.BottomRight().X() );
3202 aLine.append( ' ' );
3203 aLine.append( (sal_Int32)(rInfo.m_aFontBBox.BottomRight().Y()+1) );
3204 aLine.append( "]/ItalicAngle " );
3205 if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
3206 aLine.append( "-30" );
3207 else
3208 aLine.append( "0" );
3209 aLine.append( "\n"
3210 "/Ascent " );
3211 aLine.append( (sal_Int32)rInfo.m_nAscent );
3212 aLine.append( "\n"
3213 "/Descent " );
3214 aLine.append( (sal_Int32)-rInfo.m_nDescent );
3215 aLine.append( "\n"
3216 "/CapHeight " );
3217 aLine.append( (sal_Int32)rInfo.m_nCapHeight );
3218 // According to PDF reference 1.4 StemV is required
3219 // seems a tad strange to me, but well ...
3220 aLine.append( "\n"
3221 "/StemV 80\n" );
3222 if( nFontStream )
3224 aLine.append( "/FontFile" );
3225 switch( rInfo.m_nFontType )
3227 case FontType::SFNT_TTF:
3228 aLine.append( '2' );
3229 break;
3230 case FontType::TYPE1_PFA:
3231 case FontType::TYPE1_PFB:
3232 case FontType::ANY_TYPE1:
3233 break;
3234 default:
3235 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
3236 return 0;
3238 aLine.append( ' ' );
3239 aLine.append( nFontStream );
3240 aLine.append( " 0 R\n" );
3242 aLine.append( ">>\n"
3243 "endobj\n\n" );
3244 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3246 return nFontDescriptor;
3249 void PDFWriterImpl::appendBuiltinFontsToDict( OStringBuffer& rDict ) const
3251 for( std::map< sal_Int32, sal_Int32 >::const_iterator it =
3252 m_aBuiltinFontToObjectMap.begin(); it != m_aBuiltinFontToObjectMap.end(); ++it )
3254 rDict.append( m_aBuiltinFonts[it->first].getNameObject() );
3255 rDict.append( ' ' );
3256 rDict.append( it->second );
3257 rDict.append( " 0 R" );
3261 bool PDFWriterImpl::emitFonts()
3263 SalGraphics *pGraphics = m_pReferenceDevice->GetGraphics();
3265 if (!pGraphics)
3266 return false;
3268 OStringBuffer aLine( 1024 );
3270 std::map< sal_Int32, sal_Int32 > aFontIDToObject;
3272 OUString aTmpName;
3273 osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
3274 for( FontSubsetData::iterator it = m_aSubsets.begin(); it != m_aSubsets.end(); ++it )
3276 for( std::list< FontEmit >::iterator lit = it->second.m_aSubsets.begin(); lit != it->second.m_aSubsets.end(); ++lit )
3278 sal_GlyphId aGlyphIds[ 256 ];
3279 sal_Int32 pWidths[ 256 ];
3280 sal_uInt8 pEncoding[ 256 ];
3281 sal_Int32 pEncToUnicodeIndex[ 256 ];
3282 sal_Int32 pCodeUnitsPerGlyph[ 256 ];
3283 std::vector<sal_Ucs> aCodeUnits;
3284 aCodeUnits.reserve( 256 );
3285 int nGlyphs = 1;
3286 // fill arrays and prepare encoding index map
3287 sal_Int32 nToUnicodeStream = 0;
3289 memset( aGlyphIds, 0, sizeof( aGlyphIds ) );
3290 memset( pEncoding, 0, sizeof( pEncoding ) );
3291 memset( pCodeUnitsPerGlyph, 0, sizeof( pCodeUnitsPerGlyph ) );
3292 memset( pEncToUnicodeIndex, 0, sizeof( pEncToUnicodeIndex ) );
3293 for( FontEmitMapping::iterator fit = lit->m_aMapping.begin(); fit != lit->m_aMapping.end();++fit )
3295 sal_uInt8 nEnc = fit->second.getGlyphId();
3297 SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
3298 SAL_WARN_IF( nEnc > lit->m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
3300 aGlyphIds[ nEnc ] = fit->first;
3301 pEncoding[ nEnc ] = nEnc;
3302 pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
3303 pCodeUnitsPerGlyph[ nEnc ] = fit->second.countCodes();
3304 for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
3305 aCodeUnits.push_back( fit->second.getCode( n ) );
3306 if( fit->second.getCode(0) )
3307 nToUnicodeStream = 1;
3308 if( nGlyphs < 256 )
3309 nGlyphs++;
3310 else
3312 OSL_FAIL( "too many glyphs for subset" );
3315 FontSubsetInfo aSubsetInfo;
3316 if( pGraphics->CreateFontSubset( aTmpName, it->first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
3318 // create font stream
3319 osl::File aFontFile(aTmpName);
3320 if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
3321 // get file size
3322 sal_uInt64 nLength1;
3323 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
3324 if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
3325 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
3327 #if OSL_DEBUG_LEVEL > 1
3328 emitComment( "PDFWriterImpl::emitFonts" );
3329 #endif
3330 sal_Int32 nFontStream = createObject();
3331 sal_Int32 nStreamLengthObject = createObject();
3332 if ( !updateObject( nFontStream ) ) return false;
3333 aLine.setLength( 0 );
3334 aLine.append( nFontStream );
3335 aLine.append( " 0 obj\n"
3336 "<</Length " );
3337 aLine.append( nStreamLengthObject );
3338 if (!g_bDebugDisableCompression)
3339 aLine.append( " 0 R"
3340 "/Filter/FlateDecode"
3341 "/Length1 " );
3342 else
3343 aLine.append( " 0 R"
3344 "/Length1 " );
3346 sal_uInt64 nStartPos = 0;
3347 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
3349 aLine.append( (sal_Int32)nLength1 );
3351 aLine.append( ">>\n"
3352 "stream\n" );
3353 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3354 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3356 // copy font file
3357 beginCompression();
3358 checkAndEnableStreamEncryption( nFontStream );
3359 sal_Bool bEOF = false;
3362 char buf[8192];
3363 sal_uInt64 nRead;
3364 if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
3365 if ( !writeBuffer( buf, nRead ) ) return false;
3366 if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
3367 } while( ! bEOF );
3369 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
3371 // TODO: implement
3372 OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
3374 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
3376 std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
3378 sal_uInt64 nBytesRead = 0;
3379 if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
3380 SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
3381 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
3382 // get the PFB-segment lengths
3383 ThreeInts aSegmentLengths = {0,0,0};
3384 getPfbSegmentLengths(xBuffer.get(), (int)nBytesRead, aSegmentLengths);
3385 // the lengths below are mandatory for PDF-exported Type1 fonts
3386 // because the PFB segment headers get stripped! WhyOhWhy.
3387 aLine.append( (sal_Int32)aSegmentLengths[0] );
3388 aLine.append( "/Length2 " );
3389 aLine.append( (sal_Int32)aSegmentLengths[1] );
3390 aLine.append( "/Length3 " );
3391 aLine.append( (sal_Int32)aSegmentLengths[2] );
3393 aLine.append( ">>\n"
3394 "stream\n" );
3395 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3396 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3398 // emit PFB-sections without section headers
3399 beginCompression();
3400 checkAndEnableStreamEncryption( nFontStream );
3401 if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
3402 if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
3403 if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
3405 else
3407 SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << (int)aSubsetInfo.m_nFontType);
3408 aLine.append( "0 >>\nstream\n" );
3411 endCompression();
3412 disableStreamEncryption();
3413 // close the file
3414 aFontFile.close();
3416 sal_uInt64 nEndPos = 0;
3417 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
3418 // end the stream
3419 aLine.setLength( 0 );
3420 aLine.append( "\nendstream\nendobj\n\n" );
3421 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3423 // emit stream length object
3424 if ( !updateObject( nStreamLengthObject ) ) return false;
3425 aLine.setLength( 0 );
3426 aLine.append( nStreamLengthObject );
3427 aLine.append( " 0 obj\n" );
3428 aLine.append( (sal_Int64)(nEndPos-nStartPos) );
3429 aLine.append( "\nendobj\n\n" );
3430 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3432 // write font descriptor
3433 sal_Int32 nFontDescriptor = emitFontDescriptor( it->first, aSubsetInfo, lit->m_nFontID, nFontStream );
3435 if( nToUnicodeStream )
3436 nToUnicodeStream = createToUnicodeCMap( pEncoding, &aCodeUnits[0], pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
3438 sal_Int32 nFontObject = createObject();
3439 if ( !updateObject( nFontObject ) ) return false;
3440 aLine.setLength( 0 );
3441 aLine.append( nFontObject );
3443 aLine.append( " 0 obj\n" );
3444 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
3445 "<</Type/Font/Subtype/Type1/BaseFont/" :
3446 "<</Type/Font/Subtype/TrueType/BaseFont/" );
3447 appendSubsetName( lit->m_nFontID, aSubsetInfo.m_aPSName, aLine );
3448 aLine.append( "\n"
3449 "/FirstChar 0\n"
3450 "/LastChar " );
3451 aLine.append( (sal_Int32)(nGlyphs-1) );
3452 aLine.append( "\n"
3453 "/Widths[" );
3454 for( int i = 0; i < nGlyphs; i++ )
3456 aLine.append( pWidths[ i ] );
3457 aLine.append( ((i & 15) == 15) ? "\n" : " " );
3459 aLine.append( "]\n"
3460 "/FontDescriptor " );
3461 aLine.append( nFontDescriptor );
3462 aLine.append( " 0 R\n" );
3463 if( nToUnicodeStream )
3465 aLine.append( "/ToUnicode " );
3466 aLine.append( nToUnicodeStream );
3467 aLine.append( " 0 R\n" );
3469 aLine.append( ">>\n"
3470 "endobj\n\n" );
3471 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3473 aFontIDToObject[ lit->m_nFontID ] = nFontObject;
3475 else
3477 const PhysicalFontFace* pFont = it->first;
3478 OStringBuffer aErrorComment( 256 );
3479 aErrorComment.append( "CreateFontSubset failed for font \"" );
3480 aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
3481 aErrorComment.append( '\"' );
3482 if( pFont->GetItalic() == ITALIC_NORMAL )
3483 aErrorComment.append( " italic" );
3484 else if( pFont->GetItalic() == ITALIC_OBLIQUE )
3485 aErrorComment.append( " oblique" );
3486 aErrorComment.append( " weight=" );
3487 aErrorComment.append( sal_Int32(pFont->GetWeight()) );
3488 emitComment( aErrorComment.getStr() );
3492 osl_removeFile( aTmpName.pData );
3494 // emit system fonts
3495 for( FontEmbedData::iterator sit = m_aSystemFonts.begin(); sit != m_aSystemFonts.end(); ++sit )
3497 std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( sit->first, sit->second );
3498 for( std::map< sal_Int32, sal_Int32 >::iterator fit = aObjects.begin(); fit != aObjects.end(); ++fit )
3500 if ( !fit->second ) return false;
3501 aFontIDToObject[ fit->first ] = fit->second;
3505 OStringBuffer aFontDict( 1024 );
3506 aFontDict.append( getFontDictObject() );
3507 aFontDict.append( " 0 obj\n"
3508 "<<" );
3509 int ni = 0;
3510 for( std::map< sal_Int32, sal_Int32 >::iterator mit = aFontIDToObject.begin(); mit != aFontIDToObject.end(); ++mit )
3512 aFontDict.append( "/F" );
3513 aFontDict.append( mit->first );
3514 aFontDict.append( ' ' );
3515 aFontDict.append( mit->second );
3516 aFontDict.append( " 0 R" );
3517 if( ((++ni) & 7) == 0 )
3518 aFontDict.append( '\n' );
3520 // emit builtin font for widget appearances / variable text
3521 for( std::map< sal_Int32, sal_Int32 >::iterator it = m_aBuiltinFontToObjectMap.begin();
3522 it != m_aBuiltinFontToObjectMap.end(); ++it )
3524 PdfBuiltinFontFace aData(m_aBuiltinFonts[it->first]);
3525 it->second = emitBuiltinFont( &aData, it->second );
3527 appendBuiltinFontsToDict( aFontDict );
3528 aFontDict.append( "\n>>\nendobj\n\n" );
3530 if ( !updateObject( getFontDictObject() ) ) return false;
3531 if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
3532 return true;
3535 sal_Int32 PDFWriterImpl::emitResources()
3537 // emit shadings
3538 if( ! m_aGradients.empty() )
3539 CHECK_RETURN( emitGradients() );
3540 // emit tilings
3541 if( ! m_aTilings.empty() )
3542 CHECK_RETURN( emitTilings() );
3544 // emit font dict
3545 CHECK_RETURN( emitFonts() );
3547 // emit Resource dict
3548 OStringBuffer aLine( 512 );
3549 sal_Int32 nResourceDict = getResourceDictObj();
3550 CHECK_RETURN( updateObject( nResourceDict ) );
3551 aLine.setLength( 0 );
3552 aLine.append( nResourceDict );
3553 aLine.append( " 0 obj\n" );
3554 m_aGlobalResourceDict.append( aLine, getFontDictObject() );
3555 aLine.append( "endobj\n\n" );
3556 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3557 return nResourceDict;
3560 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3561 sal_Int32 nItemLevel,
3562 sal_Int32 nCurrentItemId )
3564 /* The /Count number of an item is
3565 positive: the number of visible subitems
3566 negative: the negative number of subitems that will become visible if
3567 the item gets opened
3568 see PDF ref 1.4 p 478
3571 sal_Int32 nCount = 0;
3573 if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
3574 m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
3577 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3578 sal_Int32 nChildren = rItem.m_aChildren.size();
3579 for( sal_Int32 i = 0; i < nChildren; i++ )
3580 nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3581 rCounts[nCurrentItemId] = nCount;
3582 // return 1 (this item) + visible sub items
3583 if( nCount < 0 )
3584 nCount = 0;
3585 nCount++;
3587 else
3589 // this bookmark level is invisible
3590 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3591 sal_Int32 nChildren = rItem.m_aChildren.size();
3592 rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3593 for( sal_Int32 i = 0; i < nChildren; i++ )
3594 updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3595 nCount = -1;
3598 return nCount;
3601 sal_Int32 PDFWriterImpl::emitOutline()
3603 int i, nItems = m_aOutline.size();
3605 // do we have an outline at all ?
3606 if( nItems < 2 )
3607 return 0;
3609 // reserve object numbers for all outline items
3610 for( i = 0; i < nItems; ++i )
3611 m_aOutline[i].m_nObject = createObject();
3613 // update all parent, next and prev object ids
3614 for( i = 0; i < nItems; ++i )
3616 PDFOutlineEntry& rItem = m_aOutline[i];
3617 int nChildren = rItem.m_aChildren.size();
3619 if( nChildren )
3621 for( int n = 0; n < nChildren; ++n )
3623 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3625 rChild.m_nParentObject = rItem.m_nObject;
3626 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3627 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3633 // calculate Count entries for all items
3634 std::vector< sal_Int32 > aCounts( nItems );
3635 updateOutlineItemCount( aCounts, 0, 0 );
3637 // emit hierarchy
3638 for( i = 0; i < nItems; ++i )
3640 PDFOutlineEntry& rItem = m_aOutline[i];
3641 OStringBuffer aLine( 1024 );
3643 CHECK_RETURN( updateObject( rItem.m_nObject ) );
3644 aLine.append( rItem.m_nObject );
3645 aLine.append( " 0 obj\n" );
3646 aLine.append( "<<" );
3647 // number of visible children (all levels)
3648 if( i > 0 || aCounts[0] > 0 )
3650 aLine.append( "/Count " );
3651 aLine.append( aCounts[i] );
3653 if( ! rItem.m_aChildren.empty() )
3655 // children list: First, Last
3656 aLine.append( "/First " );
3657 aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
3658 aLine.append( " 0 R/Last " );
3659 aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
3660 aLine.append( " 0 R\n" );
3662 if( i > 0 )
3664 // Title, Dest, Parent, Prev, Next
3665 aLine.append( "/Title" );
3666 appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3667 aLine.append( "\n" );
3668 // Dest is not required
3669 if( rItem.m_nDestID >= 0 && rItem.m_nDestID < (sal_Int32)m_aDests.size() )
3671 aLine.append( "/Dest" );
3672 appendDest( rItem.m_nDestID, aLine );
3674 aLine.append( "/Parent " );
3675 aLine.append( rItem.m_nParentObject );
3676 aLine.append( " 0 R" );
3677 if( rItem.m_nPrevObject )
3679 aLine.append( "/Prev " );
3680 aLine.append( rItem.m_nPrevObject );
3681 aLine.append( " 0 R" );
3683 if( rItem.m_nNextObject )
3685 aLine.append( "/Next " );
3686 aLine.append( rItem.m_nNextObject );
3687 aLine.append( " 0 R" );
3690 aLine.append( ">>\nendobj\n\n" );
3691 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3694 return m_aOutline[0].m_nObject;
3697 #undef CHECK_RETURN
3698 #define CHECK_RETURN( x ) if( !x ) return false
3700 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3702 if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() )
3704 #if OSL_DEBUG_LEVEL > 1
3705 SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << (int)nDestID << " requested");
3706 #endif
3707 return false;
3710 const PDFDest& rDest = m_aDests[ nDestID ];
3711 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
3713 rBuffer.append( '[' );
3714 rBuffer.append( rDestPage.m_nPageObject );
3715 rBuffer.append( " 0 R" );
3717 switch( rDest.m_eType )
3719 case PDFWriter::DestAreaType::XYZ:
3720 default:
3721 rBuffer.append( "/XYZ " );
3722 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3723 rBuffer.append( ' ' );
3724 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3725 rBuffer.append( " 0" );
3726 break;
3727 case PDFWriter::DestAreaType::FitRectangle:
3728 rBuffer.append( "/FitR " );
3729 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3730 rBuffer.append( ' ' );
3731 appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3732 rBuffer.append( ' ' );
3733 appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3734 rBuffer.append( ' ' );
3735 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3736 break;
3738 rBuffer.append( ']' );
3740 return true;
3743 bool PDFWriterImpl::emitScreenAnnotations()
3745 int nAnnots = m_aScreens.size();
3746 for (int i = 0; i < nAnnots; i++)
3748 const PDFScreen& rScreen = m_aScreens[i];
3750 OStringBuffer aLine;
3751 bool bEmbed = false;
3752 if (!rScreen.m_aTempFileURL.isEmpty())
3754 bEmbed = true;
3755 if (!updateObject(rScreen.m_nTempFileObject))
3756 continue;
3758 SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3759 SvMemoryStream aMemoryStream;
3760 aMemoryStream.WriteStream(aFileStream);
3762 aLine.append(rScreen.m_nTempFileObject);
3763 aLine.append(" 0 obj\n");
3764 aLine.append("<< /Type /EmbeddedFile /Length ");
3765 aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3766 aLine.append(" >>\nstream\n");
3767 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3768 aLine.setLength(0);
3770 CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3772 aLine.append("\nendstream\nendobj\n\n");
3773 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3774 aLine.setLength(0);
3777 if (!updateObject(rScreen.m_nObject))
3778 continue;
3780 // Annot dictionary.
3781 aLine.append(rScreen.m_nObject);
3782 aLine.append(" 0 obj\n");
3783 aLine.append("<</Type/Annot");
3784 aLine.append("/Subtype/Screen/Rect[");
3785 appendFixedInt(rScreen.m_aRect.Left(), aLine);
3786 aLine.append(' ');
3787 appendFixedInt(rScreen.m_aRect.Top(), aLine);
3788 aLine.append(' ');
3789 appendFixedInt(rScreen.m_aRect.Right(), aLine);
3790 aLine.append(' ');
3791 appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3792 aLine.append("]");
3794 // Action dictionary.
3795 aLine.append("/A<</Type/Action /S/Rendition /AN ");
3796 aLine.append(rScreen.m_nObject);
3797 aLine.append(" 0 R ");
3799 // Rendition dictionary.
3800 aLine.append("/R<</Type/Rendition /S/MR ");
3802 // MediaClip dictionary.
3803 aLine.append("/C<</Type/MediaClip /S/MCD ");
3804 if (bEmbed)
3806 aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
3807 aLine.append(rScreen.m_nTempFileObject);
3808 aLine.append(" 0 R >> >>");
3810 else
3812 // Linked.
3813 aLine.append("/D << /Type /Filespec /FS /URL /F ");
3814 appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3815 aLine.append(" >>");
3817 // Allow playing the video via a tempfile.
3818 aLine.append("/P <</TF (TEMPACCESS)>>");
3819 // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
3820 aLine.append("/CT (video/mp4)");
3821 aLine.append(">>");
3823 // End Rendition dictionary by requesting play/pause/stop controls.
3824 aLine.append("/P<</BE<</C true >>>>");
3825 aLine.append(">>");
3827 // End Action dictionary.
3828 aLine.append("/OP 0 >>");
3830 // End Annot dictionary.
3831 aLine.append("/P ");
3832 aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
3833 aLine.append(" 0 R\n>>\nendobj\n\n");
3834 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3837 return true;
3840 bool PDFWriterImpl::emitLinkAnnotations()
3842 int nAnnots = m_aLinks.size();
3843 for( int i = 0; i < nAnnots; i++ )
3845 const PDFLink& rLink = m_aLinks[i];
3846 if( ! updateObject( rLink.m_nObject ) )
3847 continue;
3849 OStringBuffer aLine( 1024 );
3850 aLine.append( rLink.m_nObject );
3851 aLine.append( " 0 obj\n" );
3852 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3853 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3854 aLine.append( "<</Type/Annot" );
3855 if( m_bIsPDF_A1 )
3856 aLine.append( "/F 4" );
3857 aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3859 appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3860 aLine.append( ' ' );
3861 appendFixedInt( rLink.m_aRect.Top(), aLine );
3862 aLine.append( ' ' );
3863 appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3864 aLine.append( ' ' );
3865 appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3866 aLine.append( "]" );
3867 if( rLink.m_nDest >= 0 )
3869 aLine.append( "/Dest" );
3870 appendDest( rLink.m_nDest, aLine );
3872 else
3875 destination is external to the document, so
3876 we check in the following sequence:
3878 if target type is neither .pdf, nor .od[tpgs], then
3879 check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3880 end processing
3881 else if target is .od[tpgs]: then
3882 if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
3883 processing continue
3885 if (new)target is .pdf : then
3886 if GotToR is requested, then
3887 convert the target in GoToR where the fragment of the URI is
3888 considered the named destination in the target file, set relative or absolute as requested
3889 else strip the fragment from URL and then set URI or 'launch application' as requested
3892 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3893 // are the correct one!!
3895 // extract target file type
3896 auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3898 INetURLObject aDocumentURL( m_aContext.BaseURL );
3899 INetURLObject aTargetURL( url );
3900 bool bSetGoToRMode = false;
3901 bool bTargetHasPDFExtension = false;
3902 INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3903 bool bIsUNCPath = false;
3905 // check if the protocol is a known one, or if there is no protocol at all (on target only)
3906 // if there is no protocol, make the target relative to the current document directory
3907 // getting the needed URL information from the current document path
3908 if( eTargetProtocol == INetProtocol::NotValid )
3910 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3912 bIsUNCPath = true;
3914 else
3916 INetURLObject aNewBase( aDocumentURL );//duplicate document URL
3917 aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
3918 //target document
3919 aNewBase.insertName( url );
3920 aTargetURL = aNewBase;//reassign the new target URL
3921 //recompute the target protocol, with the new URL
3922 //normal URL processing resumes
3923 eTargetProtocol = aTargetURL.GetProtocol();
3927 OUString aFileExtension = aTargetURL.GetFileExtension();
3929 // Check if the URL ends in '/': if yes it's a directory,
3930 // it will be forced to a URI link.
3931 // possibly a malformed URI, leave it as it is, force as URI
3932 if( aTargetURL.hasFinalSlash() )
3933 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3935 if( !aFileExtension.isEmpty() )
3937 if( m_aContext.ConvertOOoTargetToPDFTarget )
3939 bool bChangeFileExtensionToPDF = false;
3940 //examine the file type (.odm .odt. .odp, odg, ods)
3941 if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3942 bChangeFileExtensionToPDF = true;
3943 if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3944 bChangeFileExtensionToPDF = true;
3945 else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3946 bChangeFileExtensionToPDF = true;
3947 else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3948 bChangeFileExtensionToPDF = true;
3949 else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3950 bChangeFileExtensionToPDF = true;
3951 if( bChangeFileExtensionToPDF )
3952 aTargetURL.setExtension("pdf" );
3954 //check if extension is pdf, see if GoToR should be forced
3955 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3956 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3957 bSetGoToRMode = true;
3959 //prepare the URL, if relative or not
3960 INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3961 //queue the string common to all types of actions
3962 aLine.append( "/A<</Type/Action/S");
3963 if( bIsUNCPath ) // handle Win UNC paths
3965 aLine.append( "/Launch/Win<</F" );
3966 // INetURLObject is not good with UNC paths, use original path
3967 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3968 aLine.append( ">>" );
3970 else
3972 bool bSetRelative = false;
3973 bool bFileSpec = false;
3974 //check if relative file link is requested and if the protocol is 'file://'
3975 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3976 bSetRelative = true;
3978 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3979 if( !bSetGoToRMode )
3981 switch( m_aContext.DefaultLinkAction )
3983 default:
3984 case PDFWriter::URIAction :
3985 case PDFWriter::URIActionDestination :
3986 aLine.append( "/URI/URI" );
3987 break;
3988 case PDFWriter::LaunchAction:
3989 // now:
3990 // if a launch action is requested and the hyperlink target has a fragment
3991 // and the target file does not have a pdf extension, or it's not a 'file:://'
3992 // protocol then force the uri action on it
3993 // This code will permit the correct opening of application on web pages,
3994 // the one that normally have fragments (but I may be wrong...)
3995 // and will force the use of URI when the protocol is not file:
3996 if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3997 eTargetProtocol != INetProtocol::File )
3999 aLine.append( "/URI/URI" );
4001 else
4003 aLine.append( "/Launch/F" );
4004 bFileSpec = true;
4006 break;
4010 //fragment are encoded in the same way as in the named destination processing
4011 if( bSetGoToRMode )
4013 //add the fragment
4014 OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
4015 aLine.append("/GoToR");
4016 aLine.append("/F");
4017 appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
4018 INetURLObject::EncodeMechanism::WasEncoded,
4019 INetURLObject::DecodeMechanism::WithCharset ) :
4020 aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
4021 if( !aFragment.isEmpty() )
4023 aLine.append("/D/");
4024 appendDestinationName( aFragment , aLine );
4027 else
4029 // change the fragment to accommodate the bookmark (only if the file extension
4030 // is PDF and the requested action is of the correct type)
4031 if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
4032 bTargetHasPDFExtension && !aFragment.isEmpty() )
4034 OStringBuffer aLineLoc( 1024 );
4035 appendDestinationName( aFragment , aLineLoc );
4036 //substitute the fragment
4037 aTargetURL.SetMark( OStringToOUString(aLineLoc.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
4039 OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE );
4040 appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
4041 INetURLObject::EncodeMechanism::WasEncoded,
4042 bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
4044 aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
4047 aLine.append( ">>\n" );
4049 if( rLink.m_nStructParent > 0 )
4051 aLine.append( "/StructParent " );
4052 aLine.append( rLink.m_nStructParent );
4054 aLine.append( ">>\nendobj\n\n" );
4055 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4058 return true;
4061 bool PDFWriterImpl::emitNoteAnnotations()
4063 // emit note annotations
4064 int nAnnots = m_aNotes.size();
4065 for( int i = 0; i < nAnnots; i++ )
4067 const PDFNoteEntry& rNote = m_aNotes[i];
4068 if( ! updateObject( rNote.m_nObject ) )
4069 return false;
4071 OStringBuffer aLine( 1024 );
4072 aLine.append( rNote.m_nObject );
4073 aLine.append( " 0 obj\n" );
4074 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
4075 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
4076 aLine.append( "<</Type/Annot" );
4077 if( m_bIsPDF_A1 )
4078 aLine.append( "/F 4" );
4079 aLine.append( "/Subtype/Text/Rect[" );
4081 appendFixedInt( rNote.m_aRect.Left(), aLine );
4082 aLine.append( ' ' );
4083 appendFixedInt( rNote.m_aRect.Top(), aLine );
4084 aLine.append( ' ' );
4085 appendFixedInt( rNote.m_aRect.Right(), aLine );
4086 aLine.append( ' ' );
4087 appendFixedInt( rNote.m_aRect.Bottom(), aLine );
4088 aLine.append( "]" );
4090 // contents of the note (type text string)
4091 aLine.append( "/Contents\n" );
4092 appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine );
4093 aLine.append( "\n" );
4095 // optional title
4096 if( !rNote.m_aContents.Title.isEmpty() )
4098 aLine.append( "/T" );
4099 appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine );
4100 aLine.append( "\n" );
4103 aLine.append( ">>\nendobj\n\n" );
4104 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4106 return true;
4109 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
4111 bool bAdjustSize = false;
4113 Font aFont( rControlFont );
4114 if( aFont.GetFamilyName().isEmpty() )
4116 aFont = rAppSetFont;
4117 if( rControlFont.GetFontHeight() )
4118 aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
4119 else
4120 bAdjustSize = true;
4121 if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
4122 aFont.SetItalic( rControlFont.GetItalic() );
4123 if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
4124 aFont.SetWeight( rControlFont.GetWeight() );
4126 else if( ! aFont.GetFontHeight() )
4128 aFont.SetFontSize( rAppSetFont.GetFontSize() );
4129 bAdjustSize = true;
4131 if( bAdjustSize )
4133 Size aFontSize = aFont.GetFontSize();
4134 OutputDevice* pDefDev = Application::GetDefaultDevice();
4135 aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
4136 aFont.SetFontSize( aFontSize );
4138 return aFont;
4141 sal_Int32 PDFWriterImpl::getBestBuiltinFont( const vcl::Font& rFont )
4143 sal_Int32 nBest = 4; // default to Helvetica
4144 OUString aFontName( rFont.GetFamilyName() );
4145 aFontName = aFontName.toAsciiLowerCase();
4147 if( aFontName.indexOf( "times" ) != -1 )
4148 nBest = 8;
4149 else if( aFontName.indexOf( "courier" ) != -1 )
4150 nBest = 0;
4151 else if( aFontName.indexOf( "dingbats" ) != -1 )
4152 nBest = 13;
4153 else if( aFontName.indexOf( "symbol" ) != -1 )
4154 nBest = 12;
4155 if( nBest < 12 )
4157 if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
4158 nBest += 1;
4159 if( rFont.GetWeight() > WEIGHT_MEDIUM )
4160 nBest += 2;
4163 if( m_aBuiltinFontToObjectMap.find( nBest ) == m_aBuiltinFontToObjectMap.end() )
4164 m_aBuiltinFontToObjectMap[ nBest ] = createObject();
4166 return nBest;
4169 static inline const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
4171 return (rCol1 == Color( COL_TRANSPARENT )) ? rCol2 : rCol1;
4174 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
4176 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4178 // save graphics state
4179 push( PushFlags::ALL );
4181 // transform relative to control's coordinates since an
4182 // appearance stream is a form XObject
4183 // this relies on the m_aRect member of rButton NOT already being transformed
4184 // to default user space
4185 if( rWidget.Background || rWidget.Border )
4187 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : Color( COL_TRANSPARENT ) );
4188 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : Color( COL_TRANSPARENT ) );
4189 drawRectangle( rWidget.Location );
4191 // prepare font to use
4192 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
4193 setFont( aFont );
4194 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
4196 drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
4198 // create DA string while local mapmode is still in place
4199 // (that is before endRedirect())
4200 OStringBuffer aDA( 256 );
4201 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
4202 Font aDummyFont( "Helvetica", aFont.GetFontSize() );
4203 sal_Int32 nDummyBuiltin = getBestBuiltinFont( aDummyFont );
4204 aDA.append( ' ' );
4205 aDA.append( m_aBuiltinFonts[nDummyBuiltin].getNameObject() );
4206 aDA.append( ' ' );
4207 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4208 aDA.append( " Tf" );
4209 rButton.m_aDAString = aDA.makeStringAndClear();
4211 pop();
4213 rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
4215 /* seems like a bad hack but at least works in both AR5 and 6:
4216 we draw the button ourselves and tell AR
4217 the button would be totally transparent with no text
4219 One would expect that simply setting a normal appearance
4220 should suffice, but no, as soon as the user actually presses
4221 the button and an action is tied to it (gasp! a button that
4222 does something) the appearance gets replaced by some crap that AR
4223 creates on the fly even if no DA or MK is given. On AR6 at least
4224 the DA and MK work as expected, but on AR5 this creates a region
4225 filled with the background color but nor text. Urgh.
4227 rButton.m_aMKDict = "/BC [] /BG [] /CA";
4228 rButton.m_aMKDictCAString = "";
4231 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
4232 const PDFWriter::AnyWidget& rWidget,
4233 const StyleSettings& rSettings )
4235 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
4237 if( rWidget.Background || rWidget.Border )
4239 if( rWidget.Border && rWidget.BorderColor == Color( COL_TRANSPARENT ) )
4241 sal_Int32 nDelta = getReferenceDevice()->GetDPIX() / 500;
4242 if( nDelta < 1 )
4243 nDelta = 1;
4244 setLineColor( Color( COL_TRANSPARENT ) );
4245 tools::Rectangle aRect = rIntern.m_aRect;
4246 setFillColor( rSettings.GetLightBorderColor() );
4247 drawRectangle( aRect );
4248 aRect.Left() += nDelta; aRect.Top() += nDelta;
4249 aRect.Right() -= nDelta; aRect.Bottom() -= nDelta;
4250 setFillColor( rSettings.GetFieldColor() );
4251 drawRectangle( aRect );
4252 setFillColor( rSettings.GetLightColor() );
4253 drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
4254 drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
4255 setFillColor( rSettings.GetDarkShadowColor() );
4256 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
4257 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
4259 else
4261 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : Color( COL_TRANSPARENT ) );
4262 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) );
4263 drawRectangle( rIntern.m_aRect );
4266 if( rWidget.Border )
4268 // adjust edit area accounting for border
4269 sal_Int32 nDelta = aFont.GetFontHeight()/4;
4270 if( nDelta < 1 )
4271 nDelta = 1;
4272 rIntern.m_aRect.Left() += nDelta;
4273 rIntern.m_aRect.Top() += nDelta;
4274 rIntern.m_aRect.Right() -= nDelta;
4275 rIntern.m_aRect.Bottom()-= nDelta;
4278 return aFont;
4281 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
4283 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4284 SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
4286 push( PushFlags::ALL );
4288 // prepare font to use, draw field border
4289 Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
4290 sal_Int32 nBest = getSystemFont( aFont );
4292 // prepare DA string
4293 OStringBuffer aDA( 32 );
4294 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4295 aDA.append( ' ' );
4296 aDA.append( "/F" );
4297 aDA.append( nBest );
4299 OStringBuffer aDR( 32 );
4300 aDR.append( "/Font " );
4301 aDR.append( getFontDictObject() );
4302 aDR.append( " 0 R" );
4303 rEdit.m_aDRDict = aDR.makeStringAndClear();
4304 aDA.append( ' ' );
4305 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4306 aDA.append( " Tf" );
4308 /* create an empty appearance stream, let the viewer create
4309 the appearance at runtime. This is because AR5 seems to
4310 paint the widget appearance always, and a dynamically created
4311 appearance on top of it. AR6 is well behaved in that regard, so
4312 that behaviour seems to be a bug. Anyway this empty appearance
4313 relies on /NeedAppearances in the AcroForm dictionary set to "true"
4315 beginRedirect( pEditStream, rEdit.m_aRect );
4316 OStringBuffer aAppearance( 32 );
4317 aAppearance.append( "/Tx BMC\nEMC\n" );
4318 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
4320 endRedirect();
4321 pop();
4323 rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
4325 rEdit.m_aDAString = aDA.makeStringAndClear();
4328 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
4330 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4331 SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
4333 push( PushFlags::ALL );
4335 // prepare font to use, draw field border
4336 Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
4337 sal_Int32 nBest = getSystemFont( aFont );
4339 beginRedirect( pListBoxStream, rBox.m_aRect );
4340 OStringBuffer aAppearance( 64 );
4342 setLineColor( Color( COL_TRANSPARENT ) );
4343 setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
4344 drawRectangle( rBox.m_aRect );
4346 // empty appearance, see createDefaultEditAppearance for reference
4347 aAppearance.append( "/Tx BMC\nEMC\n" );
4348 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
4350 endRedirect();
4351 pop();
4353 rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
4355 // prepare DA string
4356 OStringBuffer aDA( 256 );
4357 // prepare DA string
4358 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4359 aDA.append( ' ' );
4360 aDA.append( "/F" );
4361 aDA.append( nBest );
4363 OStringBuffer aDR( 32 );
4364 aDR.append( "/Font " );
4365 aDR.append( getFontDictObject() );
4366 aDR.append( " 0 R" );
4367 rBox.m_aDRDict = aDR.makeStringAndClear();
4368 aDA.append( ' ' );
4369 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4370 aDA.append( " Tf" );
4371 rBox.m_aDAString = aDA.makeStringAndClear();
4374 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
4376 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4378 // save graphics state
4379 push( PushFlags::ALL );
4381 if( rWidget.Background || rWidget.Border )
4383 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) );
4384 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) );
4385 drawRectangle( rBox.m_aRect );
4388 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4389 setFont( aFont );
4390 Size aFontSize = aFont.GetFontSize();
4391 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4392 aFontSize.Height() = rBox.m_aRect.GetHeight();
4393 sal_Int32 nDelta = aFontSize.Height()/10;
4394 if( nDelta < 1 )
4395 nDelta = 1;
4397 tools::Rectangle aCheckRect, aTextRect;
4399 aCheckRect.Left() = rBox.m_aRect.Left() + nDelta;
4400 aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2;
4401 aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height();
4402 aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height();
4404 // #i74206# handle small controls without text area
4405 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4407 aCheckRect.Right() -= nDelta;
4408 aCheckRect.Top() += nDelta/2;
4409 aCheckRect.Bottom() -= nDelta - (nDelta/2);
4412 aTextRect.Left() = rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta;
4413 aTextRect.Top() = rBox.m_aRect.Top();
4414 aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta;
4415 aTextRect.Bottom() = rBox.m_aRect.Bottom();
4417 setLineColor( Color( COL_BLACK ) );
4418 setFillColor( Color( COL_TRANSPARENT ) );
4419 OStringBuffer aLW( 32 );
4420 aLW.append( "q " );
4421 m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
4422 aLW.append( " w " );
4423 writeBuffer( aLW.getStr(), aLW.getLength() );
4424 drawRectangle( aCheckRect );
4425 writeBuffer( " Q\n", 3 );
4426 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4427 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4429 pop();
4431 OStringBuffer aDA( 256 );
4432 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4433 sal_Int32 nBest = getBestBuiltinFont( Font( "ZapfDingbats", aFont.GetFontSize() ) );
4434 aDA.append( ' ' );
4435 aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4436 aDA.append( " 0 Tf" );
4437 rBox.m_aDAString = aDA.makeStringAndClear();
4438 rBox.m_aMKDict = "/CA";
4439 rBox.m_aMKDictCAString = "8";
4440 rBox.m_aRect = aCheckRect;
4442 // create appearance streams
4443 sal_Char cMark = '8';
4444 sal_Int32 nCharXOffset = 1000-m_aBuiltinFonts[13].m_aWidths[sal_Int32(cMark)];
4445 nCharXOffset *= aCheckRect.GetHeight();
4446 nCharXOffset /= 2000;
4447 sal_Int32 nCharYOffset = 1000-
4448 (m_aBuiltinFonts[13].m_nAscent+m_aBuiltinFonts[13].m_nDescent); // descent is negative
4449 nCharYOffset *= aCheckRect.GetHeight();
4450 nCharYOffset /= 2000;
4452 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4453 beginRedirect( pCheckStream, aCheckRect );
4454 aDA.append( "/Tx BMC\nq BT\n" );
4455 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4456 aDA.append( ' ' );
4457 aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4458 aDA.append( ' ' );
4459 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4460 aDA.append( " Tf\n" );
4461 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
4462 aDA.append( " " );
4463 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
4464 aDA.append( " Td (" );
4465 aDA.append( cMark );
4466 aDA.append( ") Tj\nET\nQ\nEMC\n" );
4467 writeBuffer( aDA.getStr(), aDA.getLength() );
4468 endRedirect();
4469 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4471 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4472 beginRedirect( pUncheckStream, aCheckRect );
4473 writeBuffer( "/Tx BMC\nEMC\n", 12 );
4474 endRedirect();
4475 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4478 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
4480 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
4482 // save graphics state
4483 push( PushFlags::ALL );
4485 if( rWidget.Background || rWidget.Border )
4487 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) );
4488 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) );
4489 drawRectangle( rBox.m_aRect );
4492 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4493 setFont( aFont );
4494 Size aFontSize = aFont.GetFontSize();
4495 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4496 aFontSize.Height() = rBox.m_aRect.GetHeight();
4497 sal_Int32 nDelta = aFontSize.Height()/10;
4498 if( nDelta < 1 )
4499 nDelta = 1;
4501 tools::Rectangle aCheckRect, aTextRect;
4503 aCheckRect.Left() = rBox.m_aRect.Left() + nDelta;
4504 aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2;
4505 aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height();
4506 aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height();
4508 // #i74206# handle small controls without text area
4509 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4511 aCheckRect.Right() -= nDelta;
4512 aCheckRect.Top() += nDelta/2;
4513 aCheckRect.Bottom() -= nDelta - (nDelta/2);
4516 aTextRect.Left() = rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta;
4517 aTextRect.Top() = rBox.m_aRect.Top();
4518 aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta;
4519 aTextRect.Bottom() = rBox.m_aRect.Bottom();
4521 setLineColor( Color( COL_BLACK ) );
4522 setFillColor( Color( COL_TRANSPARENT ) );
4523 OStringBuffer aLW( 32 );
4524 aLW.append( "q " );
4525 m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4526 aLW.append( " w " );
4527 writeBuffer( aLW.getStr(), aLW.getLength() );
4528 drawEllipse( aCheckRect );
4529 writeBuffer( " Q\n", 3 );
4530 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4531 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4533 pop();
4535 OStringBuffer aDA( 256 );
4536 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4537 sal_Int32 nBest = getBestBuiltinFont( Font( "ZapfDingbats", aFont.GetFontSize() ) );
4538 aDA.append( ' ' );
4539 aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4540 aDA.append( " 0 Tf" );
4541 rBox.m_aDAString = aDA.makeStringAndClear();
4542 //to encrypt this (el)
4543 rBox.m_aMKDict = "/CA";
4544 //after this assignement, to m_aMKDic cannot be added anything
4545 rBox.m_aMKDictCAString = "l";
4547 rBox.m_aRect = aCheckRect;
4549 // create appearance streams
4550 push( PushFlags::ALL);
4551 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4553 beginRedirect( pCheckStream, aCheckRect );
4554 aDA.append( "/Tx BMC\nq BT\n" );
4555 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4556 aDA.append( ' ' );
4557 aDA.append( m_aBuiltinFonts[nBest].getNameObject() );
4558 aDA.append( ' ' );
4559 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4560 aDA.append( " Tf\n0 0 Td\nET\nQ\n" );
4561 writeBuffer( aDA.getStr(), aDA.getLength() );
4562 setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4563 setLineColor( Color( COL_TRANSPARENT ) );
4564 aCheckRect.Left() += 3*nDelta;
4565 aCheckRect.Top() += 3*nDelta;
4566 aCheckRect.Bottom() -= 3*nDelta;
4567 aCheckRect.Right() -= 3*nDelta;
4568 drawEllipse( aCheckRect );
4569 writeBuffer( "\nEMC\n", 5 );
4570 endRedirect();
4572 pop();
4573 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4575 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4576 beginRedirect( pUncheckStream, aCheckRect );
4577 writeBuffer( "/Tx BMC\nEMC\n", 12 );
4578 endRedirect();
4579 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4582 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4584 // TODO: check and insert default streams
4585 OString aStandardAppearance;
4586 switch( rWidget.m_eType )
4588 case PDFWriter::CheckBox:
4589 aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4590 break;
4591 default:
4592 break;
4595 if( !rWidget.m_aAppearances.empty() )
4597 rAnnotDict.append( "/AP<<\n" );
4598 for( PDFAppearanceMap::iterator dict_it = rWidget.m_aAppearances.begin(); dict_it != rWidget.m_aAppearances.end(); ++dict_it )
4600 rAnnotDict.append( "/" );
4601 rAnnotDict.append( dict_it->first );
4602 bool bUseSubDict = (dict_it->second.size() > 1);
4603 rAnnotDict.append( bUseSubDict ? "<<" : " " );
4605 for( PDFAppearanceStreams::const_iterator stream_it = dict_it->second.begin();
4606 stream_it != dict_it->second.end(); ++stream_it )
4608 SvMemoryStream* pApppearanceStream = stream_it->second;
4609 dict_it->second[ stream_it->first ] = nullptr;
4611 bool bDeflate = compressStream( pApppearanceStream );
4613 pApppearanceStream->Seek( STREAM_SEEK_TO_END );
4614 sal_Int64 nStreamLen = pApppearanceStream->Tell();
4615 pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4616 sal_Int32 nObject = createObject();
4617 CHECK_RETURN( updateObject( nObject ) );
4618 #if OSL_DEBUG_LEVEL > 1
4619 emitComment( "PDFWriterImpl::emitAppearances" );
4620 #endif
4621 OStringBuffer aLine;
4622 aLine.append( nObject );
4624 aLine.append( " 0 obj\n"
4625 "<</Type/XObject\n"
4626 "/Subtype/Form\n"
4627 "/BBox[0 0 " );
4628 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4629 aLine.append( " " );
4630 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4631 aLine.append( "]\n"
4632 "/Resources " );
4633 aLine.append( getResourceDictObj() );
4634 aLine.append( " 0 R\n"
4635 "/Length " );
4636 aLine.append( nStreamLen );
4637 aLine.append( "\n" );
4638 if( bDeflate )
4639 aLine.append( "/Filter/FlateDecode\n" );
4640 aLine.append( ">>\nstream\n" );
4641 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4642 checkAndEnableStreamEncryption( nObject );
4643 CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
4644 disableStreamEncryption();
4645 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
4647 if( bUseSubDict )
4649 rAnnotDict.append( " /" );
4650 rAnnotDict.append( stream_it->first );
4651 rAnnotDict.append( " " );
4653 rAnnotDict.append( nObject );
4654 rAnnotDict.append( " 0 R" );
4656 delete pApppearanceStream;
4659 rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4661 rAnnotDict.append( ">>\n" );
4662 if( !aStandardAppearance.isEmpty() )
4664 rAnnotDict.append( "/AS /" );
4665 rAnnotDict.append( aStandardAppearance );
4666 rAnnotDict.append( "\n" );
4670 return true;
4673 bool PDFWriterImpl::emitWidgetAnnotations()
4675 ensureUniqueRadioOnValues();
4677 int nAnnots = m_aWidgets.size();
4678 for( int a = 0; a < nAnnots; a++ )
4680 PDFWidget& rWidget = m_aWidgets[a];
4682 OStringBuffer aLine( 1024 );
4683 OStringBuffer aValue( 256 );
4684 aLine.append( rWidget.m_nObject );
4685 aLine.append( " 0 obj\n"
4686 "<<" );
4687 if( rWidget.m_eType != PDFWriter::Hierarchy )
4689 // emit widget annotation only for terminal fields
4690 if( rWidget.m_aKids.empty() )
4692 int iRectMargin;
4694 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4696 if (rWidget.m_eType == PDFWriter::Signature)
4698 aLine.append( "132\n" ); // Print & Locked
4699 iRectMargin = 0;
4701 else
4703 aLine.append( "4\n" );
4704 iRectMargin = 1;
4707 aLine.append("/Rect[" );
4708 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4709 aLine.append( ' ' );
4710 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4711 aLine.append( ' ' );
4712 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4713 aLine.append( ' ' );
4714 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4715 aLine.append( "]\n" );
4717 aLine.append( "/FT/" );
4718 switch( rWidget.m_eType )
4720 case PDFWriter::RadioButton:
4721 case PDFWriter::CheckBox:
4722 // for radio buttons only the RadioButton field, not the
4723 // CheckBox children should have a value, else acrobat reader
4724 // does not always check the right button
4725 // of course real check boxes (not belonging to a radio group)
4726 // need their values, too
4727 if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4729 aValue.append( "/" );
4730 // check for radio group with all buttons unpressed
4731 if( rWidget.m_aValue.isEmpty() )
4732 aValue.append( "Off" );
4733 else
4734 appendName( rWidget.m_aValue, aValue );
4736 SAL_FALLTHROUGH;
4737 case PDFWriter::PushButton:
4738 aLine.append( "Btn" );
4739 break;
4740 case PDFWriter::ListBox:
4741 if( rWidget.m_nFlags & 0x200000 ) // multiselect
4743 aValue.append( "[" );
4744 for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4746 sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4747 if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) )
4748 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4750 aValue.append( "]" );
4752 else if( rWidget.m_aSelectedEntries.size() > 0 &&
4753 rWidget.m_aSelectedEntries[0] >= 0 &&
4754 rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) )
4756 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4758 else
4759 appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4760 aLine.append( "Ch" );
4761 break;
4762 case PDFWriter::ComboBox:
4763 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4764 aLine.append( "Ch" );
4765 break;
4766 case PDFWriter::Edit:
4767 aLine.append( "Tx" );
4768 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4769 break;
4770 case PDFWriter::Signature:
4771 aLine.append( "Sig" );
4772 aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4773 break;
4774 case PDFWriter::Hierarchy: // make the compiler happy
4775 break;
4777 aLine.append( "\n" );
4778 aLine.append( "/P " );
4779 aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4780 aLine.append( " 0 R\n" );
4782 if( rWidget.m_nParent )
4784 aLine.append( "/Parent " );
4785 aLine.append( rWidget.m_nParent );
4786 aLine.append( " 0 R\n" );
4788 if( rWidget.m_aKids.size() )
4790 aLine.append( "/Kids[" );
4791 for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4793 aLine.append( rWidget.m_aKids[i] );
4794 aLine.append( " 0 R" );
4795 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4797 aLine.append( "]\n" );
4799 if( !rWidget.m_aName.isEmpty() )
4801 aLine.append( "/T" );
4802 appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4803 aLine.append( "\n" );
4805 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
4807 // the alternate field name should be unicode able since it is
4808 // supposed to be used in UI
4809 aLine.append( "/TU" );
4810 appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4811 aLine.append( "\n" );
4814 if( rWidget.m_nFlags )
4816 aLine.append( "/Ff " );
4817 aLine.append( rWidget.m_nFlags );
4818 aLine.append( "\n" );
4820 if( !aValue.isEmpty() )
4822 OString aVal = aValue.makeStringAndClear();
4823 aLine.append( "/V " );
4824 aLine.append( aVal );
4825 aLine.append( "\n"
4826 "/DV " );
4827 aLine.append( aVal );
4828 aLine.append( "\n" );
4830 if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4832 sal_Int32 nTI = -1;
4833 aLine.append( "/Opt[\n" );
4834 sal_Int32 i = 0;
4835 for( std::vector< OUString >::const_iterator it = rWidget.m_aListEntries.begin(); it != rWidget.m_aListEntries.end(); ++it, ++i )
4837 appendUnicodeTextStringEncrypt( *it, rWidget.m_nObject, aLine );
4838 aLine.append( "\n" );
4839 if( *it == rWidget.m_aValue )
4840 nTI = i;
4842 aLine.append( "]\n" );
4843 if( nTI > 0 )
4845 aLine.append( "/TI " );
4846 aLine.append( nTI );
4847 aLine.append( "\n" );
4848 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4850 aLine.append( "/I [" );
4851 aLine.append( nTI );
4852 aLine.append( "]\n" );
4856 if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
4858 aLine.append( "/MaxLen " );
4859 aLine.append( rWidget.m_nMaxLen );
4860 aLine.append( "\n" );
4862 if( rWidget.m_eType == PDFWriter::PushButton )
4864 if(!m_bIsPDF_A1)
4866 OStringBuffer aDest;
4867 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4869 aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4870 aLine.append( aDest.makeStringAndClear() );
4871 aLine.append( ">>>>\n" );
4873 else if( rWidget.m_aListEntries.empty() )
4875 // create a reset form action
4876 aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4878 else if( rWidget.m_bSubmit )
4880 // create a submit form action
4881 aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4882 appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4883 aLine.append( "/Flags " );
4885 sal_Int32 nFlags = 0;
4886 switch( m_aContext.SubmitFormat )
4888 case PDFWriter::HTML:
4889 nFlags |= 4;
4890 break;
4891 case PDFWriter::XML:
4892 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4893 nFlags |= 32;
4894 break;
4895 case PDFWriter::PDF:
4896 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4897 nFlags |= 256;
4898 break;
4899 case PDFWriter::FDF:
4900 default:
4901 break;
4903 if( rWidget.m_bSubmitGet )
4904 nFlags |= 8;
4905 aLine.append( nFlags );
4906 aLine.append( ">>>>\n" );
4908 else
4910 // create a URI action
4911 aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4912 aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4913 aLine.append( ")>>>>\n" );
4916 else
4917 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4919 if( !rWidget.m_aDAString.isEmpty() )
4921 if( !rWidget.m_aDRDict.isEmpty() )
4923 aLine.append( "/DR<<" );
4924 aLine.append( rWidget.m_aDRDict );
4925 aLine.append( ">>\n" );
4927 else
4929 aLine.append( "/DR<</Font<<" );
4930 appendBuiltinFontsToDict( aLine );
4931 aLine.append( ">>>>\n" );
4933 aLine.append( "/DA" );
4934 appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
4935 aLine.append( "\n" );
4936 if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4937 aLine.append( "/Q 1\n" );
4938 else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4939 aLine.append( "/Q 2\n" );
4941 // appearance characteristics for terminal fields
4942 // which are supposed to have an appearance constructed
4943 // by the viewer application
4944 if( !rWidget.m_aMKDict.isEmpty() )
4946 aLine.append( "/MK<<" );
4947 aLine.append( rWidget.m_aMKDict );
4948 //add the CA string, encrypting it
4949 appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
4950 aLine.append( ">>\n" );
4953 CHECK_RETURN( emitAppearances( rWidget, aLine ) );
4955 aLine.append( ">>\n"
4956 "endobj\n\n" );
4957 CHECK_RETURN( updateObject( rWidget.m_nObject ) );
4958 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4960 return true;
4963 bool PDFWriterImpl::emitAnnotations()
4965 if( m_aPages.size() < 1 )
4966 return false;
4968 CHECK_RETURN( emitLinkAnnotations() );
4969 CHECK_RETURN(emitScreenAnnotations());
4970 CHECK_RETURN( emitNoteAnnotations() );
4971 CHECK_RETURN( emitWidgetAnnotations() );
4973 return true;
4976 bool PDFWriterImpl::emitEmbeddedFiles()
4978 for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4980 if (!updateObject(rEmbeddedFile.m_nObject))
4981 continue;
4983 OStringBuffer aLine;
4984 aLine.append(rEmbeddedFile.m_nObject);
4985 aLine.append(" 0 obj\n");
4986 aLine.append("<< /Type /EmbeddedFile /Length ");
4987 aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_aData.getLength()));
4988 aLine.append(" >>\nstream\n");
4989 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4990 aLine.setLength(0);
4992 CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aData.getArray(), rEmbeddedFile.m_aData.getLength()));
4994 aLine.append("\nendstream\nendobj\n\n");
4995 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4997 return true;
5000 #undef CHECK_RETURN
5001 #define CHECK_RETURN( x ) if( !x ) return false
5003 bool PDFWriterImpl::emitCatalog()
5005 // build page tree
5006 // currently there is only one node that contains all leaves
5008 // first create a page tree node id
5009 sal_Int32 nTreeNode = createObject();
5011 // emit global resource dictionary (page emit needs it)
5012 CHECK_RETURN( emitResources() );
5014 // emit all pages
5015 for( std::vector<PDFPage>::iterator it = m_aPages.begin(); it != m_aPages.end(); ++it )
5016 if( ! it->emit( nTreeNode ) )
5017 return false;
5019 sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
5021 sal_Int32 nOutlineDict = emitOutline();
5023 // emit Output intent
5024 sal_Int32 nOutputIntentObject = emitOutputIntent();
5026 // emit metadata
5027 sal_Int32 nMetadataObject = emitDocumentMetadata();
5029 sal_Int32 nStructureDict = 0;
5030 if(m_aStructure.size() > 1)
5032 // check if dummy structure containers are needed
5033 addInternalStructureContainer(m_aStructure[0]);
5034 nStructureDict = m_aStructure[0].m_nObject = createObject();
5035 emitStructure( m_aStructure[ 0 ] );
5038 // adjust tree node file offset
5039 if( ! updateObject( nTreeNode ) )
5040 return false;
5042 // emit tree node
5043 OStringBuffer aLine( 2048 );
5044 aLine.append( nTreeNode );
5045 aLine.append( " 0 obj\n" );
5046 aLine.append( "<</Type/Pages\n" );
5047 aLine.append( "/Resources " );
5048 aLine.append( getResourceDictObj() );
5049 aLine.append( " 0 R\n" );
5051 sal_Int32 nMediaBoxWidth = 0;
5052 sal_Int32 nMediaBoxHeight = 0;
5053 if( m_aPages.empty() ) // sanity check, this should not happen
5055 nMediaBoxWidth = m_nInheritedPageWidth;
5056 nMediaBoxHeight = m_nInheritedPageHeight;
5058 else
5060 for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter )
5062 if( iter->m_nPageWidth > nMediaBoxWidth )
5063 nMediaBoxWidth = iter->m_nPageWidth;
5064 if( iter->m_nPageHeight > nMediaBoxHeight )
5065 nMediaBoxHeight = iter->m_nPageHeight;
5068 aLine.append( "/MediaBox[ 0 0 " );
5069 aLine.append( nMediaBoxWidth );
5070 aLine.append( ' ' );
5071 aLine.append( nMediaBoxHeight );
5072 aLine.append( " ]\n"
5073 "/Kids[ " );
5074 unsigned int i = 0;
5075 for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter, i++ )
5077 aLine.append( iter->m_nPageObject );
5078 aLine.append( " 0 R" );
5079 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
5081 aLine.append( "]\n"
5082 "/Count " );
5083 aLine.append( (sal_Int32)m_aPages.size() );
5084 aLine.append( ">>\n"
5085 "endobj\n\n" );
5086 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5088 // emit annotation objects
5089 CHECK_RETURN( emitAnnotations() );
5090 CHECK_RETURN( emitEmbeddedFiles() );
5092 // emit Catalog
5093 m_nCatalogObject = createObject();
5094 if( ! updateObject( m_nCatalogObject ) )
5095 return false;
5096 aLine.setLength( 0 );
5097 aLine.append( m_nCatalogObject );
5098 aLine.append( " 0 obj\n"
5099 "<</Type/Catalog/Pages " );
5100 aLine.append( nTreeNode );
5101 aLine.append( " 0 R\n" );
5103 // check if there are named destinations to emit (root must be inside the catalog)
5104 if( nNamedDestinationsDictionary )
5106 aLine.append("/Dests ");
5107 aLine.append( nNamedDestinationsDictionary );
5108 aLine.append( " 0 R\n" );
5111 if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
5112 switch( m_aContext.PageLayout )
5114 default :
5115 case PDFWriter::SinglePage :
5116 aLine.append( "/PageLayout/SinglePage\n" );
5117 break;
5118 case PDFWriter::Continuous :
5119 aLine.append( "/PageLayout/OneColumn\n" );
5120 break;
5121 case PDFWriter::ContinuousFacing :
5122 // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
5123 aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
5124 break;
5126 if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
5127 switch( m_aContext.PDFDocumentMode )
5129 default :
5130 aLine.append( "/PageMode/UseNone\n" );
5131 break;
5132 case PDFWriter::UseOutlines :
5133 aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
5134 break;
5135 case PDFWriter::UseThumbs :
5136 aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
5137 break;
5139 else if( m_aContext.OpenInFullScreenMode )
5140 aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
5142 OStringBuffer aInitPageRef;
5143 if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < (sal_Int32)m_aPages.size() )
5145 aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
5146 aInitPageRef.append( " 0 R" );
5148 else
5149 aInitPageRef.append( "0" );
5151 switch( m_aContext.PDFDocumentAction )
5153 case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
5154 default:
5155 if( aInitPageRef.getLength() > 1 )
5157 aLine.append( "/OpenAction[" );
5158 aLine.append( aInitPageRef.makeStringAndClear() );
5159 aLine.append( " /XYZ null null 0]\n" );
5161 break;
5162 case PDFWriter::FitInWindow :
5163 aLine.append( "/OpenAction[" );
5164 aLine.append( aInitPageRef.makeStringAndClear() );
5165 aLine.append( " /Fit]\n" ); //Open fit page
5166 break;
5167 case PDFWriter::FitWidth :
5168 aLine.append( "/OpenAction[" );
5169 aLine.append( aInitPageRef.makeStringAndClear() );
5170 aLine.append( " /FitH " );
5171 aLine.append( m_nInheritedPageHeight );//Open fit width
5172 aLine.append( "]\n" );
5173 break;
5174 case PDFWriter::FitVisible :
5175 aLine.append( "/OpenAction[" );
5176 aLine.append( aInitPageRef.makeStringAndClear() );
5177 aLine.append( " /FitBH " );
5178 aLine.append( m_nInheritedPageHeight );//Open fit visible
5179 aLine.append( "]\n" );
5180 break;
5181 case PDFWriter::ActionZoom :
5182 aLine.append( "/OpenAction[" );
5183 aLine.append( aInitPageRef.makeStringAndClear() );
5184 aLine.append( " /XYZ null null " );
5185 if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
5186 aLine.append( (double)m_aContext.Zoom/100.0 );
5187 else
5188 aLine.append( "0" );
5189 aLine.append( "]\n" );
5190 break;
5193 // viewer preferences, if we had some, then emit
5194 if( m_aContext.HideViewerToolbar ||
5195 ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
5196 m_aContext.HideViewerMenubar ||
5197 m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
5198 m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
5199 m_aContext.OpenInFullScreenMode )
5201 aLine.append( "/ViewerPreferences<<" );
5202 if( m_aContext.HideViewerToolbar )
5203 aLine.append( "/HideToolbar true\n" );
5204 if( m_aContext.HideViewerMenubar )
5205 aLine.append( "/HideMenubar true\n" );
5206 if( m_aContext.HideViewerWindowControls )
5207 aLine.append( "/HideWindowUI true\n" );
5208 if( m_aContext.FitWindow )
5209 aLine.append( "/FitWindow true\n" );
5210 if( m_aContext.CenterWindow )
5211 aLine.append( "/CenterWindow true\n" );
5212 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
5213 aLine.append( "/DisplayDocTitle true\n" );
5214 if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
5215 aLine.append( "/Direction/R2L\n" );
5216 if( m_aContext.OpenInFullScreenMode )
5217 switch( m_aContext.PDFDocumentMode )
5219 default :
5220 case PDFWriter::ModeDefault :
5221 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
5222 break;
5223 case PDFWriter::UseOutlines :
5224 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
5225 break;
5226 case PDFWriter::UseThumbs :
5227 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
5228 break;
5230 aLine.append( ">>\n" );
5233 if( nOutlineDict )
5235 aLine.append( "/Outlines " );
5236 aLine.append( nOutlineDict );
5237 aLine.append( " 0 R\n" );
5239 if( nStructureDict )
5241 aLine.append( "/StructTreeRoot " );
5242 aLine.append( nStructureDict );
5243 aLine.append( " 0 R\n" );
5245 if( !m_aContext.DocumentLocale.Language.isEmpty() )
5247 /* PDF allows only RFC 3066, see above in emitStructure(). */
5248 LanguageTag aLanguageTag( m_aContext.DocumentLocale);
5249 OUString aLanguage, aScript, aCountry;
5250 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
5251 if (!aLanguage.isEmpty())
5253 OUStringBuffer aLocBuf( 16 );
5254 aLocBuf.append( aLanguage );
5255 if( !aCountry.isEmpty() )
5257 aLocBuf.append( '-' );
5258 aLocBuf.append( aCountry );
5260 aLine.append( "/Lang" );
5261 appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine );
5262 aLine.append( "\n" );
5265 if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
5267 aLine.append( "/MarkInfo<</Marked true>>\n" );
5269 if( m_aWidgets.size() > 0 )
5271 aLine.append( "/AcroForm<</Fields[\n" );
5272 int nWidgets = m_aWidgets.size();
5273 int nOut = 0;
5274 for( int j = 0; j < nWidgets; j++ )
5276 // output only root fields
5277 if( m_aWidgets[j].m_nParent < 1 )
5279 aLine.append( m_aWidgets[j].m_nObject );
5280 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
5283 aLine.append( "\n]" );
5285 #if HAVE_FEATURE_NSS
5286 if (m_nSignatureObject != -1)
5287 aLine.append( "/SigFlags 3");
5288 #endif
5290 aLine.append( "/DR " );
5291 aLine.append( getResourceDictObj() );
5292 aLine.append( " 0 R" );
5293 // NeedAppearances must not be used if PDF is signed
5294 if( m_bIsPDF_A1
5295 #if HAVE_FEATURE_NSS
5296 || ( m_nSignatureObject != -1 )
5297 #endif
5299 aLine.append( ">>\n" );
5300 else
5301 aLine.append( "/NeedAppearances true>>\n" );
5304 //check if there is a Metadata object
5305 if( nOutputIntentObject )
5307 aLine.append("/OutputIntents[");
5308 aLine.append( nOutputIntentObject );
5309 aLine.append( " 0 R]" );
5312 if( nMetadataObject )
5314 aLine.append("/Metadata ");
5315 aLine.append( nMetadataObject );
5316 aLine.append( " 0 R" );
5319 aLine.append( ">>\n"
5320 "endobj\n\n" );
5321 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5323 return true;
5326 #if HAVE_FEATURE_NSS
5328 bool PDFWriterImpl::emitSignature()
5330 if( !updateObject( m_nSignatureObject ) )
5331 return false;
5333 OStringBuffer aLine( 0x5000 );
5334 aLine.append( m_nSignatureObject );
5335 aLine.append( " 0 obj\n" );
5336 aLine.append("<</Contents <" );
5338 sal_uInt64 nOffset = ~0U;
5339 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
5341 m_nSignatureContentOffset = nOffset + aLine.getLength();
5343 // reserve some space for the PKCS#7 object
5344 OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
5345 comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
5346 aLine.append( aContentFiller.makeStringAndClear() );
5347 aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5349 if( !m_aContext.DocumentInfo.Author.isEmpty() )
5351 aLine.append( "/Name" );
5352 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
5355 aLine.append( " /M ");
5356 appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
5358 aLine.append( " /ByteRange [ 0 ");
5359 aLine.append( m_nSignatureContentOffset - 1 );
5360 aLine.append( " " );
5361 aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5362 aLine.append( " " );
5364 m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5366 // mark the last ByteRange no and add some space. Now, we don't know
5367 // how many bytes we need for this ByteRange value
5368 // The real value will be overwritten in the finalizeSignature method
5369 OStringBuffer aByteRangeFiller( 100 );
5370 comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
5371 aLine.append( aByteRangeFiller.makeStringAndClear() );
5372 aLine.append(" /Filter/Adobe.PPKMS");
5374 //emit reason, location and contactinfo
5375 if ( !m_aContext.SignReason.isEmpty() )
5377 aLine.append("/Reason");
5378 appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
5381 if ( !m_aContext.SignLocation.isEmpty() )
5383 aLine.append("/Location");
5384 appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
5387 if ( !m_aContext.SignContact.isEmpty() )
5389 aLine.append("/ContactInfo");
5390 appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
5393 aLine.append(" >>\nendobj\n\n" );
5395 if (!writeBuffer( aLine.getStr(), aLine.getLength() ))
5396 return false;
5398 return true;
5401 #if HAVE_FEATURE_NSS && !defined(_WIN32)
5403 namespace {
5405 char *PDFSigningPKCS7PasswordCallback(PK11SlotInfo * /*slot*/, PRBool /*retry*/, void *arg)
5407 return PL_strdup(static_cast<char *>(arg));
5410 class HashContextScope {
5411 HASHContext *mpPtr;
5412 public:
5413 explicit HashContextScope(HASHContext *pPtr) : mpPtr(pPtr) {}
5414 ~HashContextScope() { clear(); }
5415 void clear() { if (mpPtr) { HASH_Destroy(mpPtr); } mpPtr = nullptr; }
5416 HASHContext *get() { return mpPtr; }
5419 // ASN.1 used in the (much simpler) time stamp request. From RFC3161
5420 // and other sources.
5423 AlgorithmIdentifier ::= SEQUENCE {
5424 algorithm OBJECT IDENTIFIER,
5425 parameters ANY DEFINED BY algorithm OPTIONAL }
5426 -- contains a value of the type
5427 -- registered for use with the
5428 -- algorithm object identifier value
5430 MessageImprint ::= SEQUENCE {
5431 hashAlgorithm AlgorithmIdentifier,
5432 hashedMessage OCTET STRING }
5435 typedef struct {
5436 SECAlgorithmID hashAlgorithm;
5437 SECItem hashedMessage;
5438 } MessageImprint;
5441 Extension ::= SEQUENCE {
5442 extnID OBJECT IDENTIFIER,
5443 critical BOOLEAN DEFAULT FALSE,
5444 extnValue OCTET STRING }
5447 typedef struct {
5448 SECItem extnID;
5449 SECItem critical;
5450 SECItem extnValue;
5451 } Extension;
5454 Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
5458 TSAPolicyId ::= OBJECT IDENTIFIER
5460 TimeStampReq ::= SEQUENCE {
5461 version INTEGER { v1(1) },
5462 messageImprint MessageImprint,
5463 --a hash algorithm OID and the hash value of the data to be
5464 --time-stamped
5465 reqPolicy TSAPolicyId OPTIONAL,
5466 nonce INTEGER OPTIONAL,
5467 certReq BOOLEAN DEFAULT FALSE,
5468 extensions [0] IMPLICIT Extensions OPTIONAL }
5471 typedef struct {
5472 SECItem version;
5473 MessageImprint messageImprint;
5474 SECItem reqPolicy;
5475 SECItem nonce;
5476 SECItem certReq;
5477 Extension *extensions;
5478 } TimeStampReq;
5481 * General name, defined by RFC 3280.
5483 struct GeneralName
5485 CERTName name;
5489 * List of general names (only one for now), defined by RFC 3280.
5491 struct GeneralNames
5493 GeneralName names;
5497 * Supplies different fields to identify a certificate, defined by RFC 5035.
5499 struct IssuerSerial
5501 GeneralNames issuer;
5502 SECItem serialNumber;
5506 * Supplies different fields that are used to identify certificates, defined by
5507 * RFC 5035.
5509 struct ESSCertIDv2
5511 SECAlgorithmID hashAlgorithm;
5512 SECItem certHash;
5513 IssuerSerial issuerSerial;
5517 * This attribute uses the ESSCertIDv2 structure, defined by RFC 5035.
5519 struct SigningCertificateV2
5521 ESSCertIDv2** certs;
5523 SigningCertificateV2()
5524 : certs(nullptr)
5529 // (Partial) ASN.1 for the time stamp response. Very complicated. Pulled
5530 // together from various RFCs.
5533 Accuracy ::= SEQUENCE {
5534 seconds INTEGER OPTIONAL,
5535 millis [0] INTEGER (1..999) OPTIONAL,
5536 micros [1] INTEGER (1..999) OPTIONAL }
5538 PKIStatus ::= INTEGER {
5539 granted (0),
5540 -- when the PKIStatus contains the value zero a TimeStampToken, as requested, is present.
5541 grantedWithMods (1),
5542 -- when the PKIStatus contains the value one a TimeStampToken, with modifications, is present.
5543 rejection (2),
5544 waiting (3),
5545 revocationWarning (4),
5546 -- this message contains a warning that a revocation is
5547 -- imminent
5548 revocationNotification (5)
5549 -- notification that a revocation has occurred
5552 PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
5553 -- text encoded as UTF-8 String [RFC3629] (note: each
5554 -- UTF8String MAY include an [RFC3066] language tag
5555 -- to indicate the language of the contained text
5556 -- see [RFC2482] for details)
5558 PKIFailureInfo ::= BIT STRING {
5559 badAlg (0),
5560 -- unrecognized or unsupported Algorithm Identifier
5561 badRequest (2),
5562 -- transaction not permitted or supported
5563 badDataFormat (5),
5564 -- the data submitted has the wrong format
5565 timeNotAvailable (14),
5566 -- the TSA's time source is not available
5567 unacceptedPolicy (15),
5568 -- the requested TSA policy is not supported by the TSA.
5569 unacceptedExtension (16),
5570 -- the requested extension is not supported by the TSA.
5571 addInfoNotAvailable (17),
5572 -- the additional information requested could not be understood
5573 -- or is not available
5574 systemFailure (25)
5575 -- the request cannot be handled due to system failure
5578 PKIStatusInfo ::= SEQUENCE {
5579 status PKIStatus,
5580 statusString PKIFreeText OPTIONAL,
5581 failInfo PKIFailureInfo OPTIONAL }
5583 ContentType ::= OBJECT IDENTIFIER
5585 ContentInfo ::= SEQUENCE {
5586 contentType ContentType,
5587 content [0] EXPLICIT ANY DEFINED BY contentType }
5589 CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
5591 DigestAlgorithmIdentifier ::= AlgorithmIdentifier
5593 DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
5595 ContentType ::= OBJECT IDENTIFIER
5597 EncapsulatedContentInfo ::= SEQUENCE {
5598 eContentType ContentType,
5599 eContent [0] EXPLICIT OCTET STRING OPTIONAL }
5601 OtherCertificateFormat ::= SEQUENCE {
5602 otherCertFormat OBJECT IDENTIFIER,
5603 otherCert ANY DEFINED BY otherCertFormat }
5605 CertificateChoices ::= CHOICE {
5606 certificate Certificate,
5607 extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
5608 v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
5609 v2AttrCert [2] IMPLICIT AttributeCertificateV2,
5610 other [3] IMPLICIT OtherCertificateFormat }
5612 CertificateSet ::= SET OF CertificateChoices
5614 CertificateList ::= SEQUENCE {
5615 tbsCertList TBSCertList,
5616 signatureAlgorithm AlgorithmIdentifier,
5617 signatureValue BIT STRING }
5619 TBSCertList ::= SEQUENCE {
5620 version Version OPTIONAL,
5621 -- if present, MUST be v2
5622 signature AlgorithmIdentifier,
5623 issuer Name,
5624 thisUpdate Time,
5625 nextUpdate Time OPTIONAL,
5626 revokedCertificates SEQUENCE OF SEQUENCE {
5627 userCertificate CertificateSerialNumber,
5628 revocationDate Time,
5629 crlEntryExtensions Extensions OPTIONAL
5630 -- if present, version MUST be v2
5631 } OPTIONAL,
5632 crlExtensions [0] EXPLICIT Extensions OPTIONAL
5633 -- if present, version MUST be v2
5636 OtherRevocationInfoFormat ::= SEQUENCE {
5637 otherRevInfoFormat OBJECT IDENTIFIER,
5638 otherRevInfo ANY DEFINED BY otherRevInfoFormat }
5640 RevocationInfoChoice ::= CHOICE {
5641 crl CertificateList,
5642 other [1] IMPLICIT OtherRevocationInfoFormat }
5644 RevocationInfoChoices ::= SET OF RevocationInfoChoice
5646 SignerIdentifier ::= CHOICE {
5647 issuerAndSerialNumber IssuerAndSerialNumber,
5648 subjectKeyIdentifier [0] SubjectKeyIdentifier }
5650 AttributeValue ::= ANY
5652 Attribute ::= SEQUENCE {
5653 attrType OBJECT IDENTIFIER,
5654 attrValues SET OF AttributeValue }
5656 SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
5658 SignatureValue ::= OCTET STRING
5660 UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
5662 SignerInfo ::= SEQUENCE {
5663 version CMSVersion,
5664 sid SignerIdentifier,
5665 digestAlgorithm DigestAlgorithmIdentifier,
5666 signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
5667 signatureAlgorithm SignatureAlgorithmIdentifier,
5668 signature SignatureValue,
5669 unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
5671 SignerInfos ::= SET OF SignerInfo
5673 SignedData ::= SEQUENCE {
5674 version CMSVersion,
5675 digestAlgorithms DigestAlgorithmIdentifiers,
5676 encapContentInfo EncapsulatedContentInfo,
5677 certificates [0] IMPLICIT CertificateSet OPTIONAL,
5678 crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
5679 signerInfos SignerInfos }
5681 TimeStampToken ::= ContentInfo
5682 -- contentType is id-signedData as defined in [CMS]
5683 -- content is SignedData as defined in([CMS])
5684 -- eContentType within SignedData is id-ct-TSTInfo
5685 -- eContent within SignedData is TSTInfo
5687 TSTInfo ::= SEQUENCE {
5688 version INTEGER { v1(1) },
5689 policy TSAPolicyId,
5690 messageImprint MessageImprint,
5691 -- MUST have the same value as the similar field in
5692 -- TimeStampReq
5693 serialNumber INTEGER,
5694 -- Time-Stamping users MUST be ready to accommodate integers
5695 -- up to 160 bits.
5696 genTime GeneralizedTime,
5697 accuracy Accuracy OPTIONAL,
5698 ordering BOOLEAN DEFAULT FALSE,
5699 nonce INTEGER OPTIONAL,
5700 -- MUST be present if the similar field was present
5701 -- in TimeStampReq. In that case it MUST have the same value.
5702 tsa [0] GeneralName OPTIONAL,
5703 extensions [1] IMPLICIT Extensions OPTIONAL }
5705 TimeStampResp ::= SEQUENCE {
5706 status PKIStatusInfo,
5707 timeStampToken TimeStampToken OPTIONAL }
5710 const SEC_ASN1Template MessageImprint_Template[] =
5712 { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(MessageImprint) },
5713 { SEC_ASN1_INLINE, offsetof(MessageImprint, hashAlgorithm), SECOID_AlgorithmIDTemplate, 0 },
5714 { SEC_ASN1_OCTET_STRING, offsetof(MessageImprint, hashedMessage), nullptr, 0 },
5715 { 0, 0, nullptr, 0 }
5718 const SEC_ASN1Template Extension_Template[] =
5720 { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(Extension) },
5721 { SEC_ASN1_OBJECT_ID, offsetof(Extension, extnID), nullptr, 0 },
5722 { SEC_ASN1_BOOLEAN, offsetof(Extension, critical), nullptr, 0 },
5723 { SEC_ASN1_OCTET_STRING, offsetof(Extension, extnValue), nullptr, 0 },
5724 { 0, 0, nullptr, 0 }
5727 const SEC_ASN1Template Extensions_Template[] =
5729 { SEC_ASN1_SEQUENCE_OF, 0, Extension_Template, 0 }
5732 const SEC_ASN1Template TimeStampReq_Template[] =
5734 { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampReq) },
5735 { SEC_ASN1_INTEGER, offsetof(TimeStampReq, version), nullptr, 0 },
5736 { SEC_ASN1_INLINE, offsetof(TimeStampReq, messageImprint), MessageImprint_Template, 0 },
5737 { SEC_ASN1_OBJECT_ID | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, reqPolicy), nullptr, 0 },
5738 { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, nonce), nullptr, 0 },
5739 { SEC_ASN1_BOOLEAN | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, certReq), nullptr, 0 },
5740 { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(TimeStampReq, extensions), Extensions_Template, 0 },
5741 { 0, 0, nullptr, 0 }
5745 * GeneralName ::= CHOICE {
5746 * otherName [0] OtherName,
5747 * rfc822Name [1] IA5String,
5748 * dNSName [2] IA5String,
5749 * x400Address [3] ORAddress,
5750 * directoryName [4] Name,
5751 * ediPartyName [5] EDIPartyName,
5752 * uniformResourceIdentifier [6] IA5String,
5753 * iPAddress [7] OCTET STRING,
5754 * registeredID [8] OBJECT IDENTIFIER
5757 const SEC_ASN1Template GeneralNameTemplate[] =
5759 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralName)},
5760 {SEC_ASN1_INLINE, offsetof(GeneralName, name), CERT_NameTemplate, 0},
5761 {0, 0, nullptr, 0}
5765 * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
5767 const SEC_ASN1Template GeneralNamesTemplate[] =
5769 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralNames)},
5770 {SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 4, offsetof(GeneralNames, names), GeneralNameTemplate, 0},
5771 {0, 0, nullptr, 0}
5775 * IssuerSerial ::= SEQUENCE {
5776 * issuer GeneralNames,
5777 * serialNumber CertificateSerialNumber
5780 const SEC_ASN1Template IssuerSerialTemplate[] =
5782 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(IssuerSerial)},
5783 {SEC_ASN1_INLINE, offsetof(IssuerSerial, issuer), GeneralNamesTemplate, 0},
5784 {SEC_ASN1_INTEGER, offsetof(IssuerSerial, serialNumber), nullptr, 0},
5785 {0, 0, nullptr, 0}
5789 * Hash ::= OCTET STRING
5791 * ESSCertIDv2 ::= SEQUENCE {
5792 * hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256},
5793 * certHash Hash,
5794 * issuerSerial IssuerSerial OPTIONAL
5797 const SEC_ASN1Template ESSCertIDv2Template[] =
5799 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(ESSCertIDv2)},
5800 {SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, hashAlgorithm), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate), 0},
5801 {SEC_ASN1_OCTET_STRING, offsetof(ESSCertIDv2, certHash), nullptr, 0},
5802 {SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, issuerSerial), IssuerSerialTemplate, 0},
5803 {0, 0, nullptr, 0}
5807 * SigningCertificateV2 ::= SEQUENCE {
5810 const SEC_ASN1Template SigningCertificateV2Template[] =
5812 {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(SigningCertificateV2)},
5813 {SEC_ASN1_SEQUENCE_OF, offsetof(SigningCertificateV2, certs), ESSCertIDv2Template, 0},
5814 {0, 0, nullptr, 0}
5817 typedef struct {
5818 SECItem status;
5819 SECItem statusString;
5820 SECItem failInfo;
5821 } PKIStatusInfo;
5823 const SEC_ASN1Template PKIStatusInfo_Template[] =
5825 { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PKIStatusInfo) },
5826 { SEC_ASN1_INTEGER, offsetof(PKIStatusInfo, status), nullptr, 0 },
5827 { SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, statusString), nullptr, 0 },
5828 { SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, failInfo), nullptr, 0 },
5829 { 0, 0, nullptr, 0 }
5832 const SEC_ASN1Template Any_Template[] =
5834 { SEC_ASN1_ANY, 0, nullptr, sizeof(SECItem) }
5837 typedef struct {
5838 PKIStatusInfo status;
5839 SECItem timeStampToken;
5840 } TimeStampResp;
5842 const SEC_ASN1Template TimeStampResp_Template[] =
5844 { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampResp) },
5845 { SEC_ASN1_INLINE, offsetof(TimeStampResp, status), PKIStatusInfo_Template, 0 },
5846 { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, offsetof(TimeStampResp, timeStampToken), Any_Template, 0 },
5847 { 0, 0, nullptr, 0 }
5850 /* Will see if these are needed or not
5851 typedef struct {
5852 SECItem seconds;
5853 SECItem millis;
5854 SECItem micros;
5855 } Accuracy;
5857 const SEC_ASN1Template Integer_Template[] =
5859 { SEC_ASN1_INTEGER, 0, NULL, sizeof(SECItem) }
5862 const SEC_ASN1Template Accuracy_Template[] =
5864 { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(Accuracy) },
5865 { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(Accuracy, seconds), 0, 0 },
5866 { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(Accuracy, millis), Integer_Template, 0 },
5867 { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 1, offsetof(Accuracy, micros), Integer_Template, 0 },
5868 { 0, 0, 0, 0 }
5872 size_t AppendToBuffer(char *ptr, size_t size, size_t nmemb, void *userdata)
5874 OStringBuffer *pBuffer = static_cast<OStringBuffer*>(userdata);
5875 pBuffer->append(ptr, size*nmemb);
5877 return size*nmemb;
5880 OUString PKIStatusToString(int n)
5882 switch (n)
5884 case 0: return OUString("granted");
5885 case 1: return OUString("grantedWithMods");
5886 case 2: return OUString("rejection");
5887 case 3: return OUString("waiting");
5888 case 4: return OUString("revocationWarning");
5889 case 5: return OUString("revocationNotification");
5890 default: return "unknown (" + OUString::number(n) + ")";
5894 OUString PKIStatusInfoToString(const PKIStatusInfo& rStatusInfo)
5896 OUString result;
5898 result += "{status=";
5899 if (rStatusInfo.status.len == 1)
5900 result += PKIStatusToString(rStatusInfo.status.data[0]);
5901 else
5902 result += "unknown (len=" + OUString::number(rStatusInfo.status.len);
5904 // FIXME: Perhaps look at rStatusInfo.statusString.data but note
5905 // that we of course can't assume it contains proper UTF-8. After
5906 // all, it is data from an external source. Also, RFC3161 claims
5907 // it should be a SEQUENCE (1..MAX) OF UTF8String, but another
5908 // source claimed it would be a single UTF8String, hmm?
5910 // FIXME: Worth it to decode failInfo to cleartext, probably not at least as long as this is only for a SAL_INFO
5912 result += "}";
5914 return result;
5917 // SEC_StringToOID() and NSS_CMSSignerInfo_AddUnauthAttr() are
5918 // not exported from libsmime, so copy them here. Sigh.
5920 SECStatus
5921 my_SEC_StringToOID(SECItem *to, const char *from, PRUint32 len)
5923 PRUint32 decimal_numbers = 0;
5924 PRUint32 result_bytes = 0;
5925 SECStatus rv;
5926 PRUint8 result[1024];
5928 static const PRUint32 max_decimal = (0xffffffff / 10);
5929 static const char OIDstring[] = {"OID."};
5931 if (!from || !to) {
5932 PORT_SetError(SEC_ERROR_INVALID_ARGS);
5933 return SECFailure;
5935 if (!len) {
5936 len = PL_strlen(from);
5938 if (len >= 4 && !PL_strncasecmp(from, OIDstring, 4)) {
5939 from += 4; /* skip leading "OID." if present */
5940 len -= 4;
5942 if (!len) {
5943 bad_data:
5944 PORT_SetError(SEC_ERROR_BAD_DATA);
5945 return SECFailure;
5947 do {
5948 PRUint32 decimal = 0;
5949 while (len > 0 && rtl::isAsciiDigit(static_cast<unsigned char>(*from))) {
5950 PRUint32 addend = (*from++ - '0');
5951 --len;
5952 if (decimal > max_decimal) /* overflow */
5953 goto bad_data;
5954 decimal = (decimal * 10) + addend;
5955 if (decimal < addend) /* overflow */
5956 goto bad_data;
5958 if (len != 0 && *from != '.') {
5959 goto bad_data;
5961 if (decimal_numbers == 0) {
5962 if (decimal > 2)
5963 goto bad_data;
5964 result[0] = decimal * 40;
5965 result_bytes = 1;
5966 } else if (decimal_numbers == 1) {
5967 if (decimal > 40)
5968 goto bad_data;
5969 result[0] += decimal;
5970 } else {
5971 /* encode the decimal number, */
5972 PRUint8 * rp;
5973 PRUint32 num_bytes = 0;
5974 PRUint32 tmp = decimal;
5975 while (tmp) {
5976 num_bytes++;
5977 tmp >>= 7;
5979 if (!num_bytes )
5980 ++num_bytes; /* use one byte for a zero value */
5981 if (num_bytes + result_bytes > sizeof result)
5982 goto bad_data;
5983 tmp = num_bytes;
5984 rp = result + result_bytes - 1;
5985 rp[tmp] = (PRUint8)(decimal & 0x7f);
5986 decimal >>= 7;
5987 while (--tmp > 0) {
5988 rp[tmp] = (PRUint8)(decimal | 0x80);
5989 decimal >>= 7;
5991 result_bytes += num_bytes;
5993 ++decimal_numbers;
5994 if (len > 0) { /* skip trailing '.' */
5995 ++from;
5996 --len;
5998 } while (len > 0);
5999 /* now result contains result_bytes of data */
6000 if (to->data && to->len >= result_bytes) {
6001 PORT_Memcpy(to->data, result, to->len = result_bytes);
6002 rv = SECSuccess;
6003 } else {
6004 SECItem result_item = {siBuffer, nullptr, 0 };
6005 result_item.data = result;
6006 result_item.len = result_bytes;
6007 rv = SECITEM_CopyItem(nullptr, to, &result_item);
6009 return rv;
6012 NSSCMSAttribute *
6013 my_NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only)
6015 SECOidData *oid;
6016 NSSCMSAttribute *attr1, *attr2;
6018 if (attrs == nullptr)
6019 return nullptr;
6021 oid = SECOID_FindOIDByTag(oidtag);
6022 if (oid == nullptr)
6023 return nullptr;
6025 while ((attr1 = *attrs++) != nullptr) {
6026 if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data,
6027 oid->oid.data,
6028 oid->oid.len) == 0)
6029 break;
6032 if (attr1 == nullptr)
6033 return nullptr;
6035 if (!only)
6036 return attr1;
6038 while ((attr2 = *attrs++) != nullptr) {
6039 if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data,
6040 oid->oid.data,
6041 oid->oid.len) == 0)
6042 break;
6045 if (attr2 != nullptr)
6046 return nullptr;
6048 return attr1;
6051 SECStatus
6052 my_NSS_CMSArray_Add(PLArenaPool *poolp, void ***array, void *obj)
6054 int n = 0;
6055 void **dest;
6057 PORT_Assert(array != NULL);
6058 if (array == nullptr)
6059 return SECFailure;
6061 if (*array == nullptr) {
6062 dest = static_cast<void **>(PORT_ArenaAlloc(poolp, 2 * sizeof(void *)));
6063 } else {
6064 void **p = *array;
6065 while (*p++)
6066 n++;
6067 dest = static_cast<void **>(PORT_ArenaGrow (poolp,
6068 *array,
6069 (n + 1) * sizeof(void *),
6070 (n + 2) * sizeof(void *)));
6073 if (dest == nullptr)
6074 return SECFailure;
6076 dest[n] = obj;
6077 dest[n+1] = nullptr;
6078 *array = dest;
6079 return SECSuccess;
6082 SECOidTag
6083 my_NSS_CMSAttribute_GetType(NSSCMSAttribute *attr)
6085 SECOidData *typetag;
6087 typetag = SECOID_FindOID(&(attr->type));
6088 if (typetag == nullptr)
6089 return SEC_OID_UNKNOWN;
6091 return typetag->offset;
6094 SECStatus
6095 my_NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr)
6097 NSSCMSAttribute *oattr;
6098 void *mark;
6099 SECOidTag type;
6101 mark = PORT_ArenaMark(poolp);
6103 /* find oidtag of attr */
6104 type = my_NSS_CMSAttribute_GetType(attr);
6106 /* see if we have one already */
6107 oattr = my_NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE);
6108 PORT_Assert (oattr == NULL);
6109 if (oattr != nullptr)
6110 goto loser; /* XXX or would it be better to replace it? */
6112 /* no, shove it in */
6113 if (my_NSS_CMSArray_Add(poolp, reinterpret_cast<void ***>(attrs), static_cast<void *>(attr)) != SECSuccess)
6114 goto loser;
6116 PORT_ArenaUnmark(poolp, mark);
6117 return SECSuccess;
6119 loser:
6120 PORT_ArenaRelease(poolp, mark);
6121 return SECFailure;
6124 SECStatus
6125 my_NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
6127 return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->unAuthAttr), attr);
6130 SECStatus
6131 my_NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
6133 return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr);
6136 NSSCMSMessage *CreateCMSMessage(PRTime* time,
6137 NSSCMSSignedData **cms_sd,
6138 NSSCMSSignerInfo **cms_signer,
6139 CERTCertificate *cert,
6140 SECItem *digest)
6142 NSSCMSMessage *result = NSS_CMSMessage_Create(nullptr);
6143 if (!result)
6145 SAL_WARN("vcl.pdfwriter", "NSS_CMSMessage_Create failed");
6146 return nullptr;
6149 *cms_sd = NSS_CMSSignedData_Create(result);
6150 if (!*cms_sd)
6152 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignedData_Create failed");
6153 NSS_CMSMessage_Destroy(result);
6154 return nullptr;
6157 NSSCMSContentInfo *cms_cinfo = NSS_CMSMessage_GetContentInfo(result);
6158 if (NSS_CMSContentInfo_SetContent_SignedData(result, cms_cinfo, *cms_sd) != SECSuccess)
6160 SAL_WARN("vcl.pdfwriter", "NSS_CMSContentInfo_SetContent_SignedData failed");
6161 NSS_CMSSignedData_Destroy(*cms_sd);
6162 NSS_CMSMessage_Destroy(result);
6163 return nullptr;
6166 cms_cinfo = NSS_CMSSignedData_GetContentInfo(*cms_sd);
6168 // Attach NULL data as detached data
6169 if (NSS_CMSContentInfo_SetContent_Data(result, cms_cinfo, nullptr, PR_TRUE) != SECSuccess)
6171 SAL_WARN("vcl.pdfwriter", "NSS_CMSContentInfo_SetContent_Data failed");
6172 NSS_CMSSignedData_Destroy(*cms_sd);
6173 NSS_CMSMessage_Destroy(result);
6174 return nullptr;
6177 *cms_signer = NSS_CMSSignerInfo_Create(result, cert, SEC_OID_SHA256);
6178 if (!*cms_signer)
6180 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignerInfo_Create failed");
6181 NSS_CMSSignedData_Destroy(*cms_sd);
6182 NSS_CMSMessage_Destroy(result);
6183 return nullptr;
6186 if (time && NSS_CMSSignerInfo_AddSigningTime(*cms_signer, *time) != SECSuccess)
6188 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignerInfo_AddSigningTime failed");
6189 NSS_CMSSignedData_Destroy(*cms_sd);
6190 NSS_CMSMessage_Destroy(result);
6191 return nullptr;
6194 if (NSS_CMSSignerInfo_IncludeCerts(*cms_signer, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess)
6196 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignerInfo_IncludeCerts failed");
6197 NSS_CMSSignedData_Destroy(*cms_sd);
6198 NSS_CMSMessage_Destroy(result);
6199 return nullptr;
6202 if (NSS_CMSSignedData_AddCertificate(*cms_sd, cert) != SECSuccess)
6204 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignedData_AddCertificate failed");
6205 NSS_CMSSignedData_Destroy(*cms_sd);
6206 NSS_CMSMessage_Destroy(result);
6207 return nullptr;
6210 if (NSS_CMSSignedData_AddSignerInfo(*cms_sd, *cms_signer) != SECSuccess)
6212 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignedData_AddSignerInfo failed");
6213 NSS_CMSSignedData_Destroy(*cms_sd);
6214 NSS_CMSMessage_Destroy(result);
6215 return nullptr;
6218 if (NSS_CMSSignedData_SetDigestValue(*cms_sd, SEC_OID_SHA256, digest) != SECSuccess)
6220 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignedData_SetDigestValue failed");
6221 NSS_CMSSignedData_Destroy(*cms_sd);
6222 NSS_CMSMessage_Destroy(result);
6223 return nullptr;
6226 return result;
6229 } // anonymous namespace
6231 #endif // HAVE_FEATURE_NSS && !defined(_WIN32)
6233 #ifdef _WIN32
6235 typedef BOOL (WINAPI *PointerTo_CryptRetrieveTimeStamp)(LPCWSTR wszUrl,
6236 DWORD dwRetrievalFlags,
6237 DWORD dwTimeout,
6238 LPCSTR pszHashId,
6239 const CRYPT_TIMESTAMP_PARA *pPara,
6240 const BYTE *pbData,
6241 DWORD cbData,
6242 PCRYPT_TIMESTAMP_CONTEXT *ppTsContext,
6243 PCCERT_CONTEXT *ppTsSigner,
6244 HCERTSTORE phStore);
6246 namespace
6249 /// Counts how many bytes are needed to encode a given length.
6250 size_t GetDERLengthOfLength(size_t nLength)
6252 size_t nRet = 1;
6254 if(nLength > 127)
6256 while (nLength >> (nRet * 8))
6257 ++nRet;
6258 // Long form means one additional byte: the length of the length and
6259 // the length itself.
6260 ++nRet;
6262 return nRet;
6265 /// Writes the length part of the header.
6266 void WriteDERLength(SvStream& rStream, size_t nLength)
6268 size_t nLengthOfLength = GetDERLengthOfLength(nLength);
6269 if (nLengthOfLength == 1)
6271 // We can use the short form.
6272 rStream.WriteUInt8(nLength);
6273 return;
6276 // 0x80 means that the we use the long form: the first byte is the length
6277 // of length with the highest bit set to 1, not the actual length.
6278 rStream.WriteUInt8(0x80 | (nLengthOfLength - 1));
6279 for (size_t i = 1; i < nLengthOfLength; ++i)
6280 rStream.WriteUInt8(nLength >> ((nLengthOfLength - i - 1) * 8));
6283 const unsigned nASN1_INTEGER = 0x02;
6284 const unsigned nASN1_OCTET_STRING = 0x04;
6285 const unsigned nASN1_NULL = 0x05;
6286 const unsigned nASN1_OBJECT_IDENTIFIER = 0x06;
6287 const unsigned nASN1_SEQUENCE = 0x10;
6288 /// An explicit tag on a constructed value.
6289 const unsigned nASN1_TAGGED_CONSTRUCTED = 0xa0;
6290 const unsigned nASN1_CONSTRUCTED = 0x20;
6292 /// Create payload for the 'signing-certificate' signed attribute.
6293 bool CreateSigningCertificateAttribute(vcl::PDFWriter::PDFSignContext& rContext, PCCERT_CONTEXT pCertContext, SvStream& rEncodedCertificate)
6295 // CryptEncodeObjectEx() does not support encoding arbitrary ASN.1
6296 // structures, like SigningCertificateV2 from RFC 5035, so let's build it
6297 // manually.
6299 // Count the certificate hash and put it to aHash.
6300 // 2.16.840.1.101.3.4.2.1, i.e. sha256.
6301 std::vector<unsigned char> aSHA256{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01};
6303 HCRYPTPROV hProv = 0;
6304 if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
6306 SAL_WARN("vcl.pdfwriter", "CryptAcquireContext() failed");
6307 return false;
6310 HCRYPTHASH hHash = 0;
6311 if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
6313 SAL_WARN("vcl.pdfwriter", "CryptCreateHash() failed");
6314 return false;
6317 if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(rContext.m_pDerEncoded), rContext.m_nDerEncoded, 0))
6319 SAL_WARN("vcl.pdfwriter", "CryptHashData() failed");
6320 return false;
6323 DWORD nHash = 0;
6324 if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nHash, 0))
6326 SAL_WARN("vcl.pdfwriter", "CryptGetHashParam() failed to provide the hash length");
6327 return false;
6330 std::vector<unsigned char> aHash(nHash);
6331 if (!CryptGetHashParam(hHash, HP_HASHVAL, aHash.data(), &nHash, 0))
6333 SAL_WARN("vcl.pdfwriter", "CryptGetHashParam() failed to provide the hash");
6334 return false;
6337 CryptDestroyHash(hHash);
6338 CryptReleaseContext(hProv, 0);
6340 // Collect info for IssuerSerial.
6341 BYTE* pIssuer = pCertContext->pCertInfo->Issuer.pbData;
6342 DWORD nIssuer = pCertContext->pCertInfo->Issuer.cbData;
6343 BYTE* pSerial = pCertContext->pCertInfo->SerialNumber.pbData;
6344 DWORD nSerial = pCertContext->pCertInfo->SerialNumber.cbData;
6345 // pSerial is LE, aSerial is BE.
6346 std::vector<BYTE> aSerial(nSerial);
6347 for (size_t i = 0; i < nSerial; ++i)
6348 aSerial[i] = *(pSerial + nSerial - i - 1);
6350 // We now have all the info to count the lengths.
6351 // The layout of the payload is:
6352 // SEQUENCE: SigningCertificateV2
6353 // SEQUENCE: SEQUENCE OF ESSCertIDv2
6354 // SEQUENCE: ESSCertIDv2
6355 // SEQUENCE: AlgorithmIdentifier
6356 // OBJECT: algorithm
6357 // NULL: parameters
6358 // OCTET STRING: certHash
6359 // SEQUENCE: IssuerSerial
6360 // SEQUENCE: GeneralNames
6361 // cont [ 4 ]: Name
6362 // SEQUENCE: Issuer blob
6363 // INTEGER: CertificateSerialNumber
6365 size_t nAlgorithm = 1 + GetDERLengthOfLength(aSHA256.size()) + aSHA256.size();
6366 size_t nParameters = 1 + GetDERLengthOfLength(1);
6367 size_t nAlgorithmIdentifier = 1 + GetDERLengthOfLength(nAlgorithm + nParameters) + nAlgorithm + nParameters;
6368 size_t nCertHash = 1 + GetDERLengthOfLength(aHash.size()) + aHash.size();
6369 size_t nName = 1 + GetDERLengthOfLength(nIssuer) + nIssuer;
6370 size_t nGeneralNames = 1 + GetDERLengthOfLength(nName) + nName;
6371 size_t nCertificateSerialNumber = 1 + GetDERLengthOfLength(nSerial) + nSerial;
6372 size_t nIssuerSerial = 1 + GetDERLengthOfLength(nGeneralNames + nCertificateSerialNumber) + nGeneralNames + nCertificateSerialNumber;
6373 size_t nESSCertIDv2 = 1 + GetDERLengthOfLength(nAlgorithmIdentifier + nCertHash + nIssuerSerial) + nAlgorithmIdentifier + nCertHash + nIssuerSerial;
6374 size_t nESSCertIDv2s = 1 + GetDERLengthOfLength(nESSCertIDv2) + nESSCertIDv2;
6376 // Write SigningCertificateV2.
6377 rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
6378 WriteDERLength(rEncodedCertificate, nESSCertIDv2s);
6379 // Write SEQUENCE OF ESSCertIDv2.
6380 rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
6381 WriteDERLength(rEncodedCertificate, nESSCertIDv2);
6382 // Write ESSCertIDv2.
6383 rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
6384 WriteDERLength(rEncodedCertificate, nAlgorithmIdentifier + nCertHash + nIssuerSerial);
6385 // Write AlgorithmIdentifier.
6386 rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
6387 WriteDERLength(rEncodedCertificate, nAlgorithm + nParameters);
6388 // Write algorithm.
6389 rEncodedCertificate.WriteUInt8(nASN1_OBJECT_IDENTIFIER);
6390 WriteDERLength(rEncodedCertificate, aSHA256.size());
6391 rEncodedCertificate.WriteBytes(aSHA256.data(), aSHA256.size());
6392 // Write parameters.
6393 rEncodedCertificate.WriteUInt8(nASN1_NULL);
6394 rEncodedCertificate.WriteUInt8(0);
6395 // Write certHash.
6396 rEncodedCertificate.WriteUInt8(nASN1_OCTET_STRING);
6397 WriteDERLength(rEncodedCertificate, aHash.size());
6398 rEncodedCertificate.WriteBytes(aHash.data(), aHash.size());
6399 // Write IssuerSerial.
6400 rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
6401 WriteDERLength(rEncodedCertificate, nGeneralNames + nCertificateSerialNumber);
6402 // Write GeneralNames.
6403 rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
6404 WriteDERLength(rEncodedCertificate, nName);
6405 // Write Name.
6406 rEncodedCertificate.WriteUInt8(nASN1_TAGGED_CONSTRUCTED | 4);
6407 WriteDERLength(rEncodedCertificate, nIssuer);
6408 rEncodedCertificate.WriteBytes(pIssuer, nIssuer);
6409 // Write CertificateSerialNumber.
6410 rEncodedCertificate.WriteUInt8(nASN1_INTEGER);
6411 WriteDERLength(rEncodedCertificate, nSerial);
6412 rEncodedCertificate.WriteBytes(aSerial.data(), aSerial.size());
6414 return true;
6416 } // anonymous namespace
6418 #endif
6420 bool PDFWriter::Sign(PDFSignContext& rContext)
6422 #ifndef _WIN32
6424 CERTCertificate *cert = CERT_DecodeCertFromPackage(reinterpret_cast<char *>(rContext.m_pDerEncoded), rContext.m_nDerEncoded);
6426 if (!cert)
6428 SAL_WARN("vcl.pdfwriter", "CERT_DecodeCertFromPackage failed");
6429 return false;
6432 HashContextScope hc(HASH_Create(HASH_AlgSHA256));
6433 if (!hc.get())
6435 SAL_WARN("vcl.pdfwriter", "HASH_Create failed");
6436 return false;
6439 HASH_Begin(hc.get());
6441 HASH_Update(hc.get(), static_cast<const unsigned char*>(rContext.m_pByteRange1), rContext.m_nByteRange1);
6443 HASH_Update(hc.get(), static_cast<const unsigned char*>(rContext.m_pByteRange2), rContext.m_nByteRange2);
6445 SECItem digest;
6446 unsigned char hash[SHA256_LENGTH];
6447 digest.data = hash;
6448 HASH_End(hc.get(), digest.data, &digest.len, SHA256_LENGTH);
6449 hc.clear();
6451 #ifdef DBG_UTIL
6453 FILE *out = fopen("PDFWRITER.hash.data", "wb");
6454 fwrite(hash, SHA256_LENGTH, 1, out);
6455 fclose(out);
6457 #endif
6459 PRTime now = PR_Now();
6460 NSSCMSSignedData *cms_sd;
6461 NSSCMSSignerInfo *cms_signer;
6462 NSSCMSMessage *cms_msg = CreateCMSMessage(nullptr, &cms_sd, &cms_signer, cert, &digest);
6463 if (!cms_msg)
6464 return false;
6466 char *pass(strdup(OUStringToOString( rContext.m_aSignPassword, RTL_TEXTENCODING_UTF8 ).getStr()));
6468 TimeStampReq src;
6469 OStringBuffer response_buffer;
6470 TimeStampResp response;
6471 SECItem response_item;
6472 NSSCMSAttribute timestamp;
6473 SECItem values[2];
6474 SECItem *valuesp[2];
6475 valuesp[0] = values;
6476 valuesp[1] = nullptr;
6477 SECOidData typetag;
6479 if( !rContext.m_aSignTSA.isEmpty() )
6481 // Create another CMS message with the same contents as cms_msg, because it doesn't seem
6482 // possible to encode a message twice (once to get something to timestamp, and then after
6483 // adding the timestamp attribute).
6485 NSSCMSSignedData *ts_cms_sd;
6486 NSSCMSSignerInfo *ts_cms_signer;
6487 NSSCMSMessage *ts_cms_msg = CreateCMSMessage(&now, &ts_cms_sd, &ts_cms_signer, cert, &digest);
6488 if (!ts_cms_msg)
6490 free(pass);
6491 return false;
6494 SECItem ts_cms_output;
6495 ts_cms_output.data = nullptr;
6496 ts_cms_output.len = 0;
6497 PLArenaPool *ts_arena = PORT_NewArena(10000);
6498 NSSCMSEncoderContext *ts_cms_ecx;
6499 ts_cms_ecx = NSS_CMSEncoder_Start(ts_cms_msg, nullptr, nullptr, &ts_cms_output, ts_arena, PDFSigningPKCS7PasswordCallback, pass, nullptr, nullptr, nullptr, nullptr);
6501 if (NSS_CMSEncoder_Finish(ts_cms_ecx) != SECSuccess)
6503 SAL_WARN("vcl.pdfwriter", "NSS_CMSEncoder_Finish failed");
6504 free(pass);
6505 return false;
6508 // I have compared the ts_cms_output produced here with the cms_output produced below, with
6509 // the DONTCALLADDUNAUTHATTR env var set (i.e. without actually calling
6510 // my_NSS_CMSSignerInfo_AddUnauthAttr()), and they are identical.
6512 #ifdef DBG_UTIL
6514 FILE *out = fopen("PDFWRITER.ts_cms.data", "wb");
6515 fwrite(ts_cms_output.data, ts_cms_output.len, 1, out);
6516 fclose(out);
6518 #endif
6520 HashContextScope ts_hc(HASH_Create(HASH_AlgSHA256));
6521 if (!ts_hc.get())
6523 SAL_WARN("vcl.pdfwriter", "HASH_Create failed");
6524 free(pass);
6525 return false;
6528 HASH_Begin(ts_hc.get());
6529 HASH_Update(ts_hc.get(), ts_cms_signer->encDigest.data, ts_cms_signer->encDigest.len);
6530 SECItem ts_digest;
6531 unsigned char ts_hash[SHA256_LENGTH];
6532 ts_digest.type = siBuffer;
6533 ts_digest.data = ts_hash;
6534 HASH_End(ts_hc.get(), ts_digest.data, &ts_digest.len, SHA256_LENGTH);
6535 ts_hc.clear();
6537 #ifdef DBG_UTIL
6539 FILE *out = fopen("PDFWRITER.ts_hash.data", "wb");
6540 fwrite(ts_hash, SHA256_LENGTH, 1, out);
6541 fclose(out);
6543 #endif
6545 unsigned char cOne = 1;
6546 src.version.type = siUnsignedInteger;
6547 src.version.data = &cOne;
6548 src.version.len = sizeof(cOne);
6550 src.messageImprint.hashAlgorithm.algorithm.data = nullptr;
6551 src.messageImprint.hashAlgorithm.parameters.data = nullptr;
6552 SECOID_SetAlgorithmID(nullptr, &src.messageImprint.hashAlgorithm, SEC_OID_SHA256, nullptr);
6553 src.messageImprint.hashedMessage = ts_digest;
6555 src.reqPolicy.type = siBuffer;
6556 src.reqPolicy.data = nullptr;
6557 src.reqPolicy.len = 0;
6559 unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32);
6560 src.nonce.type = siUnsignedInteger;
6561 src.nonce.data = reinterpret_cast<unsigned char*>(&nNonce);
6562 src.nonce.len = sizeof(nNonce);
6564 src.certReq.type = siUnsignedInteger;
6565 src.certReq.data = &cOne;
6566 src.certReq.len = sizeof(cOne);
6568 src.extensions = nullptr;
6570 SECItem* timestamp_request = SEC_ASN1EncodeItem(nullptr, nullptr, &src, TimeStampReq_Template);
6571 if (timestamp_request == nullptr)
6573 SAL_WARN("vcl.pdfwriter", "SEC_ASN1EncodeItem failed");
6574 free(pass);
6575 return false;
6578 if (timestamp_request->data == nullptr)
6580 SAL_WARN("vcl.pdfwriter", "SEC_ASN1EncodeItem succeeded but got NULL data");
6581 free(pass);
6582 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6583 return false;
6586 SAL_INFO("vcl.pdfwriter", "request length=" << timestamp_request->len);
6588 #ifdef DBG_UTIL
6590 FILE *out = fopen("PDFWRITER.timestampreq.data", "wb");
6591 fwrite(timestamp_request->data, timestamp_request->len, 1, out);
6592 fclose(out);
6594 #endif
6596 // Send time stamp request to TSA server, receive response
6598 CURL* curl = curl_easy_init();
6599 CURLcode rc;
6600 struct curl_slist* slist = nullptr;
6602 if (!curl)
6604 SAL_WARN("vcl.pdfwriter", "curl_easy_init failed");
6605 free(pass);
6606 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6607 return false;
6610 SAL_INFO("vcl.pdfwriter", "Setting curl to verbose: " << (curl_easy_setopt(curl, CURLOPT_VERBOSE, 1) == CURLE_OK ? "OK" : "FAIL"));
6612 if ((rc = curl_easy_setopt(curl, CURLOPT_URL, OUStringToOString(rContext.m_aSignTSA, RTL_TEXTENCODING_UTF8).getStr())) != CURLE_OK)
6614 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_URL) failed: " << curl_easy_strerror(rc));
6615 free(pass);
6616 curl_easy_cleanup(curl);
6617 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6618 return false;
6621 slist = curl_slist_append(slist, "Content-Type: application/timestamp-query");
6622 slist = curl_slist_append(slist, "Accept: application/timestamp-reply");
6624 if ((rc = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) != CURLE_OK)
6626 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " << curl_easy_strerror(rc));
6627 free(pass);
6628 curl_slist_free_all(slist);
6629 curl_easy_cleanup(curl);
6630 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6631 return false;
6634 if ((rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(timestamp_request->len))) != CURLE_OK ||
6635 (rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, timestamp_request->data)) != CURLE_OK)
6637 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_POSTFIELDSIZE or CURLOPT_POSTFIELDS) failed: " << curl_easy_strerror(rc));
6638 free(pass);
6639 curl_easy_cleanup(curl);
6640 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6641 return false;
6644 if ((rc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer)) != CURLE_OK ||
6645 (rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToBuffer)) != CURLE_OK)
6647 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_WRITEDATA or CURLOPT_WRITEFUNCTION) failed: " << curl_easy_strerror(rc));
6648 free(pass);
6649 curl_easy_cleanup(curl);
6650 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6651 return false;
6654 if ((rc = curl_easy_setopt(curl, CURLOPT_POST, 1)) != CURLE_OK)
6656 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_POST) failed: " << curl_easy_strerror(rc));
6657 free(pass);
6658 curl_easy_cleanup(curl);
6659 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6660 return false;
6663 char error_buffer[CURL_ERROR_SIZE];
6664 if ((rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer)) != CURLE_OK)
6666 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_ERRORBUFFER) failed: " << curl_easy_strerror(rc));
6667 free(pass);
6668 curl_easy_cleanup(curl);
6669 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6670 return false;
6673 // Use a ten second timeout
6674 if ((rc = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10)) != CURLE_OK ||
6675 (rc = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10)) != CURLE_OK)
6677 SAL_WARN("vcl.pdfwriter", "curl_easy_setopt(CURLOPT_TIMEOUT or CURLOPT_CONNECTTIMEOUT) failed: " << curl_easy_strerror(rc));
6678 free(pass);
6679 curl_easy_cleanup(curl);
6680 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6681 return false;
6684 if (curl_easy_perform(curl) != CURLE_OK)
6686 SAL_WARN("vcl.pdfwriter", "curl_easy_perform failed: " << error_buffer);
6687 free(pass);
6688 curl_easy_cleanup(curl);
6689 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6690 return false;
6693 SAL_INFO("vcl.pdfwriter", "PDF signing: got response, length=" << response_buffer.getLength());
6695 #ifdef DBG_UTIL
6697 FILE *out = fopen("PDFWRITER.reply.data", "wb");
6698 fwrite(response_buffer.getStr(), response_buffer.getLength(), 1, out);
6699 fclose(out);
6701 #endif
6703 curl_slist_free_all(slist);
6704 curl_easy_cleanup(curl);
6705 SECITEM_FreeItem(timestamp_request, PR_TRUE);
6707 memset(&response, 0, sizeof(response));
6709 response_item.type = siBuffer;
6710 response_item.data = reinterpret_cast<unsigned char*>(const_cast<char*>(response_buffer.getStr()));
6711 response_item.len = response_buffer.getLength();
6713 if (SEC_ASN1DecodeItem(nullptr, &response, TimeStampResp_Template, &response_item) != SECSuccess)
6715 SAL_WARN("vcl.pdfwriter", "SEC_ASN1DecodeItem failed");
6716 free(pass);
6717 return false;
6720 SAL_INFO("vcl.pdfwriter", "TimeStampResp received and decoded, status=" << PKIStatusInfoToString(response.status));
6722 if (response.status.status.len != 1 ||
6723 (response.status.status.data[0] != 0 && response.status.status.data[0] != 1))
6725 SAL_WARN("vcl.pdfwriter", "Timestamp request was not granted");
6726 free(pass);
6727 return false;
6730 // timestamp.type filled in below
6732 // Not sure if we actually need two entries in the values array, now when valuesp is an
6733 // array too, the pointer to the values array followed by a null pointer. But I don't feel
6734 // like experimenting.
6735 values[0] = response.timeStampToken;
6736 values[1].type = siBuffer;
6737 values[1].data = nullptr;
6738 values[1].len = 0;
6740 timestamp.values = valuesp;
6742 typetag.oid.data = nullptr;
6743 // id-aa-timeStampToken OBJECT IDENTIFIER ::= { iso(1)
6744 // member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9)
6745 // smime(16) aa(2) 14 }
6746 if (my_SEC_StringToOID(&typetag.oid, "1.2.840.113549.1.9.16.2.14", 0) != SECSuccess)
6748 SAL_WARN("vcl.pdfwriter", "SEC_StringToOID failed");
6749 free(pass);
6750 return false;
6752 typetag.offset = SEC_OID_UNKNOWN; // ???
6753 typetag.desc = "id-aa-timeStampToken";
6754 typetag.mechanism = CKM_SHA_1; // ???
6755 typetag.supportedExtension = UNSUPPORTED_CERT_EXTENSION; // ???
6756 timestamp.typeTag = &typetag;
6758 timestamp.type = typetag.oid; // ???
6760 timestamp.encoded = PR_TRUE; // ???
6762 #ifdef DBG_UTIL
6763 if (getenv("DONTCALLADDUNAUTHATTR"))
6765 else
6766 #endif
6767 if (my_NSS_CMSSignerInfo_AddUnauthAttr(cms_signer, &timestamp) != SECSuccess)
6769 SAL_WARN("vcl.pdfwriter", "NSS_CMSSignerInfo_AddUnauthAttr failed");
6770 free(pass);
6771 return false;
6775 // Add the signing certificate as a signed attribute.
6776 ESSCertIDv2* aCertIDs[2];
6777 ESSCertIDv2 aCertID;
6778 // Write ESSCertIDv2.hashAlgorithm.
6779 aCertID.hashAlgorithm.algorithm.data = nullptr;
6780 aCertID.hashAlgorithm.parameters.data = nullptr;
6781 SECOID_SetAlgorithmID(nullptr, &aCertID.hashAlgorithm, SEC_OID_SHA256, nullptr);
6782 // Write ESSCertIDv2.certHash.
6783 SECItem aCertHashItem;
6784 unsigned char aCertHash[SHA256_LENGTH];
6785 HashContextScope aCertHashContext(HASH_Create(HASH_AlgSHA256));
6786 if (!aCertHashContext.get())
6788 SAL_WARN("vcl.pdfwriter", "HASH_Create() failed");
6789 free(pass);
6790 return false;
6792 HASH_Begin(aCertHashContext.get());
6793 HASH_Update(aCertHashContext.get(), reinterpret_cast<const unsigned char *>(rContext.m_pDerEncoded), rContext.m_nDerEncoded);
6794 aCertHashItem.type = siBuffer;
6795 aCertHashItem.data = aCertHash;
6796 HASH_End(aCertHashContext.get(), aCertHashItem.data, &aCertHashItem.len, SHA256_LENGTH);
6797 aCertID.certHash = aCertHashItem;
6798 // Write ESSCertIDv2.issuerSerial.
6799 IssuerSerial aSerial;
6800 GeneralName aName;
6801 aName.name = cert->issuer;
6802 aSerial.issuer.names = aName;
6803 aSerial.serialNumber = cert->serialNumber;
6804 aCertID.issuerSerial = aSerial;
6805 // Write SigningCertificateV2.certs.
6806 aCertIDs[0] = &aCertID;
6807 aCertIDs[1] = nullptr;
6808 SigningCertificateV2 aCertificate;
6809 aCertificate.certs = &aCertIDs[0];
6810 SECItem* pEncodedCertificate = SEC_ASN1EncodeItem(nullptr, nullptr, &aCertificate, SigningCertificateV2Template);
6811 if (!pEncodedCertificate)
6813 SAL_WARN("vcl.pdfwriter", "SEC_ASN1EncodeItem() failed");
6814 free(pass);
6815 return false;
6818 NSSCMSAttribute aAttribute;
6819 SECItem aAttributeValues[2];
6820 SECItem* pAttributeValues[2];
6821 pAttributeValues[0] = aAttributeValues;
6822 pAttributeValues[1] = nullptr;
6823 aAttributeValues[0] = *pEncodedCertificate;
6824 aAttributeValues[1].type = siBuffer;
6825 aAttributeValues[1].data = nullptr;
6826 aAttributeValues[1].len = 0;
6827 aAttribute.values = pAttributeValues;
6829 SECOidData aOidData;
6830 aOidData.oid.data = nullptr;
6832 * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
6833 * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
6834 * smime(16) id-aa(2) 47 }
6836 if (my_SEC_StringToOID(&aOidData.oid, "1.2.840.113549.1.9.16.2.47", 0) != SECSuccess)
6838 SAL_WARN("vcl.pdfwriter", "my_SEC_StringToOID() failed");
6839 free(pass);
6840 return false;
6842 aOidData.offset = SEC_OID_UNKNOWN;
6843 aOidData.desc = "id-aa-signingCertificateV2";
6844 aOidData.mechanism = CKM_SHA_1;
6845 aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION;
6846 aAttribute.typeTag = &aOidData;
6847 aAttribute.type = aOidData.oid;
6848 aAttribute.encoded = PR_TRUE;
6850 if (my_NSS_CMSSignerInfo_AddAuthAttr(cms_signer, &aAttribute) != SECSuccess)
6852 SAL_WARN("vcl.pdfwriter", "my_NSS_CMSSignerInfo_AddAuthAttr() failed");
6853 free(pass);
6854 return false;
6857 SECItem cms_output;
6858 cms_output.data = nullptr;
6859 cms_output.len = 0;
6860 PLArenaPool *arena = PORT_NewArena(10000);
6861 NSSCMSEncoderContext *cms_ecx;
6863 // Possibly it would work to even just pass NULL for the password callback function and its
6864 // argument here. After all, at least with the hardware token and associated software I tested
6865 // with, the software itself pops up a dialog asking for the PIN (password). But I am not going
6866 // to test it and risk locking up my token...
6868 cms_ecx = NSS_CMSEncoder_Start(cms_msg, nullptr, nullptr, &cms_output, arena, PDFSigningPKCS7PasswordCallback, pass, nullptr, nullptr, nullptr, nullptr);
6870 if (!cms_ecx)
6872 SAL_WARN("vcl.pdfwriter", "NSS_CMSEncoder_Start failed");
6873 free(pass);
6874 return false;
6877 if (NSS_CMSEncoder_Finish(cms_ecx) != SECSuccess)
6879 SAL_WARN("vcl.pdfwriter", "NSS_CMSEncoder_Finish failed");
6880 free(pass);
6881 return false;
6884 free(pass);
6886 #ifdef DBG_UTIL
6888 FILE *out = fopen("PDFWRITER.cms.data", "wb");
6889 fwrite(cms_output.data, cms_output.len, 1, out);
6890 fclose(out);
6892 #endif
6894 if (cms_output.len*2 > MAX_SIGNATURE_CONTENT_LENGTH)
6896 SAL_WARN("vcl.pdfwriter", "Signature requires more space (" << cms_output.len*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")");
6897 NSS_CMSMessage_Destroy(cms_msg);
6898 return false;
6901 for (unsigned int i = 0; i < cms_output.len ; i++)
6902 appendHex(cms_output.data[i], rContext.m_rCMSHexBuffer);
6904 SECITEM_FreeItem(pEncodedCertificate, PR_TRUE);
6905 NSS_CMSMessage_Destroy(cms_msg);
6907 return true;
6909 #else // _WIN32
6910 PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, reinterpret_cast<const BYTE*>(rContext.m_pDerEncoded), rContext.m_nDerEncoded);
6911 if (pCertContext == nullptr)
6913 SAL_WARN("vcl.pdfwriter", "CertCreateCertificateContext failed: " << WindowsErrorString(GetLastError()));
6914 return false;
6917 CRYPT_SIGN_MESSAGE_PARA aPara;
6919 memset(&aPara, 0, sizeof(aPara));
6920 aPara.cbSize = sizeof(aPara);
6921 aPara.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
6922 aPara.pSigningCert = pCertContext;
6923 aPara.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256);
6924 aPara.HashAlgorithm.Parameters.cbData = 0;
6925 aPara.cMsgCert = 1;
6926 aPara.rgpMsgCert = &pCertContext;
6928 HCRYPTPROV hCryptProv;
6929 DWORD nKeySpec;
6930 BOOL bFreeNeeded;
6932 if (!CryptAcquireCertificatePrivateKey(pCertContext,
6933 CRYPT_ACQUIRE_CACHE_FLAG,
6934 nullptr,
6935 &hCryptProv,
6936 &nKeySpec,
6937 &bFreeNeeded))
6939 SAL_WARN("vcl.pdfwriter", "CryptAcquireCertificatePrivateKey failed: " << WindowsErrorString(GetLastError()));
6940 CertFreeCertificateContext(pCertContext);
6941 return false;
6943 assert(!bFreeNeeded);
6945 CMSG_SIGNER_ENCODE_INFO aSignerInfo;
6947 memset(&aSignerInfo, 0, sizeof(aSignerInfo));
6948 aSignerInfo.cbSize = sizeof(aSignerInfo);
6949 aSignerInfo.pCertInfo = pCertContext->pCertInfo;
6950 aSignerInfo.hCryptProv = hCryptProv;
6951 aSignerInfo.dwKeySpec = nKeySpec;
6952 aSignerInfo.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256);
6953 aSignerInfo.HashAlgorithm.Parameters.cbData = 0;
6955 // Add the signing certificate as a signed attribute.
6956 CRYPT_INTEGER_BLOB aCertificateBlob;
6957 SvMemoryStream aEncodedCertificate;
6958 if (!CreateSigningCertificateAttribute(rContext, pCertContext, aEncodedCertificate))
6960 SAL_WARN("vcl.pdfwriter", "CreateSigningCertificateAttribute() failed");
6961 return false;
6963 aCertificateBlob.pbData = const_cast<BYTE*>(static_cast<const BYTE*>(aEncodedCertificate.GetData()));
6964 aCertificateBlob.cbData = aEncodedCertificate.GetSize();
6965 CRYPT_ATTRIBUTE aCertificateAttribute;
6967 * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
6968 * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
6969 * smime(16) id-aa(2) 47 }
6971 aCertificateAttribute.pszObjId = const_cast<LPSTR>("1.2.840.113549.1.9.16.2.47");
6972 aCertificateAttribute.cValue = 1;
6973 aCertificateAttribute.rgValue = &aCertificateBlob;
6974 aSignerInfo.cAuthAttr = 1;
6975 aSignerInfo.rgAuthAttr = &aCertificateAttribute;
6977 CMSG_SIGNED_ENCODE_INFO aSignedInfo;
6978 memset(&aSignedInfo, 0, sizeof(aSignedInfo));
6979 aSignedInfo.cbSize = sizeof(aSignedInfo);
6980 aSignedInfo.cSigners = 1;
6981 aSignedInfo.rgSigners = &aSignerInfo;
6983 CERT_BLOB aCertBlob;
6985 aCertBlob.cbData = pCertContext->cbCertEncoded;
6986 aCertBlob.pbData = pCertContext->pbCertEncoded;
6988 aSignedInfo.cCertEncoded = 1;
6989 aSignedInfo.rgCertEncoded = &aCertBlob;
6991 HCRYPTMSG hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
6992 CMSG_DETACHED_FLAG,
6993 CMSG_SIGNED,
6994 &aSignedInfo,
6995 nullptr,
6996 nullptr);
6997 if (!hMsg)
6999 SAL_WARN("vcl.pdfwriter", "CryptMsgOpenToEncode failed: " << WindowsErrorString(GetLastError()));
7000 CertFreeCertificateContext(pCertContext);
7001 return false;
7004 if (!CryptMsgUpdate(hMsg, static_cast<const BYTE *>(rContext.m_pByteRange1), rContext.m_nByteRange1, FALSE) ||
7005 !CryptMsgUpdate(hMsg, static_cast<const BYTE *>(rContext.m_pByteRange2), rContext.m_nByteRange2, TRUE))
7007 SAL_WARN("vcl.pdfwriter", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
7008 CryptMsgClose(hMsg);
7009 CertFreeCertificateContext(pCertContext);
7010 return false;
7013 PCRYPT_TIMESTAMP_CONTEXT pTsContext = nullptr;
7015 if( !rContext.m_aSignTSA.isEmpty() )
7017 PointerTo_CryptRetrieveTimeStamp crts = reinterpret_cast<PointerTo_CryptRetrieveTimeStamp>(GetProcAddress(LoadLibrary("crypt32.dll"), "CryptRetrieveTimeStamp"));
7018 if (!crts)
7020 SAL_WARN("vcl.pdfwriter", "Could not find the CryptRetrieveTimeStamp function in crypt32.dll: " << WindowsErrorString(GetLastError()));
7021 CryptMsgClose(hMsg);
7022 CertFreeCertificateContext(pCertContext);
7023 return false;
7026 HCRYPTMSG hDecodedMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
7027 CMSG_DETACHED_FLAG,
7028 CMSG_SIGNED,
7029 NULL,
7030 nullptr,
7031 nullptr);
7032 if (!hDecodedMsg)
7034 SAL_WARN("vcl.pdfwriter", "CryptMsgOpenToDecode failed: " << WindowsErrorString(GetLastError()));
7035 CryptMsgClose(hMsg);
7036 CertFreeCertificateContext(pCertContext);
7037 return false;
7040 DWORD nTsSigLen = 0;
7042 if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, nullptr, &nTsSigLen))
7044 SAL_WARN("vcl.pdfwriter", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
7045 CryptMsgClose(hDecodedMsg);
7046 CryptMsgClose(hMsg);
7047 CertFreeCertificateContext(pCertContext);
7048 return false;
7051 SAL_INFO("vcl.pdfwriter", "nTsSigLen=" << nTsSigLen);
7053 std::unique_ptr<BYTE[]> pTsSig(new BYTE[nTsSigLen]);
7055 if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, pTsSig.get(), &nTsSigLen))
7057 SAL_WARN("vcl.pdfwriter", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
7058 CryptMsgClose(hDecodedMsg);
7059 CryptMsgClose(hMsg);
7060 CertFreeCertificateContext(pCertContext);
7061 return false;
7064 if (!CryptMsgUpdate(hDecodedMsg, pTsSig.get(), nTsSigLen, TRUE))
7066 SAL_WARN("vcl.pdfwriter", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
7067 CryptMsgClose(hDecodedMsg);
7068 CryptMsgClose(hMsg);
7069 CertFreeCertificateContext(pCertContext);
7070 return false;
7073 DWORD nDecodedSignerInfoLen = 0;
7074 if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr, &nDecodedSignerInfoLen))
7076 SAL_WARN("vcl.pdfwriter", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << WindowsErrorString(GetLastError()));
7077 CryptMsgClose(hDecodedMsg);
7078 CryptMsgClose(hMsg);
7079 CertFreeCertificateContext(pCertContext);
7080 return false;
7083 std::unique_ptr<BYTE[]> pDecodedSignerInfoBuf(new BYTE[nDecodedSignerInfoLen]);
7085 if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, pDecodedSignerInfoBuf.get(), &nDecodedSignerInfoLen))
7087 SAL_WARN("vcl.pdfwriter", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << WindowsErrorString(GetLastError()));
7088 CryptMsgClose(hDecodedMsg);
7089 CryptMsgClose(hMsg);
7090 CertFreeCertificateContext(pCertContext);
7091 return false;
7094 CMSG_SIGNER_INFO *pDecodedSignerInfo = reinterpret_cast<CMSG_SIGNER_INFO *>(pDecodedSignerInfoBuf.get());
7096 CRYPT_TIMESTAMP_PARA aTsPara;
7097 unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32);
7099 aTsPara.pszTSAPolicyId = nullptr;
7100 aTsPara.fRequestCerts = TRUE;
7101 aTsPara.Nonce.cbData = sizeof(nNonce);
7102 aTsPara.Nonce.pbData = reinterpret_cast<BYTE *>(&nNonce);
7103 aTsPara.cExtension = 0;
7104 aTsPara.rgExtension = nullptr;
7106 if (!(*crts)(SAL_W(rContext.m_aSignTSA.getStr()),
7108 10000,
7109 szOID_NIST_sha256,
7110 &aTsPara,
7111 pDecodedSignerInfo->EncryptedHash.pbData,
7112 pDecodedSignerInfo->EncryptedHash.cbData,
7113 &pTsContext,
7114 nullptr,
7115 nullptr))
7117 SAL_WARN("vcl.pdfwriter", "CryptRetrieveTimeStamp failed: " << WindowsErrorString(GetLastError()));
7118 CryptMsgClose(hDecodedMsg);
7119 CryptMsgClose(hMsg);
7120 CertFreeCertificateContext(pCertContext);
7121 return false;
7124 SAL_INFO("vcl.pdfwriter", "Time stamp size is " << pTsContext->cbEncoded << " bytes");
7126 #ifdef DBG_UTIL
7128 FILE *out = fopen("PDFWRITER.tstoken.data", "wb");
7129 fwrite(pTsContext->pbEncoded, pTsContext->cbEncoded, 1, out);
7130 fclose(out);
7132 #endif
7134 // I tried to use CryptMsgControl() with CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR to add the
7135 // timestamp, but that failed with "The parameter is incorrect". Probably it is too late to
7136 // modify the message once its data has already been encoded as part of the
7137 // CryptMsgGetParam() with CMSG_BARE_CONTENT_PARAM above. So close the message and re-do its
7138 // creation steps, but now with an amended aSignerInfo.
7140 CRYPT_INTEGER_BLOB aTimestampBlob;
7141 aTimestampBlob.cbData = pTsContext->cbEncoded;
7142 aTimestampBlob.pbData = pTsContext->pbEncoded;
7144 CRYPT_ATTRIBUTE aTimestampAttribute;
7145 aTimestampAttribute.pszObjId = const_cast<LPSTR>(
7146 "1.2.840.113549.1.9.16.2.14");
7147 aTimestampAttribute.cValue = 1;
7148 aTimestampAttribute.rgValue = &aTimestampBlob;
7150 aSignerInfo.cUnauthAttr = 1;
7151 aSignerInfo.rgUnauthAttr = &aTimestampAttribute;
7153 CryptMsgClose(hMsg);
7155 hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
7156 CMSG_DETACHED_FLAG,
7157 CMSG_SIGNED,
7158 &aSignedInfo,
7159 nullptr,
7160 nullptr);
7161 if (!hMsg ||
7162 !CryptMsgUpdate(hMsg, static_cast<const BYTE *>(rContext.m_pByteRange1), rContext.m_nByteRange1, FALSE) ||
7163 !CryptMsgUpdate(hMsg, static_cast<const BYTE *>(rContext.m_pByteRange1), rContext.m_nByteRange2, TRUE))
7165 SAL_WARN("vcl.pdfwriter", "Re-creating the message failed: " << WindowsErrorString(GetLastError()));
7166 CryptMemFree(pTsContext);
7167 CryptMsgClose(hDecodedMsg);
7168 CryptMsgClose(hMsg);
7169 CertFreeCertificateContext(pCertContext);
7170 return false;
7173 CryptMsgClose(hDecodedMsg);
7176 DWORD nSigLen = 0;
7178 if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nSigLen))
7180 SAL_WARN("vcl.pdfwriter", "CryptMsgGetParam(CMSG_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
7181 if (pTsContext)
7182 CryptMemFree(pTsContext);
7183 CryptMsgClose(hMsg);
7184 CertFreeCertificateContext(pCertContext);
7185 return false;
7188 if (nSigLen*2 > MAX_SIGNATURE_CONTENT_LENGTH)
7190 SAL_WARN("vcl.pdfwriter", "Signature requires more space (" << nSigLen*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")");
7191 if (pTsContext)
7192 CryptMemFree(pTsContext);
7193 CryptMsgClose(hMsg);
7194 CertFreeCertificateContext(pCertContext);
7195 return false;
7198 SAL_INFO("vcl.pdfwriter", "Signature size is " << nSigLen << " bytes");
7199 std::unique_ptr<BYTE[]> pSig(new BYTE[nSigLen]);
7201 if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, pSig.get(), &nSigLen))
7203 SAL_WARN("vcl.pdfwriter", "CryptMsgGetParam(CMSG_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
7204 if (pTsContext)
7205 CryptMemFree(pTsContext);
7206 CryptMsgClose(hMsg);
7207 CertFreeCertificateContext(pCertContext);
7208 return false;
7211 #ifdef DBG_UTIL
7213 FILE *out = fopen("PDFWRITER.signature.data", "wb");
7214 fwrite(pSig.get(), nSigLen, 1, out);
7215 fclose(out);
7217 #endif
7219 // Release resources
7220 if (pTsContext)
7221 CryptMemFree(pTsContext);
7222 CryptMsgClose(hMsg);
7223 CertFreeCertificateContext(pCertContext);
7225 for (unsigned int i = 0; i < nSigLen ; i++)
7226 appendHex(pSig[i], rContext.m_rCMSHexBuffer);
7228 return true;
7229 #endif
7232 bool PDFWriterImpl::finalizeSignature()
7235 if (!m_aContext.SignCertificate.is())
7236 return false;
7238 // 1- calculate last ByteRange value
7239 sal_uInt64 nOffset = ~0U;
7240 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
7242 sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
7244 // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
7245 sal_uInt64 nWritten = 0;
7246 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
7247 OStringBuffer aByteRangeNo( 256 );
7248 aByteRangeNo.append( nLastByteRangeNo );
7249 aByteRangeNo.append( " ]" );
7251 if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
7253 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
7254 return false;
7257 // 3- create the PKCS#7 object using NSS
7258 css::uno::Sequence< sal_Int8 > derEncoded = m_aContext.SignCertificate->getEncoded();
7260 if (!derEncoded.hasElements())
7261 return false;
7263 sal_Int8* n_derArray = derEncoded.getArray();
7264 sal_Int32 n_derLength = derEncoded.getLength();
7266 #ifndef _WIN32
7268 // Prepare buffer and calculate PDF file digest
7269 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
7271 std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
7272 sal_uInt64 bytesRead1;
7274 //FIXME: Check if hash is calculated from the correct byterange
7275 CHECK_RETURN( (osl::File::E_None == m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1)) );
7276 if (bytesRead1 != (sal_uInt64)m_nSignatureContentOffset - 1)
7277 SAL_WARN("vcl.pdfwriter", "First buffer read failed");
7279 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1)) );
7280 std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
7281 sal_uInt64 bytesRead2;
7282 CHECK_RETURN( (osl::File::E_None == m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2)) );
7283 if (bytesRead2 != (sal_uInt64) nLastByteRangeNo)
7284 SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
7286 OStringBuffer cms_hexbuffer;
7287 PDFWriter::PDFSignContext aSignContext(cms_hexbuffer);
7288 aSignContext.m_pDerEncoded = n_derArray;
7289 aSignContext.m_nDerEncoded = n_derLength;
7290 aSignContext.m_pByteRange1 = buffer1.get();
7291 aSignContext.m_nByteRange1 = bytesRead1;
7292 aSignContext.m_pByteRange2 = buffer2.get();
7293 aSignContext.m_nByteRange2 = bytesRead2;
7294 aSignContext.m_aSignTSA = m_aContext.SignTSA;
7295 aSignContext.m_aSignPassword = m_aContext.SignPassword;
7296 if (!PDFWriter::Sign(aSignContext))
7298 SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
7299 return false;
7302 assert(cms_hexbuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
7304 // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
7305 nWritten = 0;
7306 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
7307 m_aFile.write(cms_hexbuffer.getStr(), cms_hexbuffer.getLength(), nWritten);
7309 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
7310 return true;
7312 #else
7314 // Prepare buffer and calculate PDF file digest
7315 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
7317 std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset - 1]);
7318 sal_uInt64 bytesRead1;
7320 if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
7321 bytesRead1 != (sal_uInt64)m_nSignatureContentOffset - 1)
7323 SAL_WARN("vcl.pdfwriter", "First buffer read failed");
7324 return false;
7327 std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo]);
7328 sal_uInt64 bytesRead2;
7330 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
7331 osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
7332 bytesRead2 != (sal_uInt64) nLastByteRangeNo)
7334 SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
7335 return false;
7338 OStringBuffer cms_hexbuffer;
7339 PDFWriter::PDFSignContext aSignContext(cms_hexbuffer);
7340 aSignContext.m_pDerEncoded = n_derArray;
7341 aSignContext.m_nDerEncoded = n_derLength;
7342 aSignContext.m_pByteRange1 = buffer1.get();
7343 aSignContext.m_nByteRange1 = bytesRead1;
7344 aSignContext.m_pByteRange2 = buffer2.get();
7345 aSignContext.m_nByteRange2 = bytesRead2;
7346 aSignContext.m_aSignTSA = m_aContext.SignTSA;
7347 aSignContext.m_aSignPassword = m_aContext.SignPassword;
7348 if (!PDFWriter::Sign(aSignContext))
7350 SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
7351 return false;
7354 assert(cms_hexbuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
7356 // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
7357 nWritten = 0;
7358 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
7359 m_aFile.write(cms_hexbuffer.getStr(), cms_hexbuffer.getLength(), nWritten);
7361 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
7363 return true;
7364 #endif
7367 #else // !HAVE_FEATURE_NSS
7368 bool PDFWriter::Sign(PDFSignContext& /*rContext*/)
7370 // Not implemented.
7371 return false;
7373 #endif
7375 sal_Int32 PDFWriterImpl::emitInfoDict( )
7377 sal_Int32 nObject = createObject();
7379 if( updateObject( nObject ) )
7381 OStringBuffer aLine( 1024 );
7382 aLine.append( nObject );
7383 aLine.append( " 0 obj\n"
7384 "<<" );
7385 if( !m_aContext.DocumentInfo.Title.isEmpty() )
7387 aLine.append( "/Title" );
7388 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
7389 aLine.append( "\n" );
7391 if( !m_aContext.DocumentInfo.Author.isEmpty() )
7393 aLine.append( "/Author" );
7394 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
7395 aLine.append( "\n" );
7397 if( !m_aContext.DocumentInfo.Subject.isEmpty() )
7399 aLine.append( "/Subject" );
7400 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
7401 aLine.append( "\n" );
7403 if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
7405 aLine.append( "/Keywords" );
7406 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
7407 aLine.append( "\n" );
7409 if( !m_aContext.DocumentInfo.Creator.isEmpty() )
7411 aLine.append( "/Creator" );
7412 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
7413 aLine.append( "\n" );
7415 if( !m_aContext.DocumentInfo.Producer.isEmpty() )
7417 aLine.append( "/Producer" );
7418 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
7419 aLine.append( "\n" );
7422 aLine.append( "/CreationDate" );
7423 appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
7424 aLine.append( ">>\nendobj\n\n" );
7425 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
7426 nObject = 0;
7428 else
7429 nObject = 0;
7431 return nObject;
7434 // Part of this function may be shared with method appendDest.
7435 sal_Int32 PDFWriterImpl::emitNamedDestinations()
7437 sal_Int32 nCount = m_aNamedDests.size();
7438 if( nCount <= 0 )
7439 return 0;//define internal error
7441 //get the object number for all the destinations
7442 sal_Int32 nObject = createObject();
7444 if( updateObject( nObject ) )
7446 //emit the dictionary
7447 OStringBuffer aLine( 1024 );
7448 aLine.append( nObject );
7449 aLine.append( " 0 obj\n"
7450 "<<" );
7452 sal_Int32 nDestID;
7453 for( nDestID = 0; nDestID < nCount; nDestID++ )
7455 const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
7456 // In order to correctly function both under an Internet browser and
7457 // directly with a reader (provided the reader has the feature) we
7458 // need to set the name of the destination the same way it will be encoded
7459 // in an Internet link
7460 INetURLObject aLocalURL( "http://ahost.ax" ); //dummy location, won't be used
7461 aLocalURL.SetMark( rDest.m_aDestName );
7463 const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
7464 // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
7465 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
7467 aLine.append( '/' );
7468 appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
7469 aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
7470 //maps the preceding character properly
7471 aLine.append( rDestPage.m_nPageObject );
7472 aLine.append( " 0 R" );
7474 switch( rDest.m_eType )
7476 case PDFWriter::DestAreaType::XYZ:
7477 default:
7478 aLine.append( "/XYZ " );
7479 appendFixedInt( rDest.m_aRect.Left(), aLine );
7480 aLine.append( ' ' );
7481 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
7482 aLine.append( " 0" );
7483 break;
7484 case PDFWriter::DestAreaType::FitRectangle:
7485 aLine.append( "/FitR " );
7486 appendFixedInt( rDest.m_aRect.Left(), aLine );
7487 aLine.append( ' ' );
7488 appendFixedInt( rDest.m_aRect.Top(), aLine );
7489 aLine.append( ' ' );
7490 appendFixedInt( rDest.m_aRect.Right(), aLine );
7491 aLine.append( ' ' );
7492 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
7493 break;
7495 aLine.append( "]\n" );
7498 //close
7499 aLine.append( ">>\nendobj\n\n" );
7500 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
7501 nObject = 0;
7503 else
7504 nObject = 0;
7506 return nObject;
7509 // emits the output intent dictionary
7510 sal_Int32 PDFWriterImpl::emitOutputIntent()
7512 if( !m_bIsPDF_A1 )
7513 return 0;
7515 //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
7517 OStringBuffer aLine( 1024 );
7518 sal_Int32 nICCObject = createObject();
7519 sal_Int32 nStreamLengthObject = createObject();
7521 aLine.append( nICCObject );
7522 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
7523 aLine.append( " 0 obj\n<</N 3/Length " );
7524 aLine.append( nStreamLengthObject );
7525 aLine.append( " 0 R" );
7526 if (!g_bDebugDisableCompression)
7527 aLine.append( "/Filter/FlateDecode" );
7528 aLine.append( ">>\nstream\n" );
7529 if ( !updateObject( nICCObject ) ) return 0;
7530 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
7531 //get file position
7532 sal_uInt64 nBeginStreamPos = 0;
7533 m_aFile.getPos(nBeginStreamPos);
7534 beginCompression();
7535 checkAndEnableStreamEncryption( nICCObject );
7536 cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
7537 //force ICC profile version 2.1
7538 cmsSetProfileVersion(hProfile, 2.1);
7539 cmsUInt32Number nBytesNeeded = 0;
7540 cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
7541 if (!nBytesNeeded)
7542 return 0;
7543 std::vector<unsigned char> aBuffer(nBytesNeeded);
7544 cmsSaveProfileToMem(hProfile, &aBuffer[0], &nBytesNeeded);
7545 cmsCloseProfile(hProfile);
7546 bool written = writeBuffer( &aBuffer[0], (sal_Int32) aBuffer.size() );
7547 disableStreamEncryption();
7548 endCompression();
7549 sal_uInt64 nEndStreamPos = 0;
7550 m_aFile.getPos(nEndStreamPos);
7552 if( !written )
7553 return 0;
7554 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
7555 return 0 ;
7556 aLine.setLength( 0 );
7558 //emit the stream length object
7559 if ( !updateObject( nStreamLengthObject ) ) return 0;
7560 aLine.setLength( 0 );
7561 aLine.append( nStreamLengthObject );
7562 aLine.append( " 0 obj\n" );
7563 aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos) );
7564 aLine.append( "\nendobj\n\n" );
7565 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
7566 aLine.setLength( 0 );
7568 //emit the OutputIntent dictionary
7569 sal_Int32 nOIObject = createObject();
7570 if ( !updateObject( nOIObject ) ) return 0;
7571 aLine.append( nOIObject );
7572 aLine.append( " 0 obj\n"
7573 "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
7575 OUString aComment( "sRGB IEC61966-2.1" );
7576 appendLiteralStringEncrypt( aComment ,nOIObject, aLine );
7577 aLine.append("/DestOutputProfile ");
7578 aLine.append( nICCObject );
7579 aLine.append( " 0 R>>\nendobj\n\n" );
7580 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
7582 return nOIObject;
7585 // formats the string for the XML stream
7586 static void escapeStringXML( const OUString& rStr, OUString &rValue)
7588 const sal_Unicode* pUni = rStr.getStr();
7589 int nLen = rStr.getLength();
7590 for( ; nLen; nLen--, pUni++ )
7592 switch( *pUni )
7594 case u'&':
7595 rValue += "&amp;";
7596 break;
7597 case u'<':
7598 rValue += "&lt;";
7599 break;
7600 case u'>':
7601 rValue += "&gt;";
7602 break;
7603 case u'\'':
7604 rValue += "&apos;";
7605 break;
7606 case u'"':
7607 rValue += "&quot;";
7608 break;
7609 default:
7610 rValue += OUStringLiteral1( *pUni );
7611 break;
7616 // emits the document metadata
7617 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
7619 if( !m_bIsPDF_A1 )
7620 return 0;
7622 //get the object number for all the destinations
7623 sal_Int32 nObject = createObject();
7625 if( updateObject( nObject ) )
7627 // the following string are written in UTF-8 unicode
7628 OStringBuffer aMetadataStream( 8192 );
7630 aMetadataStream.append( "<?xpacket begin=\"" );
7631 // these lines write Unicode "zero width non-breaking space character" (U+FEFF)
7632 // (aka byte-order mark ) used as a byte-order marker.
7633 aMetadataStream.append( OUStringToOString( OUString( u'\xFEFF' ), RTL_TEXTENCODING_UTF8 ) );
7634 aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" );
7635 aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" );
7636 aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" );
7637 //PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
7638 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
7639 aMetadataStream.append( " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" );
7640 aMetadataStream.append( " <pdfaid:part>1</pdfaid:part>\n" );
7641 aMetadataStream.append( " <pdfaid:conformance>A</pdfaid:conformance>\n" );
7642 aMetadataStream.append( " </rdf:Description>\n" );
7643 //... Dublin Core properties go here
7644 if( !m_aContext.DocumentInfo.Title.isEmpty() ||
7645 !m_aContext.DocumentInfo.Author.isEmpty() ||
7646 !m_aContext.DocumentInfo.Subject.isEmpty() )
7648 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
7649 aMetadataStream.append( " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" );
7650 if( !m_aContext.DocumentInfo.Title.isEmpty() )
7652 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
7653 aMetadataStream.append( " <dc:title>\n" );
7654 aMetadataStream.append( " <rdf:Alt>\n" );
7655 aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" );
7656 OUString aTitle;
7657 escapeStringXML( m_aContext.DocumentInfo.Title, aTitle );
7658 aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 ) );
7659 aMetadataStream.append( "</rdf:li>\n" );
7660 aMetadataStream.append( " </rdf:Alt>\n" );
7661 aMetadataStream.append( " </dc:title>\n" );
7663 if( !m_aContext.DocumentInfo.Author.isEmpty() )
7665 aMetadataStream.append( " <dc:creator>\n" );
7666 aMetadataStream.append( " <rdf:Seq>\n" );
7667 aMetadataStream.append( " <rdf:li>" );
7668 OUString aAuthor;
7669 escapeStringXML( m_aContext.DocumentInfo.Author, aAuthor );
7670 aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 ) );
7671 aMetadataStream.append( "</rdf:li>\n" );
7672 aMetadataStream.append( " </rdf:Seq>\n" );
7673 aMetadataStream.append( " </dc:creator>\n" );
7675 if( !m_aContext.DocumentInfo.Subject.isEmpty() )
7677 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
7678 aMetadataStream.append( " <dc:description>\n" );
7679 aMetadataStream.append( " <rdf:Alt>\n" );
7680 aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" );
7681 OUString aSubject;
7682 escapeStringXML( m_aContext.DocumentInfo.Subject, aSubject );
7683 aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 ) );
7684 aMetadataStream.append( "</rdf:li>\n" );
7685 aMetadataStream.append( " </rdf:Alt>\n" );
7686 aMetadataStream.append( " </dc:description>\n" );
7688 aMetadataStream.append( " </rdf:Description>\n" );
7691 //... PDF properties go here
7692 if( !m_aContext.DocumentInfo.Producer.isEmpty() ||
7693 !m_aContext.DocumentInfo.Keywords.isEmpty() )
7695 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
7696 aMetadataStream.append( " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" );
7697 if( !m_aContext.DocumentInfo.Producer.isEmpty() )
7699 aMetadataStream.append( " <pdf:Producer>" );
7700 OUString aProducer;
7701 escapeStringXML( m_aContext.DocumentInfo.Producer, aProducer );
7702 aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 ) );
7703 aMetadataStream.append( "</pdf:Producer>\n" );
7705 if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
7707 aMetadataStream.append( " <pdf:Keywords>" );
7708 OUString aKeywords;
7709 escapeStringXML( m_aContext.DocumentInfo.Keywords, aKeywords );
7710 aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 ) );
7711 aMetadataStream.append( "</pdf:Keywords>\n" );
7713 aMetadataStream.append( " </rdf:Description>\n" );
7716 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
7717 aMetadataStream.append( " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" );
7718 if( !m_aContext.DocumentInfo.Creator.isEmpty() )
7720 aMetadataStream.append( " <xmp:CreatorTool>" );
7721 OUString aCreator;
7722 escapeStringXML( m_aContext.DocumentInfo.Creator, aCreator );
7723 aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 ) );
7724 aMetadataStream.append( "</xmp:CreatorTool>\n" );
7726 //creation date
7727 aMetadataStream.append( " <xmp:CreateDate>" );
7728 aMetadataStream.append( m_aCreationMetaDateString );
7729 aMetadataStream.append( "</xmp:CreateDate>\n" );
7731 aMetadataStream.append( " </rdf:Description>\n" );
7732 aMetadataStream.append( " </rdf:RDF>\n" );
7733 aMetadataStream.append( "</x:xmpmeta>\n" );
7735 //add the padding
7736 for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ )
7738 aMetadataStream.append( " " );
7739 if( nSpaces % 100 == 0 )
7740 aMetadataStream.append( "\n" );
7743 aMetadataStream.append( "<?xpacket end=\"w\"?>\n" );
7745 OStringBuffer aMetadataObj( 1024 );
7747 aMetadataObj.append( nObject );
7748 aMetadataObj.append( " 0 obj\n" );
7750 aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
7752 aMetadataObj.append( aMetadataStream.getLength() );
7753 aMetadataObj.append( ">>\nstream\n" );
7754 if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
7755 return 0;
7756 //emit the stream
7757 if ( !writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) )
7758 return 0;
7760 aMetadataObj.setLength( 0 );
7761 aMetadataObj.append( "\nendstream\nendobj\n\n" );
7762 if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
7763 nObject = 0;
7765 else
7766 nObject = 0;
7768 return nObject;
7771 bool PDFWriterImpl::emitTrailer()
7773 // emit doc info
7774 sal_Int32 nDocInfoObject = emitInfoDict( );
7776 sal_Int32 nSecObject = 0;
7778 if( m_aContext.Encryption.Encrypt() )
7780 //emit the security information
7781 //must be emitted as indirect dictionary object, since
7782 //Acrobat Reader 5 works only with this kind of implementation
7783 nSecObject = createObject();
7785 if( updateObject( nSecObject ) )
7787 OStringBuffer aLineS( 1024 );
7788 aLineS.append( nSecObject );
7789 aLineS.append( " 0 obj\n"
7790 "<</Filter/Standard/V " );
7791 // check the version
7792 aLineS.append( "2/Length 128/R 3" );
7794 // emit the owner password, must not be encrypted
7795 aLineS.append( "/O(" );
7796 appendLiteralString( reinterpret_cast<char*>(&m_aContext.Encryption.OValue[0]), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
7797 aLineS.append( ")/U(" );
7798 appendLiteralString( reinterpret_cast<char*>(&m_aContext.Encryption.UValue[0]), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
7799 aLineS.append( ")/P " );// the permission set
7800 aLineS.append( m_nAccessPermissions );
7801 aLineS.append( ">>\nendobj\n\n" );
7802 if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
7803 nSecObject = 0;
7805 else
7806 nSecObject = 0;
7808 // emit xref table
7809 // remember start
7810 sal_uInt64 nXRefOffset = 0;
7811 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
7812 CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
7814 sal_Int32 nObjects = m_aObjects.size();
7815 OStringBuffer aLine;
7816 aLine.append( "0 " );
7817 aLine.append( (sal_Int32)(nObjects+1) );
7818 aLine.append( "\n" );
7819 aLine.append( "0000000000 65535 f \n" );
7820 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
7822 for( sal_Int32 i = 0; i < nObjects; i++ )
7824 aLine.setLength( 0 );
7825 OString aOffset = OString::number( m_aObjects[i] );
7826 for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
7827 aLine.append( '0' );
7828 aLine.append( aOffset );
7829 aLine.append( " 00000 n \n" );
7830 SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
7831 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
7834 // prepare document checksum
7835 OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
7836 if( m_aDocDigest )
7838 sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
7839 rtl_digest_getMD5( m_aDocDigest, nMD5Sum, sizeof(nMD5Sum) );
7840 for(sal_uInt8 i : nMD5Sum)
7841 appendHex( i, aDocChecksum );
7843 // document id set in setDocInfo method
7844 // emit trailer
7845 aLine.setLength( 0 );
7846 aLine.append( "trailer\n"
7847 "<</Size " );
7848 aLine.append( (sal_Int32)(nObjects+1) );
7849 aLine.append( "/Root " );
7850 aLine.append( m_nCatalogObject );
7851 aLine.append( " 0 R\n" );
7852 if( nSecObject )
7854 aLine.append( "/Encrypt ");
7855 aLine.append( nSecObject );
7856 aLine.append( " 0 R\n" );
7858 if( nDocInfoObject )
7860 aLine.append( "/Info " );
7861 aLine.append( nDocInfoObject );
7862 aLine.append( " 0 R\n" );
7864 if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
7866 aLine.append( "/ID [ <" );
7867 for( std::vector< sal_uInt8 >::const_iterator it = m_aContext.Encryption.DocumentIdentifier.begin();
7868 it != m_aContext.Encryption.DocumentIdentifier.end(); ++it )
7870 appendHex( sal_Int8(*it), aLine );
7872 aLine.append( ">\n"
7873 "<" );
7874 for( std::vector< sal_uInt8 >::const_iterator it = m_aContext.Encryption.DocumentIdentifier.begin();
7875 it != m_aContext.Encryption.DocumentIdentifier.end(); ++it )
7877 appendHex( sal_Int8(*it), aLine );
7879 aLine.append( "> ]\n" );
7881 if( !aDocChecksum.isEmpty() )
7883 aLine.append( "/DocChecksum /" );
7884 aLine.append( aDocChecksum.makeStringAndClear() );
7885 aLine.append( "\n" );
7887 if( m_aAdditionalStreams.size() > 0 )
7889 aLine.append( "/AdditionalStreams [" );
7890 for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
7892 aLine.append( "/" );
7893 appendName( rAdditionalStream.m_aMimeType, aLine );
7894 aLine.append( " " );
7895 aLine.append( rAdditionalStream.m_nStreamObject );
7896 aLine.append( " 0 R\n" );
7898 aLine.append( "]\n" );
7900 aLine.append( ">>\n"
7901 "startxref\n" );
7902 aLine.append( (sal_Int64)nXRefOffset );
7903 aLine.append( "\n"
7904 "%%EOF\n" );
7905 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
7907 return true;
7910 struct AnnotationSortEntry
7912 sal_Int32 nTabOrder;
7913 sal_Int32 nObject;
7914 sal_Int32 nWidgetIndex;
7916 AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
7917 nTabOrder( nTab ),
7918 nObject( nObj ),
7919 nWidgetIndex( nI )
7923 struct AnnotSortContainer
7925 std::set< sal_Int32 > aObjects;
7926 std::vector< AnnotationSortEntry > aSortedAnnots;
7929 struct AnnotSorterLess
7931 std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets;
7933 explicit AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {}
7935 bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
7937 if( rLeft.nTabOrder < rRight.nTabOrder )
7938 return true;
7939 if( rRight.nTabOrder < rLeft.nTabOrder )
7940 return false;
7941 if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
7942 return false;
7943 if( rRight.nWidgetIndex < 0 )
7944 return true;
7945 if( rLeft.nWidgetIndex < 0 )
7946 return false;
7947 // remember: widget rects are in PDF coordinates, so they are ordered down up
7948 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
7949 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
7950 return true;
7951 if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
7952 m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
7953 return false;
7954 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
7955 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
7956 return true;
7957 return false;
7961 void PDFWriterImpl::sortWidgets()
7963 // sort widget annotations on each page as per their
7964 // TabOrder attribute
7965 std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
7966 int nWidgets = m_aWidgets.size();
7967 for( int nW = 0; nW < nWidgets; nW++ )
7969 const PDFWidget& rWidget = m_aWidgets[nW];
7970 if( rWidget.m_nPage >= 0 )
7972 AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
7973 // optimize vector allocation
7974 if( rCont.aSortedAnnots.empty() )
7975 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
7976 // insert widget to tab sorter
7977 // RadioButtons are not page annotations, only their individual check boxes are
7978 if( rWidget.m_eType != PDFWriter::RadioButton )
7980 rCont.aObjects.insert( rWidget.m_nObject );
7981 rCont.aSortedAnnots.push_back( AnnotationSortEntry( rWidget.m_nTabOrder, rWidget.m_nObject, nW ) );
7985 for( std::unordered_map< sal_Int32, AnnotSortContainer >::iterator it = sorted.begin(); it != sorted.end(); ++it )
7987 // append entries for non widget annotations
7988 PDFPage& rPage = m_aPages[ it->first ];
7989 unsigned int nAnnots = rPage.m_aAnnotations.size();
7990 for( unsigned int nA = 0; nA < nAnnots; nA++ )
7991 if( it->second.aObjects.find( rPage.m_aAnnotations[nA] ) == it->second.aObjects.end())
7992 it->second.aSortedAnnots.push_back( AnnotationSortEntry( 10000, rPage.m_aAnnotations[nA], -1 ) );
7994 AnnotSorterLess aLess( m_aWidgets );
7995 std::stable_sort( it->second.aSortedAnnots.begin(), it->second.aSortedAnnots.end(), aLess );
7996 // sanity check
7997 if( it->second.aSortedAnnots.size() == nAnnots)
7999 for( unsigned int nA = 0; nA < nAnnots; nA++ )
8000 rPage.m_aAnnotations[nA] = it->second.aSortedAnnots[nA].nObject;
8002 else
8004 SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
8005 #if OSL_DEBUG_LEVEL > 0
8006 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
8007 "on page nr " << (long int)it->first << ", " <<
8008 (long int)it->second.aSortedAnnots.size() << " sorted and " <<
8009 (long int)nAnnots << " unsorted");
8010 #endif
8014 // FIXME: implement tab order in structure tree for PDF 1.5
8017 namespace vcl {
8018 class PDFStreamIf :
8019 public cppu::WeakImplHelper< css::io::XOutputStream >
8021 PDFWriterImpl* m_pWriter;
8022 bool m_bWrite;
8023 public:
8024 explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
8026 virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
8027 virtual void SAL_CALL flush() override;
8028 virtual void SAL_CALL closeOutput() override;
8032 void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
8034 if( m_bWrite && aData.getLength() )
8036 sal_Int32 nBytes = aData.getLength();
8037 m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
8041 void SAL_CALL PDFStreamIf::flush()
8045 void SAL_CALL PDFStreamIf::closeOutput()
8047 m_bWrite = false;
8050 bool PDFWriterImpl::emitAdditionalStreams()
8052 unsigned int nStreams = m_aAdditionalStreams.size();
8053 for( unsigned int i = 0; i < nStreams; i++ )
8055 PDFAddStream& rStream = m_aAdditionalStreams[i];
8056 rStream.m_nStreamObject = createObject();
8057 sal_Int32 nSizeObject = createObject();
8059 if( ! updateObject( rStream.m_nStreamObject ) )
8060 return false;
8062 OStringBuffer aLine;
8063 aLine.append( rStream.m_nStreamObject );
8064 aLine.append( " 0 obj\n<</Length " );
8065 aLine.append( nSizeObject );
8066 aLine.append( " 0 R" );
8067 if( rStream.m_bCompress )
8068 aLine.append( "/Filter/FlateDecode" );
8069 aLine.append( ">>\nstream\n" );
8070 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
8071 return false;
8072 sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
8073 if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
8075 m_aFile.close();
8076 m_bOpen = false;
8078 if( rStream.m_bCompress )
8079 beginCompression();
8081 checkAndEnableStreamEncryption( rStream.m_nStreamObject );
8082 css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
8083 assert(rStream.m_pStream);
8084 if (!rStream.m_pStream)
8085 return false;
8086 rStream.m_pStream->write( xStream );
8087 xStream.clear();
8088 delete rStream.m_pStream;
8089 rStream.m_pStream = nullptr;
8090 disableStreamEncryption();
8092 if( rStream.m_bCompress )
8093 endCompression();
8095 if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
8097 m_aFile.close();
8098 m_bOpen = false;
8099 return false;
8101 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
8102 return false ;
8103 // emit stream length object
8104 if( ! updateObject( nSizeObject ) )
8105 return false;
8106 aLine.setLength( 0 );
8107 aLine.append( nSizeObject );
8108 aLine.append( " 0 obj\n" );
8109 aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos) );
8110 aLine.append( "\nendobj\n\n" );
8111 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
8112 return false;
8114 return true;
8117 bool PDFWriterImpl::emit()
8119 endPage();
8121 // resort structure tree and annotations if necessary
8122 // needed for widget tab order
8123 sortWidgets();
8125 #if HAVE_FEATURE_NSS
8126 if( m_aContext.SignPDF )
8128 // sign the document
8129 PDFWriter::SignatureWidget aSignature;
8130 aSignature.Name = "Signature1";
8131 createControl( aSignature, 0 );
8133 #endif
8135 // emit additional streams
8136 CHECK_RETURN( emitAdditionalStreams() );
8138 // emit catalog
8139 CHECK_RETURN( emitCatalog() );
8141 #if HAVE_FEATURE_NSS
8142 if (m_nSignatureObject != -1) // if document is signed, emit sigdict
8144 if( !emitSignature() )
8146 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
8147 return false;
8150 #endif
8152 // emit trailer
8153 CHECK_RETURN( emitTrailer() );
8155 #if HAVE_FEATURE_NSS
8156 if (m_nSignatureObject != -1) // finalize the signature
8158 if( !finalizeSignature() )
8160 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
8161 return false;
8164 #endif
8166 m_aFile.close();
8167 m_bOpen = false;
8169 return true;
8173 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
8175 getReferenceDevice()->Push();
8176 getReferenceDevice()->SetFont( i_rFont );
8177 getReferenceDevice()->ImplNewFont();
8179 const PhysicalFontFace* pDevFont = m_pReferenceDevice->mpFontInstance->maFontSelData.mpFontData;
8180 sal_Int32 nFontID = 0;
8181 FontEmbedData::iterator it = m_aSystemFonts.find( pDevFont );
8182 if( it != m_aSystemFonts.end() )
8183 nFontID = it->second.m_nNormalFontID;
8184 else
8186 nFontID = m_nNextFID++;
8187 m_aSystemFonts[ pDevFont ] = EmbedFont();
8188 m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
8191 getReferenceDevice()->Pop();
8192 getReferenceDevice()->ImplNewFont();
8194 return nFontID;
8197 void PDFWriterImpl::registerGlyphs( int nGlyphs,
8198 const GlyphItem** pGlyphs,
8199 sal_Int32* pGlyphWidths,
8200 sal_Ucs* pCodeUnits,
8201 sal_Int32* pCodeUnitsPerGlyph,
8202 sal_uInt8* pMappedGlyphs,
8203 sal_Int32* pMappedFontObjects,
8204 const PhysicalFontFace* pFallbackFonts[] )
8206 SalGraphics *pGraphics = m_pReferenceDevice->GetGraphics();
8208 if (!pGraphics)
8209 return;
8211 const PhysicalFontFace* pDevFont = m_pReferenceDevice->mpFontInstance->maFontSelData.mpFontData;
8212 sal_Ucs* pCurUnicode = pCodeUnits;
8213 for( int i = 0; i < nGlyphs; pCurUnicode += pCodeUnitsPerGlyph[i] , i++ )
8215 const int nFontGlyphId = pGlyphs[i]->maGlyphId;
8216 const PhysicalFontFace* pCurrentFont = pFallbackFonts[i] ? pFallbackFonts[i] : pDevFont;
8218 FontSubset& rSubset = m_aSubsets[ pCurrentFont ];
8219 // search for font specific glyphID
8220 FontMapping::iterator it = rSubset.m_aMapping.find( nFontGlyphId );
8221 if( it != rSubset.m_aMapping.end() )
8223 pMappedFontObjects[i] = it->second.m_nFontID;
8224 pMappedGlyphs[i] = it->second.m_nSubsetGlyphID;
8226 else
8228 // create new subset if necessary
8229 if( rSubset.m_aSubsets.empty()
8230 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
8232 rSubset.m_aSubsets.push_back( FontEmit( m_nNextFID++ ) );
8235 // copy font id
8236 pMappedFontObjects[i] = rSubset.m_aSubsets.back().m_nFontID;
8237 // create new glyph in subset
8238 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
8239 pMappedGlyphs[i] = nNewId;
8241 // add new glyph to emitted font subset
8242 GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
8243 rNewGlyphEmit.setGlyphId( nNewId );
8244 for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[i]; n++ )
8245 rNewGlyphEmit.addCode( pCurUnicode[n] );
8247 // add new glyph to font mapping
8248 Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
8249 rNewGlyph.m_nFontID = pMappedFontObjects[i];
8250 rNewGlyph.m_nSubsetGlyphID = nNewId;
8252 if (!getReferenceDevice()->AcquireGraphics())
8253 return;
8254 pGlyphWidths[i] = m_aFontCache.getGlyphWidth( pCurrentFont,
8255 nFontGlyphId,
8256 pGlyphs[i]->IsVertical(),
8257 pGraphics );
8261 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
8263 push( PushFlags::ALL );
8265 FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
8267 Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
8268 Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
8269 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
8270 Color aReliefColor( COL_LIGHTGRAY );
8271 if( aTextColor == COL_BLACK )
8272 aTextColor = Color( COL_WHITE );
8273 if( aTextLineColor == COL_BLACK )
8274 aTextLineColor = Color( COL_WHITE );
8275 if( aOverlineColor == COL_BLACK )
8276 aOverlineColor = Color( COL_WHITE );
8277 if( aTextColor == COL_WHITE )
8278 aReliefColor = Color( COL_BLACK );
8280 Font aSetFont = m_aCurrentPDFState.m_aFont;
8281 aSetFont.SetRelief( FontRelief::NONE );
8282 aSetFont.SetShadow( false );
8284 aSetFont.SetColor( aReliefColor );
8285 setTextLineColor( aReliefColor );
8286 setOverlineColor( aReliefColor );
8287 setFont( aSetFont );
8288 long nOff = 1 + getReferenceDevice()->mnDPIX/300;
8289 if( eRelief == FontRelief::Engraved )
8290 nOff = -nOff;
8292 rLayout.DrawOffset() += Point( nOff, nOff );
8293 updateGraphicsState();
8294 drawLayout( rLayout, rText, bTextLines );
8296 rLayout.DrawOffset() -= Point( nOff, nOff );
8297 setTextLineColor( aTextLineColor );
8298 setOverlineColor( aOverlineColor );
8299 aSetFont.SetColor( aTextColor );
8300 setFont( aSetFont );
8301 updateGraphicsState();
8302 drawLayout( rLayout, rText, bTextLines );
8304 // clean up the mess
8305 pop();
8308 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
8310 Font aSaveFont = m_aCurrentPDFState.m_aFont;
8311 Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
8312 Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
8314 Font& rFont = m_aCurrentPDFState.m_aFont;
8315 if( rFont.GetColor() == Color( COL_BLACK ) || rFont.GetColor().GetLuminance() < 8 )
8316 rFont.SetColor( Color( COL_LIGHTGRAY ) );
8317 else
8318 rFont.SetColor( Color( COL_BLACK ) );
8319 rFont.SetShadow( false );
8320 rFont.SetOutline( false );
8321 setFont( rFont );
8322 setTextLineColor( rFont.GetColor() );
8323 setOverlineColor( rFont.GetColor() );
8324 updateGraphicsState();
8326 long nOff = 1 + ((m_pReferenceDevice->mpFontInstance->mnLineHeight-24)/24);
8327 if( rFont.IsOutline() )
8328 nOff++;
8329 rLayout.DrawBase() += Point( nOff, nOff );
8330 drawLayout( rLayout, rText, bTextLines );
8331 rLayout.DrawBase() -= Point( nOff, nOff );
8333 setFont( aSaveFont );
8334 setTextLineColor( aSaveTextLineColor );
8335 setOverlineColor( aSaveOverlineColor );
8336 updateGraphicsState();
8339 void PDFWriterImpl::drawVerticalGlyphs(
8340 const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
8341 OStringBuffer& rLine,
8342 const Point& rAlignOffset,
8343 const Matrix3& rRotScale,
8344 double fAngle,
8345 double fXScale,
8346 double fSkew,
8347 sal_Int32 nFontHeight )
8349 long nXOffset = 0;
8350 Point aCurPos( rGlyphs[0].m_aPos );
8351 aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos );
8352 aCurPos += rAlignOffset;
8353 for( size_t i = 0; i < rGlyphs.size(); i++ )
8355 // have to emit each glyph on its own
8356 double fDeltaAngle = 0.0;
8357 double fYScale = 1.0;
8358 double fTempXScale = fXScale;
8359 double fSkewB = fSkew;
8360 double fSkewA = 0.0;
8362 Point aDeltaPos;
8363 if (rGlyphs[i].m_bVertical)
8365 fDeltaAngle = M_PI/2.0;
8366 aDeltaPos.X() = m_pReferenceDevice->GetFontMetric().GetAscent();
8367 aDeltaPos.Y() = (int)((double)m_pReferenceDevice->GetFontMetric().GetDescent() * fXScale);
8368 fYScale = fXScale;
8369 fTempXScale = 1.0;
8370 fSkewA = -fSkewB;
8371 fSkewB = 0.0;
8373 aDeltaPos += (m_pReferenceDevice->PixelToLogic( Point( (int)((double)nXOffset/fXScale), 0 ) ) - m_pReferenceDevice->PixelToLogic( Point() ) );
8374 if( i < rGlyphs.size()-1 )
8375 // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
8377 long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
8378 long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
8379 nXOffset += (int)sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY));
8381 if( ! rGlyphs[i].m_nGlyphId )
8382 continue;
8384 aDeltaPos = rRotScale.transform( aDeltaPos );
8386 Matrix3 aMat;
8387 if( fSkewB != 0.0 || fSkewA != 0.0 )
8388 aMat.skew( fSkewA, fSkewB );
8389 aMat.scale( fTempXScale, fYScale );
8390 aMat.rotate( fAngle+fDeltaAngle );
8391 aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
8392 aMat.append( m_aPages.back(), rLine );
8393 rLine.append( " Tm" );
8394 if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
8396 rLine.append( " /F" );
8397 rLine.append( rGlyphs[i].m_nMappedFontId );
8398 rLine.append( ' ' );
8399 m_aPages.back().appendMappedLength( nFontHeight, rLine );
8400 rLine.append( " Tf" );
8402 rLine.append( "<" );
8403 appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
8404 rLine.append( ">Tj\n" );
8408 void PDFWriterImpl::drawHorizontalGlyphs(
8409 const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
8410 OStringBuffer& rLine,
8411 const Point& rAlignOffset,
8412 double fAngle,
8413 double fXScale,
8414 double fSkew,
8415 sal_Int32 nFontHeight,
8416 sal_Int32 nPixelFontHeight
8419 // horizontal (= normal) case
8421 // fill in run end indices
8422 // end is marked by index of the first glyph of the next run
8423 // a run is marked by same mapped font id and same Y position
8424 std::vector< sal_uInt32 > aRunEnds;
8425 aRunEnds.reserve( rGlyphs.size() );
8426 for( size_t i = 1; i < rGlyphs.size(); i++ )
8428 if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
8429 rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
8431 aRunEnds.push_back(i);
8434 // last run ends at last glyph
8435 aRunEnds.push_back( rGlyphs.size() );
8437 // loop over runs of the same font
8438 sal_uInt32 nBeginRun = 0;
8439 for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
8441 // setup text matrix
8442 Point aCurPos = rGlyphs[nBeginRun].m_aPos;
8443 // back transformation to current coordinate system
8444 aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos );
8445 aCurPos += rAlignOffset;
8446 // the first run can be set with "Td" operator
8447 // subsequent use of that operator would move
8448 // the textline matrix relative to what was set before
8449 // making use of that would drive us into rounding issues
8450 Matrix3 aMat;
8451 if( nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
8453 m_aPages.back().appendPoint( aCurPos, rLine );
8454 rLine.append( " Td " );
8456 else
8458 if( fSkew != 0.0 )
8459 aMat.skew( 0.0, fSkew );
8460 aMat.scale( fXScale, 1.0 );
8461 aMat.rotate( fAngle );
8462 aMat.translate( aCurPos.X(), aCurPos.Y() );
8463 aMat.append( m_aPages.back(), rLine );
8464 rLine.append( " Tm\n" );
8466 // set up correct font
8467 rLine.append( "/F" );
8468 rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
8469 rLine.append( ' ' );
8470 m_aPages.back().appendMappedLength( nFontHeight, rLine );
8471 rLine.append( " Tf" );
8473 // output glyphs using Tj or TJ
8474 OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
8475 aKernedLine.append( "[<" );
8476 aUnkernedLine.append( '<' );
8477 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
8478 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
8480 aMat.invert();
8481 bool bNeedKern = false;
8482 for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
8484 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
8485 // check if default glyph positioning is sufficient
8486 const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
8487 const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
8488 double fAdvance = aThisPos.X() - aPrevPos.X();
8489 fAdvance *= 1000.0 / nPixelFontHeight;
8490 const sal_Int32 nAdjustment = (sal_Int32)(rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5);
8491 if( nAdjustment != 0 )
8493 // apply individual glyph positioning
8494 bNeedKern = true;
8495 aKernedLine.append( ">" );
8496 aKernedLine.append( nAdjustment );
8497 aKernedLine.append( "<" );
8499 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
8501 aKernedLine.append( ">]TJ\n" );
8502 aUnkernedLine.append( ">Tj\n" );
8503 rLine.append(
8504 (bNeedKern ? aKernedLine : aUnkernedLine).makeStringAndClear() );
8506 // set beginning of next run
8507 nBeginRun = aRunEnds[nRun];
8511 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
8513 // relief takes precedence over shadow (see outdev3.cxx)
8514 if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
8516 drawRelief( rLayout, rText, bTextLines );
8517 return;
8519 else if( m_aCurrentPDFState.m_aFont.IsShadow() )
8520 drawShadow( rLayout, rText, bTextLines );
8522 OStringBuffer aLine( 512 );
8524 const int nMaxGlyphs = 256;
8526 const GlyphItem* pGlyphs[nMaxGlyphs] = { nullptr };
8527 const PhysicalFontFace* pFallbackFonts[nMaxGlyphs] = { nullptr };
8528 sal_Int32 pGlyphWidths[nMaxGlyphs];
8529 sal_uInt8 pMappedGlyphs[nMaxGlyphs];
8530 sal_Int32 pMappedFontObjects[nMaxGlyphs];
8531 std::vector<sal_Ucs> aCodeUnits;
8532 aCodeUnits.reserve(nMaxGlyphs);
8533 std::vector<sal_Int32> aCodeUnitsPerGlyph;
8534 aCodeUnits.reserve(nMaxGlyphs);
8535 bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
8536 int nGlyphs;
8537 int nIndex = 0;
8538 int nMinCharPos = 0, nMaxCharPos = rText.getLength()-1;
8539 double fXScale = 1.0;
8540 double fSkew = 0.0;
8541 sal_Int32 nPixelFontHeight = m_pReferenceDevice->mpFontInstance->maFontSelData.mnHeight;
8542 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
8544 // transform font height back to current units
8545 // note: the layout calculates in outdevs device pixel !!
8546 sal_Int32 nFontHeight = m_pReferenceDevice->ImplDevicePixelToLogicHeight( nPixelFontHeight );
8547 if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
8549 Font aFont( m_aCurrentPDFState.m_aFont );
8550 aFont.SetAverageFontWidth( 0 );
8551 FontMetric aMetric = m_pReferenceDevice->GetFontMetric( aFont );
8552 if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
8554 fXScale =
8555 (double)m_aCurrentPDFState.m_aFont.GetAverageFontWidth() /
8556 (double)aMetric.GetAverageFontWidth();
8558 // force state before GetFontMetric
8559 m_pReferenceDevice->ImplNewFont();
8562 // perform artificial italics if necessary
8563 if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
8564 m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
8565 !( m_pReferenceDevice->mpFontInstance->maFontSelData.mpFontData->GetItalic() == ITALIC_NORMAL ||
8566 m_pReferenceDevice->mpFontInstance->maFontSelData.mpFontData->GetItalic() == ITALIC_OBLIQUE )
8569 fSkew = M_PI/12.0;
8572 // if the mapmode is distorted we need to adjust for that also
8573 if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
8575 fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
8578 int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
8579 // normalize angles
8580 while( nAngle < 0 )
8581 nAngle += 3600;
8582 nAngle = nAngle % 3600;
8583 double fAngle = (double)nAngle * M_PI / 1800.0;
8585 Matrix3 aRotScale;
8586 aRotScale.scale( fXScale, 1.0 );
8587 if( fAngle != 0.0 )
8588 aRotScale.rotate( -fAngle );
8590 bool bPop = false;
8591 bool bABold = false;
8592 // artificial bold necessary ?
8593 if( m_pReferenceDevice->mpFontInstance->maFontSelData.mpFontData->GetWeight() <= WEIGHT_MEDIUM &&
8594 m_pReferenceDevice->mpFontInstance->maFontSelData.GetWeight() > WEIGHT_MEDIUM )
8596 if( ! bPop )
8597 aLine.append( "q " );
8598 bPop = true;
8599 bABold = true;
8601 // setup text colors (if necessary)
8602 Color aStrokeColor( COL_TRANSPARENT );
8603 Color aNonStrokeColor( COL_TRANSPARENT );
8605 if( m_aCurrentPDFState.m_aFont.IsOutline() )
8607 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
8608 aNonStrokeColor = Color( COL_WHITE );
8610 else
8611 aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
8612 if( bABold )
8613 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
8615 if( aStrokeColor != Color( COL_TRANSPARENT ) && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
8617 if( ! bPop )
8618 aLine.append( "q " );
8619 bPop = true;
8620 appendStrokingColor( aStrokeColor, aLine );
8621 aLine.append( "\n" );
8623 if( aNonStrokeColor != Color( COL_TRANSPARENT ) && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
8625 if( ! bPop )
8626 aLine.append( "q " );
8627 bPop = true;
8628 appendNonStrokingColor( aNonStrokeColor, aLine );
8629 aLine.append( "\n" );
8632 // begin text object
8633 aLine.append( "BT\n" );
8634 // outline attribute ?
8635 if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
8637 // set correct text mode, set stroke width
8638 aLine.append( "2 Tr " ); // fill, then stroke
8640 if( m_aCurrentPDFState.m_aFont.IsOutline() )
8642 // unclear what to do in case of outline and artificial bold
8643 // for the time being outline wins
8644 aLine.append( "0.25 w \n" );
8646 else
8648 double fW = (double)m_aCurrentPDFState.m_aFont.GetFontHeight() / 30.0;
8649 m_aPages.back().appendMappedLength( fW, aLine );
8650 aLine.append ( " w\n" );
8654 FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric();
8656 // collect the glyphs into a single array
8657 const int nTmpMaxGlyphs = rLayout.GetOrientation() ? 1 : nMaxGlyphs; // #i97991# temporary workaround for #i87686#
8658 std::vector< PDFGlyph > aGlyphs;
8659 aGlyphs.reserve( nTmpMaxGlyphs );
8660 // first get all the glyphs and register them; coordinates still in Pixel
8661 Point aGNGlyphPos;
8662 while ((nGlyphs = rLayout.GetNextGlyphs(nTmpMaxGlyphs, pGlyphs, aGNGlyphPos, nIndex, pFallbackFonts)) != 0)
8664 aCodeUnits.clear();
8665 for( int i = 0; i < nGlyphs; i++ )
8667 // default case: 1 glyph is one unicode
8668 aCodeUnitsPerGlyph.push_back(1);
8669 if (pGlyphs[i]->mnCharPos >= nMinCharPos && pGlyphs[i]->mnCharPos <= nMaxCharPos)
8671 int nChars = 1;
8672 // try to handle ligatures and such
8673 if( i < nGlyphs-1 )
8675 nChars = pGlyphs[i+1]->mnCharPos - pGlyphs[i]->mnCharPos;
8676 int start = pGlyphs[i]->mnCharPos;
8677 // #i115618# fix for simple RTL+CTL cases
8678 // supports RTL ligatures. TODO: more complex CTL, etc.
8679 if( nChars < 0 )
8681 nChars = -nChars;
8682 start = pGlyphs[i+1]->mnCharPos + 1;
8684 else if (nChars == 0)
8685 nChars = 1;
8686 aCodeUnitsPerGlyph.back() = nChars;
8687 for( int n = 0; n < nChars; n++ )
8688 aCodeUnits.push_back( rText[ start + n ] );
8690 else
8691 aCodeUnits.push_back(rText[pGlyphs[i]->mnCharPos]);
8693 else
8694 aCodeUnits.push_back( 0 );
8695 // note: in case of ctl one character may result
8696 // in multiple glyphs. The current SalLayout
8697 // implementations set -1 then to indicate that no direct
8698 // mapping is possible
8701 registerGlyphs( nGlyphs, pGlyphs, pGlyphWidths, aCodeUnits.data(), aCodeUnitsPerGlyph.data(), pMappedGlyphs, pMappedFontObjects, pFallbackFonts );
8703 for( int i = 0; i < nGlyphs; i++ )
8705 aGlyphs.push_back( PDFGlyph( aGNGlyphPos,
8706 pGlyphWidths[i],
8707 pGlyphs[i]->maGlyphId,
8708 pMappedFontObjects[i],
8709 pMappedGlyphs[i],
8710 pGlyphs[i]->IsVertical() ) );
8711 if( bVertical )
8712 aGNGlyphPos.Y() += pGlyphs[i]->mnNewWidth/rLayout.GetUnitsPerPixel();
8713 else
8714 aGNGlyphPos.X() += pGlyphs[i]->mnNewWidth/rLayout.GetUnitsPerPixel();
8718 // Avoid fill color when map mode is in pixels, the below code assumes
8719 // logic map mode.
8720 bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
8721 if (m_aCurrentPDFState.m_aFont.GetFillColor() != Color(COL_TRANSPARENT) && !bPixel)
8723 // PDF doesn't have a text fill color, so draw a rectangle before
8724 // drawing the actual text.
8725 push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
8726 setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
8727 // Avoid border around the rectangle for Writer shape text.
8728 setLineColor(Color(COL_TRANSPARENT));
8730 // The rectangle is the bounding box of the text, but also includes
8731 // ascent / descent to match the on-screen rendering.
8732 tools::Rectangle aRectangle;
8733 // This is the top left of the text without ascent / descent.
8734 aRectangle.SetPos(m_pReferenceDevice->PixelToLogic(rLayout.GetDrawPosition()));
8735 aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
8736 aRectangle.SetSize(m_pReferenceDevice->PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
8737 // This includes ascent / descent.
8738 aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
8740 LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance;
8741 if (pFontInstance->mnOrientation)
8743 // Adapt rectangle for rotated text.
8744 tools::Polygon aPolygon(aRectangle);
8745 aPolygon.Rotate(m_pReferenceDevice->PixelToLogic(rLayout.GetDrawPosition()), pFontInstance->mnOrientation);
8746 drawPolygon(aPolygon);
8748 else
8749 drawRectangle(aRectangle);
8751 pop();
8754 Point aAlignOffset;
8755 if ( eAlign == ALIGN_BOTTOM )
8756 aAlignOffset.Y() -= aRefDevFontMetric.GetDescent();
8757 else if ( eAlign == ALIGN_TOP )
8758 aAlignOffset.Y() += aRefDevFontMetric.GetAscent();
8759 if( aAlignOffset.X() || aAlignOffset.Y() )
8760 aAlignOffset = aRotScale.transform( aAlignOffset );
8762 /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
8763 string contained only on of the UTF16 BOMs
8765 if( ! aGlyphs.empty() )
8767 if( bVertical )
8768 drawVerticalGlyphs( aGlyphs, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight );
8769 else
8770 drawHorizontalGlyphs( aGlyphs, aLine, aAlignOffset, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight );
8773 // end textobject
8774 aLine.append( "ET\n" );
8775 if( bPop )
8776 aLine.append( "Q\n" );
8778 writeBuffer( aLine.getStr(), aLine.getLength() );
8780 // draw eventual textlines
8781 FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
8782 FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
8783 FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
8784 if( bTextLines &&
8786 ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
8787 ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
8788 ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
8792 bool bUnderlineAbove = OutputDevice::ImplIsUnderlineAbove( m_aCurrentPDFState.m_aFont );
8793 if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
8795 Point aPos, aStartPt;
8796 sal_Int32 nWidth = 0;
8797 const GlyphItem* pGlyph;
8798 int nStart = 0;
8799 while (rLayout.GetNextGlyphs(1, &pGlyph, aPos, nStart))
8801 if (!pGlyph->IsSpacing())
8803 if( !nWidth )
8804 aStartPt = aPos;
8806 nWidth += pGlyph->mnNewWidth;
8808 else if( nWidth > 0 )
8810 drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
8811 m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
8812 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
8813 nWidth = 0;
8817 if( nWidth > 0 )
8819 drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
8820 m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
8821 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
8824 else
8826 Point aStartPt = rLayout.GetDrawPosition();
8827 int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
8828 drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
8829 m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
8830 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
8834 // write eventual emphasis marks
8835 if( m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style )
8837 tools::PolyPolygon aEmphPoly;
8838 tools::Rectangle aEmphRect1;
8839 tools::Rectangle aEmphRect2;
8840 long nEmphYOff;
8841 long nEmphWidth;
8842 long nEmphHeight;
8843 bool bEmphPolyLine;
8844 FontEmphasisMark nEmphMark;
8846 push( PushFlags::ALL );
8848 aLine.setLength( 0 );
8849 aLine.append( "q\n" );
8851 nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
8852 if ( nEmphMark & FontEmphasisMark::PosBelow )
8853 nEmphHeight = m_pReferenceDevice->mnEmphasisDescent;
8854 else
8855 nEmphHeight = m_pReferenceDevice->mnEmphasisAscent;
8856 m_pReferenceDevice->ImplGetEmphasisMark( aEmphPoly,
8857 bEmphPolyLine,
8858 aEmphRect1,
8859 aEmphRect2,
8860 nEmphYOff,
8861 nEmphWidth,
8862 nEmphMark,
8863 m_pReferenceDevice->ImplDevicePixelToLogicWidth(nEmphHeight) );
8864 if ( bEmphPolyLine )
8866 setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
8867 setFillColor( Color( COL_TRANSPARENT ) );
8869 else
8871 setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
8872 setLineColor( Color( COL_TRANSPARENT ) );
8874 writeBuffer( aLine.getStr(), aLine.getLength() );
8876 Point aOffset = Point(0,0);
8878 if ( nEmphMark & FontEmphasisMark::PosBelow )
8879 aOffset.Y() += m_pReferenceDevice->mpFontInstance->mxFontMetric->GetDescent() + nEmphYOff;
8880 else
8881 aOffset.Y() -= m_pReferenceDevice->mpFontInstance->mxFontMetric->GetAscent() + nEmphYOff;
8883 long nEmphWidth2 = nEmphWidth / 2;
8884 long nEmphHeight2 = nEmphHeight / 2;
8885 aOffset += Point( nEmphWidth2, nEmphHeight2 );
8887 if ( eAlign == ALIGN_BOTTOM )
8888 aOffset.Y() -= m_pReferenceDevice->mpFontInstance->mxFontMetric->GetDescent();
8889 else if ( eAlign == ALIGN_TOP )
8890 aOffset.Y() += m_pReferenceDevice->mpFontInstance->mxFontMetric->GetAscent();
8892 Point aPos;
8893 const GlyphItem* pGlyph;
8894 int nStart = 0;
8895 while (rLayout.GetNextGlyphs(1, &pGlyph, aPos, nStart))
8897 if (pGlyph->IsSpacing())
8899 Point aAdjOffset = aOffset;
8900 aAdjOffset.X() += (pGlyph->mnNewWidth - nEmphWidth) / 2;
8901 aAdjOffset = aRotScale.transform( aAdjOffset );
8903 aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
8905 aPos += aAdjOffset;
8906 aPos = m_pReferenceDevice->PixelToLogic( aPos );
8907 drawEmphasisMark( aPos.X(), aPos.Y(),
8908 aEmphPoly, bEmphPolyLine,
8909 aEmphRect1, aEmphRect2 );
8913 writeBuffer( "Q\n", 2 );
8914 pop();
8918 void PDFWriterImpl::drawEmphasisMark( long nX, long nY,
8919 const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
8920 const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
8922 // TODO: pass nWidth as width of this mark
8923 // long nWidth = 0;
8925 if ( rPolyPoly.Count() )
8927 if ( bPolyLine )
8929 tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
8930 aPoly.Move( nX, nY );
8931 drawPolyLine( aPoly );
8933 else
8935 tools::PolyPolygon aPolyPoly = rPolyPoly;
8936 aPolyPoly.Move( nX, nY );
8937 drawPolyPolygon( aPolyPoly );
8941 if ( !rRect1.IsEmpty() )
8943 tools::Rectangle aRect( Point( nX+rRect1.Left(),
8944 nY+rRect1.Top() ), rRect1.GetSize() );
8945 drawRectangle( aRect );
8948 if ( !rRect2.IsEmpty() )
8950 tools::Rectangle aRect( Point( nX+rRect2.Left(),
8951 nY+rRect2.Top() ), rRect2.GetSize() );
8953 drawRectangle( aRect );
8957 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
8959 MARK( "drawText" );
8961 updateGraphicsState();
8963 // get a layout from the OuputDevice's SalGraphics
8964 // this also enforces font substitution and sets the font on SalGraphics
8965 SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos );
8966 if( pLayout )
8968 drawLayout( *pLayout, rText, bTextLines );
8969 pLayout->Release();
8973 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
8975 MARK( "drawText with array" );
8977 updateGraphicsState();
8979 // get a layout from the OuputDevice's SalGraphics
8980 // this also enforces font substitution and sets the font on SalGraphics
8981 SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
8982 if( pLayout )
8984 drawLayout( *pLayout, rText, true );
8985 pLayout->Release();
8989 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
8991 MARK( "drawStretchText" );
8993 updateGraphicsState();
8995 // get a layout from the OuputDevice's SalGraphics
8996 // this also enforces font substitution and sets the font on SalGraphics
8997 SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, nWidth );
8998 if( pLayout )
9000 drawLayout( *pLayout, rText, true );
9001 pLayout->Release();
9005 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
9007 long nWidth = rRect.GetWidth();
9008 long nHeight = rRect.GetHeight();
9010 if ( nWidth <= 0 || nHeight <= 0 )
9011 return;
9013 MARK( "drawText with rectangle" );
9015 updateGraphicsState();
9017 // clip with rectangle
9018 OStringBuffer aLine;
9019 aLine.append( "q " );
9020 m_aPages.back().appendRect( rRect, aLine );
9021 aLine.append( " W* n\n" );
9022 writeBuffer( aLine.getStr(), aLine.getLength() );
9024 // if disabled text is needed, put in here
9026 Point aPos = rRect.TopLeft();
9028 long nTextHeight = m_pReferenceDevice->GetTextHeight();
9029 sal_Int32 nMnemonicPos = -1;
9031 OUString aStr = rOrigStr;
9032 if ( nStyle & DrawTextFlags::Mnemonic )
9033 aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
9035 // multiline text
9036 if ( nStyle & DrawTextFlags::MultiLine )
9038 OUString aLastLine;
9039 ImplMultiTextLineInfo aMultiLineInfo;
9040 ImplTextLineInfo* pLineInfo;
9041 sal_Int32 i;
9042 sal_Int32 nLines;
9043 sal_Int32 nFormatLines;
9045 if ( nTextHeight )
9047 vcl::DefaultTextLayout aLayout( *m_pReferenceDevice );
9048 OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
9049 nLines = nHeight/nTextHeight;
9050 nFormatLines = aMultiLineInfo.Count();
9051 if ( !nLines )
9052 nLines = 1;
9053 if ( nFormatLines > nLines )
9055 if ( nStyle & DrawTextFlags::EndEllipsis )
9057 // handle last line
9058 nFormatLines = nLines-1;
9060 pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
9061 aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF);
9062 // replace line feed by space
9063 aLastLine = aLastLine.replace('\n', ' ');
9064 aLastLine = m_pReferenceDevice->GetEllipsisString( aLastLine, nWidth, nStyle );
9065 nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
9066 nStyle |= DrawTextFlags::Top;
9070 // vertical alignment
9071 if ( nStyle & DrawTextFlags::Bottom )
9072 aPos.Y() += nHeight-(nFormatLines*nTextHeight);
9073 else if ( nStyle & DrawTextFlags::VCenter )
9074 aPos.Y() += (nHeight-(nFormatLines*nTextHeight))/2;
9076 // draw all lines excluding the last
9077 for ( i = 0; i < nFormatLines; i++ )
9079 pLineInfo = aMultiLineInfo.GetLine( i );
9080 if ( nStyle & DrawTextFlags::Right )
9081 aPos.X() += nWidth-pLineInfo->GetWidth();
9082 else if ( nStyle & DrawTextFlags::Center )
9083 aPos.X() += (nWidth-pLineInfo->GetWidth())/2;
9084 sal_Int32 nIndex = pLineInfo->GetIndex();
9085 sal_Int32 nLineLen = pLineInfo->GetLen();
9086 drawText( aPos, aStr, nIndex, nLineLen );
9087 // mnemonics should not appear in documents,
9088 // if the need arises, put them in here
9089 aPos.Y() += nTextHeight;
9090 aPos.X() = rRect.Left();
9093 // output last line left adjusted since it was shortened
9094 if (!aLastLine.isEmpty())
9095 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
9098 else
9100 long nTextWidth = m_pReferenceDevice->GetTextWidth( aStr );
9102 // Evt. Text kuerzen
9103 if ( nTextWidth > nWidth )
9105 if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
9107 aStr = m_pReferenceDevice->GetEllipsisString( aStr, nWidth, nStyle );
9108 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
9109 nStyle |= DrawTextFlags::Left;
9110 nTextWidth = m_pReferenceDevice->GetTextWidth( aStr );
9114 // vertical alignment
9115 if ( nStyle & DrawTextFlags::Right )
9116 aPos.X() += nWidth-nTextWidth;
9117 else if ( nStyle & DrawTextFlags::Center )
9118 aPos.X() += (nWidth-nTextWidth)/2;
9120 if ( nStyle & DrawTextFlags::Bottom )
9121 aPos.Y() += nHeight-nTextHeight;
9122 else if ( nStyle & DrawTextFlags::VCenter )
9123 aPos.Y() += (nHeight-nTextHeight)/2;
9125 // mnemonics should be inserted here if the need arises
9127 // draw the actual text
9128 drawText( aPos, aStr, 0, aStr.getLength() );
9131 // reset clip region to original value
9132 aLine.setLength( 0 );
9133 aLine.append( "Q\n" );
9134 writeBuffer( aLine.getStr(), aLine.getLength() );
9137 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
9139 MARK( "drawLine" );
9141 updateGraphicsState();
9143 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
9144 return;
9146 OStringBuffer aLine;
9147 m_aPages.back().appendPoint( rStart, aLine );
9148 aLine.append( " m " );
9149 m_aPages.back().appendPoint( rStop, aLine );
9150 aLine.append( " l S\n" );
9152 writeBuffer( aLine.getStr(), aLine.getLength() );
9155 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
9157 MARK( "drawLine with LineInfo" );
9158 updateGraphicsState();
9160 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
9161 return;
9163 if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
9165 drawLine( rStart, rStop );
9166 return;
9169 OStringBuffer aLine;
9171 aLine.append( "q " );
9172 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
9174 m_aPages.back().appendPoint( rStart, aLine );
9175 aLine.append( " m " );
9176 m_aPages.back().appendPoint( rStop, aLine );
9177 aLine.append( " l S Q\n" );
9179 writeBuffer( aLine.getStr(), aLine.getLength() );
9181 else
9183 PDFWriter::ExtLineInfo aInfo;
9184 convertLineInfoToExtLineInfo( rInfo, aInfo );
9185 Point aPolyPoints[2] = { rStart, rStop };
9186 tools::Polygon aPoly( 2, aPolyPoints );
9187 drawPolyLine( aPoly, aInfo );
9191 #define HCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicHeight( x )
9193 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
9195 // note: units in pFontInstance are ref device pixel
9196 LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance;
9197 long nLineHeight = 0;
9198 long nLinePos = 0;
9200 appendStrokingColor( aColor, aLine );
9201 aLine.append( "\n" );
9203 if ( bIsAbove )
9205 if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
9206 m_pReferenceDevice->ImplInitAboveTextLineSize();
9207 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
9208 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
9210 else
9212 if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
9213 m_pReferenceDevice->ImplInitTextLineSize();
9214 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
9215 nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
9217 if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
9218 nLineHeight = 3;
9220 long nLineWidth = getReferenceDevice()->mnDPIX/450;
9221 if ( ! nLineWidth )
9222 nLineWidth = 1;
9224 if ( eTextLine == LINESTYLE_BOLDWAVE )
9225 nLineWidth = 3*nLineWidth;
9227 m_aPages.back().appendMappedLength( (sal_Int32)nLineWidth, aLine );
9228 aLine.append( " w " );
9230 if ( eTextLine == LINESTYLE_DOUBLEWAVE )
9232 long nOrgLineHeight = nLineHeight;
9233 nLineHeight /= 3;
9234 if ( nLineHeight < 2 )
9236 if ( nOrgLineHeight > 1 )
9237 nLineHeight = 2;
9238 else
9239 nLineHeight = 1;
9241 long nLineDY = nOrgLineHeight-(nLineHeight*2);
9242 if ( nLineDY < nLineWidth )
9243 nLineDY = nLineWidth;
9244 long nLineDY2 = nLineDY/2;
9245 if ( !nLineDY2 )
9246 nLineDY2 = 1;
9248 nLinePos -= nLineWidth-nLineDY2;
9250 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
9252 nLinePos += nLineWidth+nLineDY;
9253 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
9255 else
9257 if ( eTextLine != LINESTYLE_BOLDWAVE )
9258 nLinePos -= nLineWidth/2;
9259 m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
9263 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
9265 // note: units in pFontInstance are ref device pixel
9266 LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance;
9267 long nLineHeight = 0;
9268 long nLinePos = 0;
9269 long nLinePos2 = 0;
9271 if ( eTextLine > LINESTYLE_BOLDWAVE )
9272 eTextLine = LINESTYLE_SINGLE;
9274 switch ( eTextLine )
9276 case LINESTYLE_SINGLE:
9277 case LINESTYLE_DOTTED:
9278 case LINESTYLE_DASH:
9279 case LINESTYLE_LONGDASH:
9280 case LINESTYLE_DASHDOT:
9281 case LINESTYLE_DASHDOTDOT:
9282 if ( bIsAbove )
9284 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
9285 m_pReferenceDevice->ImplInitAboveTextLineSize();
9286 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
9287 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
9289 else
9291 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
9292 m_pReferenceDevice->ImplInitTextLineSize();
9293 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
9294 nLinePos = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
9296 break;
9297 case LINESTYLE_BOLD:
9298 case LINESTYLE_BOLDDOTTED:
9299 case LINESTYLE_BOLDDASH:
9300 case LINESTYLE_BOLDLONGDASH:
9301 case LINESTYLE_BOLDDASHDOT:
9302 case LINESTYLE_BOLDDASHDOTDOT:
9303 if ( bIsAbove )
9305 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
9306 m_pReferenceDevice->ImplInitAboveTextLineSize();
9307 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
9308 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
9310 else
9312 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
9313 m_pReferenceDevice->ImplInitTextLineSize();
9314 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
9315 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
9316 nLinePos += nLineHeight/2;
9318 break;
9319 case LINESTYLE_DOUBLE:
9320 if ( bIsAbove )
9322 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
9323 m_pReferenceDevice->ImplInitAboveTextLineSize();
9324 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
9325 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
9326 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
9328 else
9330 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
9331 m_pReferenceDevice->ImplInitTextLineSize();
9332 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
9333 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
9334 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
9336 break;
9337 default:
9338 break;
9341 if ( nLineHeight )
9343 m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine );
9344 aLine.append( " w " );
9345 appendStrokingColor( aColor, aLine );
9346 aLine.append( "\n" );
9348 switch ( eTextLine )
9350 case LINESTYLE_DOTTED:
9351 case LINESTYLE_BOLDDOTTED:
9352 aLine.append( "[ " );
9353 m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
9354 aLine.append( " ] 0 d\n" );
9355 break;
9356 case LINESTYLE_DASH:
9357 case LINESTYLE_LONGDASH:
9358 case LINESTYLE_BOLDDASH:
9359 case LINESTYLE_BOLDLONGDASH:
9361 sal_Int32 nDashLength = 4*nLineHeight;
9362 sal_Int32 nVoidLength = 2*nLineHeight;
9363 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
9364 nDashLength = 8*nLineHeight;
9366 aLine.append( "[ " );
9367 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
9368 aLine.append( ' ' );
9369 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
9370 aLine.append( " ] 0 d\n" );
9372 break;
9373 case LINESTYLE_DASHDOT:
9374 case LINESTYLE_BOLDDASHDOT:
9376 sal_Int32 nDashLength = 4*nLineHeight;
9377 sal_Int32 nVoidLength = 2*nLineHeight;
9378 aLine.append( "[ " );
9379 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
9380 aLine.append( ' ' );
9381 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
9382 aLine.append( ' ' );
9383 m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
9384 aLine.append( ' ' );
9385 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
9386 aLine.append( " ] 0 d\n" );
9388 break;
9389 case LINESTYLE_DASHDOTDOT:
9390 case LINESTYLE_BOLDDASHDOTDOT:
9392 sal_Int32 nDashLength = 4*nLineHeight;
9393 sal_Int32 nVoidLength = 2*nLineHeight;
9394 aLine.append( "[ " );
9395 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
9396 aLine.append( ' ' );
9397 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
9398 aLine.append( ' ' );
9399 m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
9400 aLine.append( ' ' );
9401 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
9402 aLine.append( ' ' );
9403 m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
9404 aLine.append( ' ' );
9405 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
9406 aLine.append( " ] 0 d\n" );
9408 break;
9409 default:
9410 break;
9413 aLine.append( "0 " );
9414 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine );
9415 aLine.append( " m " );
9416 m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false );
9417 aLine.append( ' ' );
9418 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine );
9419 aLine.append( " l S\n" );
9420 if ( eTextLine == LINESTYLE_DOUBLE )
9422 aLine.append( "0 " );
9423 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine );
9424 aLine.append( " m " );
9425 m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false );
9426 aLine.append( ' ' );
9427 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine );
9428 aLine.append( " l S\n" );
9433 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor )
9435 // note: units in pFontInstance are ref device pixel
9436 LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance;
9437 long nLineHeight = 0;
9438 long nLinePos = 0;
9439 long nLinePos2 = 0;
9441 if ( eStrikeout > STRIKEOUT_X )
9442 eStrikeout = STRIKEOUT_SINGLE;
9444 switch ( eStrikeout )
9446 case STRIKEOUT_SINGLE:
9447 if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
9448 m_pReferenceDevice->ImplInitTextLineSize();
9449 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
9450 nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
9451 break;
9452 case STRIKEOUT_BOLD:
9453 if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
9454 m_pReferenceDevice->ImplInitTextLineSize();
9455 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
9456 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
9457 break;
9458 case STRIKEOUT_DOUBLE:
9459 if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
9460 m_pReferenceDevice->ImplInitTextLineSize();
9461 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
9462 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
9463 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
9464 break;
9465 default:
9466 break;
9469 if ( nLineHeight )
9471 m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine );
9472 aLine.append( " w " );
9473 appendStrokingColor( aColor, aLine );
9474 aLine.append( "\n" );
9476 aLine.append( "0 " );
9477 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine );
9478 aLine.append( " m " );
9479 m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine );
9480 aLine.append( ' ' );
9481 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine );
9482 aLine.append( " l S\n" );
9484 if ( eStrikeout == STRIKEOUT_DOUBLE )
9486 aLine.append( "0 " );
9487 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine );
9488 aLine.append( " m " );
9489 m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine );
9490 aLine.append( ' ' );
9491 m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine );
9492 aLine.append( " l S\n" );
9497 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout )
9499 //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
9500 //to tweak this
9502 OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
9503 OUString aStrikeout = aStrikeoutChar;
9504 while( m_pReferenceDevice->GetTextWidth( aStrikeout ) < nWidth )
9505 aStrikeout += aStrikeout;
9507 // do not get broader than nWidth modulo 1 character
9508 while( m_pReferenceDevice->GetTextWidth( aStrikeout ) >= nWidth )
9509 aStrikeout = aStrikeout.replaceAt( 0, 1, "" );
9510 aStrikeout += aStrikeoutChar;
9511 bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
9512 if ( bShadow )
9514 Font aFont = m_aCurrentPDFState.m_aFont;
9515 aFont.SetShadow( false );
9516 setFont( aFont );
9517 updateGraphicsState();
9520 // strikeout string is left aligned non-CTL text
9521 ComplexTextLayoutFlags nOrigTLM = m_pReferenceDevice->GetLayoutMode();
9522 m_pReferenceDevice->SetLayoutMode(ComplexTextLayoutFlags::BiDiStrong);
9524 push( PushFlags::CLIPREGION );
9525 FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric();
9526 tools::Rectangle aRect;
9527 aRect.Left() = rPos.X();
9528 aRect.Right() = aRect.Left()+nWidth;
9529 aRect.Bottom() = rPos.Y()+aRefDevFontMetric.GetDescent();
9530 aRect.Top() = rPos.Y()-aRefDevFontMetric.GetAscent();
9532 LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance;
9533 if (pFontInstance->mnOrientation)
9535 tools::Polygon aPoly( aRect );
9536 aPoly.Rotate( rPos, pFontInstance->mnOrientation);
9537 aRect = aPoly.GetBoundRect();
9540 intersectClipRegion( aRect );
9541 drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
9542 pop();
9544 m_pReferenceDevice->SetLayoutMode( nOrigTLM );
9546 if ( bShadow )
9548 Font aFont = m_aCurrentPDFState.m_aFont;
9549 aFont.SetShadow( true );
9550 setFont( aFont );
9551 updateGraphicsState();
9555 void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
9557 if ( !nWidth ||
9558 ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
9559 ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
9560 ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) )
9561 return;
9563 MARK( "drawTextLine" );
9564 updateGraphicsState();
9566 // note: units in pFontInstance are ref device pixel
9567 LogicalFontInstance* pFontInstance = m_pReferenceDevice->mpFontInstance;
9568 Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
9569 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
9570 Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
9571 bool bStrikeoutDone = false;
9572 bool bUnderlineDone = false;
9573 bool bOverlineDone = false;
9575 if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
9577 drawStrikeoutChar( rPos, nWidth, eStrikeout );
9578 bStrikeoutDone = true;
9581 Point aPos( rPos );
9582 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
9583 if( eAlign == ALIGN_TOP )
9584 aPos.Y() += HCONV( pFontInstance->mxFontMetric->GetAscent() );
9585 else if( eAlign == ALIGN_BOTTOM )
9586 aPos.Y() -= HCONV( pFontInstance->mxFontMetric->GetDescent() );
9588 OStringBuffer aLine( 512 );
9589 // save GS
9590 aLine.append( "q " );
9592 // rotate and translate matrix
9593 double fAngle = (double)m_aCurrentPDFState.m_aFont.GetOrientation() * M_PI / 1800.0;
9594 Matrix3 aMat;
9595 aMat.rotate( fAngle );
9596 aMat.translate( aPos.X(), aPos.Y() );
9597 aMat.append( m_aPages.back(), aLine );
9598 aLine.append( " cm\n" );
9600 if ( aUnderlineColor.GetTransparency() != 0 )
9601 aUnderlineColor = aStrikeoutColor;
9603 if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
9604 (eUnderline == LINESTYLE_WAVE) ||
9605 (eUnderline == LINESTYLE_DOUBLEWAVE) ||
9606 (eUnderline == LINESTYLE_BOLDWAVE) )
9608 drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
9609 bUnderlineDone = true;
9612 if ( (eOverline == LINESTYLE_SMALLWAVE) ||
9613 (eOverline == LINESTYLE_WAVE) ||
9614 (eOverline == LINESTYLE_DOUBLEWAVE) ||
9615 (eOverline == LINESTYLE_BOLDWAVE) )
9617 drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
9618 bOverlineDone = true;
9621 if ( !bUnderlineDone )
9623 drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
9626 if ( !bOverlineDone )
9628 drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
9631 if ( !bStrikeoutDone )
9633 drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
9636 aLine.append( "Q\n" );
9637 writeBuffer( aLine.getStr(), aLine.getLength() );
9640 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
9642 MARK( "drawPolygon" );
9644 updateGraphicsState();
9646 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
9647 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
9648 return;
9650 int nPoints = rPoly.GetSize();
9651 OStringBuffer aLine( 20 * nPoints );
9652 m_aPages.back().appendPolygon( rPoly, aLine );
9653 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
9654 m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
9655 aLine.append( "B*\n" );
9656 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
9657 aLine.append( "S\n" );
9658 else
9659 aLine.append( "f*\n" );
9661 writeBuffer( aLine.getStr(), aLine.getLength() );
9664 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
9666 MARK( "drawPolyPolygon" );
9668 updateGraphicsState();
9670 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
9671 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
9672 return;
9674 int nPolygons = rPolyPoly.Count();
9676 OStringBuffer aLine( 40 * nPolygons );
9677 m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
9678 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
9679 m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
9680 aLine.append( "B*\n" );
9681 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
9682 aLine.append( "S\n" );
9683 else
9684 aLine.append( "f*\n" );
9686 writeBuffer( aLine.getStr(), aLine.getLength() );
9689 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
9691 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
9692 nTransparentPercent = nTransparentPercent % 100;
9694 MARK( "drawTransparent" );
9696 updateGraphicsState();
9698 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
9699 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
9700 return;
9702 if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9704 m_aErrors.insert( m_bIsPDF_A1 ?
9705 PDFWriter::Warning_Transparency_Omitted_PDFA :
9706 PDFWriter::Warning_Transparency_Omitted_PDF13 );
9708 drawPolyPolygon( rPolyPoly );
9709 return;
9712 // create XObject
9713 m_aTransparentObjects.push_back( TransparencyEmit() );
9714 // FIXME: polygons with beziers may yield incorrect bound rect
9715 m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
9716 // convert rectangle to default user space
9717 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
9718 m_aTransparentObjects.back().m_nObject = createObject();
9719 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
9720 m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0;
9721 m_aTransparentObjects.back().m_pContentStream = new SvMemoryStream( 256, 256 );
9722 // create XObject's content stream
9723 OStringBuffer aContent( 256 );
9724 m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
9725 if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) &&
9726 m_aCurrentPDFState.m_aFillColor != Color( COL_TRANSPARENT ) )
9727 aContent.append( " B*\n" );
9728 else if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) )
9729 aContent.append( " S\n" );
9730 else
9731 aContent.append( " f*\n" );
9732 m_aTransparentObjects.back().m_pContentStream->WriteBytes(
9733 aContent.getStr(), aContent.getLength() );
9735 OStringBuffer aObjName( 16 );
9736 aObjName.append( "Tr" );
9737 aObjName.append( m_aTransparentObjects.back().m_nObject );
9738 OString aTrName( aObjName.makeStringAndClear() );
9739 aObjName.append( "EGS" );
9740 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
9741 OString aExtName( aObjName.makeStringAndClear() );
9743 OStringBuffer aLine( 80 );
9744 // insert XObject
9745 aLine.append( "q /" );
9746 aLine.append( aExtName );
9747 aLine.append( " gs /" );
9748 aLine.append( aTrName );
9749 aLine.append( " Do Q\n" );
9750 writeBuffer( aLine.getStr(), aLine.getLength() );
9752 pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
9753 pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
9756 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
9758 if( nObject >= 0 )
9760 switch( eKind )
9762 case ResXObject:
9763 m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
9764 if( ! m_aOutputStreams.empty() )
9765 m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
9766 break;
9767 case ResExtGState:
9768 m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
9769 if( ! m_aOutputStreams.empty() )
9770 m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
9771 break;
9772 case ResShading:
9773 m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
9774 if( ! m_aOutputStreams.empty() )
9775 m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
9776 break;
9777 case ResPattern:
9778 m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
9779 if( ! m_aOutputStreams.empty() )
9780 m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
9781 break;
9786 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
9788 push( PushFlags::ALL );
9790 // force reemitting clip region inside the new stream, and
9791 // prevent emitting an unbalanced "Q" at the start
9792 clearClipRegion();
9793 // this is needed to point m_aCurrentPDFState at the pushed state
9794 // ... but it's pointless to actually write into the "outer" stream here!
9795 updateGraphicsState(NOWRITE);
9797 m_aOutputStreams.push_front( StreamRedirect() );
9798 m_aOutputStreams.front().m_pStream = pStream;
9799 m_aOutputStreams.front().m_aMapMode = m_aMapMode;
9801 if( !rTargetRect.IsEmpty() )
9803 m_aOutputStreams.front().m_aTargetRect =
9804 lcl_convert( m_aGraphicsStack.front().m_aMapMode,
9805 m_aMapMode,
9806 getReferenceDevice(),
9807 rTargetRect );
9808 Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
9809 long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
9810 aDelta.Y() = -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom());
9811 m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
9814 // setup graphics state for independent object stream
9816 // force reemitting colors
9817 m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT );
9818 m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT );
9821 SvStream* PDFWriterImpl::endRedirect()
9823 SvStream* pStream = nullptr;
9824 if( ! m_aOutputStreams.empty() )
9826 pStream = m_aOutputStreams.front().m_pStream;
9827 m_aMapMode = m_aOutputStreams.front().m_aMapMode;
9828 m_aOutputStreams.pop_front();
9831 pop();
9833 m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT );
9834 m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT );
9836 // needed after pop() to set m_aCurrentPDFState
9837 updateGraphicsState(NOWRITE);
9839 return pStream;
9842 void PDFWriterImpl::beginTransparencyGroup()
9844 updateGraphicsState();
9845 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9846 beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
9849 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
9851 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
9852 nTransparentPercent = nTransparentPercent % 100;
9854 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9856 // create XObject
9857 m_aTransparentObjects.push_back( TransparencyEmit() );
9858 m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
9859 // convert rectangle to default user space
9860 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
9861 m_aTransparentObjects.back().m_nObject = createObject();
9862 m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0;
9863 // get XObject's content stream
9864 m_aTransparentObjects.back().m_pContentStream = static_cast<SvMemoryStream*>(endRedirect());
9865 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
9867 OStringBuffer aObjName( 16 );
9868 aObjName.append( "Tr" );
9869 aObjName.append( m_aTransparentObjects.back().m_nObject );
9870 OString aTrName( aObjName.makeStringAndClear() );
9871 aObjName.append( "EGS" );
9872 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
9873 OString aExtName( aObjName.makeStringAndClear() );
9875 OStringBuffer aLine( 80 );
9876 // insert XObject
9877 aLine.append( "q /" );
9878 aLine.append( aExtName );
9879 aLine.append( " gs /" );
9880 aLine.append( aTrName );
9881 aLine.append( " Do Q\n" );
9882 writeBuffer( aLine.getStr(), aLine.getLength() );
9884 pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
9885 pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
9889 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
9891 MARK( "drawRectangle" );
9893 updateGraphicsState();
9895 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
9896 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
9897 return;
9899 OStringBuffer aLine( 40 );
9900 m_aPages.back().appendRect( rRect, aLine );
9902 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
9903 m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
9904 aLine.append( " B*\n" );
9905 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
9906 aLine.append( " S\n" );
9907 else
9908 aLine.append( " f*\n" );
9910 writeBuffer( aLine.getStr(), aLine.getLength() );
9913 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
9915 MARK( "drawRectangle with rounded edges" );
9917 if( !nHorzRound && !nVertRound )
9918 drawRectangle( rRect );
9920 updateGraphicsState();
9922 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
9923 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
9924 return;
9926 if( nHorzRound > (sal_uInt32)rRect.GetWidth()/2 )
9927 nHorzRound = rRect.GetWidth()/2;
9928 if( nVertRound > (sal_uInt32)rRect.GetHeight()/2 )
9929 nVertRound = rRect.GetHeight()/2;
9931 Point aPoints[16];
9932 const double kappa = 0.5522847498;
9933 const sal_uInt32 kx = (sal_uInt32)((kappa*(double)nHorzRound)+0.5);
9934 const sal_uInt32 ky = (sal_uInt32)((kappa*(double)nVertRound)+0.5);
9936 aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
9937 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
9938 aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
9939 aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
9941 aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
9942 aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
9943 aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
9944 aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
9946 aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
9947 aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
9948 aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
9949 aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
9951 aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
9952 aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
9953 aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
9954 aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
9956 OStringBuffer aLine( 80 );
9957 m_aPages.back().appendPoint( aPoints[1], aLine );
9958 aLine.append( " m " );
9959 m_aPages.back().appendPoint( aPoints[2], aLine );
9960 aLine.append( " l " );
9961 m_aPages.back().appendPoint( aPoints[3], aLine );
9962 aLine.append( ' ' );
9963 m_aPages.back().appendPoint( aPoints[4], aLine );
9964 aLine.append( ' ' );
9965 m_aPages.back().appendPoint( aPoints[5], aLine );
9966 aLine.append( " c\n" );
9967 m_aPages.back().appendPoint( aPoints[6], aLine );
9968 aLine.append( " l " );
9969 m_aPages.back().appendPoint( aPoints[7], aLine );
9970 aLine.append( ' ' );
9971 m_aPages.back().appendPoint( aPoints[8], aLine );
9972 aLine.append( ' ' );
9973 m_aPages.back().appendPoint( aPoints[9], aLine );
9974 aLine.append( " c\n" );
9975 m_aPages.back().appendPoint( aPoints[10], aLine );
9976 aLine.append( " l " );
9977 m_aPages.back().appendPoint( aPoints[11], aLine );
9978 aLine.append( ' ' );
9979 m_aPages.back().appendPoint( aPoints[12], aLine );
9980 aLine.append( ' ' );
9981 m_aPages.back().appendPoint( aPoints[13], aLine );
9982 aLine.append( " c\n" );
9983 m_aPages.back().appendPoint( aPoints[14], aLine );
9984 aLine.append( " l " );
9985 m_aPages.back().appendPoint( aPoints[15], aLine );
9986 aLine.append( ' ' );
9987 m_aPages.back().appendPoint( aPoints[0], aLine );
9988 aLine.append( ' ' );
9989 m_aPages.back().appendPoint( aPoints[1], aLine );
9990 aLine.append( " c " );
9992 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
9993 m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
9994 aLine.append( "b*\n" );
9995 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
9996 aLine.append( "s\n" );
9997 else
9998 aLine.append( "f*\n" );
10000 writeBuffer( aLine.getStr(), aLine.getLength() );
10003 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
10005 MARK( "drawEllipse" );
10007 updateGraphicsState();
10009 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
10010 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
10011 return;
10013 Point aPoints[12];
10014 const double kappa = 0.5522847498;
10015 const sal_uInt32 kx = (sal_uInt32)((kappa*(double)rRect.GetWidth()/2.0)+0.5);
10016 const sal_uInt32 ky = (sal_uInt32)((kappa*(double)rRect.GetHeight()/2.0)+0.5);
10018 aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
10019 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
10020 aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
10022 aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 );
10023 aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
10024 aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
10026 aPoints[7] = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 );
10027 aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
10028 aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
10030 aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
10031 aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
10032 aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
10034 OStringBuffer aLine( 80 );
10035 m_aPages.back().appendPoint( aPoints[1], aLine );
10036 aLine.append( " m " );
10037 m_aPages.back().appendPoint( aPoints[2], aLine );
10038 aLine.append( ' ' );
10039 m_aPages.back().appendPoint( aPoints[3], aLine );
10040 aLine.append( ' ' );
10041 m_aPages.back().appendPoint( aPoints[4], aLine );
10042 aLine.append( " c\n" );
10043 m_aPages.back().appendPoint( aPoints[5], aLine );
10044 aLine.append( ' ' );
10045 m_aPages.back().appendPoint( aPoints[6], aLine );
10046 aLine.append( ' ' );
10047 m_aPages.back().appendPoint( aPoints[7], aLine );
10048 aLine.append( " c\n" );
10049 m_aPages.back().appendPoint( aPoints[8], aLine );
10050 aLine.append( ' ' );
10051 m_aPages.back().appendPoint( aPoints[9], aLine );
10052 aLine.append( ' ' );
10053 m_aPages.back().appendPoint( aPoints[10], aLine );
10054 aLine.append( " c\n" );
10055 m_aPages.back().appendPoint( aPoints[11], aLine );
10056 aLine.append( ' ' );
10057 m_aPages.back().appendPoint( aPoints[0], aLine );
10058 aLine.append( ' ' );
10059 m_aPages.back().appendPoint( aPoints[1], aLine );
10060 aLine.append( " c " );
10062 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
10063 m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
10064 aLine.append( "b*\n" );
10065 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
10066 aLine.append( "s\n" );
10067 else
10068 aLine.append( "f*\n" );
10070 writeBuffer( aLine.getStr(), aLine.getLength() );
10073 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
10075 Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
10076 (rRect.Top()+rRect.Bottom()+1)/2);
10077 Point aPoint = rPoint - aOrigin;
10079 double fX = (double)aPoint.X();
10080 double fY = (double)-aPoint.Y();
10082 if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
10083 throw o3tl::divide_by_zero();
10085 if( rRect.GetWidth() > rRect.GetHeight() )
10086 fY = fY*((double)rRect.GetWidth()/(double)rRect.GetHeight());
10087 else if( rRect.GetHeight() > rRect.GetWidth() )
10088 fX = fX*((double)rRect.GetHeight()/(double)rRect.GetWidth());
10089 return atan2( fY, fX );
10092 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
10094 MARK( "drawArc" );
10096 updateGraphicsState();
10098 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
10099 m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
10100 return;
10102 // calculate start and stop angles
10103 const double fStartAngle = calcAngle( rRect, rStart );
10104 double fStopAngle = calcAngle( rRect, rStop );
10105 while( fStopAngle < fStartAngle )
10106 fStopAngle += 2.0*M_PI;
10107 const int nFragments = (int)((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
10108 const double fFragmentDelta = (fStopAngle-fStartAngle)/(double)nFragments;
10109 const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
10110 const double halfWidth = (double)rRect.GetWidth()/2.0;
10111 const double halfHeight = (double)rRect.GetHeight()/2.0;
10113 const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
10114 (rRect.Top()+rRect.Bottom()+1)/2 );
10116 OStringBuffer aLine( 30*nFragments );
10117 Point aPoint( (int)(halfWidth * cos(fStartAngle) ),
10118 -(int)(halfHeight * sin(fStartAngle) ) );
10119 aPoint += aCenter;
10120 m_aPages.back().appendPoint( aPoint, aLine );
10121 aLine.append( " m " );
10122 if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
10124 for( int i = 0; i < nFragments; i++ )
10126 const double fStartFragment = fStartAngle + (double)i*fFragmentDelta;
10127 const double fStopFragment = fStartFragment + fFragmentDelta;
10128 aPoint = Point( (int)(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
10129 -(int)(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
10130 aPoint += aCenter;
10131 m_aPages.back().appendPoint( aPoint, aLine );
10132 aLine.append( ' ' );
10134 aPoint = Point( (int)(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
10135 -(int)(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
10136 aPoint += aCenter;
10137 m_aPages.back().appendPoint( aPoint, aLine );
10138 aLine.append( ' ' );
10140 aPoint = Point( (int)(halfWidth * cos(fStopFragment) ),
10141 -(int)(halfHeight * sin(fStopFragment) ) );
10142 aPoint += aCenter;
10143 m_aPages.back().appendPoint( aPoint, aLine );
10144 aLine.append( " c\n" );
10147 if( bWithChord || bWithPie )
10149 if( bWithPie )
10151 m_aPages.back().appendPoint( aCenter, aLine );
10152 aLine.append( " l " );
10154 aLine.append( "h " );
10156 if( ! bWithChord && ! bWithPie )
10157 aLine.append( "S\n" );
10158 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
10159 m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
10160 aLine.append( "B*\n" );
10161 else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
10162 aLine.append( "S\n" );
10163 else
10164 aLine.append( "f*\n" );
10166 writeBuffer( aLine.getStr(), aLine.getLength() );
10169 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
10171 MARK( "drawPolyLine" );
10173 sal_uInt16 nPoints = rPoly.GetSize();
10174 if( nPoints < 2 )
10175 return;
10177 updateGraphicsState();
10179 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
10180 return;
10182 OStringBuffer aLine( 20 * nPoints );
10183 m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
10184 aLine.append( "S\n" );
10186 writeBuffer( aLine.getStr(), aLine.getLength() );
10189 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
10191 MARK( "drawPolyLine with LineInfo" );
10193 updateGraphicsState();
10195 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
10196 return;
10198 OStringBuffer aLine;
10199 aLine.append( "q " );
10200 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
10202 writeBuffer( aLine.getStr(), aLine.getLength() );
10203 drawPolyLine( rPoly );
10204 writeBuffer( "Q\n", 2 );
10206 else
10208 PDFWriter::ExtLineInfo aInfo;
10209 convertLineInfoToExtLineInfo( rInfo, aInfo );
10210 drawPolyLine( rPoly, aInfo );
10214 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
10216 SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
10217 rOut.m_fLineWidth = rIn.GetWidth();
10218 rOut.m_fTransparency = 0.0;
10219 rOut.m_eCap = PDFWriter::capButt;
10220 rOut.m_eJoin = PDFWriter::joinMiter;
10221 rOut.m_fMiterLimit = 10;
10222 rOut.m_aDashArray.clear();
10224 // add DashDot to DashArray
10225 const int nDashes = rIn.GetDashCount();
10226 const int nDashLen = rIn.GetDashLen();
10227 const int nDistance = rIn.GetDistance();
10229 for( int n = 0; n < nDashes; n++ )
10231 rOut.m_aDashArray.push_back( nDashLen );
10232 rOut.m_aDashArray.push_back( nDistance );
10234 const int nDots = rIn.GetDotCount();
10235 const int nDotLen = rIn.GetDotLen();
10237 for( int n = 0; n < nDots; n++ )
10239 rOut.m_aDashArray.push_back( nDotLen );
10240 rOut.m_aDashArray.push_back( nDistance );
10243 // add LineJoin
10244 switch(rIn.GetLineJoin())
10246 case basegfx::B2DLineJoin::Bevel :
10248 rOut.m_eJoin = PDFWriter::joinBevel;
10249 break;
10251 // Pdf has no 'none' lineJoin, default is miter
10252 case basegfx::B2DLineJoin::NONE :
10253 case basegfx::B2DLineJoin::Miter :
10255 rOut.m_eJoin = PDFWriter::joinMiter;
10256 break;
10258 case basegfx::B2DLineJoin::Round :
10260 rOut.m_eJoin = PDFWriter::joinRound;
10261 break;
10265 // add LineCap
10266 switch(rIn.GetLineCap())
10268 default: /* css::drawing::LineCap_BUTT */
10270 rOut.m_eCap = PDFWriter::capButt;
10271 break;
10273 case css::drawing::LineCap_ROUND:
10275 rOut.m_eCap = PDFWriter::capRound;
10276 break;
10278 case css::drawing::LineCap_SQUARE:
10280 rOut.m_eCap = PDFWriter::capSquare;
10281 break;
10286 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
10288 MARK( "drawPolyLine with ExtLineInfo" );
10290 updateGraphicsState();
10292 if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
10293 return;
10295 if( rInfo.m_fTransparency >= 1.0 )
10296 return;
10298 if( rInfo.m_fTransparency != 0.0 )
10299 beginTransparencyGroup();
10301 OStringBuffer aLine;
10302 aLine.append( "q " );
10303 m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
10304 aLine.append( " w" );
10305 if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
10307 switch( rInfo.m_eCap )
10309 default:
10310 case PDFWriter::capButt: aLine.append( " 0 J" );break;
10311 case PDFWriter::capRound: aLine.append( " 1 J" );break;
10312 case PDFWriter::capSquare: aLine.append( " 2 J" );break;
10314 switch( rInfo.m_eJoin )
10316 default:
10317 case PDFWriter::joinMiter:
10319 double fLimit = rInfo.m_fMiterLimit;
10320 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
10321 fLimit = fLimit / rInfo.m_fLineWidth;
10322 if( fLimit < 1.0 )
10323 fLimit = 1.0;
10324 aLine.append( " 0 j " );
10325 appendDouble( fLimit, aLine );
10326 aLine.append( " M" );
10328 break;
10329 case PDFWriter::joinRound: aLine.append( " 1 j" );break;
10330 case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
10332 if( rInfo.m_aDashArray.size() > 0 )
10334 aLine.append( " [ " );
10335 for( std::vector<double>::const_iterator it = rInfo.m_aDashArray.begin();
10336 it != rInfo.m_aDashArray.end(); ++it )
10338 m_aPages.back().appendMappedLength( *it, aLine );
10339 aLine.append( ' ' );
10341 aLine.append( "] 0 d" );
10343 aLine.append( "\n" );
10344 writeBuffer( aLine.getStr(), aLine.getLength() );
10345 drawPolyLine( rPoly );
10347 else
10349 basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
10350 basegfx::B2DPolyPolygon aPolyPoly;
10352 basegfx::tools::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
10354 // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
10355 // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
10356 // this line needs to be removed and the loop below adapted accordingly
10357 aPolyPoly = basegfx::tools::adaptiveSubdivideByAngle(aPolyPoly);
10359 const sal_uInt32 nPolygonCount(aPolyPoly.count());
10361 for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
10363 aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
10364 aPoly = aPolyPoly.getB2DPolygon( nPoly );
10365 const sal_uInt32 nPointCount(aPoly.count());
10367 if(nPointCount)
10369 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
10370 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
10372 for(sal_uInt32 a(0); a < nEdgeCount; a++)
10374 if( a > 0 )
10375 aLine.append( " " );
10376 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
10377 const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
10379 m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
10380 FRound(aCurrent.getY()) ),
10381 aLine );
10382 aLine.append( " m " );
10383 m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
10384 FRound(aNext.getY()) ),
10385 aLine );
10386 aLine.append( " l" );
10388 // prepare next edge
10389 aCurrent = aNext;
10393 aLine.append( " S " );
10394 writeBuffer( aLine.getStr(), aLine.getLength() );
10396 writeBuffer( "Q\n", 2 );
10398 if( rInfo.m_fTransparency != 0.0 )
10400 // FIXME: actually this may be incorrect with bezier polygons
10401 tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
10402 // avoid clipping with thick lines
10403 if( rInfo.m_fLineWidth > 0.0 )
10405 sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
10406 aBoundRect.Top() -= nLW;
10407 aBoundRect.Left() -= nLW;
10408 aBoundRect.Right() += nLW;
10409 aBoundRect.Bottom() += nLW;
10411 endTransparencyGroup( aBoundRect, (sal_uInt16)(100.0*rInfo.m_fTransparency) );
10415 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
10417 MARK( "drawPixel" );
10419 Color aColor = ( rColor == Color( COL_TRANSPARENT ) ? m_aGraphicsStack.front().m_aLineColor : rColor );
10421 if( aColor == Color( COL_TRANSPARENT ) )
10422 return;
10424 // pixels are drawn in line color, so have to set
10425 // the nonstroking color to line color
10426 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
10427 setFillColor( aColor );
10429 updateGraphicsState();
10431 OStringBuffer aLine( 20 );
10432 m_aPages.back().appendPoint( rPoint, aLine );
10433 aLine.append( ' ' );
10434 appendDouble( 1.0/double(getReferenceDevice()->GetDPIX()), aLine );
10435 aLine.append( ' ' );
10436 appendDouble( 1.0/double(getReferenceDevice()->GetDPIY()), aLine );
10437 aLine.append( " re f\n" );
10438 writeBuffer( aLine.getStr(), aLine.getLength() );
10440 setFillColor( aOldFillColor );
10443 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
10445 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
10447 bool bFlateFilter = compressStream( rObject.m_pContentStream );
10448 rObject.m_pContentStream->Seek( STREAM_SEEK_TO_END );
10449 sal_uLong nSize = rObject.m_pContentStream->Tell();
10450 rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
10451 #if OSL_DEBUG_LEVEL > 1
10452 emitComment( "PDFWriterImpl::writeTransparentObject" );
10453 #endif
10454 OStringBuffer aLine( 512 );
10455 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
10456 aLine.append( rObject.m_nObject );
10457 aLine.append( " 0 obj\n"
10458 "<</Type/XObject\n"
10459 "/Subtype/Form\n"
10460 "/BBox[ " );
10461 appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
10462 aLine.append( ' ' );
10463 appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
10464 aLine.append( ' ' );
10465 appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
10466 aLine.append( ' ' );
10467 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
10468 aLine.append( " ]\n" );
10469 if( ! rObject.m_pSoftMaskStream )
10471 if( ! m_bIsPDF_A1 )
10473 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
10476 /* #i42884# the PDF reference recommends that each Form XObject
10477 * should have a resource dict; alas if that is the same object
10478 * as the one of the page it triggers an endless recursion in
10479 * acroread 5 (6 and up have that fixed). Since we have only one
10480 * resource dict anyway, let's use the one from the page by NOT
10481 * emitting a Resources entry.
10484 aLine.append( "/Length " );
10485 aLine.append( (sal_Int32)(nSize) );
10486 aLine.append( "\n" );
10487 if( bFlateFilter )
10488 aLine.append( "/Filter/FlateDecode\n" );
10489 aLine.append( ">>\n"
10490 "stream\n" );
10491 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10492 checkAndEnableStreamEncryption( rObject.m_nObject );
10493 CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
10494 disableStreamEncryption();
10495 aLine.setLength( 0 );
10496 aLine.append( "\n"
10497 "endstream\n"
10498 "endobj\n\n" );
10499 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10501 // write ExtGState dict for this XObject
10502 aLine.setLength( 0 );
10503 aLine.append( rObject.m_nExtGStateObject );
10504 aLine.append( " 0 obj\n"
10505 "<<" );
10506 if( ! rObject.m_pSoftMaskStream )
10508 if( m_bIsPDF_A1 )
10510 aLine.append( "/CA 1.0/ca 1.0" );
10511 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
10513 else
10515 aLine.append( "/CA " );
10516 appendDouble( rObject.m_fAlpha, aLine );
10517 aLine.append( "\n"
10518 " /ca " );
10519 appendDouble( rObject.m_fAlpha, aLine );
10521 aLine.append( "\n" );
10523 else
10525 if( m_bIsPDF_A1 )
10527 aLine.append( "/SMask/None" );
10528 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
10530 else
10532 rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_END );
10533 sal_Int32 nMaskSize = (sal_Int32)rObject.m_pSoftMaskStream->Tell();
10534 rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
10535 sal_Int32 nMaskObject = createObject();
10536 aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
10537 aLine.append( nMaskObject );
10538 aLine.append( " 0 R>>\n" );
10540 OStringBuffer aMask;
10541 aMask.append( nMaskObject );
10542 aMask.append( " 0 obj\n"
10543 "<</Type/XObject\n"
10544 "/Subtype/Form\n"
10545 "/BBox[" );
10546 appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
10547 aMask.append( ' ' );
10548 appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
10549 aMask.append( ' ' );
10550 appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
10551 aMask.append( ' ' );
10552 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
10553 aMask.append( "]\n" );
10555 /* #i42884# see above */
10556 aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
10557 aMask.append( "/Length " );
10558 aMask.append( nMaskSize );
10559 aMask.append( ">>\n"
10560 "stream\n" );
10561 CHECK_RETURN2( updateObject( nMaskObject ) );
10562 checkAndEnableStreamEncryption( nMaskObject );
10563 CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
10564 CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
10565 disableStreamEncryption();
10566 aMask.setLength( 0 );
10567 aMask.append( "\nendstream\n"
10568 "endobj\n\n" );
10569 CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
10572 aLine.append( ">>\n"
10573 "endobj\n\n" );
10574 CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
10575 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10578 bool PDFWriterImpl::writeGradientFunction( GradientEmit& rObject )
10580 // LO internal gradient -> PDF shading type:
10581 // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
10582 // [t=0:colorStart, t=1:colorEnd]
10583 // * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
10584 // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
10585 // * other styles: function shading with aSize.Width() * aSize.Height() samples
10586 sal_Int32 nFunctionObject = createObject();
10587 CHECK_RETURN( updateObject( nFunctionObject ) );
10589 ScopedVclPtrInstance< VirtualDevice > aDev;
10590 aDev->SetOutputSizePixel( rObject.m_aSize );
10591 aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
10592 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
10593 aDev->SetDrawMode( aDev->GetDrawMode() |
10594 ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
10595 DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
10596 aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
10598 Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
10599 Bitmap::ScopedReadAccess pAccess(aSample);
10601 Size aSize = aSample.GetSizePixel();
10603 sal_Int32 nStreamLengthObject = createObject();
10604 #if OSL_DEBUG_LEVEL > 1
10605 emitComment( "PDFWriterImpl::writeGradientFunction" );
10606 #endif
10607 OStringBuffer aLine( 120 );
10608 aLine.append( nFunctionObject );
10609 aLine.append( " 0 obj\n"
10610 "<</FunctionType 0\n");
10611 switch (rObject.m_aGradient.GetStyle())
10613 case GradientStyle::Linear:
10614 case GradientStyle::Axial:
10615 aLine.append("/Domain[ 0 1]\n");
10616 break;
10617 default:
10618 aLine.append("/Domain[ 0 1 0 1]\n");
10620 aLine.append("/Size[ " );
10621 switch (rObject.m_aGradient.GetStyle())
10623 case GradientStyle::Linear:
10624 aLine.append('2');
10625 break;
10626 case GradientStyle::Axial:
10627 aLine.append('3');
10628 break;
10629 default:
10630 aLine.append( (sal_Int32)aSize.Width() );
10631 aLine.append( ' ' );
10632 aLine.append( (sal_Int32)aSize.Height() );
10634 aLine.append( " ]\n"
10635 "/BitsPerSample 8\n"
10636 "/Range[ 0 1 0 1 0 1 ]\n"
10637 "/Order 3\n"
10638 "/Length " );
10639 aLine.append( nStreamLengthObject );
10640 if (!g_bDebugDisableCompression)
10641 aLine.append( " 0 R\n"
10642 "/Filter/FlateDecode"
10643 ">>\n"
10644 "stream\n" );
10645 else
10646 aLine.append( " 0 R\n"
10647 ">>\n"
10648 "stream\n" );
10649 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10651 sal_uInt64 nStartStreamPos = 0;
10652 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
10654 checkAndEnableStreamEncryption( nFunctionObject );
10655 beginCompression();
10656 sal_uInt8 aCol[3];
10657 switch (rObject.m_aGradient.GetStyle())
10659 case GradientStyle::Axial:
10660 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
10661 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
10662 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
10663 CHECK_RETURN( writeBuffer( aCol, 3 ) );
10664 SAL_FALLTHROUGH;
10665 case GradientStyle::Linear:
10667 aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
10668 aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
10669 aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
10670 CHECK_RETURN( writeBuffer( aCol, 3 ) );
10672 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
10673 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
10674 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
10675 CHECK_RETURN( writeBuffer( aCol, 3 ) );
10676 break;
10678 default:
10679 for( int y = aSize.Height()-1; y >= 0; y-- )
10681 for( long x = 0; x < aSize.Width(); x++ )
10683 BitmapColor aColor = pAccess->GetColor( y, x );
10684 aCol[0] = aColor.GetRed();
10685 aCol[1] = aColor.GetGreen();
10686 aCol[2] = aColor.GetBlue();
10687 CHECK_RETURN( writeBuffer( aCol, 3 ) );
10691 endCompression();
10692 disableStreamEncryption();
10694 sal_uInt64 nEndStreamPos = 0;
10695 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
10697 aLine.setLength( 0 );
10698 aLine.append( "\nendstream\nendobj\n\n" );
10699 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10701 // write stream length
10702 CHECK_RETURN( updateObject( nStreamLengthObject ) );
10703 aLine.setLength( 0 );
10704 aLine.append( nStreamLengthObject );
10705 aLine.append( " 0 obj\n" );
10706 aLine.append( (sal_Int64)(nEndStreamPos-nStartStreamPos) );
10707 aLine.append( "\nendobj\n\n" );
10708 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10710 CHECK_RETURN( updateObject( rObject.m_nObject ) );
10711 aLine.setLength( 0 );
10712 aLine.append( rObject.m_nObject );
10713 aLine.append( " 0 obj\n");
10714 switch (rObject.m_aGradient.GetStyle())
10716 case GradientStyle::Linear:
10717 case GradientStyle::Axial:
10718 aLine.append("<</ShadingType 2\n");
10719 break;
10720 default:
10721 aLine.append("<</ShadingType 1\n");
10723 aLine.append("/ColorSpace/DeviceRGB\n"
10724 "/AntiAlias true\n");
10726 // Determination of shading axis
10727 // See: OutputDevice::ImplDrawLinearGradient for reference
10728 tools::Rectangle aRect;
10729 aRect.Left() = aRect.Top() = 0;
10730 aRect.Right() = aSize.Width();
10731 aRect.Bottom() = aSize.Height();
10733 tools::Rectangle aBoundRect;
10734 Point aCenter;
10735 sal_uInt16 nAngle = rObject.m_aGradient.GetAngle() % 3600;
10736 rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
10738 const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
10739 double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
10740 if ( !bLinear )
10742 fBorder /= 2.0;
10745 aBoundRect.Bottom() -= fBorder;
10746 if (!bLinear)
10748 aBoundRect.Top() += fBorder;
10751 switch (rObject.m_aGradient.GetStyle())
10753 case GradientStyle::Linear:
10754 case GradientStyle::Axial:
10756 aLine.append("/Domain[ 0 1 ]\n"
10757 "/Coords[ " );
10758 tools::Polygon aPoly( 2 );
10759 aPoly[0] = aBoundRect.BottomCenter();
10760 aPoly[1] = aBoundRect.TopCenter();
10761 aPoly.Rotate( aCenter, 3600 - nAngle );
10763 aLine.append( (sal_Int32) aPoly[0].X() );
10764 aLine.append( " " );
10765 aLine.append( (sal_Int32) aPoly[0].Y() );
10766 aLine.append( " " );
10767 aLine.append( (sal_Int32) aPoly[1].X());
10768 aLine.append( " ");
10769 aLine.append( (sal_Int32) aPoly[1].Y());
10770 aLine.append( " ]\n");
10771 aLine.append("/Extend [true true]\n");
10772 break;
10774 default:
10775 aLine.append("/Domain[ 0 1 0 1 ]\n"
10776 "/Matrix[ " );
10777 aLine.append( (sal_Int32)aSize.Width() );
10778 aLine.append( " 0 0 " );
10779 aLine.append( (sal_Int32)aSize.Height() );
10780 aLine.append( " 0 0 ]\n");
10782 aLine.append("/Function " );
10783 aLine.append( nFunctionObject );
10784 aLine.append( " 0 R\n"
10785 ">>\n"
10786 "endobj\n\n" );
10787 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10789 return true;
10792 void PDFWriterImpl::writeJPG( JPGEmit& rObject )
10794 if (rObject.m_aReferenceXObject.m_aPDFData.hasElements() && !m_aContext.UseReferenceXObject)
10796 writeReferenceXObject(rObject.m_aReferenceXObject);
10797 return;
10800 CHECK_RETURN2( rObject.m_pStream );
10801 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
10803 sal_Int32 nLength = 0;
10804 rObject.m_pStream->Seek( STREAM_SEEK_TO_END );
10805 nLength = rObject.m_pStream->Tell();
10806 rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
10808 sal_Int32 nMaskObject = 0;
10809 if( !!rObject.m_aMask )
10811 if( rObject.m_aMask.GetBitCount() == 1 ||
10812 ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && !m_bIsPDF_A1 )
10815 nMaskObject = createObject();
10817 else if( m_bIsPDF_A1 )
10818 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
10819 else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
10820 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
10823 #if OSL_DEBUG_LEVEL > 1
10824 emitComment( "PDFWriterImpl::writeJPG" );
10825 #endif
10827 OStringBuffer aLine(200);
10828 aLine.append( rObject.m_nObject );
10829 aLine.append( " 0 obj\n"
10830 "<</Type/XObject/Subtype/Image/Width " );
10831 aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Width() );
10832 aLine.append( " /Height " );
10833 aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Height() );
10834 aLine.append( " /BitsPerComponent 8 " );
10835 if( rObject.m_bTrueColor )
10836 aLine.append( "/ColorSpace/DeviceRGB" );
10837 else
10838 aLine.append( "/ColorSpace/DeviceGray" );
10839 aLine.append( "/Filter/DCTDecode/Length " );
10840 aLine.append( nLength );
10841 if( nMaskObject )
10843 aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
10844 aLine.append( nMaskObject );
10845 aLine.append( " 0 R " );
10847 aLine.append( ">>\nstream\n" );
10848 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10850 checkAndEnableStreamEncryption( rObject.m_nObject );
10851 CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
10852 disableStreamEncryption();
10854 aLine.setLength( 0 );
10855 aLine.append( "\nendstream\nendobj\n\n" );
10856 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
10858 if( nMaskObject )
10860 BitmapEmit aEmit;
10861 aEmit.m_nObject = nMaskObject;
10862 if( rObject.m_aMask.GetBitCount() == 1 )
10863 aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
10864 else if( rObject.m_aMask.GetBitCount() == 8 )
10865 aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
10866 writeBitmapObject( aEmit, true );
10869 writeReferenceXObject(rObject.m_aReferenceXObject);
10872 sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources)
10874 auto it = rCopiedResources.find(rObject.GetObjectValue());
10875 if (it != rCopiedResources.end())
10876 // This resource was already copied once, nothing to do.
10877 return it->second;
10879 sal_Int32 nObject = createObject();
10880 // Remember what is the ID of this object in our output.
10881 rCopiedResources[rObject.GetObjectValue()] = nObject;
10882 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject);
10884 OStringBuffer aLine;
10885 aLine.append(nObject);
10886 aLine.append(" 0 obj\n");
10887 if (rObject.GetDictionary())
10889 aLine.append("<<");
10891 // Complex case: can't copy the dictionary byte array as is, as it may contain references.
10892 bool bDone = false;
10893 sal_uInt64 nCopyStart = 0;
10894 for (auto pReference : rObject.GetDictionaryReferences())
10896 if (pReference)
10898 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
10899 if (pReferenced)
10901 // Copy the referenced object.
10902 sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
10904 sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
10905 sal_uInt64 nReferenceEnd = pReference->GetOffset();
10906 sal_uInt64 nOffset = 0;
10907 if (nCopyStart == 0)
10908 // Dict start -> reference start.
10909 nOffset = rObject.GetDictionaryOffset();
10910 else
10911 // Previous reference end -> reference start.
10912 nOffset = nCopyStart;
10913 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
10914 // Write the updated reference.
10915 aLine.append(" ");
10916 aLine.append(nRef);
10917 aLine.append(" 0 R");
10918 // Start copying here next time.
10919 nCopyStart = nReferenceEnd;
10921 bDone = true;
10926 if (bDone)
10928 // Copy the last part here, in the complex case.
10929 sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength();
10930 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nDictEnd - nCopyStart);
10932 else
10933 // Can copy it as-is.
10934 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength());
10936 aLine.append(">>\n");
10939 if (filter::PDFStreamElement* pStream = rObject.GetStream())
10941 aLine.append("stream\n");
10942 SvMemoryStream& rStream = pStream->GetMemory();
10943 aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize());
10944 aLine.append("\nendstream\n");
10947 if (filter::PDFArrayElement* pArray = rObject.GetArray())
10949 aLine.append("[");
10951 const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
10952 bool bDone = false;
10953 // Complex case: can't copy the array byte array as is, as it may contain references.
10954 sal_uInt64 nCopyStart = 0;
10955 for (const auto pElement : rElements)
10957 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
10958 if (pReference)
10960 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
10961 if (pReferenced)
10963 // Copy the referenced object.
10964 sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
10966 sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
10967 sal_uInt64 nReferenceEnd = pReference->GetOffset();
10968 sal_uInt64 nOffset = 0;
10969 if (nCopyStart == 0)
10970 // Array start -> reference start.
10971 nOffset = rObject.GetArrayOffset();
10972 else
10973 // Previous reference end -> reference start.
10974 nOffset = nCopyStart;
10975 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
10977 // Write the updated reference.
10978 aLine.append(" ");
10979 aLine.append(nRef);
10980 aLine.append(" 0 R");
10981 // Start copying here next time.
10982 nCopyStart = nReferenceEnd;
10984 bDone = true;
10989 if (bDone)
10991 // Copy the last part here, in the complex case.
10992 sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength();
10993 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nArrEnd - nCopyStart);
10995 else
10996 // Can copy it as-is.
10997 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength());
10999 aLine.append("]\n");
11002 // If the object has a number element outside a dictionary or array, copy that.
11003 if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
11005 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + pNumber->GetLocation(), pNumber->GetLength());
11006 aLine.append("\n");
11010 aLine.append("endobj\n\n");
11012 // We have the whole object, now write it to the output.
11013 if (!updateObject(nObject))
11014 return -1;
11015 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
11016 return -1;
11018 return nObject;
11021 OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources)
11023 // A name - object ID map, IDs as they appear in our output, not the
11024 // original ones.
11025 std::map<OString, sal_Int32> aRet;
11027 // Get the rKind subset of the resource dictionary.
11028 std::map<OString, filter::PDFElement*> aItems;
11029 if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
11031 // Resources is a direct dictionary.
11032 if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pResources->LookupElement(rKind)))
11033 aItems = pDictionary->GetItems();
11035 else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
11037 // Resources is an indirect object.
11038 filter::PDFElement* pValue = pPageResources->Lookup(rKind);
11039 if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
11040 // Kind is a direct dictionary.
11041 aItems = pDictionary->GetItems();
11042 else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
11043 // Kind is an indirect object.
11044 aItems = pObject->GetDictionaryItems();
11046 if (aItems.empty())
11047 return OString();
11049 SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
11051 for (const auto& rItem : aItems)
11053 // For each item copy it over to our output then insert it into aRet.
11054 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
11055 if (!pReference)
11056 continue;
11058 filter::PDFObjectElement* pValue = pReference->LookupObject();
11059 if (!pValue)
11060 continue;
11062 // Then copying over an object copy its dictionary and its stream.
11063 sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
11064 aRet[rItem.first] = nObject;
11067 // Build the dictionary entry string.
11068 OString sRet = "/" + rKind + "<<";
11069 for (const auto& rPair : aRet)
11071 sRet += "/" + rPair.first + " " + OString::number(rPair.second) + " 0 R";
11073 sRet += ">>";
11075 return sRet;
11078 void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
11080 if (rEmit.m_nFormObject <= 0)
11081 return;
11083 // Count /Matrix and /BBox.
11084 // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
11085 sal_Int32 nOldDPIX = getReferenceDevice()->GetDPIX();
11086 getReferenceDevice()->SetDPIX(96);
11087 sal_Int32 nOldDPIY = getReferenceDevice()->GetDPIY();
11088 getReferenceDevice()->SetDPIY(96);
11089 Size aSize = getReferenceDevice()->PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
11090 getReferenceDevice()->SetDPIX(nOldDPIX);
11091 getReferenceDevice()->SetDPIY(nOldDPIY);
11092 double fScaleX = 1.0 / aSize.Width();
11093 double fScaleY = 1.0 / aSize.Height();
11095 sal_Int32 nWrappedFormObject = 0;
11096 if (!m_aContext.UseReferenceXObject)
11098 // Parse the PDF data, we need that to write the PDF dictionary of our
11099 // object.
11100 SvMemoryStream aPDFStream;
11101 aPDFStream.WriteBytes(rEmit.m_aPDFData.getArray(), rEmit.m_aPDFData.getLength());
11102 aPDFStream.Seek(0);
11103 filter::PDFDocument aPDFDocument;
11104 if (!aPDFDocument.Read(aPDFStream))
11106 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
11107 return;
11109 std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
11110 if (aPages.empty())
11112 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
11113 return;
11116 filter::PDFObjectElement* pPage = aPages[0];
11117 if (!pPage)
11119 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
11120 return;
11123 std::vector<filter::PDFObjectElement*> aContentStreams;
11124 if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
11125 aContentStreams.push_back(pContentStream);
11126 else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
11128 for (const auto pElement : pArray->GetElements())
11130 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
11131 if (!pReference)
11132 continue;
11134 filter::PDFObjectElement* pObject = pReference->LookupObject();
11135 if (!pObject)
11136 continue;
11138 aContentStreams.push_back(pObject);
11142 if (aContentStreams.empty())
11144 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
11145 return;
11148 // Maps from source object id (PDF image) to target object id (export result).
11149 std::map<sal_Int32, sal_Int32> aCopiedResources;
11151 nWrappedFormObject = createObject();
11152 // Write the form XObject wrapped below. This is a separate object from
11153 // the wrapper, this way there is no need to alter the stream contents.
11155 OStringBuffer aLine;
11156 aLine.append(nWrappedFormObject);
11157 aLine.append(" 0 obj\n");
11158 aLine.append("<< /Type /XObject");
11159 aLine.append(" /Subtype /Form");
11160 aLine.append(" /Resources <<");
11161 static const std::initializer_list<OString> aKeys =
11163 "ColorSpace",
11164 "ExtGState",
11165 "Font",
11166 "XObject",
11167 "Shading"
11169 for (const auto& rKey : aKeys)
11170 aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
11171 aLine.append(">>");
11172 aLine.append(" /BBox [ 0 0 ");
11173 aLine.append(aSize.Width());
11174 aLine.append(" ");
11175 aLine.append(aSize.Height());
11176 aLine.append(" ]");
11178 if (!g_bDebugDisableCompression)
11179 aLine.append(" /Filter/FlateDecode");
11180 aLine.append(" /Length ");
11182 SvMemoryStream aStream;
11183 for (auto pContent : aContentStreams)
11185 filter::PDFStreamElement* pPageStream = pContent->GetStream();
11186 if (!pPageStream)
11188 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
11189 continue;
11192 SvMemoryStream& rPageStream = pPageStream->GetMemory();
11194 auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
11195 if (pFilter)
11197 if (pFilter->GetValue() != "FlateDecode")
11198 continue;
11200 SvMemoryStream aMemoryStream;
11201 ZCodec aZCodec;
11202 rPageStream.Seek(0);
11203 aZCodec.BeginCompression();
11204 aZCodec.Decompress(rPageStream, aMemoryStream);
11205 if (!aZCodec.EndCompression())
11207 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
11208 continue;
11211 aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
11213 else
11214 aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
11217 compressStream(&aStream);
11218 sal_Int32 nLength = aStream.Tell();
11219 aLine.append(nLength);
11221 aLine.append(">>\nstream\n");
11222 // Copy the original page streams to the form XObject stream.
11223 aLine.append(static_cast<const sal_Char*>(aStream.GetData()), aStream.GetSize());
11224 aLine.append("\nendstream\nendobj\n\n");
11225 if (!updateObject(nWrappedFormObject))
11226 return;
11227 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
11228 return;
11231 OStringBuffer aLine;
11232 if (!updateObject(rEmit.m_nFormObject))
11233 return;
11235 // Now have all the info to write the form XObject.
11236 aLine.append(rEmit.m_nFormObject);
11237 aLine.append(" 0 obj\n");
11238 aLine.append("<< /Type /XObject");
11239 aLine.append(" /Subtype /Form");
11240 aLine.append(" /Resources << /XObject<<");
11242 sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
11243 aLine.append(" /Im");
11244 aLine.append(nObject);
11245 aLine.append(" ");
11246 aLine.append(nObject);
11247 aLine.append(" 0 R");
11249 aLine.append(">> >>");
11250 aLine.append(" /Matrix [ ");
11251 appendDouble(fScaleX, aLine);
11252 aLine.append(" 0 0 ");
11253 appendDouble(fScaleY, aLine);
11254 aLine.append(" 0 0 ]");
11255 aLine.append(" /BBox [ 0 0 ");
11256 aLine.append(aSize.Width());
11257 aLine.append(" ");
11258 aLine.append(aSize.Height());
11259 aLine.append(" ]\n");
11261 if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
11263 // Write the reference dictionary.
11264 aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
11265 aLine.append(rEmit.m_nEmbeddedObject);
11266 aLine.append(" 0 R >> >> /Page 0 >>\n");
11269 aLine.append("/Length ");
11271 OStringBuffer aStream;
11272 aStream.append("q ");
11273 if (m_aContext.UseReferenceXObject)
11275 // Reference XObject markup is used, just refer to the fallback bitmap
11276 // here.
11277 aStream.append(aSize.Width());
11278 aStream.append(" 0 0 ");
11279 aStream.append(aSize.Height());
11280 aStream.append(" 0 0 cm\n");
11281 aStream.append("/Im");
11282 aStream.append(rEmit.m_nBitmapObject);
11283 aStream.append(" Do\n");
11285 else
11287 // Reset line width to the default.
11288 aStream.append(" 1 w\n");
11290 // No reference XObject, draw the form XObject containing the original
11291 // page streams.
11292 aStream.append("/Im");
11293 aStream.append(nWrappedFormObject);
11294 aStream.append(" Do\n");
11296 aStream.append("Q");
11297 aLine.append(aStream.getLength());
11299 aLine.append(">>\nstream\n");
11300 aLine.append(aStream.getStr());
11301 aLine.append("\nendstream\nendobj\n\n");
11302 CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
11305 namespace
11307 unsigned char reverseByte(unsigned char b)
11309 b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
11310 b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
11311 b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
11312 return b;
11315 //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
11316 Bitmap getExportBitmap(const Bitmap &rBitmap)
11318 Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
11319 const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
11320 if (eFormat != ScanlineFormat::N1BitLsbPal)
11321 return rBitmap;
11322 Bitmap aNewBmp(rBitmap);
11323 Bitmap::ScopedWriteAccess xWriteAcc(aNewBmp);
11324 const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
11325 for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
11327 Scanline pBitSwap = xWriteAcc->GetScanline(nY);
11328 for (int x = 0; x < nScanLineBytes; ++x)
11329 pBitSwap[x] = reverseByte(pBitSwap[x]);
11331 return aNewBmp;
11335 bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
11337 if (rObject.m_aReferenceXObject.m_aPDFData.hasElements() && !m_aContext.UseReferenceXObject)
11339 writeReferenceXObject(rObject.m_aReferenceXObject);
11340 return true;
11343 CHECK_RETURN( updateObject( rObject.m_nObject ) );
11345 Bitmap aBitmap;
11346 Color aTransparentColor( COL_TRANSPARENT );
11347 bool bWriteMask = false;
11348 if( ! bMask )
11350 aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
11351 if( rObject.m_aBitmap.IsAlpha() )
11353 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
11354 bWriteMask = true;
11355 // else draw without alpha channel
11357 else
11359 switch( rObject.m_aBitmap.GetTransparentType() )
11361 case TransparentType::NONE:
11362 break;
11363 case TransparentType::Color:
11364 aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
11365 break;
11366 case TransparentType::Bitmap:
11367 bWriteMask = true;
11368 break;
11372 else
11374 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
11376 aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
11377 aBitmap.Convert( BmpConversion::N1BitThreshold );
11378 SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
11380 else if( aBitmap.GetBitCount() != 8 )
11382 aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
11383 aBitmap.Convert( BmpConversion::N8BitGreys );
11384 SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
11388 Bitmap::ScopedReadAccess pAccess(aBitmap);
11390 bool bTrueColor;
11391 sal_Int32 nBitsPerComponent;
11392 switch( aBitmap.GetBitCount() )
11394 case 1:
11395 case 2:
11396 case 4:
11397 case 8:
11398 bTrueColor = false;
11399 nBitsPerComponent = aBitmap.GetBitCount();
11400 break;
11401 default:
11402 bTrueColor = true;
11403 nBitsPerComponent = 8;
11404 break;
11407 sal_Int32 nStreamLengthObject = createObject();
11408 sal_Int32 nMaskObject = 0;
11410 #if OSL_DEBUG_LEVEL > 1
11411 emitComment( "PDFWriterImpl::writeBitmapObject" );
11412 #endif
11413 OStringBuffer aLine(1024);
11414 aLine.append( rObject.m_nObject );
11415 aLine.append( " 0 obj\n"
11416 "<</Type/XObject/Subtype/Image/Width " );
11417 aLine.append( (sal_Int32)aBitmap.GetSizePixel().Width() );
11418 aLine.append( "/Height " );
11419 aLine.append( (sal_Int32)aBitmap.GetSizePixel().Height() );
11420 aLine.append( "/BitsPerComponent " );
11421 aLine.append( nBitsPerComponent );
11422 aLine.append( "/Length " );
11423 aLine.append( nStreamLengthObject );
11424 aLine.append( " 0 R\n" );
11425 if (!g_bDebugDisableCompression)
11427 if( nBitsPerComponent != 1 )
11429 aLine.append( "/Filter/FlateDecode" );
11431 else
11433 aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
11434 aLine.append( (sal_Int32)aBitmap.GetSizePixel().Width() );
11435 aLine.append( ">>\n" );
11438 if( ! bMask )
11440 aLine.append( "/ColorSpace" );
11441 if( bTrueColor )
11442 aLine.append( "/DeviceRGB\n" );
11443 else if( aBitmap.HasGreyPalette() )
11445 aLine.append( "/DeviceGray\n" );
11446 if( aBitmap.GetBitCount() == 1 )
11448 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
11449 sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) );
11450 SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
11451 if( nBlackIndex == 1 )
11452 aLine.append( "/Decode[1 0]\n" );
11455 else
11457 aLine.append( "[ /Indexed/DeviceRGB " );
11458 aLine.append( (sal_Int32)(pAccess->GetPaletteEntryCount()-1) );
11459 aLine.append( "\n<" );
11460 if( m_aContext.Encryption.Encrypt() )
11462 enableStringEncryption( rObject.m_nObject );
11463 //check encryption buffer size
11464 if( checkEncryptionBufferSize( pAccess->GetPaletteEntryCount()*3 ) )
11466 int nChar = 0;
11467 //fill the encryption buffer
11468 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
11470 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
11471 m_pEncryptionBuffer[nChar++] = rColor.GetRed();
11472 m_pEncryptionBuffer[nChar++] = rColor.GetGreen();
11473 m_pEncryptionBuffer[nChar++] = rColor.GetBlue();
11475 //encrypt the colorspace lookup table
11476 rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChar, m_pEncryptionBuffer, nChar );
11477 //now queue the data for output
11478 nChar = 0;
11479 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
11481 appendHex(m_pEncryptionBuffer[nChar++], aLine );
11482 appendHex(m_pEncryptionBuffer[nChar++], aLine );
11483 appendHex(m_pEncryptionBuffer[nChar++], aLine );
11487 else //no encryption requested (PDF/A-1a program flow drops here)
11489 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
11491 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
11492 appendHex( rColor.GetRed(), aLine );
11493 appendHex( rColor.GetGreen(), aLine );
11494 appendHex( rColor.GetBlue(), aLine );
11497 aLine.append( ">\n]\n" );
11500 else
11502 if( aBitmap.GetBitCount() == 1 )
11504 aLine.append( "/ImageMask true\n" );
11505 sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) );
11506 SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
11507 if( nBlackIndex )
11508 aLine.append( "/Decode[ 1 0 ]\n" );
11509 else
11510 aLine.append( "/Decode[ 0 1 ]\n" );
11512 else if( aBitmap.GetBitCount() == 8 )
11514 aLine.append( "/ColorSpace/DeviceGray\n"
11515 "/Decode [ 1 0 ]\n" );
11519 if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
11521 if( bWriteMask )
11523 nMaskObject = createObject();
11524 if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11525 aLine.append( "/SMask " );
11526 else
11527 aLine.append( "/Mask " );
11528 aLine.append( nMaskObject );
11529 aLine.append( " 0 R\n" );
11531 else if( aTransparentColor != Color( COL_TRANSPARENT ) )
11533 aLine.append( "/Mask[ " );
11534 if( bTrueColor )
11536 aLine.append( (sal_Int32)aTransparentColor.GetRed() );
11537 aLine.append( ' ' );
11538 aLine.append( (sal_Int32)aTransparentColor.GetRed() );
11539 aLine.append( ' ' );
11540 aLine.append( (sal_Int32)aTransparentColor.GetGreen() );
11541 aLine.append( ' ' );
11542 aLine.append( (sal_Int32)aTransparentColor.GetGreen() );
11543 aLine.append( ' ' );
11544 aLine.append( (sal_Int32)aTransparentColor.GetBlue() );
11545 aLine.append( ' ' );
11546 aLine.append( (sal_Int32)aTransparentColor.GetBlue() );
11548 else
11550 sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
11551 aLine.append( nIndex );
11553 aLine.append( " ]\n" );
11556 else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != Color( COL_TRANSPARENT )) )
11557 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
11559 aLine.append( ">>\n"
11560 "stream\n" );
11561 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
11562 sal_uInt64 nStartPos = 0;
11563 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
11565 checkAndEnableStreamEncryption( rObject.m_nObject );
11566 if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
11568 writeG4Stream(pAccess.get());
11570 else
11572 beginCompression();
11573 if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
11575 //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
11576 const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
11578 for( long i = 0; i < pAccess->Height(); i++ )
11580 CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
11583 else
11585 const int nScanLineBytes = pAccess->Width()*3;
11586 std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
11587 for( long y = 0; y < pAccess->Height(); y++ )
11589 for( long x = 0; x < pAccess->Width(); x++ )
11591 BitmapColor aColor = pAccess->GetColor( y, x );
11592 xCol[3*x+0] = aColor.GetRed();
11593 xCol[3*x+1] = aColor.GetGreen();
11594 xCol[3*x+2] = aColor.GetBlue();
11596 CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
11599 endCompression();
11601 disableStreamEncryption();
11603 sal_uInt64 nEndPos = 0;
11604 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
11605 aLine.setLength( 0 );
11606 aLine.append( "\nendstream\nendobj\n\n" );
11607 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
11608 CHECK_RETURN( updateObject( nStreamLengthObject ) );
11609 aLine.setLength( 0 );
11610 aLine.append( nStreamLengthObject );
11611 aLine.append( " 0 obj\n" );
11612 aLine.append( (sal_Int64)(nEndPos-nStartPos) );
11613 aLine.append( "\nendobj\n\n" );
11614 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
11616 if( nMaskObject )
11618 BitmapEmit aEmit;
11619 aEmit.m_nObject = nMaskObject;
11620 aEmit.m_aBitmap = rObject.m_aBitmap;
11621 return writeBitmapObject( aEmit, true );
11624 writeReferenceXObject(rObject.m_aReferenceXObject);
11626 return true;
11629 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
11631 // The bitmap object is always a valid identifier, even if the graphic has
11632 // no pdf data.
11633 rEmit.m_nBitmapObject = nBitmapObject;
11635 if (!rGraphic.getPdfData().hasElements())
11636 return;
11638 if (m_aContext.UseReferenceXObject)
11640 // Store the original PDF data as an embedded file.
11641 m_aEmbeddedFiles.push_back(PDFEmbeddedFile());
11642 m_aEmbeddedFiles.back().m_nObject = createObject();
11643 m_aEmbeddedFiles.back().m_aData = rGraphic.getPdfData();
11645 rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
11647 else
11648 rEmit.m_aPDFData = rGraphic.getPdfData();
11650 rEmit.m_nFormObject = createObject();
11651 rEmit.m_aPixelSize = rGraphic.GetBitmap().GetPrefSize();
11654 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, const Graphic& rGraphic )
11656 MARK( "drawJPGBitmap" );
11658 OStringBuffer aLine( 80 );
11659 updateGraphicsState();
11661 // #i40055# sanity check
11662 if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
11663 return;
11664 if( ! (rSizePixel.Width() && rSizePixel.Height()) )
11665 return;
11667 rDCTData.Seek( 0 );
11668 if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
11670 // need to convert to grayscale;
11671 // load stream to bitmap and draw the bitmap instead
11672 Graphic aGraphic;
11673 GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
11674 Bitmap aBmp( aGraphic.GetBitmap() );
11675 if( !!rMask && rMask.GetSizePixel() == aBmp.GetSizePixel() )
11677 BitmapEx aBmpEx( aBmp, rMask );
11678 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
11680 else
11681 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmp );
11682 return;
11685 SvMemoryStream* pStream = new SvMemoryStream;
11686 pStream->WriteStream( rDCTData );
11687 pStream->Seek( STREAM_SEEK_TO_END );
11689 BitmapID aID;
11690 aID.m_aPixelSize = rSizePixel;
11691 aID.m_nSize = pStream->Tell();
11692 pStream->Seek( STREAM_SEEK_TO_BEGIN );
11693 aID.m_nChecksum = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
11694 if( ! rMask.IsEmpty() )
11695 aID.m_nMaskChecksum = rMask.GetChecksum();
11697 std::list< JPGEmit >::const_iterator it;
11698 for( it = m_aJPGs.begin(); it != m_aJPGs.end() && ! (aID == it->m_aID); ++it )
11700 if( it == m_aJPGs.end() )
11702 m_aJPGs.emplace( m_aJPGs.begin() );
11703 JPGEmit& rEmit = m_aJPGs.front();
11704 if (!rGraphic.getPdfData().hasElements() || m_aContext.UseReferenceXObject)
11705 rEmit.m_nObject = createObject();
11706 rEmit.m_aID = aID;
11707 rEmit.m_pStream.reset( pStream );
11708 rEmit.m_bTrueColor = bIsTrueColor;
11709 if( !! rMask && rMask.GetSizePixel() == rSizePixel )
11710 rEmit.m_aMask = rMask;
11711 createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
11713 it = m_aJPGs.begin();
11715 else
11716 delete pStream;
11718 aLine.append( "q " );
11719 sal_Int32 nCheckWidth = 0;
11720 m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetWidth(), aLine, false, &nCheckWidth );
11721 aLine.append( " 0 0 " );
11722 sal_Int32 nCheckHeight = 0;
11723 m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetHeight(), aLine, true, &nCheckHeight );
11724 aLine.append( ' ' );
11725 m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
11726 aLine.append( " cm\n/Im" );
11727 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
11728 aLine.append(nObject);
11729 aLine.append( " Do Q\n" );
11730 if( nCheckWidth == 0 || nCheckHeight == 0 )
11732 // #i97512# avoid invalid current matrix
11733 aLine.setLength( 0 );
11734 aLine.append( "\n%jpeg image /Im" );
11735 aLine.append( it->m_nObject );
11736 aLine.append( " scaled to zero size, omitted\n" );
11738 writeBuffer( aLine.getStr(), aLine.getLength() );
11740 OStringBuffer aObjName( 16 );
11741 aObjName.append( "Im" );
11742 aObjName.append(nObject);
11743 pushResource( ResXObject, aObjName.makeStringAndClear(), nObject );
11747 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
11749 OStringBuffer aLine( 80 );
11750 updateGraphicsState();
11752 aLine.append( "q " );
11753 if( rFillColor != Color( COL_TRANSPARENT ) )
11755 appendNonStrokingColor( rFillColor, aLine );
11756 aLine.append( ' ' );
11758 sal_Int32 nCheckWidth = 0;
11759 m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Width(), aLine, false, &nCheckWidth );
11760 aLine.append( " 0 0 " );
11761 sal_Int32 nCheckHeight = 0;
11762 m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Height(), aLine, true, &nCheckHeight );
11763 aLine.append( ' ' );
11764 m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
11765 aLine.append( " cm\n/Im" );
11766 sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
11767 aLine.append(nObject);
11768 aLine.append( " Do Q\n" );
11769 if( nCheckWidth == 0 || nCheckHeight == 0 )
11771 // #i97512# avoid invalid current matrix
11772 aLine.setLength( 0 );
11773 aLine.append( "\n%bitmap image /Im" );
11774 aLine.append( rBitmap.m_nObject );
11775 aLine.append( " scaled to zero size, omitted\n" );
11777 writeBuffer( aLine.getStr(), aLine.getLength() );
11780 const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
11782 BitmapEx aBitmap( i_rBitmap );
11783 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
11785 BmpConversion eConv = BmpConversion::N8BitGreys;
11786 int nDepth = aBitmap.GetBitmap().GetBitCount();
11787 if( nDepth <= 4 )
11788 eConv = BmpConversion::N4BitGreys;
11789 if( nDepth > 1 )
11790 aBitmap.Convert( eConv );
11792 BitmapID aID;
11793 aID.m_aPixelSize = aBitmap.GetSizePixel();
11794 aID.m_nSize = aBitmap.GetBitCount();
11795 aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
11796 aID.m_nMaskChecksum = 0;
11797 if( aBitmap.IsAlpha() )
11798 aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
11799 else
11801 Bitmap aMask = aBitmap.GetMask();
11802 if( ! aMask.IsEmpty() )
11803 aID.m_nMaskChecksum = aMask.GetChecksum();
11805 std::list< BitmapEmit >::const_iterator it;
11806 for( it = m_aBitmaps.begin(); it != m_aBitmaps.end(); ++it )
11808 if( aID == it->m_aID )
11809 break;
11811 if( it == m_aBitmaps.end() )
11813 m_aBitmaps.push_front( BitmapEmit() );
11814 m_aBitmaps.front().m_aID = aID;
11815 m_aBitmaps.front().m_aBitmap = aBitmap;
11816 if (!rGraphic.getPdfData().hasElements() || m_aContext.UseReferenceXObject)
11817 m_aBitmaps.front().m_nObject = createObject();
11818 createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
11819 it = m_aBitmaps.begin();
11822 OStringBuffer aObjName( 16 );
11823 aObjName.append( "Im" );
11824 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
11825 aObjName.append(nObject);
11826 pushResource( ResXObject, aObjName.makeStringAndClear(), nObject );
11828 return *it;
11831 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
11833 MARK( "drawBitmap (Bitmap)" );
11835 // #i40055# sanity check
11836 if( ! (rDestSize.Width() && rDestSize.Height()) )
11837 return;
11839 const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
11840 drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) );
11843 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
11845 MARK( "drawBitmap (BitmapEx)" );
11847 // #i40055# sanity check
11848 if( ! (rDestSize.Width() && rDestSize.Height()) )
11849 return;
11851 const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
11852 drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) );
11855 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
11857 Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
11858 MapMode( MapUnit::MapPoint ),
11859 getReferenceDevice(),
11860 rSize ) );
11861 // check if we already have this gradient
11862 std::list<GradientEmit>::iterator it;
11863 // rounding to point will generally lose some pixels
11864 // round up to point boundary
11865 aPtSize.Width()++;
11866 aPtSize.Height()++;
11867 for( it = m_aGradients.begin(); it != m_aGradients.end(); ++it )
11869 if( it->m_aGradient == rGradient )
11871 if( it->m_aSize == aPtSize )
11872 break;
11875 if( it == m_aGradients.end() )
11877 m_aGradients.push_front( GradientEmit() );
11878 m_aGradients.front().m_aGradient = rGradient;
11879 m_aGradients.front().m_nObject = createObject();
11880 m_aGradients.front().m_aSize = aPtSize;
11881 it = m_aGradients.begin();
11884 OStringBuffer aObjName( 16 );
11885 aObjName.append( 'P' );
11886 aObjName.append( it->m_nObject );
11887 pushResource( ResShading, aObjName.makeStringAndClear(), it->m_nObject );
11889 return it->m_nObject;
11892 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
11894 MARK( "drawGradient (Rectangle)" );
11896 if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
11898 drawRectangle( rRect );
11899 return;
11902 sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
11904 Point aTranslate( rRect.BottomLeft() );
11905 aTranslate += Point( 0, 1 );
11907 updateGraphicsState();
11909 OStringBuffer aLine( 80 );
11910 aLine.append( "q 1 0 0 1 " );
11911 m_aPages.back().appendPoint( aTranslate, aLine );
11912 aLine.append( " cm " );
11913 // if a stroke is appended reset the clip region before stroke
11914 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
11915 aLine.append( "q " );
11916 aLine.append( "0 0 " );
11917 m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false );
11918 aLine.append( ' ' );
11919 m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine );
11920 aLine.append( " re W n\n" );
11922 aLine.append( "/P" );
11923 aLine.append( nGradient );
11924 aLine.append( " sh " );
11925 if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
11927 aLine.append( "Q 0 0 " );
11928 m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false );
11929 aLine.append( ' ' );
11930 m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine );
11931 aLine.append( " re S " );
11933 aLine.append( "Q\n" );
11934 writeBuffer( aLine.getStr(), aLine.getLength() );
11937 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
11939 MARK( "drawHatch" );
11941 updateGraphicsState();
11943 if( rPolyPoly.Count() )
11945 tools::PolyPolygon aPolyPoly( rPolyPoly );
11947 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
11948 push( PushFlags::LINECOLOR );
11949 setLineColor( rHatch.GetColor() );
11950 getReferenceDevice()->DrawHatch( aPolyPoly, rHatch, false );
11951 pop();
11955 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
11957 MARK( "drawWallpaper" );
11959 bool bDrawColor = false;
11960 bool bDrawGradient = false;
11961 bool bDrawBitmap = false;
11963 BitmapEx aBitmap;
11964 Point aBmpPos = rRect.TopLeft();
11965 Size aBmpSize;
11966 if( rWall.IsBitmap() )
11968 aBitmap = rWall.GetBitmap();
11969 aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
11970 getMapMode(),
11971 getReferenceDevice(),
11972 aBitmap.GetPrefSize() );
11973 tools::Rectangle aRect( rRect );
11974 if( rWall.IsRect() )
11976 aRect = rWall.GetRect();
11977 aBmpPos = aRect.TopLeft();
11978 aBmpSize = aRect.GetSize();
11980 if( rWall.GetStyle() != WallpaperStyle::Scale )
11982 if( rWall.GetStyle() != WallpaperStyle::Tile )
11984 bDrawBitmap = true;
11985 if( rWall.IsGradient() )
11986 bDrawGradient = true;
11987 else
11988 bDrawColor = true;
11989 switch( rWall.GetStyle() )
11991 case WallpaperStyle::TopLeft:
11992 break;
11993 case WallpaperStyle::Top:
11994 aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2;
11995 break;
11996 case WallpaperStyle::Left:
11997 aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2;
11998 break;
11999 case WallpaperStyle::TopRight:
12000 aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width();
12001 break;
12002 case WallpaperStyle::Center:
12003 aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2;
12004 aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2;
12005 break;
12006 case WallpaperStyle::Right:
12007 aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width();
12008 aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2;
12009 break;
12010 case WallpaperStyle::BottomLeft:
12011 aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height();
12012 break;
12013 case WallpaperStyle::Bottom:
12014 aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2;
12015 aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height();
12016 break;
12017 case WallpaperStyle::BottomRight:
12018 aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width();
12019 aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height();
12020 break;
12021 default: ;
12024 else
12026 // push the bitmap
12027 const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ), Graphic() );
12029 // convert to page coordinates; this needs to be done here
12030 // since the emit does not know the page anymore
12031 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
12032 m_aPages.back().convertRect( aConvertRect );
12034 OStringBuffer aNameBuf(16);
12035 aNameBuf.append( "Im" );
12036 aNameBuf.append( rEmit.m_nObject );
12037 OString aImageName( aNameBuf.makeStringAndClear() );
12039 // push the pattern
12040 OStringBuffer aTilingStream( 32 );
12041 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
12042 aTilingStream.append( " 0 0 " );
12043 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
12044 aTilingStream.append( " 0 0 cm\n/" );
12045 aTilingStream.append( aImageName );
12046 aTilingStream.append( " Do\n" );
12048 m_aTilings.push_back( TilingEmit() );
12049 m_aTilings.back().m_nObject = createObject();
12050 m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
12051 m_aTilings.back().m_pTilingStream = new SvMemoryStream();
12052 m_aTilings.back().m_pTilingStream->WriteBytes(
12053 aTilingStream.getStr(), aTilingStream.getLength() );
12054 // phase the tiling so wallpaper begins on upper left
12055 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
12056 throw o3tl::divide_by_zero();
12057 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
12058 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
12059 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
12061 updateGraphicsState();
12063 OStringBuffer aObjName( 16 );
12064 aObjName.append( 'P' );
12065 aObjName.append( m_aTilings.back().m_nObject );
12066 OString aPatternName( aObjName.makeStringAndClear() );
12067 pushResource( ResPattern, aPatternName, m_aTilings.back().m_nObject );
12069 // fill a rRect with the pattern
12070 OStringBuffer aLine( 100 );
12071 aLine.append( "q /Pattern cs /" );
12072 aLine.append( aPatternName );
12073 aLine.append( " scn " );
12074 m_aPages.back().appendRect( rRect, aLine );
12075 aLine.append( " f Q\n" );
12076 writeBuffer( aLine.getStr(), aLine.getLength() );
12079 else
12081 aBmpPos = aRect.TopLeft();
12082 aBmpSize = aRect.GetSize();
12083 bDrawBitmap = true;
12086 if( aBitmap.IsTransparent() )
12088 if( rWall.IsGradient() )
12089 bDrawGradient = true;
12090 else
12091 bDrawColor = true;
12094 else if( rWall.IsGradient() )
12095 bDrawGradient = true;
12096 else
12097 bDrawColor = true;
12099 if( bDrawGradient )
12101 drawGradient( rRect, rWall.GetGradient() );
12103 if( bDrawColor )
12105 Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
12106 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
12107 setLineColor( Color( COL_TRANSPARENT ) );
12108 setFillColor( rWall.GetColor() );
12109 drawRectangle( rRect );
12110 setLineColor( aOldLineColor );
12111 setFillColor( aOldFillColor );
12113 if( bDrawBitmap )
12115 // set temporary clip region since aBmpPos and aBmpSize
12116 // may be outside rRect
12117 OStringBuffer aLine( 20 );
12118 aLine.append( "q " );
12119 m_aPages.back().appendRect( rRect, aLine );
12120 aLine.append( " W n\n" );
12121 writeBuffer( aLine.getStr(), aLine.getLength() );
12122 drawBitmap( aBmpPos, aBmpSize, aBitmap );
12123 writeBuffer( "Q\n", 2 );
12127 void PDFWriterImpl::updateGraphicsState(Mode const mode)
12129 OStringBuffer aLine( 256 );
12130 GraphicsState& rNewState = m_aGraphicsStack.front();
12131 // first set clip region since it might invalidate everything else
12133 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion) )
12135 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
12137 if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
12138 ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
12140 if( m_aCurrentPDFState.m_bClipRegion )
12142 aLine.append( "Q " );
12143 // invalidate everything but the clip region
12144 m_aCurrentPDFState = GraphicsState();
12145 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
12147 if( rNewState.m_bClipRegion )
12149 // clip region is always stored in private PDF mapmode
12150 MapMode aNewMapMode = rNewState.m_aMapMode;
12151 rNewState.m_aMapMode = m_aMapMode;
12152 getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
12153 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
12155 aLine.append("q ");
12156 if ( rNewState.m_aClipRegion.count() )
12158 m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
12159 aLine.append( "W* n\n" );
12162 rNewState.m_aMapMode = aNewMapMode;
12163 getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
12164 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
12169 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode) )
12171 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
12172 getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
12175 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font) )
12177 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
12178 getReferenceDevice()->SetFont( rNewState.m_aFont );
12179 getReferenceDevice()->ImplNewFont();
12182 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode) )
12184 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
12185 getReferenceDevice()->SetLayoutMode( rNewState.m_nLayoutMode );
12188 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage) )
12190 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
12191 getReferenceDevice()->SetDigitLanguage( rNewState.m_aDigitLanguage );
12194 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor) )
12196 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
12197 if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
12198 rNewState.m_aLineColor != Color( COL_TRANSPARENT ) )
12200 appendStrokingColor( rNewState.m_aLineColor, aLine );
12201 aLine.append( "\n" );
12205 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor) )
12207 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
12208 if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
12209 rNewState.m_aFillColor != Color( COL_TRANSPARENT ) )
12211 appendNonStrokingColor( rNewState.m_aFillColor, aLine );
12212 aLine.append( "\n" );
12216 if( (rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent) )
12218 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
12219 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && m_aCurrentPDFState.m_nTransparentPercent != rNewState.m_nTransparentPercent )
12221 // TODO: switch extended graphicsstate
12225 // everything is up to date now
12226 m_aCurrentPDFState = m_aGraphicsStack.front();
12227 if ((mode != NOWRITE) && !aLine.isEmpty())
12228 writeBuffer( aLine.getStr(), aLine.getLength() );
12231 /* #i47544# imitate OutputDevice behaviour:
12232 * if a font with a nontransparent color is set, it overwrites the current
12233 * text color. OTOH setting the text color will overwrite the color of the font.
12235 void PDFWriterImpl::setFont( const vcl::Font& rFont )
12237 Color aColor = rFont.GetColor();
12238 if( aColor == Color( COL_TRANSPARENT ) )
12239 aColor = m_aGraphicsStack.front().m_aFont.GetColor();
12240 m_aGraphicsStack.front().m_aFont = rFont;
12241 m_aGraphicsStack.front().m_aFont.SetColor( aColor );
12242 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
12245 void PDFWriterImpl::push( PushFlags nFlags )
12247 OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
12248 m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
12249 m_aGraphicsStack.front().m_nFlags = nFlags;
12252 void PDFWriterImpl::pop()
12254 OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
12255 if( m_aGraphicsStack.size() < 2 )
12256 return;
12258 GraphicsState aState = m_aGraphicsStack.front();
12259 m_aGraphicsStack.pop_front();
12260 GraphicsState& rOld = m_aGraphicsStack.front();
12262 // move those parameters back that were not pushed
12263 // in the first place
12264 if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
12265 setLineColor( aState.m_aLineColor );
12266 if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
12267 setFillColor( aState.m_aFillColor );
12268 if( ! (aState.m_nFlags & PushFlags::FONT) )
12269 setFont( aState.m_aFont );
12270 if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
12271 setTextColor( aState.m_aFont.GetColor() );
12272 if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
12273 setMapMode( aState.m_aMapMode );
12274 if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
12276 // do not use setClipRegion here
12277 // it would convert again assuming the current mapmode
12278 rOld.m_aClipRegion = aState.m_aClipRegion;
12279 rOld.m_bClipRegion = aState.m_bClipRegion;
12281 if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
12282 setTextLineColor( aState.m_aTextLineColor );
12283 if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
12284 setOverlineColor( aState.m_aOverlineColor );
12285 if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
12286 setTextAlign( aState.m_aFont.GetAlignment() );
12287 if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
12288 setTextFillColor( aState.m_aFont.GetFillColor() );
12289 if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
12291 // what ?
12293 // invalidate graphics state
12294 m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
12297 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
12299 m_aGraphicsStack.front().m_aMapMode = rMapMode;
12300 getReferenceDevice()->SetMapMode( rMapMode );
12301 m_aCurrentPDFState.m_aMapMode = rMapMode;
12304 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
12306 basegfx::B2DPolyPolygon aRegion = getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode );
12307 aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode );
12308 m_aGraphicsStack.front().m_aClipRegion = aRegion;
12309 m_aGraphicsStack.front().m_bClipRegion = true;
12310 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
12313 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
12315 if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() )
12317 Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
12318 m_aMapMode,
12319 getReferenceDevice(),
12320 Point( nX, nY ) ) );
12321 aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode,
12322 m_aMapMode,
12323 getReferenceDevice(),
12324 Point() );
12325 basegfx::B2DHomMatrix aMat;
12326 aMat.translate( aPoint.X(), aPoint.Y() );
12327 m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
12328 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
12332 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
12334 basegfx::B2DPolyPolygon aRect( basegfx::tools::createPolygonFromRect(
12335 basegfx::B2DRectangle( rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() ) ) );
12336 intersectClipRegion( aRect );
12339 bool PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
12341 basegfx::B2DPolyPolygon aRegion( getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ) );
12342 aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode );
12343 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
12344 if( m_aGraphicsStack.front().m_bClipRegion )
12346 basegfx::B2DPolyPolygon aOld( basegfx::tools::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
12347 aRegion = basegfx::tools::prepareForPolygonOperation( aRegion );
12348 m_aGraphicsStack.front().m_aClipRegion = basegfx::tools::solvePolygonOperationAnd( aOld, aRegion );
12350 else
12352 m_aGraphicsStack.front().m_aClipRegion = aRegion;
12353 m_aGraphicsStack.front().m_bClipRegion = true;
12355 return true;
12358 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
12360 if( nPageNr < 0 )
12361 nPageNr = m_nCurrentPage;
12363 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
12364 return;
12366 m_aNotes.push_back( PDFNoteEntry() );
12367 m_aNotes.back().m_nObject = createObject();
12368 m_aNotes.back().m_aContents = rNote;
12369 m_aNotes.back().m_aRect = rRect;
12370 // convert to default user space now, since the mapmode may change
12371 m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
12373 // insert note to page's annotation list
12374 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
12377 sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
12379 if( nPageNr < 0 )
12380 nPageNr = m_nCurrentPage;
12382 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
12383 return -1;
12385 sal_Int32 nRet = m_aLinks.size();
12387 m_aLinks.push_back( PDFLink() );
12388 m_aLinks.back().m_nObject = createObject();
12389 m_aLinks.back().m_nPage = nPageNr;
12390 m_aLinks.back().m_aRect = rRect;
12391 // convert to default user space now, since the mapmode may change
12392 m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
12394 // insert link to page's annotation list
12395 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
12397 return nRet;
12400 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
12402 if (nPageNr < 0)
12403 nPageNr = m_nCurrentPage;
12405 if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
12406 return -1;
12408 sal_Int32 nRet = m_aScreens.size();
12410 m_aScreens.push_back(PDFScreen());
12411 m_aScreens.back().m_nObject = createObject();
12412 m_aScreens.back().m_nPage = nPageNr;
12413 m_aScreens.back().m_aRect = rRect;
12414 // Convert to default user space now, since the mapmode may change.
12415 m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
12417 // Insert link to page's annotation list.
12418 m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
12420 return nRet;
12423 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
12425 if( nPageNr < 0 )
12426 nPageNr = m_nCurrentPage;
12428 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
12429 return -1;
12431 sal_Int32 nRet = m_aNamedDests.size();
12433 m_aNamedDests.push_back( PDFNamedDest() );
12434 m_aNamedDests.back().m_aDestName = sDestName;
12435 m_aNamedDests.back().m_nPage = nPageNr;
12436 m_aNamedDests.back().m_eType = eType;
12437 m_aNamedDests.back().m_aRect = rRect;
12438 // convert to default user space now, since the mapmode may change
12439 m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
12441 return nRet;
12444 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
12446 if( nPageNr < 0 )
12447 nPageNr = m_nCurrentPage;
12449 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
12450 return -1;
12452 sal_Int32 nRet = m_aDests.size();
12454 m_aDests.push_back( PDFDest() );
12455 m_aDests.back().m_nPage = nPageNr;
12456 m_aDests.back().m_eType = eType;
12457 m_aDests.back().m_aRect = rRect;
12458 // convert to default user space now, since the mapmode may change
12459 m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
12461 return nRet;
12464 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
12466 return m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
12469 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
12471 if( nLinkId < 0 || nLinkId >= (sal_Int32)m_aLinks.size() )
12472 return;
12473 if( nDestId < 0 || nDestId >= (sal_Int32)m_aDests.size() )
12474 return;
12476 m_aLinks[ nLinkId ].m_nDest = nDestId;
12479 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
12481 if( nLinkId < 0 || nLinkId >= (sal_Int32)m_aLinks.size() )
12482 return;
12484 m_aLinks[ nLinkId ].m_nDest = -1;
12486 using namespace ::com::sun::star;
12488 if (!m_xTrans.is())
12490 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
12491 m_xTrans = util::URLTransformer::create(xContext);
12494 util::URL aURL;
12495 aURL.Complete = rURL;
12497 m_xTrans->parseStrict( aURL );
12499 m_aLinks[ nLinkId ].m_aURL = aURL.Complete;
12502 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
12504 if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
12505 return;
12507 m_aScreens[nScreenId].m_aURL = rURL;
12510 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
12512 if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
12513 return;
12515 m_aScreens[nScreenId].m_aTempFileURL = rURL;
12516 m_aScreens[nScreenId].m_nTempFileObject = createObject();
12519 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
12521 m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
12524 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
12526 // create new item
12527 sal_Int32 nNewItem = m_aOutline.size();
12528 m_aOutline.push_back( PDFOutlineEntry() );
12530 // set item attributes
12531 setOutlineItemParent( nNewItem, nParent );
12532 setOutlineItemText( nNewItem, rText );
12533 setOutlineItemDest( nNewItem, nDestID );
12535 return nNewItem;
12538 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
12540 if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() )
12541 return;
12543 if( nNewParent < 0 || nNewParent >= (sal_Int32)m_aOutline.size() || nNewParent == nItem )
12545 nNewParent = 0;
12547 // insert item to new parent's list of children
12548 m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
12551 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText )
12553 if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() )
12554 return;
12556 m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
12559 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
12561 if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() ) // item does not exist
12562 return;
12563 if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() ) // dest does not exist
12564 return;
12565 m_aOutline[nItem].m_nDestID = nDestID;
12568 const sal_Char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
12570 static std::map< PDFWriter::StructElement, const char* > aTagStrings;
12571 if( aTagStrings.empty() )
12573 aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
12574 aTagStrings[ PDFWriter::Document ] = "Document";
12575 aTagStrings[ PDFWriter::Part ] = "Part";
12576 aTagStrings[ PDFWriter::Article ] = "Art";
12577 aTagStrings[ PDFWriter::Section ] = "Sect";
12578 aTagStrings[ PDFWriter::Division ] = "Div";
12579 aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
12580 aTagStrings[ PDFWriter::Caption ] = "Caption";
12581 aTagStrings[ PDFWriter::TOC ] = "TOC";
12582 aTagStrings[ PDFWriter::TOCI ] = "TOCI";
12583 aTagStrings[ PDFWriter::Index ] = "Index";
12584 aTagStrings[ PDFWriter::Paragraph ] = "P";
12585 aTagStrings[ PDFWriter::Heading ] = "H";
12586 aTagStrings[ PDFWriter::H1 ] = "H1";
12587 aTagStrings[ PDFWriter::H2 ] = "H2";
12588 aTagStrings[ PDFWriter::H3 ] = "H3";
12589 aTagStrings[ PDFWriter::H4 ] = "H4";
12590 aTagStrings[ PDFWriter::H5 ] = "H5";
12591 aTagStrings[ PDFWriter::H6 ] = "H6";
12592 aTagStrings[ PDFWriter::List ] = "L";
12593 aTagStrings[ PDFWriter::ListItem ] = "LI";
12594 aTagStrings[ PDFWriter::LILabel ] = "Lbl";
12595 aTagStrings[ PDFWriter::LIBody ] = "LBody";
12596 aTagStrings[ PDFWriter::Table ] = "Table";
12597 aTagStrings[ PDFWriter::TableRow ] = "TR";
12598 aTagStrings[ PDFWriter::TableHeader ] = "TH";
12599 aTagStrings[ PDFWriter::TableData ] = "TD";
12600 aTagStrings[ PDFWriter::Span ] = "Span";
12601 aTagStrings[ PDFWriter::Quote ] = "Quote";
12602 aTagStrings[ PDFWriter::Note ] = "Note";
12603 aTagStrings[ PDFWriter::Reference ] = "Reference";
12604 aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
12605 aTagStrings[ PDFWriter::Code ] = "Code";
12606 aTagStrings[ PDFWriter::Link ] = "Link";
12607 aTagStrings[ PDFWriter::Figure ] = "Figure";
12608 aTagStrings[ PDFWriter::Formula ] = "Formula";
12609 aTagStrings[ PDFWriter::Form ] = "Form";
12612 std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
12614 return it != aTagStrings.end() ? it->second : "Div";
12617 void PDFWriterImpl::beginStructureElementMCSeq()
12619 if( m_bEmitStructure &&
12620 m_nCurrentStructElement > 0 && // StructTreeRoot
12621 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
12624 PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
12625 OStringBuffer aLine( 128 );
12626 sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
12627 aLine.append( "/" );
12628 if( !rEle.m_aAlias.isEmpty() )
12629 aLine.append( rEle.m_aAlias );
12630 else
12631 aLine.append( getStructureTag( rEle.m_eType ) );
12632 aLine.append( "<</MCID " );
12633 aLine.append( nMCID );
12634 aLine.append( ">>BDC\n" );
12635 writeBuffer( aLine.getStr(), aLine.getLength() );
12637 // update the element's content list
12638 #if OSL_DEBUG_LEVEL > 1
12639 SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
12640 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
12641 << rEle.m_nFirstPageObject);
12642 #endif
12643 rEle.m_aKids.push_back( PDFStructureElementKid( nMCID, m_aPages[m_nCurrentPage].m_nPageObject ) );
12644 // update the page's mcid parent list
12645 m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
12646 // mark element MC sequence as open
12647 rEle.m_bOpenMCSeq = true;
12649 // handle artifacts
12650 else if( ! m_bEmitStructure && m_aContext.Tagged &&
12651 m_nCurrentStructElement > 0 &&
12652 m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
12653 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
12656 OStringBuffer aLine( 128 );
12657 aLine.append( "/Artifact BMC\n" );
12658 writeBuffer( aLine.getStr(), aLine.getLength() );
12659 // mark element MC sequence as open
12660 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
12664 void PDFWriterImpl::endStructureElementMCSeq()
12666 if( m_nCurrentStructElement > 0 && // StructTreeRoot
12667 ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
12668 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
12671 writeBuffer( "EMC\n", 4 );
12672 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
12676 bool PDFWriterImpl::checkEmitStructure()
12678 bool bEmit = false;
12679 if( m_aContext.Tagged )
12681 bEmit = true;
12682 sal_Int32 nEle = m_nCurrentStructElement;
12683 while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
12685 if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
12687 bEmit = false;
12688 break;
12690 nEle = m_aStructure[ nEle ].m_nParentElement;
12693 return bEmit;
12696 sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
12698 if( m_nCurrentPage < 0 )
12699 return -1;
12701 if( ! m_aContext.Tagged )
12702 return -1;
12704 // close eventual current MC sequence
12705 endStructureElementMCSeq();
12707 if( m_nCurrentStructElement == 0 &&
12708 eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
12710 // struct tree root hit, but not beginning document
12711 // this might happen with setCurrentStructureElement
12712 // silently insert structure into document again if one properly exists
12713 if( ! m_aStructure[ 0 ].m_aChildren.empty() )
12715 PDFWriter::StructElement childType = PDFWriter::NonStructElement;
12716 sal_Int32 nNewCurElement = 0;
12717 const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
12718 for( std::list< sal_Int32 >::const_iterator it = rRootChildren.begin();
12719 childType != PDFWriter::Document && it != rRootChildren.end(); ++it )
12721 nNewCurElement = *it;
12722 childType = m_aStructure[ nNewCurElement ].m_eType;
12724 if( childType == PDFWriter::Document )
12726 m_nCurrentStructElement = nNewCurElement;
12727 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
12729 else {
12730 OSL_FAIL( "document structure in disorder !" );
12733 else {
12734 OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
12738 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
12739 m_aStructure.push_back( PDFStructureElement() );
12740 PDFStructureElement& rEle = m_aStructure.back();
12741 rEle.m_eType = eType;
12742 rEle.m_nOwnElement = nNewId;
12743 rEle.m_nParentElement = m_nCurrentStructElement;
12744 rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
12745 m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
12746 m_nCurrentStructElement = nNewId;
12748 // handle alias names
12749 if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
12751 OStringBuffer aNameBuf( rAlias.getLength() );
12752 appendName( rAlias, aNameBuf );
12753 OString aAliasName( aNameBuf.makeStringAndClear() );
12754 rEle.m_aAlias = aAliasName;
12755 m_aRoleMap[ aAliasName ] = getStructureTag( eType );
12758 #if OSL_DEBUG_LEVEL > 1
12759 OStringBuffer aLine( "beginStructureElement " );
12760 aLine.append( m_nCurrentStructElement );
12761 aLine.append( ": " );
12762 aLine.append( getStructureTag( eType ) );
12763 if( !rEle.m_aAlias.isEmpty() )
12765 aLine.append( " aliased as \"" );
12766 aLine.append( rEle.m_aAlias );
12767 aLine.append( '\"' );
12769 emitComment( aLine.getStr() );
12770 #endif
12772 // check whether to emit structure henceforth
12773 m_bEmitStructure = checkEmitStructure();
12775 if( m_bEmitStructure ) // don't create nonexistent objects
12777 rEle.m_nObject = createObject();
12778 // update parent's kids list
12779 m_aStructure[ rEle.m_nParentElement ].m_aKids.push_back(PDFStructureElementKid(rEle.m_nObject));
12781 return nNewId;
12784 void PDFWriterImpl::endStructureElement()
12786 if( m_nCurrentPage < 0 )
12787 return;
12789 if( ! m_aContext.Tagged )
12790 return;
12792 if( m_nCurrentStructElement == 0 )
12794 // hit the struct tree root, that means there is an endStructureElement
12795 // without corresponding beginStructureElement
12796 return;
12799 // end the marked content sequence
12800 endStructureElementMCSeq();
12802 #if OSL_DEBUG_LEVEL > 1
12803 OStringBuffer aLine( "endStructureElement " );
12804 aLine.append( m_nCurrentStructElement );
12805 aLine.append( ": " );
12806 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
12807 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
12809 aLine.append( " aliased as \"" );
12810 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
12811 aLine.append( '\"' );
12813 #endif
12815 // "end" the structure element, the parent becomes current element
12816 m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
12818 // check whether to emit structure henceforth
12819 m_bEmitStructure = checkEmitStructure();
12821 #if OSL_DEBUG_LEVEL > 1
12822 if( m_bEmitStructure )
12823 emitComment( aLine.getStr() );
12824 #endif
12828 * This function adds an internal structure list container to overcome the 8191 elements array limitation
12829 * in kids element emission.
12830 * Recursive function
12833 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
12835 if( rEle.m_eType == PDFWriter::NonStructElement &&
12836 rEle.m_nOwnElement != rEle.m_nParentElement )
12837 return;
12839 for( std::list< sal_Int32 >::const_iterator it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ++it )
12841 if( *it > 0 && *it < sal_Int32(m_aStructure.size()) )
12843 PDFStructureElement& rChild = m_aStructure[ *it ];
12844 if( rChild.m_eType != PDFWriter::NonStructElement )
12846 //triggered when a child of the rEle element is found
12847 if( rChild.m_nParentElement == rEle.m_nOwnElement )
12848 addInternalStructureContainer( rChild );//examine the child
12849 else
12851 OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
12852 #if OSL_DEBUG_LEVEL > 1
12853 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure elemnt with id " << *it );
12854 #endif
12858 else
12860 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
12861 #if OSL_DEBUG_LEVEL > 1
12862 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << *it );
12863 #endif
12867 if( rEle.m_nOwnElement != rEle.m_nParentElement )
12869 if( !rEle.m_aKids.empty() )
12871 if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
12872 //then we need to add the containers for the kids elements
12873 // a list to be used for the new kid element
12874 std::list< PDFStructureElementKid > aNewKids;
12875 std::list< sal_Int32 > aNewChildren;
12877 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
12878 OStringBuffer aNameBuf( "Div" );
12879 OString aAliasName( aNameBuf.makeStringAndClear() );
12880 m_aRoleMap[ aAliasName ] = getStructureTag( PDFWriter::Division );
12882 while( rEle.m_aKids.size() > ncMaxPDFArraySize )
12884 sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
12885 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
12886 m_aStructure.push_back( PDFStructureElement() );
12887 PDFStructureElement& rEleNew = m_aStructure.back();
12888 rEleNew.m_aAlias = aAliasName;
12889 rEleNew.m_eType = PDFWriter::Division; // a new Div type container
12890 rEleNew.m_nOwnElement = nNewId;
12891 rEleNew.m_nParentElement = nCurrentStructElement;
12892 //inherit the same page as the first child to be reparented
12893 rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
12894 rEleNew.m_nObject = createObject();//assign a PDF object number
12895 //add the object to the kid list of the parent
12896 aNewKids.push_back( PDFStructureElementKid( rEleNew.m_nObject ) );
12897 aNewChildren.push_back( nNewId );
12899 std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
12900 std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
12901 advance( aChildEndIt, ncMaxPDFArraySize );
12902 advance( aKidEndIt, ncMaxPDFArraySize );
12904 rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
12905 rEle.m_aKids,
12906 rEle.m_aKids.begin(),
12907 aKidEndIt );
12908 rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
12909 rEle.m_aChildren,
12910 rEle.m_aChildren.begin(),
12911 aChildEndIt );
12912 // set the kid's new parent
12913 for( std::list< sal_Int32 >::const_iterator it = rEleNew.m_aChildren.begin();
12914 it != rEleNew.m_aChildren.end(); ++it )
12916 m_aStructure[ *it ].m_nParentElement = nNewId;
12919 //finally add the new kids resulting from the container added
12920 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
12921 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
12927 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
12929 bool bSuccess = false;
12931 if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
12933 // end eventual previous marked content sequence
12934 endStructureElementMCSeq();
12936 m_nCurrentStructElement = nEle;
12937 m_bEmitStructure = checkEmitStructure();
12938 #if OSL_DEBUG_LEVEL > 1
12939 OStringBuffer aLine( "setCurrentStructureElement " );
12940 aLine.append( m_nCurrentStructElement );
12941 aLine.append( ": " );
12942 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
12943 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
12945 aLine.append( " aliased as \"" );
12946 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
12947 aLine.append( '\"' );
12949 if( ! m_bEmitStructure )
12950 aLine.append( " (inside NonStruct)" );
12951 emitComment( aLine.getStr() );
12952 #endif
12953 bSuccess = true;
12956 return bSuccess;
12959 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
12961 if( !m_aContext.Tagged )
12962 return false;
12964 bool bInsert = false;
12965 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
12967 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
12968 switch( eAttr )
12970 case PDFWriter::Placement:
12971 if( eVal == PDFWriter::Block ||
12972 eVal == PDFWriter::Inline ||
12973 eVal == PDFWriter::Before ||
12974 eVal == PDFWriter::Start ||
12975 eVal == PDFWriter::End )
12976 bInsert = true;
12977 break;
12978 case PDFWriter::WritingMode:
12979 if( eVal == PDFWriter::LrTb ||
12980 eVal == PDFWriter::RlTb ||
12981 eVal == PDFWriter::TbRl )
12983 bInsert = true;
12985 break;
12986 case PDFWriter::TextAlign:
12987 if( eVal == PDFWriter::Start ||
12988 eVal == PDFWriter::Center ||
12989 eVal == PDFWriter::End ||
12990 eVal == PDFWriter::Justify )
12992 if( eType == PDFWriter::Paragraph ||
12993 eType == PDFWriter::Heading ||
12994 eType == PDFWriter::H1 ||
12995 eType == PDFWriter::H2 ||
12996 eType == PDFWriter::H3 ||
12997 eType == PDFWriter::H4 ||
12998 eType == PDFWriter::H5 ||
12999 eType == PDFWriter::H6 ||
13000 eType == PDFWriter::List ||
13001 eType == PDFWriter::ListItem ||
13002 eType == PDFWriter::LILabel ||
13003 eType == PDFWriter::LIBody ||
13004 eType == PDFWriter::Table ||
13005 eType == PDFWriter::TableRow ||
13006 eType == PDFWriter::TableHeader ||
13007 eType == PDFWriter::TableData )
13009 bInsert = true;
13012 break;
13013 case PDFWriter::Width:
13014 case PDFWriter::Height:
13015 if( eVal == PDFWriter::Auto )
13017 if( eType == PDFWriter::Figure ||
13018 eType == PDFWriter::Formula ||
13019 eType == PDFWriter::Form ||
13020 eType == PDFWriter::Table ||
13021 eType == PDFWriter::TableHeader ||
13022 eType == PDFWriter::TableData )
13024 bInsert = true;
13027 break;
13028 case PDFWriter::BlockAlign:
13029 if( eVal == PDFWriter::Before ||
13030 eVal == PDFWriter::Middle ||
13031 eVal == PDFWriter::After ||
13032 eVal == PDFWriter::Justify )
13034 if( eType == PDFWriter::TableHeader ||
13035 eType == PDFWriter::TableData )
13037 bInsert = true;
13040 break;
13041 case PDFWriter::InlineAlign:
13042 if( eVal == PDFWriter::Start ||
13043 eVal == PDFWriter::Center ||
13044 eVal == PDFWriter::End )
13046 if( eType == PDFWriter::TableHeader ||
13047 eType == PDFWriter::TableData )
13049 bInsert = true;
13052 break;
13053 case PDFWriter::LineHeight:
13054 if( eVal == PDFWriter::Normal ||
13055 eVal == PDFWriter::Auto )
13057 // only for ILSE and BLSE
13058 if( eType == PDFWriter::Paragraph ||
13059 eType == PDFWriter::Heading ||
13060 eType == PDFWriter::H1 ||
13061 eType == PDFWriter::H2 ||
13062 eType == PDFWriter::H3 ||
13063 eType == PDFWriter::H4 ||
13064 eType == PDFWriter::H5 ||
13065 eType == PDFWriter::H6 ||
13066 eType == PDFWriter::List ||
13067 eType == PDFWriter::ListItem ||
13068 eType == PDFWriter::LILabel ||
13069 eType == PDFWriter::LIBody ||
13070 eType == PDFWriter::Table ||
13071 eType == PDFWriter::TableRow ||
13072 eType == PDFWriter::TableHeader ||
13073 eType == PDFWriter::TableData ||
13074 eType == PDFWriter::Span ||
13075 eType == PDFWriter::Quote ||
13076 eType == PDFWriter::Note ||
13077 eType == PDFWriter::Reference ||
13078 eType == PDFWriter::BibEntry ||
13079 eType == PDFWriter::Code ||
13080 eType == PDFWriter::Link )
13082 bInsert = true;
13085 break;
13086 case PDFWriter::TextDecorationType:
13087 if( eVal == PDFWriter::NONE ||
13088 eVal == PDFWriter::Underline ||
13089 eVal == PDFWriter::Overline ||
13090 eVal == PDFWriter::LineThrough )
13092 // only for ILSE and BLSE
13093 if( eType == PDFWriter::Paragraph ||
13094 eType == PDFWriter::Heading ||
13095 eType == PDFWriter::H1 ||
13096 eType == PDFWriter::H2 ||
13097 eType == PDFWriter::H3 ||
13098 eType == PDFWriter::H4 ||
13099 eType == PDFWriter::H5 ||
13100 eType == PDFWriter::H6 ||
13101 eType == PDFWriter::List ||
13102 eType == PDFWriter::ListItem ||
13103 eType == PDFWriter::LILabel ||
13104 eType == PDFWriter::LIBody ||
13105 eType == PDFWriter::Table ||
13106 eType == PDFWriter::TableRow ||
13107 eType == PDFWriter::TableHeader ||
13108 eType == PDFWriter::TableData ||
13109 eType == PDFWriter::Span ||
13110 eType == PDFWriter::Quote ||
13111 eType == PDFWriter::Note ||
13112 eType == PDFWriter::Reference ||
13113 eType == PDFWriter::BibEntry ||
13114 eType == PDFWriter::Code ||
13115 eType == PDFWriter::Link )
13117 bInsert = true;
13120 break;
13121 case PDFWriter::ListNumbering:
13122 if( eVal == PDFWriter::NONE ||
13123 eVal == PDFWriter::Disc ||
13124 eVal == PDFWriter::Circle ||
13125 eVal == PDFWriter::Square ||
13126 eVal == PDFWriter::Decimal ||
13127 eVal == PDFWriter::UpperRoman ||
13128 eVal == PDFWriter::LowerRoman ||
13129 eVal == PDFWriter::UpperAlpha ||
13130 eVal == PDFWriter::LowerAlpha )
13132 if( eType == PDFWriter::List )
13133 bInsert = true;
13135 break;
13136 default: break;
13140 if( bInsert )
13141 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
13142 #if OSL_DEBUG_LEVEL > 1
13143 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
13144 SAL_INFO("vcl.pdfwriter",
13145 "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
13146 << ", " << getAttributeValueTag( eVal )
13147 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
13148 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr()
13149 << ") element");
13150 #endif
13152 return bInsert;
13155 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
13157 if( ! m_aContext.Tagged )
13158 return false;
13160 bool bInsert = false;
13161 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
13163 if( eAttr == PDFWriter::Language )
13165 m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
13166 return true;
13169 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
13170 switch( eAttr )
13172 case PDFWriter::SpaceBefore:
13173 case PDFWriter::SpaceAfter:
13174 case PDFWriter::StartIndent:
13175 case PDFWriter::EndIndent:
13176 // just for BLSE
13177 if( eType == PDFWriter::Paragraph ||
13178 eType == PDFWriter::Heading ||
13179 eType == PDFWriter::H1 ||
13180 eType == PDFWriter::H2 ||
13181 eType == PDFWriter::H3 ||
13182 eType == PDFWriter::H4 ||
13183 eType == PDFWriter::H5 ||
13184 eType == PDFWriter::H6 ||
13185 eType == PDFWriter::List ||
13186 eType == PDFWriter::ListItem ||
13187 eType == PDFWriter::LILabel ||
13188 eType == PDFWriter::LIBody ||
13189 eType == PDFWriter::Table ||
13190 eType == PDFWriter::TableRow ||
13191 eType == PDFWriter::TableHeader ||
13192 eType == PDFWriter::TableData )
13194 bInsert = true;
13196 break;
13197 case PDFWriter::TextIndent:
13198 // paragraph like BLSE and additional elements
13199 if( eType == PDFWriter::Paragraph ||
13200 eType == PDFWriter::Heading ||
13201 eType == PDFWriter::H1 ||
13202 eType == PDFWriter::H2 ||
13203 eType == PDFWriter::H3 ||
13204 eType == PDFWriter::H4 ||
13205 eType == PDFWriter::H5 ||
13206 eType == PDFWriter::H6 ||
13207 eType == PDFWriter::LILabel ||
13208 eType == PDFWriter::LIBody ||
13209 eType == PDFWriter::TableHeader ||
13210 eType == PDFWriter::TableData )
13212 bInsert = true;
13214 break;
13215 case PDFWriter::Width:
13216 case PDFWriter::Height:
13217 if( eType == PDFWriter::Figure ||
13218 eType == PDFWriter::Formula ||
13219 eType == PDFWriter::Form ||
13220 eType == PDFWriter::Table ||
13221 eType == PDFWriter::TableHeader ||
13222 eType == PDFWriter::TableData )
13224 bInsert = true;
13226 break;
13227 case PDFWriter::LineHeight:
13228 case PDFWriter::BaselineShift:
13229 // only for ILSE and BLSE
13230 if( eType == PDFWriter::Paragraph ||
13231 eType == PDFWriter::Heading ||
13232 eType == PDFWriter::H1 ||
13233 eType == PDFWriter::H2 ||
13234 eType == PDFWriter::H3 ||
13235 eType == PDFWriter::H4 ||
13236 eType == PDFWriter::H5 ||
13237 eType == PDFWriter::H6 ||
13238 eType == PDFWriter::List ||
13239 eType == PDFWriter::ListItem ||
13240 eType == PDFWriter::LILabel ||
13241 eType == PDFWriter::LIBody ||
13242 eType == PDFWriter::Table ||
13243 eType == PDFWriter::TableRow ||
13244 eType == PDFWriter::TableHeader ||
13245 eType == PDFWriter::TableData ||
13246 eType == PDFWriter::Span ||
13247 eType == PDFWriter::Quote ||
13248 eType == PDFWriter::Note ||
13249 eType == PDFWriter::Reference ||
13250 eType == PDFWriter::BibEntry ||
13251 eType == PDFWriter::Code ||
13252 eType == PDFWriter::Link )
13254 bInsert = true;
13256 break;
13257 case PDFWriter::RowSpan:
13258 case PDFWriter::ColSpan:
13259 // only for table cells
13260 if( eType == PDFWriter::TableHeader ||
13261 eType == PDFWriter::TableData )
13263 bInsert = true;
13265 break;
13266 case PDFWriter::LinkAnnotation:
13267 if( eType == PDFWriter::Link )
13268 bInsert = true;
13269 break;
13270 default: break;
13274 if( bInsert )
13275 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
13276 #if OSL_DEBUG_LEVEL > 1
13277 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
13278 SAL_INFO("vcl.pdfwriter",
13279 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
13280 << ", " << (int)nValue
13281 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
13282 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr()
13283 << ") element");
13284 #endif
13286 return bInsert;
13289 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
13291 sal_Int32 nPageNr = m_nCurrentPage;
13292 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() || !m_aContext.Tagged )
13293 return;
13295 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
13297 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
13298 if( eType == PDFWriter::Figure ||
13299 eType == PDFWriter::Formula ||
13300 eType == PDFWriter::Form ||
13301 eType == PDFWriter::Table )
13303 m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
13304 // convert to default user space now, since the mapmode may change
13305 m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
13310 void PDFWriterImpl::setActualText( const OUString& rText )
13312 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
13314 m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
13318 void PDFWriterImpl::setAlternateText( const OUString& rText )
13320 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
13322 m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
13326 void PDFWriterImpl::setAutoAdvanceTime( sal_uInt32 nSeconds, sal_Int32 nPageNr )
13328 if( nPageNr < 0 )
13329 nPageNr = m_nCurrentPage;
13331 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
13332 return;
13334 m_aPages[ nPageNr ].m_nDuration = nSeconds;
13337 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
13339 if( nPageNr < 0 )
13340 nPageNr = m_nCurrentPage;
13342 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
13343 return;
13345 m_aPages[ nPageNr ].m_eTransition = eType;
13346 m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
13349 void PDFWriterImpl::ensureUniqueRadioOnValues()
13351 // loop over radio groups
13352 for( std::map<sal_Int32,sal_Int32>::const_iterator group = m_aRadioGroupWidgets.begin();
13353 group != m_aRadioGroupWidgets.end(); ++group )
13355 PDFWidget& rGroupWidget = m_aWidgets[ group->second ];
13356 // check whether all kids have a unique OnValue
13357 std::unordered_map< OUString, sal_Int32, OUStringHash > aOnValues;
13358 int nChildren = rGroupWidget.m_aKidsIndex.size();
13359 bool bIsUnique = true;
13360 for( int nKid = 0; nKid < nChildren && bIsUnique; nKid++ )
13362 int nKidIndex = rGroupWidget.m_aKidsIndex[nKid];
13363 const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
13364 #if OSL_DEBUG_LEVEL > 1
13365 SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
13366 #endif
13367 if( aOnValues.find( rVal ) == aOnValues.end() )
13369 aOnValues[ rVal ] = 1;
13371 else
13373 bIsUnique = false;
13376 if( ! bIsUnique )
13378 #if OSL_DEBUG_LEVEL > 1
13379 SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
13380 #endif
13381 // make unique by using ascending OnValues
13382 for( int nKid = 0; nKid < nChildren; nKid++ )
13384 int nKidIndex = rGroupWidget.m_aKidsIndex[nKid];
13385 PDFWidget& rKid = m_aWidgets[nKidIndex];
13386 rKid.m_aOnValue = OUString::number( nKid+1 );
13387 if( rKid.m_aValue != "Off" )
13388 rKid.m_aValue = rKid.m_aOnValue;
13391 // finally move the "Yes" appearance to the OnValue appearance
13392 for( int nKid = 0; nKid < nChildren; nKid++ )
13394 int nKidIndex = rGroupWidget.m_aKidsIndex[nKid];
13395 PDFWidget& rKid = m_aWidgets[nKidIndex];
13396 PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" );
13397 if( app_it != rKid.m_aAppearances.end() )
13399 PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" );
13400 if( stream_it != app_it->second.end() )
13402 SvMemoryStream* pStream = stream_it->second;
13403 app_it->second.erase( stream_it );
13404 OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
13405 appendName( rKid.m_aOnValue, aBuf );
13406 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
13408 #if OSL_DEBUG_LEVEL > 1
13409 else
13410 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
13411 #endif
13413 // update selected radio button
13414 if( rKid.m_aValue != "Off" )
13416 rGroupWidget.m_aValue = rKid.m_aValue;
13422 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
13424 sal_Int32 nRadioGroupWidget = -1;
13426 std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
13428 if( it == m_aRadioGroupWidgets.end() )
13430 m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
13431 sal_Int32(m_aWidgets.size());
13433 // new group, insert the radiobutton
13434 m_aWidgets.push_back( PDFWidget() );
13435 m_aWidgets.back().m_nObject = createObject();
13436 m_aWidgets.back().m_nPage = m_nCurrentPage;
13437 m_aWidgets.back().m_eType = PDFWriter::RadioButton;
13438 m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
13439 m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits
13441 createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
13443 else
13444 nRadioGroupWidget = it->second;
13446 return nRadioGroupWidget;
13449 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
13451 if( nPageNr < 0 )
13452 nPageNr = m_nCurrentPage;
13454 if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
13455 return -1;
13457 bool sigHidden(true);
13458 sal_Int32 nNewWidget = m_aWidgets.size();
13459 m_aWidgets.push_back( PDFWidget() );
13461 m_aWidgets.back().m_nObject = createObject();
13462 m_aWidgets.back().m_aRect = rControl.Location;
13463 m_aWidgets.back().m_nPage = nPageNr;
13464 m_aWidgets.back().m_eType = rControl.getType();
13466 sal_Int32 nRadioGroupWidget = -1;
13467 // for unknown reasons the radio buttons of a radio group must not have a
13468 // field name, else the buttons are in fact check boxes -
13469 // that is multiple buttons of the radio group can be selected
13470 if( rControl.getType() == PDFWriter::RadioButton )
13471 nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
13472 else
13474 createWidgetFieldName( nNewWidget, rControl );
13477 // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
13478 PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
13479 rNewWidget.m_aDescription = rControl.Description;
13480 rNewWidget.m_aText = rControl.Text;
13481 rNewWidget.m_nTextStyle = rControl.TextStyle &
13482 ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
13483 DrawTextFlags::VCenter | DrawTextFlags::Bottom |
13484 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
13485 rNewWidget.m_nTabOrder = rControl.TabOrder;
13487 // various properties are set via the flags (/Ff) property of the field dict
13488 if( rControl.ReadOnly )
13489 rNewWidget.m_nFlags |= 1;
13490 if( rControl.getType() == PDFWriter::PushButton )
13492 const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
13493 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
13494 rNewWidget.m_nTextStyle =
13495 DrawTextFlags::Center | DrawTextFlags::VCenter |
13496 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
13498 rNewWidget.m_nFlags |= 0x00010000;
13499 if( !rBtn.URL.isEmpty() )
13500 rNewWidget.m_aListEntries.push_back( rBtn.URL );
13501 rNewWidget.m_bSubmit = rBtn.Submit;
13502 rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
13503 rNewWidget.m_nDest = rBtn.Dest;
13504 createDefaultPushButtonAppearance( rNewWidget, rBtn );
13506 else if( rControl.getType() == PDFWriter::RadioButton )
13508 const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
13509 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
13510 rNewWidget.m_nTextStyle =
13511 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
13512 /* PDF sees a RadioButton group as one radio button with
13513 * children which are in turn check boxes
13515 * so we need to create a radio button on demand for a new group
13516 * and insert a checkbox for each RadioButtonWidget as its child
13518 rNewWidget.m_eType = PDFWriter::CheckBox;
13519 rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
13521 SAL_WARN_IF( nRadioGroupWidget < 0 || nRadioGroupWidget >= (sal_Int32)m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
13523 PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
13524 rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
13525 rRadioButton.m_aKidsIndex.push_back( nNewWidget );
13526 rNewWidget.m_nParent = rRadioButton.m_nObject;
13528 rNewWidget.m_aValue = "Off";
13529 rNewWidget.m_aOnValue = rBtn.OnValue;
13530 if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
13532 rNewWidget.m_aValue = rNewWidget.m_aOnValue;
13533 rRadioButton.m_aValue = rNewWidget.m_aOnValue;
13535 createDefaultRadioButtonAppearance( rNewWidget, rBtn );
13537 // union rect of radio group
13538 tools::Rectangle aRect = rNewWidget.m_aRect;
13539 m_aPages[ nPageNr ].convertRect( aRect );
13540 rRadioButton.m_aRect.Union( aRect );
13542 else if( rControl.getType() == PDFWriter::CheckBox )
13544 const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
13545 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
13546 rNewWidget.m_nTextStyle =
13547 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
13549 rNewWidget.m_aValue = rBox.Checked ? OUString("Yes") : OUString("Off" );
13550 // create default appearance before m_aRect gets transformed
13551 createDefaultCheckBoxAppearance( rNewWidget, rBox );
13553 else if( rControl.getType() == PDFWriter::ListBox )
13555 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
13556 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
13558 const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
13559 rNewWidget.m_aListEntries = rLstBox.Entries;
13560 rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
13561 rNewWidget.m_aValue = rLstBox.Text;
13562 if( rLstBox.DropDown )
13563 rNewWidget.m_nFlags |= 0x00020000;
13564 if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
13565 rNewWidget.m_nFlags |= 0x00200000;
13567 createDefaultListBoxAppearance( rNewWidget, rLstBox );
13569 else if( rControl.getType() == PDFWriter::ComboBox )
13571 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
13572 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
13574 const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
13575 rNewWidget.m_aValue = rBox.Text;
13576 rNewWidget.m_aListEntries = rBox.Entries;
13577 rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
13579 PDFWriter::ListBoxWidget aLBox;
13580 aLBox.Name = rBox.Name;
13581 aLBox.Description = rBox.Description;
13582 aLBox.Text = rBox.Text;
13583 aLBox.TextStyle = rBox.TextStyle;
13584 aLBox.ReadOnly = rBox.ReadOnly;
13585 aLBox.Border = rBox.Border;
13586 aLBox.BorderColor = rBox.BorderColor;
13587 aLBox.Background = rBox.Background;
13588 aLBox.BackgroundColor = rBox.BackgroundColor;
13589 aLBox.TextFont = rBox.TextFont;
13590 aLBox.TextColor = rBox.TextColor;
13591 aLBox.DropDown = true;
13592 aLBox.MultiSelect = false;
13593 aLBox.Entries = rBox.Entries;
13595 createDefaultListBoxAppearance( rNewWidget, aLBox );
13597 else if( rControl.getType() == PDFWriter::Edit )
13599 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
13600 rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
13602 const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
13603 if( rEdit.MultiLine )
13605 rNewWidget.m_nFlags |= 0x00001000;
13606 rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
13608 if( rEdit.Password )
13609 rNewWidget.m_nFlags |= 0x00002000;
13610 if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
13611 rNewWidget.m_nFlags |= 0x00100000;
13612 rNewWidget.m_nMaxLen = rEdit.MaxLen;
13613 rNewWidget.m_aValue = rEdit.Text;
13615 createDefaultEditAppearance( rNewWidget, rEdit );
13617 #if HAVE_FEATURE_NSS
13618 else if( rControl.getType() == PDFWriter::Signature)
13620 sigHidden = true;
13622 rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
13624 m_nSignatureObject = createObject();
13625 rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
13626 rNewWidget.m_aValue += " 0 R";
13627 // let's add a fake appearance
13628 rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
13630 #endif
13632 // if control is a hidden signature, do not convert coordinates since we
13633 // need /Rect [ 0 0 0 0 ]
13634 if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && ( sigHidden ) ) )
13636 // convert to default user space now, since the mapmode may change
13637 // note: create default appearances before m_aRect gets transformed
13638 m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
13641 // insert widget to page's annotation list
13642 m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
13644 // mark page as having widgets
13645 m_aPages[ nPageNr ].m_bHasWidgets = true;
13647 return nNewWidget;
13650 void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
13652 if( pStream )
13654 m_aAdditionalStreams.push_back( PDFAddStream() );
13655 PDFAddStream& rStream = m_aAdditionalStreams.back();
13656 rStream.m_aMimeType = !rMimeType.isEmpty()
13657 ? OUString( rMimeType )
13658 : OUString( "application/octet-stream" );
13659 rStream.m_pStream = pStream;
13660 rStream.m_bCompress = false;
13664 void PDFWriterImpl::MARK( const char* pString )
13666 beginStructureElementMCSeq();
13667 if (g_bDebugDisableCompression)
13668 emitComment( pString );
13671 sal_Int32 PDFWriterImpl::ReferenceXObjectEmit::getObject() const
13673 if (m_nFormObject > 0)
13674 return m_nFormObject;
13675 else
13676 return m_nBitmapObject;
13679 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */