Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/pdf/pdfwriter_impl.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
#include <config_crypto.h>
22
23
#include <sal/types.h>
24
25
#include <math.h>
26
#include <algorithm>
27
#include <ranges>
28
#include <string_view>
29
30
#include <lcms2.h>
31
32
#include <basegfx/matrix/b2dhommatrix.hxx>
33
#include <basegfx/polygon/b2dpolygon.hxx>
34
#include <basegfx/polygon/b2dpolygontools.hxx>
35
#include <basegfx/polygon/b2dpolypolygon.hxx>
36
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
37
#include <basegfx/polygon/b2dpolypolygontools.hxx>
38
#include <basegfx/vector/b2dsize.hxx>
39
#include <memory>
40
#include <com/sun/star/awt/GradientStyle.hpp>
41
#include <com/sun/star/io/XOutputStream.hpp>
42
#include <com/sun/star/util/URL.hpp>
43
#include <com/sun/star/util/URLTransformer.hpp>
44
#include <comphelper/processfactory.hxx>
45
#include <comphelper/string.hxx>
46
#include <comphelper/xmlencode.hxx>
47
#include <cppuhelper/implbase.hxx>
48
#include <i18nlangtag/languagetag.hxx>
49
#include <o3tl/numeric.hxx>
50
#include <o3tl/safeint.hxx>
51
#include <o3tl/temporary.hxx>
52
#include <officecfg/Office/Common.hxx>
53
#include <osl/diagnose.h>
54
#include <osl/file.hxx>
55
#include <osl/thread.h>
56
#include <rtl/crc.h>
57
#include <o3tl/string_view.hxx>
58
#include <rtl/character.hxx>
59
#include <rtl/digest.h>
60
#include <rtl/uri.hxx>
61
#include <rtl/ustrbuf.hxx>
62
#include <svl/cryptosign.hxx>
63
#include <sal/log.hxx>
64
#include <svl/urihelper.hxx>
65
#include <tools/fract.hxx>
66
#include <tools/stream.hxx>
67
#include <tools/helpers.hxx>
68
#include <tools/urlobj.hxx>
69
#include <tools/UnitConversion.hxx>
70
#include <tools/zcodec.hxx>
71
#include <unotools/configmgr.hxx>
72
#include <vcl/bitmap.hxx>
73
#include <vcl/canvastools.hxx>
74
#include <vcl/cvtgrf.hxx>
75
#include <vcl/fontcharmap.hxx>
76
#include <vcl/fntstyle.hxx>
77
#include <vcl/glyphitemcache.hxx>
78
#include <vcl/kernarray.hxx>
79
#include <vcl/lineinfo.hxx>
80
#include <vcl/metric.hxx>
81
#include <vcl/mnemonic.hxx>
82
#include <vcl/pdfread.hxx>
83
#include <vcl/rendercontext/DrawModeFlags.hxx>
84
#include <vcl/settings.hxx>
85
#include <strhelper.hxx>
86
#include <vcl/svapp.hxx>
87
#include <vcl/vectorgraphicdata.hxx>
88
#include <vcl/virdev.hxx>
89
#include <vcl/filter/pdfdocument.hxx>
90
#include <vcl/filter/PngImageReader.hxx>
91
#include <comphelper/hash.hxx>
92
#include <vcl/pdf/PDFNote.hxx>
93
94
#include <svdata.hxx>
95
#include <vcl/BitmapWriteAccess.hxx>
96
#include <pdf/COSWriter.hxx>
97
#include <fontsubset.hxx>
98
#include <font/EmphasisMark.hxx>
99
#include <font/PhysicalFontFace.hxx>
100
#include <salgdi.hxx>
101
#include <textlayout.hxx>
102
#include <textlineinfo.hxx>
103
#include <impglyphitem.hxx>
104
#include <pdf/XmpMetadata.hxx>
105
#include <pdf/objectcopier.hxx>
106
#include <pdf/pdfwriter_impl.hxx>
107
#include <pdf/PdfConfig.hxx>
108
#include <pdf/PDFEncryptorR6.hxx>
109
#include <pdf/PDFEncryptor.hxx>
110
#include <o3tl/sorted_vector.hxx>
111
#include <frozen/bits/defines.h>
112
#include <frozen/bits/elsa_std.h>
113
#include <frozen/unordered_map.h>
114
115
#include "pdfwriter_utils.hxx"
116
117
using namespace::com::sun::star;
118
119
static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
120
121
namespace vcl
122
{
123
124
constexpr sal_Int32 PDFWRITER_IMPL_BUFFERSIZE = 1024;
125
126
namespace
127
{
128
129
void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
130
131
// a reader inflates a whole object stream to reach any one object in it
132
constexpr size_t nMaxObjectsPerStream = 200;
133
134
// deflates a buffer for a /FlateDecode stream whose /Length has to be known before it is written
135
std::vector<sal_uInt8> deflateBuffer(std::string_view aData)
136
0
{
137
0
    if (g_bDebugDisableCompression)
138
0
        return { aData.begin(), aData.end() };
139
140
0
    SvMemoryStream aInput(const_cast<char*>(aData.data()), aData.size(), StreamMode::READ);
141
0
    SvMemoryStream aOutput(aData.size(), 0x4000);
142
0
    ZCodec aCodec(0x4000, 0x4000);
143
0
    aCodec.BeginCompression();
144
0
    aCodec.Compress(aInput, aOutput);
145
    // it answers with the bytes it took, and a short count would leave a stream that does not
146
    // hold what its /Length claims
147
0
    if (aCodec.EndCompression() != static_cast<tools::Long>(aData.size()))
148
0
        return {};
149
150
0
    const sal_uInt8* pBegin = static_cast<const sal_uInt8*>(aOutput.GetData());
151
0
    return { pBegin, pBegin + aOutput.Tell() };
152
0
}
153
154
// the types ISO 32000-2 14.8.6 left out of the PDF 2.0 standard structure namespace
155
bool isPDF17OnlyType(StructElement eType)
156
0
{
157
0
    switch (eType)
158
0
    {
159
0
        case StructElement::Article:
160
0
        case StructElement::BlockQuote:
161
0
        case StructElement::TOC:
162
0
        case StructElement::TOCI:
163
0
        case StructElement::Index:
164
0
        case StructElement::Quote:
165
0
        case StructElement::Note:
166
0
        case StructElement::Reference:
167
0
        case StructElement::BibEntry:
168
0
        case StructElement::Code:
169
0
            return true;
170
0
        default:
171
0
            return false;
172
0
    }
173
0
}
174
175
// the attributes this writer can set that ISO 32000-2 14.8.5.4 measures in user space units;
176
// every numerical layout attribute prints as thousandths of a point, so a new one belongs here
177
bool isLengthAttribute(PDFWriter::StructAttribute eAttr)
178
0
{
179
0
    switch (eAttr)
180
0
    {
181
0
        case PDFWriter::SpaceBefore:
182
0
        case PDFWriter::SpaceAfter:
183
0
        case PDFWriter::StartIndent:
184
0
        case PDFWriter::EndIndent:
185
0
        case PDFWriter::TextIndent:
186
0
        case PDFWriter::Width:
187
0
        case PDFWriter::Height:
188
0
        case PDFWriter::LineHeight:
189
0
        case PDFWriter::BaselineShift:
190
0
            return true;
191
0
        default:
192
0
            return false;
193
0
    }
194
0
}
195
196
constexpr auto constTagStrings = frozen::make_unordered_map<StructElement, const char*>({
197
    { StructElement::NonStructElement, "NonStruct" },
198
    { StructElement::Document, "Document" },
199
    { StructElement::Part, "Part" },
200
    { StructElement::Article, "Art" },
201
    { StructElement::Section, "Sect" },
202
    { StructElement::Division, "Div" },
203
    { StructElement::BlockQuote, "BlockQuote" },
204
    { StructElement::Caption, "Caption" },
205
    { StructElement::TOC, "TOC" },
206
    { StructElement::TOCI, "TOCI" },
207
    { StructElement::Index, "Index" },
208
    { StructElement::Paragraph, "P" },
209
    { StructElement::Heading, "H" },
210
    { StructElement::H1, "H1" },
211
    { StructElement::H2, "H2" },
212
    { StructElement::H3, "H3" },
213
    { StructElement::H4, "H4" },
214
    { StructElement::H5, "H5" },
215
    { StructElement::H6, "H6" },
216
    { StructElement::List, "L" },
217
    { StructElement::ListItem, "LI" },
218
    { StructElement::LILabel, "Lbl" },
219
    { StructElement::LIBody, "LBody" },
220
    { StructElement::Table, "Table" },
221
    { StructElement::TableRow, "TR" },
222
    { StructElement::TableHeader, "TH" },
223
    { StructElement::TableData, "TD" },
224
    { StructElement::Span, "Span" },
225
    { StructElement::Quote, "Quote" },
226
    { StructElement::Note, "Note" },
227
    { StructElement::Reference, "Reference" },
228
    { StructElement::BibEntry, "BibEntry" },
229
    { StructElement::Code, "Code" },
230
    { StructElement::Link, "Link" },
231
    { StructElement::Annot, "Annot" },
232
    { StructElement::Ruby, "Ruby" },
233
    { StructElement::RB, "RB" },
234
    { StructElement::RT, "RT" },
235
    { StructElement::RP, "RP" },
236
    { StructElement::Warichu, "Warichu" },
237
    { StructElement::WT, "WT" },
238
    { StructElement::WP, "WP" },
239
    { StructElement::Figure, "Figure" },
240
    { StructElement::Formula, "Formula" },
241
    { StructElement::Form, "Form" },
242
    { StructElement::Title, "Title" },
243
    { StructElement::Emphasis, "Em" },
244
    { StructElement::Strong, "Strong" },
245
    { StructElement::FENote, "FENote" },
246
});
247
248
// standard structure types getStructureTag never returns, so the table above lacks them:
249
// ISO 32000-1 14.8.4, and the ones ISO 32000-2 adds
250
// Annot is standard from PDF 1.5, and below that getStructureTag answers Figure for it
251
constexpr std::string_view constUnusedTagStrings[]{ "Annot", "Artifact", "Private",
252
                                                    "THead", "TBody",    "TFoot" };
253
constexpr std::string_view constUnusedTagStrings20[]{ "Aside", "DocumentFragment", "Sub" };
254
255
// PDF 2.0 has heading levels without a limit, so H7 and up are standard there as well;
256
// H1 to H6 are the table's, and must not answer here, or the level could be claimed wrong
257
bool isDeepHeading(std::string_view aName)
258
0
{
259
0
    return aName.size() > 1 && aName[0] == 'H' && aName[1] != '0'
260
0
           && std::ranges::all_of(
261
0
                  aName.substr(1),
262
0
                  [](char c) { return rtl::isAsciiDigit(static_cast<sal_uInt8>(c)); })
263
0
           && o3tl::toInt32(aName.substr(1)) > 6;
264
0
}
265
266
} // end anonymous namespace
267
268
void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
269
0
{
270
    /* #i80258# previously we use appendName here
271
       however we need a slightly different coding scheme than the normal
272
       name encoding for field names
273
    */
274
0
    const OUString& rName = i_rControl.Name;
275
0
    OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
276
0
    int nLen = aStr.getLength();
277
278
0
    OStringBuffer aBuffer( rName.getLength()+64 );
279
0
    for( int i = 0; i < nLen; i++ )
280
0
    {
281
        /*  #i16920# PDF recommendation: output UTF8, any byte
282
         *  outside the interval [32(=ASCII' ');126(=ASCII'~')]
283
         *  should be escaped hexadecimal
284
         */
285
0
        if( aStr[i] >= 32 && aStr[i] <= 126 )
286
0
            aBuffer.append( aStr[i] );
287
0
        else
288
0
        {
289
0
            aBuffer.append( '#' );
290
0
            COSWriter::appendHex(static_cast<sal_Int8>(aStr[i]), aBuffer);
291
0
        }
292
0
    }
293
294
0
    OString aFullName( aBuffer.makeStringAndClear() );
295
296
    /* #i82785# create hierarchical fields down to the for each dot in i_rName */
297
0
    sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
298
0
    OString aPartialName;
299
0
    OString aDomain;
300
0
    do
301
0
    {
302
0
        nLastTokenIndex = nTokenIndex;
303
0
        aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
304
0
        if( nTokenIndex != -1 )
305
0
        {
306
            // find or create a hierarchical field
307
            // first find the fully qualified name up to this field
308
0
            aDomain = aFullName.copy( 0, nTokenIndex-1 );
309
0
            std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
310
0
            if( it == m_aFieldNameMap.end() )
311
0
            {
312
                 // create new hierarchy field
313
0
                sal_Int32 nNewWidget = m_aWidgets.size();
314
0
                m_aWidgets.emplace_back( );
315
0
                m_aWidgets[nNewWidget].m_nObject = createObject();
316
0
                m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
317
0
                m_aWidgets[nNewWidget].m_aName = aPartialName;
318
0
                m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
319
0
                m_aFieldNameMap[aDomain] = nNewWidget;
320
0
                m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
321
0
                if( nLastTokenIndex > 0 )
322
0
                {
323
                    // this field is not a root field and
324
                    // needs to be inserted to its parent
325
0
                    OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
326
0
                    it = m_aFieldNameMap.find( aParentDomain );
327
0
                    OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
328
0
                    if( it != m_aFieldNameMap.end()  )
329
0
                    {
330
0
                        OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
331
0
                        if( it->second < sal_Int32(m_aWidgets.size()) )
332
0
                        {
333
0
                            PDFWidget& rParentField( m_aWidgets[it->second] );
334
0
                            rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
335
0
                            rParentField.m_aKidsIndex.push_back( nNewWidget );
336
0
                            m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
337
0
                        }
338
0
                    }
339
0
                }
340
0
            }
341
0
            else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
342
0
            {
343
                // this is invalid, someone tries to have a terminal field as parent
344
                // example: a button with the name foo.bar exists and
345
                // another button is named foo.bar.no
346
                // workaround: put the second terminal field as much up in the hierarchy as
347
                // necessary to have a non-terminal field as parent (or none at all)
348
                // since it->second already is terminal, we just need to use its parent
349
0
                aDomain.clear();
350
0
                aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
351
0
                if( nLastTokenIndex > 0 )
352
0
                {
353
0
                    aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
354
0
                    aFullName = aDomain + "." + aPartialName;
355
0
                }
356
0
                else
357
0
                    aFullName = aPartialName;
358
0
                break;
359
0
            }
360
0
        }
361
0
    } while( nTokenIndex != -1 );
362
363
    // insert widget into its hierarchy field
364
0
    if( !aDomain.isEmpty() )
365
0
    {
366
0
        std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
367
0
        if( it != m_aFieldNameMap.end() )
368
0
        {
369
0
            OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
370
0
            if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
371
0
            {
372
0
                m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
373
0
                m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
374
0
                m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
375
0
            }
376
0
        }
377
0
    }
378
379
0
    if( aPartialName.isEmpty() )
380
0
    {
381
        // how funny, an empty field name
382
0
        if( i_rControl.getType() == PDFWriter::RadioButton )
383
0
        {
384
0
            aPartialName  = "RadioGroup" +
385
0
                OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
386
0
        }
387
0
        else
388
0
            aPartialName = "Widget"_ostr;
389
0
    }
390
391
0
    if( ! m_aContext.AllowDuplicateFieldNames )
392
0
    {
393
0
        std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
394
395
0
        if( it != m_aFieldNameMap.end() ) // not unique
396
0
        {
397
0
            std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
398
0
            OString aTry;
399
0
            sal_Int32 nTry = 2;
400
0
            do
401
0
            {
402
0
                aTry = aFullName + "_" + OString::number(nTry++);
403
0
                check_it = m_aFieldNameMap.find( aTry );
404
0
            } while( check_it != m_aFieldNameMap.end() );
405
0
            aFullName = aTry;
406
0
            m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
407
0
            aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
408
0
        }
409
0
        else
410
0
            m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
411
0
    }
412
413
    // finally
414
0
    m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
415
0
}
416
417
void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
418
8.26k
{
419
8.26k
    if( rColor != COL_TRANSPARENT )
420
8.26k
    {
421
8.26k
        const bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
422
8.26k
        appendColor( rColor, rBuffer, bGrey );
423
8.26k
        rBuffer.append( bGrey ? " G" : " RG" );
424
8.26k
    }
425
8.26k
}
426
427
void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
428
700k
{
429
700k
    if( rColor != COL_TRANSPARENT )
430
700k
    {
431
700k
        const bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
432
700k
        appendColor( rColor, rBuffer, bGrey );
433
700k
        rBuffer.append( bGrey ? " g" : " rg" );
434
700k
    }
435
700k
}
436
437
PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
438
                               const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder,
439
                               PDFWriter& i_rOuterFace)
440
28.3k
        : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
441
28.3k
        m_aMapMode( MapUnit::MapPoint, Point(), 1.0 / pointToPixel(1), 1.0 / pointToPixel(1) ),
442
28.3k
        m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
443
28.3k
        m_nCurrentStructElement( 0 ),
444
28.3k
        m_bEmitStructure( true ),
445
28.3k
        m_nNextFID( 1 ),
446
28.3k
        m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
447
28.3k
            officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
448
28.3k
        m_nCurrentPage( -1 ),
449
28.3k
        m_nCatalogObject(0),
450
28.3k
        m_nSignatureObject( -1 ),
451
28.3k
        m_nSignatureContentOffset( 0 ),
452
28.3k
        m_nSignatureLastByteRangeNoOffset( 0 ),
453
28.3k
        m_nResourceDict( -1 ),
454
28.3k
        m_nFontDictObject( -1 ),
455
28.3k
        m_aContext(rContext),
456
28.3k
        m_aFile(m_aContext.URL),
457
28.3k
        m_bOpen(false),
458
28.3k
        m_DocDigest(::comphelper::HashType::MD5),
459
28.3k
        m_rOuterFace( i_rOuterFace )
460
28.3k
{
461
28.3k
    m_aStructure.emplace_back( );
462
28.3k
    m_aStructure[0].m_nOwnElement       = 0;
463
28.3k
    m_aStructure[0].m_nParentElement    = 0;
464
    //m_StructElementStack.push(0);
465
466
    // tdf#150786 use the same settings for widgets regardless of theme
467
28.3k
    m_aWidgetStyleSettings.SetStandardStyles();
468
469
28.3k
    GraphicsState aState;
470
28.3k
    aState.m_aMapMode       = m_aMapMode;
471
28.3k
    aState.m_aFont.SetFamilyName( u"Times"_ustr );
472
28.3k
    aState.m_aFont.SetFontSize( Size( 0, 12 ) );
473
474
28.3k
    m_aGraphicsStack.push_front( aState );
475
476
28.3k
    osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
477
28.3k
    if (aError != osl::File::E_None)
478
28.3k
    {
479
28.3k
        if (aError == osl::File::E_EXIST)
480
28.3k
        {
481
28.3k
            aError = m_aFile.open(osl_File_OpenFlag_Write);
482
28.3k
            if (aError == osl::File::E_None)
483
28.3k
                aError = m_aFile.setSize(0);
484
28.3k
        }
485
28.3k
    }
486
28.3k
    if (aError != osl::File::E_None)
487
0
        return;
488
489
28.3k
    m_bOpen = true;
490
491
    // setup DocInfo
492
28.3k
    setupDocInfo();
493
494
28.3k
    if (xEncryptionMaterialHolder.is())
495
0
    {
496
0
        if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4)
497
0
            m_pPDFEncryptor.reset(new PDFEncryptorR6);
498
0
        else
499
0
            m_pPDFEncryptor.reset(new PDFEncryptor);
500
0
        m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption);
501
0
    }
502
503
28.3k
    if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
504
0
    {
505
0
        m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
506
0
    }
507
508
    // write header
509
28.3k
    if (!writeBuffer(Concat2View(OString::Concat("%PDF-") + getPDFVersionStr(m_aContext.Version)
510
28.3k
                             + "\n%\303\244\303\274\303\266\303\237\n")))
511
0
    {
512
0
        m_aFile.close();
513
0
        m_bOpen = false;
514
0
        return;
515
0
    }
516
517
    // insert outline root
518
28.3k
    m_aOutline.emplace_back( );
519
520
28.3k
    switch (m_aContext.Version)
521
28.3k
    {
522
0
        case PDFWriter::PDFVersion::PDF_A_1:
523
0
            m_nPDFA_Version = 1;
524
0
            m_bIsPDF_A1 = true;
525
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
526
0
            break;
527
0
        case PDFWriter::PDFVersion::PDF_A_2:
528
0
            m_nPDFA_Version = 2;
529
0
            m_bIsPDF_A2 = true;
530
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
531
0
            break;
532
0
        case PDFWriter::PDFVersion::PDF_A_3:
533
0
            m_nPDFA_Version = 3;
534
0
            m_bIsPDF_A3 = true;
535
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
536
0
            break;
537
0
        case PDFWriter::PDFVersion::PDF_A_4:
538
0
            m_nPDFA_Version = 4;
539
0
            m_bIsPDF_A4 = true;
540
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0;
541
0
            break;
542
28.3k
        default:
543
28.3k
            break;
544
28.3k
    }
545
546
    // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
547
28.3k
    if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7)
548
0
    {
549
0
        m_bIsPDF_UA = true;
550
0
        m_aContext.Tagged = true;
551
0
    }
552
553
    // Add common PDF 2.0 namespace when we are using PDF 2.0
554
28.3k
    if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0)
555
0
    {
556
0
        m_aNamespacesMap.emplace(constNamespacePDF2, createObject());
557
        // some types exist only in the PDF 1.7 namespace
558
0
        m_aNamespacesMap.emplace(constNamespacePDF17, createObject());
559
0
    }
560
561
28.3k
    if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
562
28.3k
        SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
563
0
    else
564
0
        SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
565
566
28.3k
    SetOutputSizePixel( Size( 640, 480 ) );
567
28.3k
    SetMapMode(MapMode(MapUnit::MapMM));
568
28.3k
}
Unexecuted instantiation: vcl::PDFWriterImpl::PDFWriterImpl(vcl::pdf::PDFWriter::PDFWriterContext const&, com::sun::star::uno::Reference<com::sun::star::beans::XMaterialHolder> const&, vcl::pdf::PDFWriter&)
vcl::PDFWriterImpl::PDFWriterImpl(vcl::pdf::PDFWriter::PDFWriterContext const&, com::sun::star::uno::Reference<com::sun::star::beans::XMaterialHolder> const&, vcl::pdf::PDFWriter&)
Line
Count
Source
440
28.3k
        : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
441
28.3k
        m_aMapMode( MapUnit::MapPoint, Point(), 1.0 / pointToPixel(1), 1.0 / pointToPixel(1) ),
442
28.3k
        m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
443
28.3k
        m_nCurrentStructElement( 0 ),
444
28.3k
        m_bEmitStructure( true ),
445
28.3k
        m_nNextFID( 1 ),
446
28.3k
        m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
447
28.3k
            officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
448
28.3k
        m_nCurrentPage( -1 ),
449
28.3k
        m_nCatalogObject(0),
450
28.3k
        m_nSignatureObject( -1 ),
451
28.3k
        m_nSignatureContentOffset( 0 ),
452
28.3k
        m_nSignatureLastByteRangeNoOffset( 0 ),
453
28.3k
        m_nResourceDict( -1 ),
454
28.3k
        m_nFontDictObject( -1 ),
455
28.3k
        m_aContext(rContext),
456
28.3k
        m_aFile(m_aContext.URL),
457
28.3k
        m_bOpen(false),
458
28.3k
        m_DocDigest(::comphelper::HashType::MD5),
459
28.3k
        m_rOuterFace( i_rOuterFace )
460
28.3k
{
461
28.3k
    m_aStructure.emplace_back( );
462
28.3k
    m_aStructure[0].m_nOwnElement       = 0;
463
28.3k
    m_aStructure[0].m_nParentElement    = 0;
464
    //m_StructElementStack.push(0);
465
466
    // tdf#150786 use the same settings for widgets regardless of theme
467
28.3k
    m_aWidgetStyleSettings.SetStandardStyles();
468
469
28.3k
    GraphicsState aState;
470
28.3k
    aState.m_aMapMode       = m_aMapMode;
471
28.3k
    aState.m_aFont.SetFamilyName( u"Times"_ustr );
472
28.3k
    aState.m_aFont.SetFontSize( Size( 0, 12 ) );
473
474
28.3k
    m_aGraphicsStack.push_front( aState );
475
476
28.3k
    osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
477
28.3k
    if (aError != osl::File::E_None)
478
28.3k
    {
479
28.3k
        if (aError == osl::File::E_EXIST)
480
28.3k
        {
481
28.3k
            aError = m_aFile.open(osl_File_OpenFlag_Write);
482
28.3k
            if (aError == osl::File::E_None)
483
28.3k
                aError = m_aFile.setSize(0);
484
28.3k
        }
485
28.3k
    }
486
28.3k
    if (aError != osl::File::E_None)
487
0
        return;
488
489
28.3k
    m_bOpen = true;
490
491
    // setup DocInfo
492
28.3k
    setupDocInfo();
493
494
28.3k
    if (xEncryptionMaterialHolder.is())
495
0
    {
496
0
        if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4)
497
0
            m_pPDFEncryptor.reset(new PDFEncryptorR6);
498
0
        else
499
0
            m_pPDFEncryptor.reset(new PDFEncryptor);
500
0
        m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption);
501
0
    }
502
503
28.3k
    if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
504
0
    {
505
0
        m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
506
0
    }
507
508
    // write header
509
28.3k
    if (!writeBuffer(Concat2View(OString::Concat("%PDF-") + getPDFVersionStr(m_aContext.Version)
510
28.3k
                             + "\n%\303\244\303\274\303\266\303\237\n")))
511
0
    {
512
0
        m_aFile.close();
513
0
        m_bOpen = false;
514
0
        return;
515
0
    }
516
517
    // insert outline root
518
28.3k
    m_aOutline.emplace_back( );
519
520
28.3k
    switch (m_aContext.Version)
521
28.3k
    {
522
0
        case PDFWriter::PDFVersion::PDF_A_1:
523
0
            m_nPDFA_Version = 1;
524
0
            m_bIsPDF_A1 = true;
525
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
526
0
            break;
527
0
        case PDFWriter::PDFVersion::PDF_A_2:
528
0
            m_nPDFA_Version = 2;
529
0
            m_bIsPDF_A2 = true;
530
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
531
0
            break;
532
0
        case PDFWriter::PDFVersion::PDF_A_3:
533
0
            m_nPDFA_Version = 3;
534
0
            m_bIsPDF_A3 = true;
535
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
536
0
            break;
537
0
        case PDFWriter::PDFVersion::PDF_A_4:
538
0
            m_nPDFA_Version = 4;
539
0
            m_bIsPDF_A4 = true;
540
0
            m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0;
541
0
            break;
542
28.3k
        default:
543
28.3k
            break;
544
28.3k
    }
545
546
    // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
547
28.3k
    if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7)
548
0
    {
549
0
        m_bIsPDF_UA = true;
550
0
        m_aContext.Tagged = true;
551
0
    }
552
553
    // Add common PDF 2.0 namespace when we are using PDF 2.0
554
28.3k
    if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0)
555
0
    {
556
0
        m_aNamespacesMap.emplace(constNamespacePDF2, createObject());
557
        // some types exist only in the PDF 1.7 namespace
558
0
        m_aNamespacesMap.emplace(constNamespacePDF17, createObject());
559
0
    }
560
561
28.3k
    if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
562
28.3k
        SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
563
0
    else
564
0
        SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
565
566
28.3k
    SetOutputSizePixel( Size( 640, 480 ) );
567
28.3k
    SetMapMode(MapMode(MapUnit::MapMM));
568
28.3k
}
569
570
PDFWriterImpl::~PDFWriterImpl()
571
28.3k
{
572
28.3k
    disposeOnce();
573
28.3k
}
574
575
void PDFWriterImpl::dispose()
576
28.3k
{
577
28.3k
    m_aPages.clear();
578
28.3k
    VirtualDevice::dispose();
579
28.3k
}
580
581
bool PDFWriterImpl::ImplNewFont() const
582
6.42M
{
583
6.42M
    const ImplSVData* pSVData = ImplGetSVData();
584
585
6.42M
    if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
586
6.42M
        ||  mxFontCache == pSVData->maGDIData.mxScreenFontCache )
587
0
    {
588
0
        const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
589
0
    }
590
591
6.42M
    return OutputDevice::ImplNewFont();
592
6.42M
}
593
594
void PDFWriterImpl::setupDocInfo()
595
28.3k
{
596
28.3k
    std::vector< sal_uInt8 > aId;
597
28.3k
    m_aCreationDateString = PDFWriter::GetDateTime();
598
28.3k
    computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString);
599
28.3k
    if( m_aContext.Encryption.DocumentIdentifier.empty() )
600
28.3k
        m_aContext.Encryption.DocumentIdentifier = std::move(aId);
601
28.3k
}
602
603
OString PDFWriter::GetDateTime(svl::crypto::SigningContext* pSigningContext)
604
28.3k
{
605
28.3k
    OStringBuffer aRet;
606
607
28.3k
    TimeValue aTVal, aGMT;
608
28.3k
    oslDateTime aDT;
609
28.3k
    osl_getSystemTime(&aGMT);
610
611
28.3k
    if (pSigningContext)
612
0
    {
613
        // The context unit is milliseconds, TimeValue is seconds + nanoseconds.
614
0
        if (pSigningContext->m_nSignatureTime)
615
0
        {
616
0
            aGMT = std::chrono::milliseconds(pSigningContext->m_nSignatureTime);
617
0
        }
618
0
        else
619
0
        {
620
0
            pSigningContext->m_nSignatureTime = static_cast<sal_Int64>(aGMT.Seconds) * 1000 + aGMT.Nanosec / 1000000;
621
0
        }
622
0
    }
623
624
28.3k
    osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
625
28.3k
    osl_getDateTimeFromTimeValue(&aTVal, &aDT);
626
627
28.3k
    sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
628
629
28.3k
    appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
630
631
28.3k
    aRet.append("'");
632
28.3k
    return aRet.makeStringAndClear();
633
28.3k
}
634
635
void PDFWriterImpl::emitComment( const char* pComment )
636
7.77k
{
637
7.77k
    OString aLine = OString::Concat("% ") + pComment + "\n";
638
7.77k
    writeBuffer( aLine );
639
7.77k
}
640
641
bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
642
6
{
643
6
    if (!g_bDebugDisableCompression)
644
6
    {
645
6
        sal_uInt64 nEndPos = pStream->TellEnd();
646
6
        pStream->Seek( STREAM_SEEK_TO_BEGIN );
647
6
        ZCodec aCodec( 0x4000, 0x4000 );
648
6
        SvMemoryStream aStream;
649
6
        aCodec.BeginCompression();
650
6
        aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
651
6
        aCodec.EndCompression();
652
6
        nEndPos = aStream.Tell();
653
6
        pStream->Seek( STREAM_SEEK_TO_BEGIN );
654
6
        aStream.Seek( STREAM_SEEK_TO_BEGIN );
655
6
        pStream->SetStreamSize( nEndPos );
656
6
        pStream->WriteBytes( aStream.GetData(), nEndPos );
657
6
        return true;
658
6
    }
659
0
    else
660
0
        return false;
661
6
}
662
663
void PDFWriterImpl::beginCompression()
664
108k
{
665
108k
    if (!g_bDebugDisableCompression)
666
108k
    {
667
108k
        m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
668
108k
        m_pMemStream = std::make_unique<SvMemoryStream>();
669
108k
        m_pCodec->BeginCompression();
670
108k
    }
671
108k
}
672
673
void PDFWriterImpl::endCompression()
674
108k
{
675
108k
    if (!g_bDebugDisableCompression && m_pCodec)
676
108k
    {
677
108k
        m_pCodec->EndCompression();
678
108k
        m_pCodec.reset();
679
108k
        sal_uInt64 nLen = m_pMemStream->Tell();
680
108k
        m_pMemStream->Seek( 0 );
681
108k
        (void)writeBufferBytes( m_pMemStream->GetData(), nLen );
682
108k
        m_pMemStream.reset();
683
108k
    }
684
108k
}
685
686
bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes )
687
2.53M
{
688
2.53M
    if( ! m_bOpen ) // we are already down the drain
689
0
        return false;
690
691
2.53M
    if( ! nBytes ) // huh ?
692
0
        return true;
693
694
2.53M
    if( !m_aOutputStreams.empty() )
695
6
    {
696
6
        m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
697
6
        m_aOutputStreams.front().m_pStream->WriteBytes(
698
6
                pBuffer, nBytes);
699
6
        return true;
700
6
    }
701
702
2.53M
    sal_uInt64 nWritten;
703
2.53M
    sal_uInt64 nActualSize = nBytes;
704
705
    // we are compressing the stream
706
2.53M
    if (m_pCodec)
707
1.11M
    {
708
1.11M
        m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), nBytes );
709
1.11M
        nWritten = nBytes;
710
1.11M
    }
711
1.42M
    else
712
1.42M
    {
713
        // is it encrypted?
714
1.42M
        bool bStreamEncryption = m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled();
715
1.42M
        if (bStreamEncryption)
716
0
        {
717
0
            nActualSize = m_pPDFEncryptor->calculateSizeIncludingHeader(nActualSize);
718
0
            m_vEncryptionBuffer.resize(nActualSize);
719
0
            m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, nActualSize);
720
0
        }
721
722
1.42M
        const void* pWriteBuffer = bStreamEncryption ? m_vEncryptionBuffer.data() : pBuffer;
723
1.42M
        m_DocDigest.update(pWriteBuffer, sal_uInt32(nActualSize));
724
725
726
1.42M
        if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != osl::File::E_None)
727
0
            nWritten = 0;
728
729
1.42M
        if (nWritten != nActualSize)
730
0
        {
731
0
            m_aFile.close();
732
0
            m_bOpen = false;
733
0
            return false;
734
0
        }
735
1.42M
        return true;
736
1.42M
    }
737
738
1.11M
    return nWritten == nActualSize;
739
2.53M
}
740
741
void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
742
96.2k
{
743
96.2k
    endPage();
744
96.2k
    m_nCurrentPage = m_aPages.size();
745
96.2k
    m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
746
747
96.2k
    const double frac = double(m_aPages.back().m_nUserUnit) / pointToPixel(1);
748
96.2k
    m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
749
750
96.2k
    m_aPages.back().beginStream();
751
752
    // setup global graphics state
753
    // linewidth is "1 pixel" by default
754
96.2k
    OStringBuffer aBuf( 16 );
755
96.2k
    appendDouble( 72.0/GetDPIX(), aBuf );
756
96.2k
    aBuf.append( " w\n" );
757
96.2k
    writeBuffer( aBuf );
758
96.2k
}
759
760
void PDFWriterImpl::endPage()
761
114k
{
762
114k
    if( m_aPages.empty() )
763
18.6k
        return;
764
765
    // close eventual MC sequence
766
96.2k
    endStructureElementMCSeq();
767
768
    // sanity check
769
96.2k
    if( !m_aOutputStreams.empty() )
770
0
    {
771
0
        OSL_FAIL( "redirection across pages !!!" );
772
0
        m_aOutputStreams.clear(); // leak !
773
0
        m_aMapMode.SetOrigin( Point() );
774
0
    }
775
776
96.2k
    m_aGraphicsStack.clear();
777
96.2k
    m_aGraphicsStack.emplace_back( );
778
779
    // this should pop the PDF graphics stack if necessary
780
96.2k
    updateGraphicsState();
781
782
96.2k
    m_aPages.back().endStream();
783
784
    // reset the default font
785
96.2k
    Font aFont;
786
96.2k
    aFont.SetFamilyName( u"Times"_ustr );
787
96.2k
    aFont.SetFontSize( Size( 0, 12 ) );
788
789
96.2k
    m_aCurrentPDFState = m_aGraphicsStack.front();
790
96.2k
    m_aGraphicsStack.front().m_aFont = std::move(aFont);
791
792
96.2k
    for (auto & bitmap : m_aBitmaps)
793
0
    {
794
0
        if( ! bitmap.m_aBitmap.IsEmpty() )
795
0
        {
796
0
            writeBitmapObject(bitmap);
797
0
            bitmap.m_aBitmap = Bitmap();
798
0
        }
799
0
    }
800
96.2k
    for (auto & jpeg : m_aJPGs)
801
0
    {
802
0
        if( jpeg.m_pStream )
803
0
        {
804
0
            writeJPG( jpeg );
805
0
            jpeg.m_pStream.reset();
806
0
            jpeg.m_aAlphaMask = AlphaMask();
807
0
        }
808
0
    }
809
96.2k
    for (auto & item : m_aTransparentObjects)
810
6
    {
811
6
        if( item.m_pContentStream )
812
6
        {
813
6
            writeTransparentObject(item);
814
6
            item.m_pContentStream.reset();
815
6
        }
816
6
    }
817
818
96.2k
}
819
820
sal_Int32 PDFWriterImpl::createObject()
821
493k
{
822
493k
    m_aObjects.emplace_back();
823
493k
    return m_aObjects.size();
824
493k
}
825
826
bool PDFWriterImpl::updateObject( sal_Int32 n )
827
493k
{
828
493k
    if( ! m_bOpen )
829
0
        return false;
830
831
493k
    sal_uInt64 nOffset = SAL_MAX_UINT64;
832
493k
    osl::File::RC aError = m_aFile.getPos(nOffset);
833
493k
    SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
834
493k
    if (aError != osl::File::E_None)
835
0
    {
836
0
        m_aFile.close();
837
0
        m_bOpen = false;
838
0
    }
839
493k
    m_aObjects[n - 1].m_nOffset = nOffset;
840
493k
    return aError == osl::File::E_None;
841
493k
}
842
843
bool PDFWriterImpl::useObjectStreams() const
844
0
{
845
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5)
846
0
        return false;
847
    // sdext reads the embedded original document out of a trailer, and a cross-reference
848
    // stream leaves none
849
0
    return m_aDocumentAttachedFiles.empty();
850
0
}
851
852
std::shared_ptr<pdf::IPDFEncryptor> PDFWriterImpl::getStringEncryptor(sal_Int32 nObject)
853
0
{
854
0
    if (!useObjectStreams())
855
0
        return m_pPDFEncryptor;
856
857
#ifndef NDEBUG
858
    m_aPlainStringObjects.insert(nObject);
859
#else
860
0
    (void)nObject;
861
0
#endif
862
    // ISO 32000-2 7.5.7: a string in an object stream is not encrypted on its own, the
863
    // stream carrying it is
864
0
    return nullptr;
865
0
}
866
867
bool PDFWriterImpl::writeObject(sal_Int32 nObject, std::string_view aBody)
868
0
{
869
0
    if (useObjectStreams())
870
0
        return writeCompressedObject(nObject, aBody);
871
872
0
    return updateObject(nObject) && writeBuffer(OString::number(nObject) + " 0 obj\n")
873
0
           && writeBuffer(aBody) && writeBuffer("\nendobj\n\n");
874
0
}
875
876
bool PDFWriterImpl::writeCompressedObject(sal_Int32 nObject, std::string_view aBody)
877
0
{
878
0
    m_aCompressedObjects.emplace_back(nObject, OString(aBody));
879
880
0
    return m_aCompressedObjects.size() < nMaxObjectsPerStream || emitObjectStream();
881
0
}
882
883
bool PDFWriterImpl::emitObjectStream()
884
0
{
885
0
    if (m_aCompressedObjects.empty())
886
0
        return true;
887
888
    // ISO 32000-2 7.5.7: /N pairs of object number and offset, then the bodies at /First
889
0
    OStringBuffer aPairs(12 * m_aCompressedObjects.size());
890
0
    OStringBuffer aBodies(4096);
891
0
    const sal_Int32 nStreamObject = createObject();
892
0
    for (const auto& rPending : m_aCompressedObjects)
893
0
    {
894
0
        aPairs.append(OString::number(rPending.m_nObject) + " "
895
0
                      + OString::number(aBodies.getLength()) + " ");
896
0
        aBodies.append(rPending.m_aBody + "\n");
897
0
    }
898
899
0
    const sal_Int32 nFirst = aPairs.getLength();
900
0
    aPairs.append(aBodies.makeStringAndClear());
901
0
    const std::vector<sal_uInt8> aStream(deflateBuffer(aPairs));
902
0
    if (aStream.empty())
903
0
        return false;
904
905
0
    OStringBuffer aLine(256);
906
0
    aLine.append(OString::number(nStreamObject) + " 0 obj\n<</Type/ObjStm/N "
907
0
                 + OString::number(static_cast<sal_Int32>(m_aCompressedObjects.size())) + "/First "
908
0
                 + OString::number(nFirst) + "/Length "
909
0
                 + OString::number(calculateStreamSize(aStream.size())));
910
0
    if (!g_bDebugDisableCompression)
911
0
        aLine.append("/Filter/FlateDecode");
912
0
    aLine.append(">>\nstream\n");
913
914
0
    if (!updateObject(nStreamObject) || !writeBuffer(aLine))
915
0
        return false;
916
917
    // ISO 32000-2 7.5.7: a string in here is not encrypted on its own, the stream holding
918
    // it is, with the cipher set up for the stream's own object number
919
0
    checkAndEnableStreamEncryption(nStreamObject);
920
    // the strings inside are covered by nothing else, so stream encryption must really be on
921
0
    assert((!m_aContext.Encryption.canEncrypt()
922
0
            || (m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled()))
923
0
           && "an object stream is going out unencrypted");
924
0
    const bool bBodies = writeBufferBytes(aStream.data(), aStream.size());
925
0
    disableStreamEncryption();
926
0
    if (!bBodies || !writeBuffer("\nendstream\nendobj\n\n"))
927
0
        return false;
928
929
    // only now can an entry name the stream, which is in the file
930
0
    sal_Int32 nIndex = 0;
931
0
    for (const auto& rPending : m_aCompressedObjects)
932
0
    {
933
0
        m_aObjects[rPending.m_nObject - 1]
934
0
            = { .m_nObjectStream = nStreamObject, .m_nIndex = nIndex };
935
0
        ++nIndex;
936
0
    }
937
0
    m_aCompressedObjects.clear();
938
0
    return true;
939
0
}
940
941
sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
942
0
{
943
0
    if( nObject > 0 )
944
0
    {
945
0
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
946
947
0
        aLine.append("<</Nums[\n");
948
0
        sal_Int32 nTreeItems = m_aStructParentTree.size();
949
0
        for( sal_Int32 n = 0; n < nTreeItems; n++ )
950
0
        {
951
0
            aLine.append( OString::number(n) + " "
952
0
                + m_aStructParentTree[n]
953
0
                + "\n" );
954
0
        }
955
0
        aLine.append("]>>");
956
0
        if (!writeObject(nObject, aLine))
957
0
            return 0;
958
0
    }
959
0
    return nObject;
960
0
}
961
962
// every structure element already has a unique object id - just use it for ID
963
static OString GenerateID(sal_Int32 const nObjectId)
964
0
{
965
0
    return "id" + OString::number(nObjectId);
966
0
}
967
968
sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
969
0
{
970
    // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
971
0
    if (nObject < 0)
972
0
    {
973
0
        return nObject;
974
0
    }
975
    // the name tree entries must be sorted lexicographically.
976
0
    std::map<OString, sal_Int32> ids;
977
0
    for (auto n : m_StructElemObjsWithID)
978
0
    {
979
0
        ids.emplace(GenerateID(n), n);
980
0
    }
981
0
    OStringBuffer buf;
982
0
    COSWriter aWriter(buf, m_aContext.Encryption.getParams(), getStringEncryptor(nObject));
983
0
    buf.append("<</Names [\n");
984
0
    for (auto const& it : ids)
985
0
    {
986
0
        aWriter.writeLiteralEncrypt(it.first, nObject);
987
0
        buf.append(" ");
988
0
        appendObjectReference(it.second, buf);
989
0
        buf.append("\n");
990
0
    }
991
0
    buf.append("] >>");
992
993
0
    if (!writeObject(nObject, buf))
994
0
        return 0;
995
996
0
    return nObject;
997
0
}
998
999
const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1000
0
{
1001
0
    static constexpr auto aAttributeStrings = frozen::make_unordered_map<PDFWriter::StructAttribute, const char*>({
1002
0
        { PDFWriter::Placement,         "Placement" },
1003
0
        { PDFWriter::WritingMode,       "WritingMode" },
1004
0
        { PDFWriter::SpaceBefore,       "SpaceBefore" },
1005
0
        { PDFWriter::SpaceAfter,        "SpaceAfter" },
1006
0
        { PDFWriter::StartIndent,       "StartIndent" },
1007
0
        { PDFWriter::EndIndent,         "EndIndent" },
1008
0
        { PDFWriter::TextIndent,        "TextIndent" },
1009
0
        { PDFWriter::TextAlign,         "TextAlign" },
1010
0
        { PDFWriter::Width,             "Width" },
1011
0
        { PDFWriter::Height,            "Height" },
1012
0
        { PDFWriter::BlockAlign,        "BlockAlign" },
1013
0
        { PDFWriter::InlineAlign,       "InlineAlign" },
1014
0
        { PDFWriter::LineHeight,        "LineHeight" },
1015
0
        { PDFWriter::BaselineShift,     "BaselineShift" },
1016
0
        { PDFWriter::TextDecorationType,"TextDecorationType" },
1017
0
        { PDFWriter::ListNumbering,     "ListNumbering" },
1018
0
        { PDFWriter::RowSpan,           "RowSpan" },
1019
0
        { PDFWriter::ColSpan,           "ColSpan" },
1020
0
        { PDFWriter::Scope,             "Scope" },
1021
0
        { PDFWriter::Role,              "Role" },
1022
0
        { PDFWriter::RubyAlign,         "RubyAlign" },
1023
0
        { PDFWriter::RubyPosition,      "RubyPosition" },
1024
0
        { PDFWriter::Type,              "Type" },
1025
0
        { PDFWriter::Subtype,           "Subtype" },
1026
0
        { PDFWriter::LinkAnnotation,    "LinkAnnotation" },
1027
0
        { PDFWriter::NoteAnnotation,    "NoteAnnotation" }
1028
0
    });
1029
1030
0
    auto it = aAttributeStrings.find( eAttr );
1031
1032
0
    if( it == aAttributeStrings.end() )
1033
0
        SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1034
1035
0
    return it != aAttributeStrings.end() ? it->second : "";
1036
0
}
1037
1038
const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1039
0
{
1040
0
    static constexpr auto aValueStrings = frozen::make_unordered_map<PDFWriter::StructAttributeValue, const char*>({
1041
0
        { PDFWriter::NONE,       "None" },
1042
0
        { PDFWriter::Block,      "Block" },
1043
0
        { PDFWriter::Inline,     "Inline" },
1044
0
        { PDFWriter::Before,     "Before" },
1045
0
        { PDFWriter::After,      "After" },
1046
0
        { PDFWriter::Start,      "Start" },
1047
0
        { PDFWriter::End,        "End" },
1048
0
        { PDFWriter::LrTb,       "LrTb" },
1049
0
        { PDFWriter::RlTb,       "RlTb" },
1050
0
        { PDFWriter::TbRl,       "TbRl" },
1051
0
        { PDFWriter::Center,     "Center" },
1052
0
        { PDFWriter::Justify,    "Justify" },
1053
0
        { PDFWriter::Auto,       "Auto" },
1054
0
        { PDFWriter::Middle,     "Middle" },
1055
0
        { PDFWriter::Normal,     "Normal" },
1056
0
        { PDFWriter::Underline,  "Underline" },
1057
0
        { PDFWriter::Overline,   "Overline" },
1058
0
        { PDFWriter::LineThrough,"LineThrough" },
1059
0
        { PDFWriter::Row,        "Row" },
1060
0
        { PDFWriter::Column,     "Column" },
1061
0
        { PDFWriter::Both,       "Both" },
1062
0
        { PDFWriter::Pagination, "Pagination" },
1063
0
        { PDFWriter::Layout,     "Layout" },
1064
0
        { PDFWriter::Page,       "Page" },
1065
0
        { PDFWriter::Background, "Background" },
1066
0
        { PDFWriter::Header,     "Header" },
1067
0
        { PDFWriter::Footer,     "Footer" },
1068
0
        { PDFWriter::Watermark,  "Watermark" },
1069
0
        { PDFWriter::Rb,         "rb" },
1070
0
        { PDFWriter::Cb,         "cb" },
1071
0
        { PDFWriter::Pb,         "pb" },
1072
0
        { PDFWriter::Tv,         "tv" },
1073
0
        { PDFWriter::RStart,     "Start" },
1074
0
        { PDFWriter::RCenter,    "Center" },
1075
0
        { PDFWriter::REnd,       "End" },
1076
0
        { PDFWriter::RJustify,   "Justify" },
1077
0
        { PDFWriter::RDistribute,"Distribute" },
1078
0
        { PDFWriter::RBefore,    "Before" },
1079
0
        { PDFWriter::RAfter,     "After" },
1080
0
        { PDFWriter::RWarichu,   "Warichu" },
1081
0
        { PDFWriter::RInline,    "Inline" },
1082
0
        { PDFWriter::Disc,       "Disc" },
1083
0
        { PDFWriter::Circle,     "Circle" },
1084
0
        { PDFWriter::Square,     "Square" },
1085
0
        { PDFWriter::Decimal,    "Decimal" },
1086
0
        { PDFWriter::UpperRoman, "UpperRoman" },
1087
0
        { PDFWriter::LowerRoman, "LowerRoman" },
1088
0
        { PDFWriter::UpperAlpha, "UpperAlpha" },
1089
0
        { PDFWriter::LowerAlpha, "LowerAlpha" }
1090
0
    });
1091
1092
0
    auto it = aValueStrings.find( eVal );
1093
1094
0
    if( it == aValueStrings.end() )
1095
0
        SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1096
1097
0
    return it != aValueStrings.end() ? it->second : "";
1098
0
}
1099
1100
static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1101
0
{
1102
0
    o_rLine.append( "/" );
1103
0
    o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1104
1105
0
    if( i_rVal.eValue != PDFWriter::Invalid )
1106
0
    {
1107
0
        o_rLine.append( "/" );
1108
0
        o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1109
0
    }
1110
0
    else
1111
0
    {
1112
        // numerical value
1113
0
        o_rLine.append( " " );
1114
0
        if( i_bIsFixedInt )
1115
0
            appendFixedInt( i_rVal.nValue, o_rLine );
1116
0
        else
1117
0
            o_rLine.append( i_rVal.nValue );
1118
0
    }
1119
0
    o_rLine.append( "\n" );
1120
0
}
1121
1122
template<typename T>
1123
void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
1124
0
{
1125
    // update struct parent of link
1126
0
    OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R");
1127
0
    m_aStructParentTree.push_back( aStructParentEntry );
1128
0
    rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
1129
0
    sal_Int32 const nAnnotObj(rAnnot.m_nObject);
1130
0
    i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj});
1131
0
}
Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFNoteEntry>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFNoteEntry&)
Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFWidget>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFWidget&)
Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFScreen>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFScreen&)
Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFLink>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFLink&)
1132
1133
OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1134
0
{
1135
    // create layout, list and table attribute sets
1136
0
    OStringBuffer aLayout(256), aList(64), aTable(64);
1137
0
    OStringBuffer aPrintField;
1138
0
    for (auto const& attribute : i_rEle.m_aAttributes)
1139
0
    {
1140
0
        if( attribute.first == PDFWriter::ListNumbering )
1141
0
            appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1142
0
        else if (attribute.first == PDFWriter::Role)
1143
0
        {
1144
0
            appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true);
1145
0
        }
1146
0
        else if( attribute.first == PDFWriter::RowSpan ||
1147
0
                 attribute.first == PDFWriter::ColSpan ||
1148
0
                 attribute.first == PDFWriter::Scope)
1149
0
        {
1150
0
            appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
1151
0
        }
1152
0
        else if (attribute.first == PDFWriter::NoteAnnotation)
1153
0
        {
1154
0
            sal_Int32 nNote = attribute.second.nValue;
1155
0
            std::map<sal_Int32, sal_Int32>::const_iterator link_it = m_aLinkPropertyMap.find(nNote);
1156
0
            if (link_it != m_aLinkPropertyMap.end())
1157
0
                nNote = link_it->second;
1158
0
            if (nNote >= 0 && o3tl::make_unsigned(nNote) < m_aNotes.size())
1159
0
            {
1160
0
                AppendAnnotKid(i_rEle, m_aNotes[nNote]);
1161
0
            }
1162
0
            else
1163
0
            {
1164
0
                OSL_FAIL("unresolved note id for Note structure");
1165
0
                SAL_INFO("vcl.pdfwriter", "unresolved note id " << nNote << " for Note structure");
1166
0
                if (g_bDebugDisableCompression)
1167
0
                {
1168
0
                    OString aLine
1169
0
                        = "unresolved note id " + OString::number(nNote) + " for Note structure";
1170
0
                    emitComment(aLine.getStr());
1171
0
                }
1172
0
            }
1173
0
        }
1174
0
        else
1175
0
            appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
1176
0
    }
1177
0
    if( ! i_rEle.m_aBBox.IsEmpty() )
1178
0
    {
1179
0
        aLayout.append( "/BBox[" );
1180
0
        appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
1181
0
        aLayout.append( " " );
1182
0
        appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
1183
0
        aLayout.append( " " );
1184
0
        appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
1185
0
        aLayout.append( " " );
1186
0
        appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
1187
0
        aLayout.append( "]\n" );
1188
0
    }
1189
1190
0
    OStringBuffer aRet(256);
1191
0
    bool isArray(false);
1192
0
    if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1)
1193
0
            + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1))
1194
0
    {
1195
0
        isArray = true;
1196
0
        aRet.append(" [");
1197
0
    }
1198
0
    auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf)
1199
0
    {
1200
0
        aRet.append(OString::Concat(" <</O")
1201
0
            + pName
1202
0
            + rBuf
1203
0
            + ">>");
1204
0
    };
1205
0
    if( !aLayout.isEmpty() )
1206
0
    {
1207
0
        WriteAttrs("/Layout", aLayout);
1208
0
    }
1209
0
    if( !aList.isEmpty() )
1210
0
    {
1211
0
        WriteAttrs("/List", aList);
1212
0
    }
1213
0
    if (!aPrintField.isEmpty())
1214
0
    {
1215
0
        WriteAttrs("/PrintField", aPrintField);
1216
0
    }
1217
0
    if( !aTable.isEmpty() )
1218
0
    {
1219
0
        WriteAttrs("/Table", aTable);
1220
0
    }
1221
1222
0
    if (isArray)
1223
0
    {
1224
0
        aRet.append( " ]" );
1225
0
    }
1226
0
    return aRet.makeStringAndClear();
1227
0
}
1228
1229
// Write the namespace objects to the stream
1230
void PDFWriterImpl::emitNamespaces()
1231
18.6k
{
1232
18.6k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
1233
18.6k
        return;
1234
1235
0
    for (auto&[sNamespace, nObject] : m_aNamespacesMap)
1236
0
    {
1237
0
        if (!updateObject(nObject))
1238
0
            return;
1239
1240
0
        OStringBuffer aLine;
1241
0
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1242
0
        aWriter.startObject(nObject);
1243
0
        aWriter.startDict();
1244
0
        aWriter.write("/Type", "/Namespace");
1245
0
        aWriter.writeKeyAndLiteralEncrypt("/NS", sNamespace, nObject);
1246
        // the 1.7 namespace needs none: for it the structure tree root's RoleMap is the fallback
1247
0
        if (sNamespace == constNamespacePDF2 && !m_aRoleMap.empty())
1248
0
        {
1249
0
            aLine.append( "/RoleMapNS<<" );
1250
0
            for (auto const& role : m_aRoleMap)
1251
0
            {
1252
0
                aLine.append("/" + role.first + "/" + role.second.m_aTag + "\n");
1253
0
            }
1254
0
            aLine.append( ">>\n" );
1255
0
        }
1256
0
        aWriter.endDict();
1257
0
        aWriter.endObject();
1258
1259
0
        if (!writeBuffer(aWriter.getLine()))
1260
0
            m_aNamespacesMap[sNamespace] = 0;
1261
0
    }
1262
0
}
1263
1264
sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
1265
0
{
1266
0
    assert(rEle.m_nOwnElement == 0 || rEle.m_oType);
1267
0
    if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root
1268
       // do not emit NonStruct and its children
1269
0
        && *rEle.m_oType == StructElement::NonStructElement)
1270
0
    {
1271
0
        return 0;
1272
0
    }
1273
1274
0
    for (auto const& child : rEle.m_aChildren)
1275
0
    {
1276
0
        if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
1277
0
        {
1278
0
            PDFStructureElement& rChild = m_aStructure[ child ];
1279
0
            if (*rChild.m_oType != StructElement::NonStructElement)
1280
0
            {
1281
0
                if( rChild.m_nParentElement == rEle.m_nOwnElement )
1282
0
                    emitStructure( rChild );
1283
0
                else
1284
0
                {
1285
0
                    OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
1286
0
                    SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
1287
0
                }
1288
0
            }
1289
0
        }
1290
0
        else
1291
0
        {
1292
0
            OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
1293
0
            SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
1294
0
        }
1295
0
    }
1296
1297
0
    OStringBuffer aLine( 512 );
1298
0
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), getStringEncryptor(rEle.m_nObject));
1299
0
    aLine.append("<</Type");
1300
0
    sal_Int32 nParentTree = -1;
1301
0
    sal_Int32 nIDTree = -1;
1302
0
    if( rEle.m_nOwnElement == rEle.m_nParentElement )
1303
0
    {
1304
0
        nParentTree = createObject();
1305
0
        if (!nParentTree)
1306
0
            return 0;
1307
0
        aLine.append("/StructTreeRoot\n");
1308
0
        aWriter.writeKeyAndReference("/ParentTree", nParentTree);
1309
1310
        // Write the references to the namespaces the elements below use
1311
0
        OStringBuffer aNamespaces;
1312
        // m_aNamespacesMap is unordered, so name them rather than iterate it
1313
0
        for (auto const& rURI : { constNamespacePDF2, constNamespacePDF17 })
1314
0
        {
1315
0
            auto iterator = m_aNamespacesMap.find(rURI);
1316
0
            if (iterator != m_aNamespacesMap.end())
1317
0
            {
1318
0
                if (!aNamespaces.isEmpty())
1319
0
                    aNamespaces.append(" ");
1320
0
                appendObjectReference(iterator->second, aNamespaces);
1321
0
            }
1322
0
        }
1323
0
        if (!aNamespaces.isEmpty())
1324
0
            aLine.append("/Namespaces [" + aNamespaces + "]");
1325
1326
0
        if( ! m_aRoleMap.empty() )
1327
0
        {
1328
0
            aLine.append( "/RoleMap<<" );
1329
0
            for (auto const& role : m_aRoleMap)
1330
0
            {
1331
0
                aLine.append("/" + role.first + "/" + role.second.m_aTag + "\n");
1332
0
            }
1333
0
            aLine.append( ">>\n" );
1334
0
        }
1335
0
        if (!m_StructElemObjsWithID.empty())
1336
0
        {
1337
0
            nIDTree = createObject();
1338
0
            aLine.append("/IDTree ");
1339
0
            appendObjectReference(nIDTree, aLine);
1340
0
            aLine.append("\n");
1341
0
        }
1342
0
    }
1343
0
    else
1344
0
    {
1345
0
        aLine.append("/StructElem");
1346
1347
        // tdf#173162 a type PDF 2.0 dropped stays in the 1.7 namespace, which ISO 14289-2
1348
        // 8.2.4 allows alongside it
1349
0
        auto iterator = m_aNamespacesMap.find(
1350
0
            isPDF17OnlyType(*rEle.m_oType) ? constNamespacePDF17 : constNamespacePDF2);
1351
0
        if (iterator != m_aNamespacesMap.end())
1352
0
            aWriter.writeKeyAndReference("/NS", iterator->second);
1353
0
        aLine.append("/S/");
1354
0
        if( !rEle.m_aAlias.isEmpty() )
1355
0
            aLine.append( rEle.m_aAlias );
1356
0
        else
1357
0
            aLine.append( getStructureTag(*rEle.m_oType) );
1358
0
        if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end())
1359
0
        {
1360
0
            aLine.append("\n/ID ");
1361
0
            aWriter.writeLiteralEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject);
1362
0
        }
1363
0
        aLine.append(
1364
0
            "\n"
1365
0
            "/P "
1366
0
            + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject)
1367
0
            + " 0 R\n"
1368
0
              "/Pg "
1369
0
            + OString::number(rEle.m_nFirstPageObject)
1370
0
            + " 0 R\n" );
1371
0
        if (!rEle.m_RefElements.empty())
1372
0
        {
1373
0
            aLine.append("/Ref[");
1374
0
            for (const auto nRef : rEle.m_RefElements)
1375
0
            {
1376
0
                aLine.append(" ");
1377
0
                appendObjectReference(m_aStructure[nRef].m_nObject, aLine);
1378
0
            }
1379
0
            aLine.append("]\n");
1380
0
        }
1381
0
        if( !rEle.m_aActualText.isEmpty() )
1382
0
        {
1383
0
            aLine.append( "/ActualText" );
1384
0
            aWriter.writeUnicodeEncrypt(rEle.m_aActualText, rEle.m_nObject);
1385
0
            aLine.append( "\n" );
1386
0
        }
1387
0
        if( !rEle.m_aAltText.isEmpty() )
1388
0
        {
1389
0
            aLine.append( "/Alt" );
1390
0
            aWriter.writeUnicodeEncrypt(rEle.m_aAltText, rEle.m_nObject);
1391
0
            aLine.append( "\n" );
1392
0
        }
1393
0
    }
1394
0
    if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
1395
0
    {
1396
0
        OString aAttribs =  emitStructureAttributes( rEle );
1397
0
        if( !aAttribs.isEmpty() )
1398
0
        {
1399
0
            aLine.append( "/A" + aAttribs + "\n" );
1400
0
        }
1401
0
    }
1402
0
    if( !rEle.m_aLocale.Language.isEmpty() )
1403
0
    {
1404
        /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
1405
         * include script tags and others.
1406
         * http://pdf.editme.com/pdfua-naturalLanguageSpecification
1407
         * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
1408
         * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
1409
         * */
1410
0
        LanguageTag aLanguageTag( rEle.m_aLocale);
1411
0
        OUString aLanguage, aScript, aCountry;
1412
0
        aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
1413
0
        if (!aLanguage.isEmpty())
1414
0
        {
1415
0
            OUStringBuffer aLocBuf( 16 );
1416
0
            aLocBuf.append( aLanguage );
1417
0
            if( !aCountry.isEmpty() )
1418
0
            {
1419
0
                aLocBuf.append( "-" + aCountry );
1420
0
            }
1421
0
            aLine.append( "/Lang" );
1422
0
            aWriter.writeLiteralEncrypt(aLocBuf.makeStringAndClear(), rEle.m_nObject);
1423
0
            aLine.append( "\n" );
1424
0
        }
1425
0
    }
1426
0
    if (!rEle.m_AnnotIds.empty())
1427
0
    {
1428
0
        for (auto const id : rEle.m_AnnotIds)
1429
0
        {
1430
0
            auto const it(m_aLinkPropertyMap.find(id));
1431
0
            assert(it != m_aLinkPropertyMap.end());
1432
1433
0
            if (*rEle.m_oType == StructElement::Form)
1434
0
            {
1435
0
                assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size());
1436
0
                AppendAnnotKid(rEle, m_aWidgets[it->second]);
1437
0
            }
1438
0
            else
1439
0
            {
1440
0
                assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size());
1441
0
                AppendAnnotKid(rEle, m_aScreens[it->second]);
1442
0
            }
1443
0
        }
1444
0
    }
1445
0
    for (const auto id : rEle.m_LinkAnnotIds)
1446
0
    {
1447
0
        sal_Int32 nLink(id);
1448
0
        const auto it(m_aLinkPropertyMap.find(nLink));
1449
0
        if (it != m_aLinkPropertyMap.end())
1450
0
            nLink = it->second;
1451
0
        if (0 <= nLink && o3tl::make_unsigned(nLink) < m_aLinks.size())
1452
0
        {
1453
0
            AppendAnnotKid(rEle, m_aLinks[nLink]);
1454
0
        }
1455
0
        else
1456
0
        {
1457
0
            SAL_WARN("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
1458
0
            if (g_bDebugDisableCompression)
1459
0
            {
1460
0
                const OString aComment
1461
0
                    = "unresolved link id " + OString::number(nLink) + " for Link structure";
1462
0
                emitComment(aComment.getStr());
1463
0
            }
1464
0
        }
1465
0
    }
1466
0
    if( ! rEle.m_aKids.empty() )
1467
0
    {
1468
0
        unsigned int i = 0;
1469
0
        aLine.append( "/K[" );
1470
0
        for (auto const& rKid : rEle.m_aKids)
1471
0
        {
1472
0
            if (std::holds_alternative<ObjReference>(rKid))
1473
0
            {
1474
0
                ObjReference const& rObj(std::get<ObjReference>(rKid));
1475
0
                appendObjectReference(rObj.nObject, aLine);
1476
0
                aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
1477
0
            }
1478
0
            else if (std::holds_alternative<ObjReferenceObj>(rKid))
1479
0
            {
1480
0
                ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid));
1481
0
                aLine.append("<</Type/OBJR/Obj ");
1482
0
                appendObjectReference(rObj.nObject, aLine);
1483
0
                aLine.append(">>\n");
1484
0
            }
1485
0
            else
1486
0
            {
1487
0
                assert(std::holds_alternative<MCIDReference>(rKid));
1488
0
                MCIDReference const& rMCID(std::get<MCIDReference>(rKid));
1489
0
                if (rMCID.nPageObj == rEle.m_nFirstPageObject)
1490
0
                {
1491
0
                    aLine.append(OString::number(rMCID.nMCID) + " ");
1492
0
                }
1493
0
                else
1494
0
                {
1495
0
                    aLine.append("<</Type/MCR/Pg ");
1496
0
                    appendObjectReference(rMCID.nPageObj, aLine);
1497
0
                    aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n");
1498
0
                }
1499
0
            }
1500
0
            ++i;
1501
0
        }
1502
0
        aLine.append( "]\n" );
1503
0
    }
1504
0
    aLine.append(">>");
1505
1506
0
    if (!writeObject(rEle.m_nObject, aLine))
1507
0
        return 0;
1508
1509
0
    if (!emitStructParentTree(nParentTree)) return 0;
1510
0
    if (!emitStructIDTree(nIDTree)) return 0;
1511
1512
0
    return rEle.m_nObject;
1513
0
}
1514
1515
bool PDFWriterImpl::emitGradients()
1516
0
{
1517
0
    return std::all_of(m_aGradients.begin(), m_aGradients.end(),
1518
0
                       [this](const auto& gradient) { return writeGradientFunction(gradient); });
1519
0
}
1520
1521
bool PDFWriterImpl::emitTilings()
1522
0
{
1523
0
    OStringBuffer aTilingObj( PDFWRITER_IMPL_BUFFERSIZE );
1524
1525
0
    for (auto & tiling : m_aTilings)
1526
0
    {
1527
0
        SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
1528
0
        if( ! tiling.m_pTilingStream )
1529
0
            continue;
1530
1531
0
        aTilingObj.setLength( 0 );
1532
1533
0
        if (g_bDebugDisableCompression)
1534
0
        {
1535
0
            emitComment( "PDFWriterImpl::emitTilings" );
1536
0
        }
1537
1538
0
        sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
1539
0
        sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
1540
0
        sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
1541
0
        sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
1542
0
        if( tiling.m_aCellSize.Width() == 0 )
1543
0
            tiling.m_aCellSize.setWidth( nW );
1544
0
        if( tiling.m_aCellSize.Height() == 0 )
1545
0
            tiling.m_aCellSize.setHeight( nH );
1546
1547
0
        bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
1548
0
        sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
1549
0
        tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
1550
1551
        // write pattern object
1552
0
        aTilingObj.append(
1553
0
            OString::number(tiling.m_nObject)
1554
0
            + " 0 obj\n"
1555
0
              "<</Type/Pattern/PatternType 1\n"
1556
0
              "/PaintType 1\n"
1557
0
              "/TilingType 2\n"
1558
0
              "/BBox[" );
1559
0
        appendFixedInt( nX, aTilingObj );
1560
0
        aTilingObj.append( ' ' );
1561
0
        appendFixedInt( nY, aTilingObj );
1562
0
        aTilingObj.append( ' ' );
1563
0
        appendFixedInt( nX+nW, aTilingObj );
1564
0
        aTilingObj.append( ' ' );
1565
0
        appendFixedInt( nY+nH, aTilingObj );
1566
0
        aTilingObj.append( "]\n"
1567
0
                           "/XStep " );
1568
0
        appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
1569
0
        aTilingObj.append( "\n"
1570
0
                           "/YStep " );
1571
0
        appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
1572
0
        aTilingObj.append( "\n" );
1573
0
        if( tiling.m_aTransform.matrix[0] != 1.0 ||
1574
0
            tiling.m_aTransform.matrix[1] != 0.0 ||
1575
0
            tiling.m_aTransform.matrix[3] != 0.0 ||
1576
0
            tiling.m_aTransform.matrix[4] != 1.0 ||
1577
0
            tiling.m_aTransform.matrix[2] != 0.0 ||
1578
0
            tiling.m_aTransform.matrix[5] != 0.0 )
1579
0
        {
1580
0
            aTilingObj.append( "/Matrix [" );
1581
            // TODO: scaling, mirroring on y, etc
1582
0
            appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
1583
0
            aTilingObj.append( ' ' );
1584
0
            appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
1585
0
            aTilingObj.append( ' ' );
1586
0
            appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
1587
0
            aTilingObj.append( ' ' );
1588
0
            appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
1589
0
            aTilingObj.append( ' ' );
1590
0
            appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
1591
0
            aTilingObj.append( ' ' );
1592
0
            appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
1593
0
            aTilingObj.append( "]\n" );
1594
0
        }
1595
0
        aTilingObj.append( "/Resources" );
1596
0
        tiling.m_aResources.append(aTilingObj, getFontDictObject(), m_aContext.Version);
1597
0
        if( bDeflate )
1598
0
            aTilingObj.append( "/Filter/FlateDecode" );
1599
0
        aTilingObj.append( "/Length "
1600
0
            + OString::number(calculateStreamSize(nTilingStreamSize))
1601
0
            + ">>\nstream\n" );
1602
0
        if ( !updateObject( tiling.m_nObject ) ) return false;
1603
0
        if ( !writeBuffer( aTilingObj ) ) return false;
1604
0
        checkAndEnableStreamEncryption( tiling.m_nObject );
1605
0
        bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
1606
0
        tiling.m_pTilingStream.reset();
1607
0
        if( !written )
1608
0
            return false;
1609
0
        disableStreamEncryption();
1610
0
        aTilingObj.setLength( 0 );
1611
0
        aTilingObj.append( "\nendstream\nendobj\n\n" );
1612
0
        if ( !writeBuffer( aTilingObj ) ) return false;
1613
0
    }
1614
0
    return true;
1615
0
}
1616
1617
sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
1618
0
{
1619
0
    if( !pFD )
1620
0
        return 0;
1621
0
    const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
1622
1623
0
    OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
1624
1625
0
    if( nFontObject <= 0 )
1626
0
        nFontObject = createObject();
1627
0
    if (!updateObject(nFontObject))
1628
0
        return 0;
1629
0
    aLine.append(
1630
0
        OString::number(nFontObject)
1631
0
        + " 0 obj\n"
1632
0
          "<</Type/Font/Subtype/Type1/BaseFont/" );
1633
0
    COSWriter::appendName( rBuildinFont.m_pPSName, aLine );
1634
0
    aLine.append( "\n" );
1635
0
    if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
1636
0
         aLine.append( "/Encoding/WinAnsiEncoding\n" );
1637
0
    aLine.append( ">>\nendobj\n\n" );
1638
0
    if (!writeBuffer(aLine))
1639
0
        return 0;
1640
0
    return nFontObject;
1641
0
}
1642
1643
1644
std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed )
1645
0
{
1646
0
    std::map< sal_Int32, sal_Int32 > aRet;
1647
1648
0
    if (g_bDebugDisableCompression)
1649
0
        emitComment("PDFWriterImpl::emitSystemFont");
1650
1651
0
    FontSubsetInfo aInfo;
1652
    // fill in dummy values
1653
0
    aInfo.m_nAscent = 1000;
1654
0
    aInfo.m_nDescent = 200;
1655
0
    aInfo.m_nCapHeight = 1000;
1656
0
    aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
1657
0
    aInfo.m_aPSName = pFace->GetFamilyName();
1658
1659
0
    sal_Int32 pWidths[256] = { 0 };
1660
0
    const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
1661
0
    auto nUPEM = pFace->UnitsPerEm();
1662
0
    for( sal_Ucs c = 32; c < 256; c++ )
1663
0
    {
1664
0
        sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
1665
0
        pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
1666
0
    }
1667
1668
    // We are interested only in filling aInfo
1669
0
    sal_GlyphId aGlyphIds[] = { 0 };
1670
0
    sal_uInt8 pEncoding[] = { 0 };
1671
0
    std::vector<sal_uInt8> aBuffer;
1672
0
    pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
1673
1674
    // write font descriptor
1675
0
    sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0, 0 );
1676
0
    if( nFontDescriptor )
1677
0
    {
1678
        // write font object
1679
0
        sal_Int32 nObject = createObject();
1680
0
        if( updateObject( nObject ) )
1681
0
        {
1682
0
            OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
1683
0
            aLine.append(
1684
0
                OString::number(nObject)
1685
0
                + " 0 obj\n"
1686
0
                  "<</Type/Font/Subtype/TrueType"
1687
0
                  "/BaseFont/" );
1688
0
            COSWriter::appendName( aInfo.m_aPSName, aLine );
1689
0
            aLine.append( "\n" );
1690
0
            if (!pFace->IsMicrosoftSymbolEncoded())
1691
0
                aLine.append( "/Encoding/WinAnsiEncoding\n" );
1692
0
            aLine.append( "/FirstChar 32 /LastChar 255\n"
1693
0
                          "/Widths[" );
1694
0
            for( int i = 32; i < 256; i++ )
1695
0
            {
1696
0
                aLine.append( pWidths[i] );
1697
0
                aLine.append( ((i&15) == 15) ? "\n" : " " );
1698
0
            }
1699
0
            aLine.append( "]\n"
1700
0
                          "/FontDescriptor "
1701
0
                + OString::number( nFontDescriptor )
1702
0
                + " 0 R>>\n"
1703
0
                  "endobj\n\n" );
1704
0
            writeBuffer( aLine );
1705
1706
0
            aRet[ rEmbed.m_nNormalFontID ] = nObject;
1707
0
        }
1708
0
    }
1709
1710
0
    return aRet;
1711
0
}
1712
1713
namespace
1714
{
1715
uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
1716
                          sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
1717
                          sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
1718
                          sal_Int32& nToUnicodeStream)
1719
20.3k
{
1720
20.3k
    rCodeUnits.reserve(256);
1721
1722
    // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
1723
20.3k
    pWidths[0] = 0;
1724
1725
20.3k
    uint32_t nGlyphs = 1;
1726
20.3k
    for (auto const& item : rSubset.m_aMapping)
1727
406k
    {
1728
406k
        sal_uInt8 nEnc = item.second.getGlyphId();
1729
1730
406k
        SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
1731
406k
                    "duplicate glyph");
1732
406k
        SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
1733
1734
406k
        pGlyphIds[nEnc] = item.first;
1735
406k
        pEncoding[nEnc] = nEnc;
1736
406k
        pEncToUnicodeIndex[nEnc] = rCodeUnits.size();
1737
406k
        pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
1738
406k
        pWidths[nEnc] = item.second.getGlyphWidth();
1739
790k
        for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
1740
383k
            rCodeUnits.push_back(item.second.getCode(n));
1741
406k
        if (item.second.getCode(0))
1742
383k
            nToUnicodeStream = 1;
1743
406k
        if (nGlyphs < 256)
1744
406k
            nGlyphs++;
1745
0
        else
1746
406k
            OSL_FAIL("too many glyphs for subset");
1747
406k
    }
1748
1749
20.3k
    return nGlyphs;
1750
20.3k
}
1751
}
1752
1753
bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace,
1754
                                  const FontSubset& rType3Font,
1755
                                  std::map<sal_Int32, sal_Int32>& rFontIDToObject)
1756
32
{
1757
32
    if (g_bDebugDisableCompression)
1758
0
        emitComment("PDFWriterImpl::emitType3Font");
1759
1760
32
    const auto& rColorPalettes = pFace->GetColorPalettes();
1761
1762
32
    FontSubsetInfo aSubsetInfo;
1763
32
    sal_GlyphId pTempGlyphIds[] = { 0 };
1764
32
    sal_uInt8 pTempEncoding[] = { 0 };
1765
32
    std::vector<sal_uInt8> aBuffer;
1766
32
    pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
1767
1768
32
    for (auto& rSubset : rType3Font.m_aSubsets)
1769
32
    {
1770
32
        sal_GlyphId pGlyphIds[256] = {};
1771
32
        sal_Int32 pWidths[256];
1772
32
        sal_uInt8 pEncoding[256] = {};
1773
32
        sal_Int32 pEncToUnicodeIndex[256] = {};
1774
32
        sal_Int32 pCodeUnitsPerGlyph[256] = {};
1775
32
        std::vector<sal_Ucs> aCodeUnits;
1776
32
        sal_Int32 nToUnicodeStream = 0;
1777
1778
        // fill arrays and prepare encoding index map
1779
32
        auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
1780
32
                                        pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
1781
1782
        // write font descriptor
1783
32
        sal_Int32 nFontDescriptor = 0;
1784
32
        if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
1785
32
            nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0, 0);
1786
1787
32
        if (nToUnicodeStream)
1788
32
            nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
1789
32
                                                   pEncToUnicodeIndex, nGlyphs);
1790
1791
        // write font object
1792
32
        sal_Int32 nFontObject = createObject();
1793
32
        if (!updateObject(nFontObject))
1794
0
            return false;
1795
1796
32
        OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
1797
32
        aLine.append(
1798
32
            OString::number(nFontObject)
1799
32
            + " 0 obj\n"
1800
32
              "<</Type/Font/Subtype/Type3/Name/");
1801
32
        COSWriter::appendName(aSubsetInfo.m_aPSName, aLine);
1802
1803
32
        aLine.append(
1804
32
            "\n/FontBBox["
1805
        // note: Top and Bottom are reversed in VCL and PDF rectangles
1806
32
            + OString::number(aSubsetInfo.m_aFontBBox.Left())
1807
32
            + " "
1808
32
            + OString::number(aSubsetInfo.m_aFontBBox.Top())
1809
32
            + " "
1810
32
            + OString::number(aSubsetInfo.m_aFontBBox.Right())
1811
32
            + " "
1812
32
            + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
1813
32
            + "]\n");
1814
1815
        // tdf#155610
1816
        // Adobe Acrobat does not seem to like certain UPEMs, so instead of
1817
        // setting the FontMatrix scale relative to the UPEM, we always set to
1818
        // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is
1819
        // different.
1820
32
        double fScale = 1000. / pFace->UnitsPerEm();
1821
1822
32
        aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n");
1823
1824
32
        sal_Int32 pGlyphStreams[256] = {};
1825
32
        aLine.append("/CharProcs<<\n");
1826
64
        for (auto i = 1u; i < nGlyphs; i++)
1827
32
        {
1828
32
            auto nStream = createObject();
1829
32
            aLine.append("/"
1830
32
                + pFace->GetGlyphName(pGlyphIds[i], true)
1831
32
                + " "
1832
32
                + OString::number(nStream)
1833
32
                + " 0 R\n");
1834
32
            pGlyphStreams[i] = nStream;
1835
32
        }
1836
32
        aLine.append(">>\n"
1837
1838
32
            "/Encoding<</Type/Encoding/Differences[1");
1839
64
        for (auto i = 1u; i < nGlyphs; i++)
1840
32
            aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
1841
32
        aLine.append("]>>\n"
1842
1843
32
            "/FirstChar 0\n"
1844
32
            "/LastChar "
1845
32
            + OString::number(nGlyphs - 1)
1846
32
            + "\n"
1847
1848
32
            "/Widths[");
1849
96
        for (auto i = 0u; i < nGlyphs; i++)
1850
64
        {
1851
64
            appendDouble(pWidths[i] * fScale, aLine);
1852
64
            aLine.append(" ");
1853
64
        }
1854
32
        aLine.append("]\n");
1855
1856
32
        if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
1857
32
        {
1858
32
            aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
1859
32
        }
1860
1861
32
        auto nResources = createObject();
1862
32
        aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
1863
1864
32
        if (nToUnicodeStream)
1865
32
        {
1866
32
            aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
1867
32
        }
1868
1869
32
        aLine.append(">>\n"
1870
32
                     "endobj\n\n");
1871
1872
32
        if (!writeBuffer(aLine))
1873
0
            return false;
1874
1875
32
        std::set<sal_Int32> aUsedFonts;
1876
32
        std::list<BitmapEmit> aUsedBitmaps;
1877
32
        std::map<sal_uInt8, sal_Int32> aUsedAlpha;
1878
32
        ResourceDict aResourceDict;
1879
32
        std::list<StreamRedirect> aOutputStreams;
1880
1881
64
        for (auto i = 1u; i < nGlyphs; i++)
1882
32
        {
1883
32
            auto nStream = pGlyphStreams[i];
1884
32
            if (!updateObject(nStream))
1885
0
                return false;
1886
32
            OStringBuffer aContents(PDFWRITER_IMPL_BUFFERSIZE);
1887
32
            appendDouble(pWidths[i] * fScale, aContents);
1888
32
            aContents.append(" 0 d0\n");
1889
1890
32
            const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
1891
32
            const auto& rLayers = rGlyph.getColorLayers();
1892
32
            for (const auto& rLayer : rLayers)
1893
0
            {
1894
0
                aUsedFonts.insert(rLayer.m_nFontID);
1895
1896
0
                aContents.append("q ");
1897
                // 0xFFFF is a special value means foreground color.
1898
0
                if (rLayer.m_nColorIndex != 0xFFFF)
1899
0
                {
1900
0
                    auto& rPalette = rColorPalettes[0];
1901
0
                    auto aColor(rPalette[rLayer.m_nColorIndex]);
1902
0
                    appendNonStrokingColor(aColor, aContents);
1903
0
                    aContents.append(" ");
1904
0
                    if (aColor.GetAlpha() != 0xFF
1905
0
                        && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4)
1906
0
                    {
1907
0
                        auto nAlpha = aColor.GetAlpha();
1908
0
                        OStringBuffer aName(16);
1909
0
                        aName.append("GS");
1910
0
                        COSWriter::appendHex(nAlpha, aName);
1911
1912
0
                        aContents.append("/" + aName + " gs ");
1913
1914
0
                        if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
1915
0
                        {
1916
0
                            auto nObject = createObject();
1917
0
                            aUsedAlpha[nAlpha] = nObject;
1918
0
                            pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
1919
0
                                         nObject, aResourceDict, aOutputStreams);
1920
0
                        }
1921
0
                    }
1922
0
                }
1923
0
                aContents.append(
1924
0
                    "BT "
1925
0
                    "/F" + OString::number(rLayer.m_nFontID) + " ");
1926
0
                appendDouble(pFace->UnitsPerEm() * fScale, aContents);
1927
0
                aContents.append(
1928
0
                    " Tf "
1929
0
                    "<");
1930
0
                COSWriter::appendHex(rLayer.m_nSubsetGlyphID, aContents);
1931
0
                aContents.append(
1932
0
                    ">Tj "
1933
0
                    "ET "
1934
0
                    "Q\n");
1935
0
            }
1936
1937
32
            tools::Rectangle aRect;
1938
32
            const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
1939
32
            if (!rBitmapData.empty())
1940
0
            {
1941
0
                SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
1942
0
                                       StreamMode::READ);
1943
0
                vcl::PngImageReader aReader(aStream);
1944
1945
0
                Bitmap aBitmap = aReader.read();
1946
0
                const BitmapEmit& rBitmapEmit = createBitmapEmit(aBitmap, Graphic(),
1947
0
                                                                 aUsedBitmaps, aResourceDict,
1948
0
                                                                 aOutputStreams);
1949
1950
0
                auto nObject = rBitmapEmit.m_aReferenceXObject.getObject();
1951
0
                aContents.append("q ");
1952
0
                appendDouble(aRect.GetWidth() * fScale, aContents);
1953
0
                aContents.append(" 0 0 ");
1954
0
                appendDouble(aRect.GetHeight() * fScale, aContents);
1955
0
                aContents.append(" ");
1956
0
                appendDouble(aRect.getX() * fScale, aContents);
1957
0
                aContents.append(" ");
1958
0
                appendDouble(aRect.getY() * fScale, aContents);
1959
0
                aContents.append(" cm /Im" + OString::number(nObject) + " Do Q\n");
1960
0
            }
1961
1962
            // The newline before `endstream` is not counted as within the stream
1963
32
            assert(aContents[aContents.getLength() - 1] == '\n');
1964
32
            aContents.setLength(aContents.getLength() - 1);
1965
32
            assert(aContents[aContents.getLength() - 1] != '\n');
1966
32
            aLine.setLength(0);
1967
32
            aLine.append(OString::number(nStream)
1968
32
                + " 0 obj\n<</Length "
1969
32
                + OString::number(calculateStreamSize(aContents.getLength()))
1970
32
                + ">>\nstream\n");
1971
32
            if (!writeBuffer(aLine))
1972
0
                return false;
1973
32
            checkAndEnableStreamEncryption(nStream);
1974
32
            if (!writeBuffer(aContents))
1975
0
                return false;
1976
32
            disableStreamEncryption();
1977
32
            aLine.setLength(0);
1978
32
            aLine.append("\nendstream\nendobj\n\n");
1979
32
            if (!writeBuffer(aLine))
1980
0
                return false;
1981
32
        }
1982
1983
        // write font dict
1984
32
        sal_Int32 nFontDict = 0;
1985
32
        if (!aUsedFonts.empty())
1986
0
        {
1987
0
            nFontDict = createObject();
1988
0
            aLine.setLength(0);
1989
0
            aLine.append(OString::number(nFontDict) + " 0 obj\n<<");
1990
0
            for (auto nFontID : aUsedFonts)
1991
0
            {
1992
0
                aLine.append("/F"
1993
0
                    + OString::number(nFontID)
1994
0
                    + " "
1995
0
                    + OString::number(rFontIDToObject[nFontID])
1996
0
                    + " 0 R");
1997
0
            }
1998
0
            aLine.append(">>\nendobj\n\n");
1999
0
            if (!updateObject(nFontDict))
2000
0
                return false;
2001
0
            if (!writeBuffer(aLine))
2002
0
                return false;
2003
0
        }
2004
2005
        // write ExtGState objects
2006
32
        if (!aUsedAlpha.empty())
2007
0
        {
2008
0
            for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
2009
0
            {
2010
0
                aLine.setLength(0);
2011
0
                aLine.append(OString::number(nObject) + " 0 obj\n<<");
2012
0
                if (m_bIsPDF_A1)
2013
0
                {
2014
0
                    aLine.append("/CA 1.0/ca 1.0");
2015
0
                    m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA);
2016
0
                }
2017
0
                else
2018
0
                {
2019
0
                    aLine.append("/CA ");
2020
0
                    appendDouble(nAlpha / 255., aLine);
2021
0
                    aLine.append("/ca ");
2022
0
                    appendDouble(nAlpha / 255., aLine);
2023
0
                }
2024
0
                aLine.append(">>\nendobj\n\n");
2025
0
                if (!updateObject(nObject))
2026
0
                    return false;
2027
0
                if (!writeBuffer(aLine))
2028
0
                    return false;
2029
0
            }
2030
0
        }
2031
2032
        // write bitmap objects
2033
32
        for (auto& aBitmap : aUsedBitmaps)
2034
0
            writeBitmapObject(aBitmap);
2035
2036
        // write resources dict
2037
32
        aLine.setLength(0);
2038
32
        aLine.append(OString::number(nResources) + " 0 obj\n");
2039
32
        aResourceDict.append(aLine, nFontDict, m_aContext.Version);
2040
32
        aLine.append("endobj\n\n");
2041
32
        if (!updateObject(nResources))
2042
0
            return false;
2043
32
        if (!writeBuffer(aLine))
2044
0
            return false;
2045
2046
32
        rFontIDToObject[rSubset.m_nFontID] = nFontObject;
2047
32
    }
2048
2049
32
    return true;
2050
32
}
2051
2052
static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
2053
37.5k
{
2054
37.5k
    if( nSubsetID )
2055
37.5k
    {
2056
263k
        for( int i = 0; i < 6; i++ )
2057
225k
        {
2058
225k
            int nOffset = nSubsetID % 26;
2059
225k
            nSubsetID /= 26;
2060
225k
            rBuffer.append( static_cast<char>('A'+nOffset) );
2061
225k
        }
2062
37.5k
        rBuffer.append( '+' );
2063
37.5k
    }
2064
37.5k
    COSWriter::appendName( rPSName, rBuffer );
2065
37.5k
}
2066
2067
sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2068
                                              const std::vector<sal_Ucs>& rCodeUnits,
2069
                                              const sal_Int32* pCodeUnitsPerGlyph,
2070
                                              const sal_Int32* pEncToUnicodeIndex,
2071
                                              uint32_t nGlyphs )
2072
12.5k
{
2073
12.5k
    int nMapped = 0;
2074
367k
    for (auto n = 0u; n < nGlyphs; ++n)
2075
355k
        if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2076
342k
            nMapped++;
2077
2078
12.5k
    if( nMapped == 0 )
2079
0
        return 0;
2080
2081
12.5k
    sal_Int32 nStream = createObject();
2082
12.5k
    if (!updateObject(nStream))
2083
0
        return 0;
2084
12.5k
    OStringBuffer aContents( PDFWRITER_IMPL_BUFFERSIZE );
2085
12.5k
    aContents.append(
2086
12.5k
                     "/CIDInit/ProcSet findresource begin\n"
2087
12.5k
                     "12 dict begin\n"
2088
12.5k
                     "begincmap\n"
2089
12.5k
                     "/CIDSystemInfo<<\n"
2090
12.5k
                     "/Registry (Adobe)\n"
2091
12.5k
                     "/Ordering (UCS)\n"
2092
12.5k
                     "/Supplement 0\n"
2093
12.5k
                     ">> def\n"
2094
12.5k
                     "/CMapName/Adobe-Identity-UCS def\n"
2095
12.5k
                     "/CMapType 2 def\n"
2096
12.5k
                     "1 begincodespacerange\n"
2097
12.5k
                     "<00> <FF>\n"
2098
12.5k
                     "endcodespacerange\n"
2099
12.5k
                     );
2100
12.5k
    int nCount = 0;
2101
367k
    for (auto n = 0u; n < nGlyphs; ++n)
2102
355k
    {
2103
355k
        if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2104
342k
        {
2105
342k
            if( (nCount % 100) == 0 )
2106
12.6k
            {
2107
12.6k
                if( nCount )
2108
128
                    aContents.append( "endbfchar\n" );
2109
12.6k
                aContents.append( OString::number(std::min(nMapped-nCount, 100) )
2110
12.6k
                    + " beginbfchar\n" );
2111
12.6k
            }
2112
342k
            aContents.append( '<' );
2113
342k
            COSWriter::appendHex(static_cast<sal_Int8>(pEncoding[n]), aContents);
2114
342k
            aContents.append( "> <" );
2115
            // TODO: handle code points>U+FFFF
2116
342k
            sal_Int32 nIndex = pEncToUnicodeIndex[n];
2117
684k
            for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2118
342k
            {
2119
342k
                COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents);
2120
342k
                COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents);
2121
342k
            }
2122
342k
            aContents.append( ">\n" );
2123
342k
            nCount++;
2124
342k
        }
2125
355k
    }
2126
12.5k
    aContents.append( "endbfchar\n"
2127
12.5k
                      "endcmap\n"
2128
12.5k
                      "CMapName currentdict /CMap defineresource pop\n"
2129
12.5k
                      "end\n"
2130
12.5k
                      "end\n" );
2131
12.5k
    SvMemoryStream aStream;
2132
12.5k
    if (!g_bDebugDisableCompression)
2133
12.5k
    {
2134
12.5k
        ZCodec aCodec( 0x4000, 0x4000 );
2135
12.5k
        aCodec.BeginCompression();
2136
12.5k
        aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2137
12.5k
        aCodec.EndCompression();
2138
12.5k
    }
2139
2140
12.5k
    if (g_bDebugDisableCompression)
2141
0
    {
2142
0
        emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2143
0
    }
2144
12.5k
    OStringBuffer aLine( 40 );
2145
2146
12.5k
    aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " );
2147
12.5k
    sal_uInt64 nLen = 0;
2148
12.5k
    if (!g_bDebugDisableCompression)
2149
12.5k
    {
2150
12.5k
        nLen = aStream.Tell();
2151
12.5k
        aStream.Seek( 0 );
2152
12.5k
        aLine.append(OString::number(calculateStreamSize(nLen)) + "/Filter/FlateDecode");
2153
12.5k
    }
2154
0
    else
2155
0
        aLine.append(OString::number(calculateStreamSize(aContents.getLength())));
2156
12.5k
    aLine.append( ">>\nstream\n" );
2157
12.5k
    if (!writeBuffer(aLine)) return 0;
2158
12.5k
    checkAndEnableStreamEncryption( nStream );
2159
12.5k
    if (!g_bDebugDisableCompression)
2160
12.5k
    {
2161
12.5k
        if(!writeBufferBytes(aStream.GetData(), nLen)) return 0;
2162
12.5k
    }
2163
0
    else
2164
0
    {
2165
0
        if (!writeBuffer(aContents)) return 0;
2166
0
    }
2167
12.5k
    disableStreamEncryption();
2168
12.5k
    aLine.setLength( 0 );
2169
12.5k
    aLine.append( "\nendstream\n"
2170
12.5k
                  "endobj\n\n" );
2171
12.5k
    if (!writeBuffer(aLine)) return 0;
2172
12.5k
    return nStream;
2173
12.5k
}
2174
2175
sal_Int32 PDFWriterImpl::emitCIDCMap(sal_Int32 nSubsetID, sal_uInt32 nGlyphs)
2176
12.5k
{
2177
12.5k
    sal_Int32 nStream = createObject();
2178
12.5k
    if (!updateObject(nStream))
2179
0
        return 0;
2180
2181
12.5k
    OStringBuffer aContents(PDFWRITER_IMPL_BUFFERSIZE);
2182
12.5k
    aContents.append("/CIDInit/ProcSet findresource begin\n"
2183
12.5k
                     "12 dict begin\n"
2184
12.5k
                     "begincmap\n"
2185
12.5k
                     "/CIDSystemInfo<<\n"
2186
12.5k
                     "/Registry (Adobe)\n"
2187
12.5k
                     "/Ordering (Identity)\n"
2188
12.5k
                     "/Supplement 0\n"
2189
12.5k
                     ">> def\n"
2190
12.5k
                     "/CMapName/Subset-"
2191
12.5k
                     + OString::number(nSubsetID)
2192
12.5k
                     + " def\n"
2193
12.5k
                       "/CMapType 1 def\n"
2194
12.5k
                       "1 begincodespacerange\n"
2195
12.5k
                       "<00> <FF>\n"
2196
12.5k
                       "endcodespacerange\n");
2197
2198
    // The subset has an identity charset, so the CID of each glyph is its glyph
2199
    // ID, which is also the code we encoded it with.
2200
12.5k
    aContents.append("1 begincidrange\n<00> <");
2201
12.5k
    COSWriter::appendHex(static_cast<sal_Int8>(nGlyphs - 1), aContents);
2202
12.5k
    aContents.append("> 0\nendcidrange\n");
2203
2204
12.5k
    aContents.append("endcmap\n"
2205
12.5k
                     "CMapName currentdict /CMap defineresource pop\n"
2206
12.5k
                     "end\n"
2207
12.5k
                     "end\n");
2208
2209
12.5k
    SvMemoryStream aStream;
2210
12.5k
    if (!g_bDebugDisableCompression)
2211
12.5k
    {
2212
12.5k
        ZCodec aCodec(0x4000, 0x4000);
2213
12.5k
        aCodec.BeginCompression();
2214
12.5k
        aCodec.Write(aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()),
2215
12.5k
                     aContents.getLength());
2216
12.5k
        aCodec.EndCompression();
2217
12.5k
    }
2218
2219
12.5k
    OStringBuffer aLine(80);
2220
12.5k
    aLine.append(OString::number(nStream)
2221
12.5k
                 + " 0 obj\n"
2222
12.5k
                   "<</Type/CMap"
2223
12.5k
                   "/CMapName/Subset-"
2224
12.5k
                 + OString::number(nSubsetID)
2225
12.5k
                 + "-CMap"
2226
12.5k
                   "/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>"
2227
12.5k
                   "/Length ");
2228
12.5k
    sal_uInt64 nLen = 0;
2229
12.5k
    if (!g_bDebugDisableCompression)
2230
12.5k
    {
2231
12.5k
        nLen = aStream.Tell();
2232
12.5k
        aStream.Seek(0);
2233
12.5k
        aLine.append(OString::number(nLen) + "/Filter/FlateDecode");
2234
12.5k
    }
2235
0
    else
2236
0
        aLine.append(aContents.getLength());
2237
12.5k
    aLine.append(">>\nstream\n");
2238
12.5k
    if (!writeBuffer(aLine))
2239
0
        return 0;
2240
12.5k
    checkAndEnableStreamEncryption(nStream);
2241
12.5k
    if (!g_bDebugDisableCompression)
2242
12.5k
    {
2243
12.5k
        if (!writeBufferBytes(aStream.GetData(), nLen))
2244
0
            return 0;
2245
12.5k
    }
2246
0
    else
2247
0
    {
2248
0
        if (!writeBuffer(aContents))
2249
0
            return 0;
2250
0
    }
2251
12.5k
    disableStreamEncryption();
2252
12.5k
    aLine.setLength(0);
2253
12.5k
    aLine.append("\nendstream\nendobj\n\n");
2254
12.5k
    if (!writeBuffer(aLine))
2255
0
        return 0;
2256
12.5k
    return nStream;
2257
12.5k
}
2258
2259
sal_Int32 PDFWriterImpl::emitCIDSet(sal_uInt32 nGlyphs)
2260
12.5k
{
2261
    // Only PDF/A-1 requires one, and PDF 2.0 deprecates it.
2262
12.5k
    if (m_nPDFA_Version != 1 || !nGlyphs)
2263
12.5k
        return 0;
2264
2265
    // A table of bits indexed by CID, high-order bit of the first byte first.
2266
    // The charset is identity, so the CIDs are 0 to nGlyphs - 1.
2267
0
    std::vector<sal_uInt8> aCIDSet((nGlyphs - 1) / 8 + 1, 0);
2268
0
    for (sal_uInt32 nCID = 0; nCID < nGlyphs; ++nCID)
2269
0
        aCIDSet[nCID / 8] |= 0x80 >> (nCID % 8);
2270
2271
0
    sal_Int32 nStream = createObject();
2272
0
    if (!updateObject(nStream))
2273
0
        return 0;
2274
2275
0
    SvMemoryStream aStream;
2276
0
    if (!g_bDebugDisableCompression)
2277
0
    {
2278
0
        ZCodec aCodec(0x4000, 0x4000);
2279
0
        aCodec.BeginCompression();
2280
0
        aCodec.Write(aStream, aCIDSet.data(), aCIDSet.size());
2281
0
        aCodec.EndCompression();
2282
0
    }
2283
2284
0
    OStringBuffer aLine(80);
2285
0
    aLine.append(OString::number(nStream) + " 0 obj\n<</Length ");
2286
0
    sal_uInt64 nLen = 0;
2287
0
    if (!g_bDebugDisableCompression)
2288
0
    {
2289
0
        nLen = aStream.Tell();
2290
0
        aStream.Seek(0);
2291
0
        aLine.append(OString::number(nLen) + "/Filter/FlateDecode");
2292
0
    }
2293
0
    else
2294
0
        aLine.append(OString::number(aCIDSet.size()));
2295
0
    aLine.append(">>\nstream\n");
2296
0
    if (!writeBuffer(aLine))
2297
0
        return 0;
2298
0
    checkAndEnableStreamEncryption(nStream);
2299
0
    if (!g_bDebugDisableCompression)
2300
0
    {
2301
0
        if (!writeBufferBytes(aStream.GetData(), nLen))
2302
0
            return 0;
2303
0
    }
2304
0
    else
2305
0
    {
2306
0
        if (!writeBufferBytes(aCIDSet.data(), aCIDSet.size()))
2307
0
            return 0;
2308
0
    }
2309
0
    disableStreamEncryption();
2310
0
    aLine.setLength(0);
2311
0
    aLine.append("\nendstream\nendobj\n\n");
2312
0
    if (!writeBuffer(aLine))
2313
0
        return 0;
2314
0
    return nStream;
2315
0
}
2316
2317
sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream, sal_Int32 nCIDSet )
2318
12.5k
{
2319
12.5k
    OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
2320
    // get font flags, see PDF reference 1.4 p. 358
2321
    // possibly characters outside Adobe standard encoding
2322
    // so set Symbolic flag
2323
12.5k
    sal_Int32 nFontFlags = (1<<2);
2324
12.5k
    if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
2325
749
        nFontFlags |= (1 << 6);
2326
12.5k
    if( pFace->GetPitch() == PITCH_FIXED )
2327
1.18k
        nFontFlags |= 1;
2328
12.5k
    if( pFace->GetFamilyType() == FAMILY_SCRIPT )
2329
0
        nFontFlags |= (1 << 3);
2330
12.5k
    else if( pFace->GetFamilyType() == FAMILY_ROMAN )
2331
0
        nFontFlags |= (1 << 1);
2332
2333
12.5k
    sal_Int32 nFontDescriptor = createObject();
2334
12.5k
    if (!updateObject(nFontDescriptor)) return 0;
2335
12.5k
    aLine.setLength( 0 );
2336
12.5k
    aLine.append(
2337
12.5k
        OString::number(nFontDescriptor)
2338
12.5k
        + " 0 obj\n"
2339
12.5k
          "<</Type/FontDescriptor/FontName/" );
2340
12.5k
    appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2341
12.5k
    aLine.append( "\n"
2342
12.5k
                  "/Flags "
2343
12.5k
        + OString::number( nFontFlags )
2344
12.5k
        + "\n"
2345
12.5k
          "/FontBBox["
2346
    // note: Top and Bottom are reversed in VCL and PDF rectangles
2347
12.5k
        + OString::number( rInfo.m_aFontBBox.Left() )
2348
12.5k
        + " "
2349
12.5k
        + OString::number( rInfo.m_aFontBBox.Top() )
2350
12.5k
        + " "
2351
12.5k
        + OString::number( rInfo.m_aFontBBox.Right() )
2352
12.5k
        + " "
2353
12.5k
        + OString::number( rInfo.m_aFontBBox.Bottom()+1 )
2354
12.5k
        +  "]/ItalicAngle " );
2355
12.5k
    if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
2356
749
        aLine.append( "-30" );
2357
11.8k
    else
2358
11.8k
        aLine.append( "0" );
2359
12.5k
    aLine.append( "\n"
2360
12.5k
                  "/Ascent "
2361
12.5k
        + OString::number( rInfo.m_nAscent )
2362
12.5k
        + "\n"
2363
12.5k
          "/Descent "
2364
12.5k
        + OString::number( -rInfo.m_nDescent )
2365
12.5k
        + "\n"
2366
12.5k
          "/CapHeight "
2367
12.5k
        + OString::number( rInfo.m_nCapHeight )
2368
    // According to PDF reference 1.4 StemV is required
2369
    // seems a tad strange to me, but well ...
2370
12.5k
        + "\n"
2371
12.5k
          "/StemV 80\n" );
2372
12.5k
    if( nFontStream )
2373
12.5k
    {
2374
12.5k
        aLine.append( "/FontFile" );
2375
12.5k
        switch( rInfo.m_nFontType )
2376
12.5k
        {
2377
12.5k
            case FontType::SFNT_TTF:
2378
12.5k
                aLine.append( '2' );
2379
12.5k
                break;
2380
0
            case FontType::CFF_FONT:
2381
0
                aLine.append( '3' );
2382
0
                break;
2383
0
            default:
2384
0
                OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2385
0
                return 0;
2386
12.5k
        }
2387
12.5k
        aLine.append( " " + OString::number(nFontStream) + " 0 R\n" );
2388
12.5k
    }
2389
12.5k
    if( nCIDSet )
2390
0
        aLine.append( "/CIDSet " + OString::number(nCIDSet) + " 0 R\n" );
2391
12.5k
    aLine.append( ">>\n"
2392
12.5k
                  "endobj\n\n" );
2393
12.5k
    if (!writeBuffer(aLine)) return 0;
2394
2395
12.5k
    return nFontDescriptor;
2396
12.5k
}
2397
2398
void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2399
18.6k
{
2400
18.6k
    for (auto const& item : m_aBuildinFontToObjectMap)
2401
0
    {
2402
0
        rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2403
0
        rDict.append( ' ' );
2404
0
        rDict.append( item.second );
2405
0
        rDict.append( " 0 R" );
2406
0
    }
2407
18.6k
}
2408
2409
bool PDFWriterImpl::emitFonts()
2410
18.6k
{
2411
18.6k
    OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
2412
2413
18.6k
    std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2414
2415
18.6k
    for (const auto & subset : m_aSubsets)
2416
20.3k
    {
2417
20.3k
        for (auto & s_subset :subset.second.m_aSubsets)
2418
20.2k
        {
2419
20.2k
            sal_GlyphId pGlyphIds[ 256 ] = {};
2420
20.2k
            sal_Int32 pWidths[ 256 ];
2421
20.2k
            sal_uInt8 pEncoding[ 256 ] = {};
2422
20.2k
            sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
2423
20.2k
            sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
2424
20.2k
            std::vector<sal_Ucs> aCodeUnits;
2425
20.2k
            sal_Int32 nToUnicodeStream = 0;
2426
2427
            // fill arrays and prepare encoding index map
2428
20.2k
            auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2429
20.2k
                                            pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2430
2431
20.2k
            std::vector<sal_uInt8> aBuffer;
2432
20.2k
            FontSubsetInfo aSubsetInfo;
2433
20.2k
            const auto* pFace = subset.first.m_pFace;
2434
20.2k
            if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo,
2435
20.2k
                                        subset.first.m_aVariations))
2436
12.5k
            {
2437
                // create font stream
2438
12.5k
                if (g_bDebugDisableCompression)
2439
0
                {
2440
0
                    emitComment( "PDFWriterImpl::emitFonts" );
2441
0
                }
2442
12.5k
                sal_Int32 nFontStream = createObject();
2443
12.5k
                sal_Int32 nStreamLengthObject = createObject();
2444
12.5k
                if ( !updateObject( nFontStream ) ) return false;
2445
12.5k
                aLine.setLength( 0 );
2446
12.5k
                aLine.append( OString::number(nFontStream)
2447
12.5k
                    + " 0 obj\n"
2448
12.5k
                      "<</Length "
2449
12.5k
                    + OString::number( nStreamLengthObject ) );
2450
12.5k
                if (!g_bDebugDisableCompression)
2451
12.5k
                    aLine.append( " 0 R"
2452
12.5k
                                 "/Filter/FlateDecode");
2453
0
                else
2454
0
                    aLine.append( " 0 R");
2455
2456
12.5k
                sal_uInt64 nStartPos = 0;
2457
12.5k
                if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
2458
12.5k
                {
2459
12.5k
                    aLine.append("/Length1 "
2460
12.5k
                        + OString::number(aBuffer.size())
2461
12.5k
                        + ">>\n"
2462
12.5k
                          "stream\n" );
2463
12.5k
                    if ( !writeBuffer( aLine ) ) return false;
2464
12.5k
                    if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2465
2466
                    // copy font file
2467
12.5k
                    beginCompression();
2468
12.5k
                    checkAndEnableStreamEncryption( nFontStream );
2469
12.5k
                    if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
2470
0
                        return false;
2471
12.5k
                }
2472
0
                else if( aSubsetInfo.m_nFontType == FontType::CFF_FONT )
2473
0
                {
2474
0
                    aLine.append("/Subtype/CIDFontType0C>>\nstream\n");
2475
0
                    if ( !writeBuffer( aLine ) ) return false;
2476
0
                    if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2477
2478
                    // copy bare CFF font program
2479
0
                    beginCompression();
2480
0
                    checkAndEnableStreamEncryption( nFontStream );
2481
0
                    if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
2482
0
                        return false;
2483
0
                }
2484
0
                else
2485
0
                {
2486
0
                    SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
2487
0
                    aLine.append( "0 >>\nstream\n" );
2488
0
                }
2489
2490
12.5k
                endCompression();
2491
12.5k
                disableStreamEncryption();
2492
2493
12.5k
                sal_uInt64 nEndPos = 0;
2494
12.5k
                if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
2495
                // end the stream
2496
12.5k
                aLine.setLength( 0 );
2497
12.5k
                aLine.append( "\nendstream\nendobj\n\n" );
2498
12.5k
                if ( !writeBuffer( aLine ) ) return false;
2499
2500
                // emit stream length object
2501
12.5k
                if ( !updateObject( nStreamLengthObject ) ) return false;
2502
12.5k
                aLine.setLength( 0 );
2503
12.5k
                aLine.append( OString::number(nStreamLengthObject)
2504
12.5k
                    + " 0 obj\n"
2505
12.5k
                    + OString::number( nEndPos-nStartPos )
2506
12.5k
                    + "\nendobj\n\n" );
2507
12.5k
                if ( !writeBuffer( aLine ) ) return false;
2508
2509
                // PDF/A-1 wants the CIDs of a composite subset listed
2510
12.5k
                sal_Int32 nCIDSet = emitCIDSet(nGlyphs);
2511
2512
                // write font descriptor
2513
12.5k
                sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first.m_pFace, aSubsetInfo, s_subset.m_nFontID, nFontStream, nCIDSet );
2514
2515
12.5k
                if( nToUnicodeStream )
2516
12.5k
                    nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
2517
2518
                // Emit the CMap and the descendant CIDFont before the Type 0
2519
                // wrapper. CFF subsets are CIDFontType0, glyf ones CIDFontType2.
2520
12.5k
                bool bTrueType = aSubsetInfo.m_nFontType == FontType::SFNT_TTF;
2521
12.5k
                sal_Int32 nCMapStream = emitCIDCMap(s_subset.m_nFontID, nGlyphs);
2522
12.5k
                if (!nCMapStream) return false;
2523
2524
12.5k
                sal_Int32 nCIDFontObject = createObject();
2525
12.5k
                if (!updateObject(nCIDFontObject)) return false;
2526
12.5k
                {
2527
12.5k
                    OStringBuffer aCIDLine;
2528
12.5k
                    aCIDLine.append(OString::number(nCIDFontObject) + " 0 obj\n"
2529
12.5k
                        "<</Type/Font/Subtype/");
2530
12.5k
                    aCIDLine.append(bTrueType ? "CIDFontType2" : "CIDFontType0");
2531
12.5k
                    aCIDLine.append("/BaseFont/");
2532
12.5k
                    appendSubsetName(s_subset.m_nFontID, aSubsetInfo.m_aPSName, aCIDLine);
2533
12.5k
                    aCIDLine.append("\n/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>\n"
2534
12.5k
                        "/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
2535
12.5k
                    if (bTrueType)
2536
12.5k
                        aCIDLine.append("/CIDToGIDMap/Identity\n");
2537
                    // /W is indexed by CID, which is the glyph ID here
2538
12.5k
                    aCIDLine.append("/W[0[");
2539
367k
                    for (auto i = 0u; i < nGlyphs; i++)
2540
355k
                    {
2541
355k
                        aCIDLine.append(pWidths[i]);
2542
355k
                        aCIDLine.append(((i & 15) == 15) ? "\n" : " ");
2543
355k
                    }
2544
12.5k
                    aCIDLine.append("]]\n>>\nendobj\n\n");
2545
12.5k
                    if (!writeBuffer(aCIDLine)) return false;
2546
12.5k
                }
2547
2548
12.5k
                sal_Int32 nFontObject = createObject();
2549
12.5k
                if ( !updateObject( nFontObject ) ) return false;
2550
12.5k
                aLine.setLength( 0 );
2551
12.5k
                aLine.append( OString::number(nFontObject) + " 0 obj\n"
2552
12.5k
                    "<</Type/Font/Subtype/Type0/BaseFont/");
2553
12.5k
                appendSubsetName(s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine);
2554
12.5k
                aLine.append("\n/Encoding " + OString::number(nCMapStream) + " 0 R\n"
2555
12.5k
                    "/DescendantFonts[" + OString::number(nCIDFontObject) + " 0 R]\n");
2556
12.5k
                if (nToUnicodeStream)
2557
12.5k
                {
2558
12.5k
                    aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
2559
12.5k
                }
2560
12.5k
                aLine.append(">>\nendobj\n\n");
2561
12.5k
                if ( !writeBuffer( aLine ) ) return false;
2562
2563
12.5k
                aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
2564
12.5k
            }
2565
7.77k
            else
2566
7.77k
            {
2567
7.77k
                OStringBuffer aErrorComment( 256 );
2568
7.77k
                aErrorComment.append( "CreateFontSubset failed for font \""
2569
7.77k
                    + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
2570
7.77k
                    + "\"" );
2571
7.77k
                if( pFace->GetItalic() == ITALIC_NORMAL )
2572
2.28k
                    aErrorComment.append( " italic" );
2573
5.48k
                else if( pFace->GetItalic() == ITALIC_OBLIQUE )
2574
0
                    aErrorComment.append( " oblique" );
2575
7.77k
                aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
2576
7.77k
                emitComment( aErrorComment.getStr() );
2577
7.77k
            }
2578
20.2k
        }
2579
20.3k
    }
2580
2581
    // emit system fonts
2582
18.6k
    for (auto const& systemFont : m_aSystemFonts)
2583
0
    {
2584
0
        std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
2585
0
        for (auto const& item : aObjects)
2586
0
        {
2587
0
            if ( !item.second ) return false;
2588
0
            aFontIDToObject[ item.first ] = item.second;
2589
0
        }
2590
0
    }
2591
2592
    // emit Type3 fonts
2593
18.6k
    for (auto const& it : m_aType3Fonts)
2594
32
    {
2595
32
        if (!emitType3Font(it.first, it.second, aFontIDToObject))
2596
0
            return false;
2597
32
    }
2598
2599
18.6k
    OStringBuffer aFontDict( PDFWRITER_IMPL_BUFFERSIZE );
2600
18.6k
    aFontDict.append( OString::number(getFontDictObject())
2601
18.6k
        + " 0 obj\n"
2602
18.6k
          "<<" );
2603
18.6k
    int ni = 0;
2604
18.6k
    for (auto const& itemMap : aFontIDToObject)
2605
12.5k
    {
2606
12.5k
        aFontDict.append( "/F"
2607
12.5k
            + OString::number( itemMap.first )
2608
12.5k
            + " "
2609
12.5k
            + OString::number( itemMap.second )
2610
12.5k
            + " 0 R" );
2611
12.5k
        if( ((++ni) & 7) == 0 )
2612
14
            aFontDict.append( '\n' );
2613
12.5k
    }
2614
    // emit builtin font for widget appearances / variable text
2615
18.6k
    for (auto & item : m_aBuildinFontToObjectMap)
2616
0
    {
2617
0
        rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
2618
0
        item.second = emitBuildinFont( aData.get(), item.second );
2619
0
    }
2620
2621
18.6k
    appendBuildinFontsToDict(aFontDict);
2622
18.6k
    aFontDict.append( "\n>>\nendobj\n\n" );
2623
2624
18.6k
    if ( !updateObject( getFontDictObject() ) ) return false;
2625
18.6k
    if ( !writeBuffer( aFontDict ) ) return false;
2626
18.6k
    return true;
2627
18.6k
}
2628
2629
sal_Int32 PDFWriterImpl::emitResources()
2630
18.6k
{
2631
    // emit shadings
2632
18.6k
    if (!m_aGradients.empty())
2633
0
    {
2634
0
        if (!emitGradients()) return 0;
2635
0
    }
2636
    // emit tilings
2637
18.6k
    if (!m_aTilings.empty())
2638
0
    {
2639
0
        if(!emitTilings()) return 0;
2640
0
    }
2641
2642
    // emit font dict
2643
18.6k
    if (!emitFonts()) return 0;
2644
2645
    // emit Resource dict
2646
18.6k
    OStringBuffer aLine( 512 );
2647
18.6k
    sal_Int32 nResourceDict = getResourceDictObj();
2648
18.6k
    if (!updateObject(nResourceDict))
2649
0
        return 0;
2650
18.6k
    aLine.setLength( 0 );
2651
18.6k
    aLine.append( OString::number(nResourceDict)
2652
18.6k
        + " 0 obj\n" );
2653
18.6k
    m_aGlobalResourceDict.append(aLine, getFontDictObject(), m_aContext.Version);
2654
18.6k
    aLine.append( "endobj\n\n" );
2655
18.6k
    if (!writeBuffer(aLine)) return 0;
2656
18.6k
    return nResourceDict;
2657
18.6k
}
2658
2659
sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
2660
                                                 sal_Int32 nItemLevel,
2661
                                                 sal_Int32 nCurrentItemId )
2662
1.50k
{
2663
    /* The /Count number of an item is
2664
       positive: the number of visible subitems
2665
       negative: the negative number of subitems that will become visible if
2666
                 the item gets opened
2667
       see PDF ref 1.4 p 478
2668
    */
2669
2670
1.50k
    sal_Int32 nCount = 0;
2671
2672
1.50k
    if( m_aContext.OpenBookmarkLevels < 0           || // all levels are visible
2673
0
        m_aContext.OpenBookmarkLevels >= nItemLevel    // this level is visible
2674
1.50k
      )
2675
1.50k
    {
2676
1.50k
        PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
2677
1.50k
        sal_Int32 nChildren = rItem.m_aChildren.size();
2678
2.37k
        for( sal_Int32 i = 0; i < nChildren; i++ )
2679
876
            nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
2680
1.50k
        rCounts[nCurrentItemId] = nCount;
2681
        // return 1 (this item) + visible sub items
2682
1.50k
        if( nCount < 0 )
2683
0
            nCount = 0;
2684
1.50k
        nCount++;
2685
1.50k
    }
2686
0
    else
2687
0
    {
2688
        // this bookmark level is invisible
2689
0
        PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
2690
0
        sal_Int32 nChildren = rItem.m_aChildren.size();
2691
0
        rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
2692
0
        for( sal_Int32 i = 0; i < nChildren; i++ )
2693
0
            updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
2694
0
        nCount = -1;
2695
0
    }
2696
2697
1.50k
    return nCount;
2698
1.50k
}
2699
2700
sal_Int32 PDFWriterImpl::emitOutline()
2701
18.6k
{
2702
18.6k
    int i, nItems = m_aOutline.size();
2703
2704
    // do we have an outline at all ?
2705
18.6k
    if( nItems < 2 )
2706
18.0k
        return 0;
2707
2708
    // reserve object numbers for all outline items
2709
2.13k
    for( i = 0; i < nItems; ++i )
2710
1.50k
        m_aOutline[i].m_nObject = createObject();
2711
2712
    // update all parent, next and prev object ids
2713
2.13k
    for( i = 0; i < nItems; ++i )
2714
1.50k
    {
2715
1.50k
        PDFOutlineEntry& rItem = m_aOutline[i];
2716
1.50k
        int nChildren = rItem.m_aChildren.size();
2717
2718
1.50k
        if( nChildren )
2719
630
        {
2720
1.50k
            for( int n = 0; n < nChildren; ++n )
2721
876
            {
2722
876
                PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
2723
2724
876
                rChild.m_nParentObject = rItem.m_nObject;
2725
876
                rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
2726
876
                rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
2727
876
            }
2728
2729
630
        }
2730
1.50k
    }
2731
2732
    // calculate Count entries for all items
2733
627
    std::vector< sal_Int32 > aCounts( nItems );
2734
627
    updateOutlineItemCount( aCounts, 0, 0 );
2735
2736
    // emit hierarchy
2737
2.13k
    for( i = 0; i < nItems; ++i )
2738
1.50k
    {
2739
1.50k
        PDFOutlineEntry& rItem = m_aOutline[i];
2740
1.50k
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
2741
1.50k
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
2742
2743
1.50k
        if (!updateObject(rItem.m_nObject))
2744
0
            return 0;
2745
1.50k
        aLine.append( OString::number(rItem.m_nObject)
2746
1.50k
            + " 0 obj\n"
2747
1.50k
              "<<" );
2748
        // number of visible children (all levels)
2749
1.50k
        if( i > 0 || aCounts[0] > 0 )
2750
1.50k
        {
2751
1.50k
            aLine.append( "/Count " + OString::number( aCounts[i] ) );
2752
1.50k
        }
2753
1.50k
        if( ! rItem.m_aChildren.empty() )
2754
630
        {
2755
            // children list: First, Last
2756
630
            aLine.append( "/First "
2757
630
                + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
2758
630
                + " 0 R/Last "
2759
630
                + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
2760
630
                + " 0 R\n" );
2761
630
        }
2762
1.50k
        if( i > 0 )
2763
876
        {
2764
            // Title, Dest, Parent, Prev, Next
2765
876
            aLine.append( "/Title" );
2766
876
            aWriter.writeUnicodeEncrypt(rItem.m_aTitle, rItem.m_nObject);
2767
876
            aLine.append( "\n" );
2768
            // Dest is not required
2769
876
            if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
2770
876
            {
2771
876
                appendDestOrGoTo(rItem.m_nDestID, aLine);
2772
876
            }
2773
876
            aLine.append( "/Parent "
2774
876
                + OString::number( rItem.m_nParentObject )
2775
876
                + " 0 R" );
2776
876
            if( rItem.m_nPrevObject )
2777
246
            {
2778
246
                aLine.append( "/Prev "
2779
246
                    + OString::number( rItem.m_nPrevObject )
2780
246
                    + " 0 R" );
2781
246
            }
2782
876
            if( rItem.m_nNextObject )
2783
246
            {
2784
246
                aLine.append( "/Next "
2785
246
                    + OString::number( rItem.m_nNextObject )
2786
246
                    + " 0 R" );
2787
246
            }
2788
876
        }
2789
1.50k
        aLine.append( ">>\nendobj\n\n" );
2790
1.50k
        if (!writeBuffer(aLine))
2791
0
            return 0;
2792
1.50k
    }
2793
2794
627
    return m_aOutline[0].m_nObject;
2795
627
}
2796
2797
bool PDFWriterImpl::appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer)
2798
3.71k
{
2799
3.71k
    if (nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size())
2800
0
        return false;
2801
2802
3.71k
    const PDFDest& rDest = m_aDests[nDestID];
2803
3.71k
    if (rDest.m_nStructElement < 0
2804
0
        || o3tl::make_unsigned(rDest.m_nStructElement) >= m_aStructure.size())
2805
3.71k
        return false;
2806
2807
0
    const sal_Int32 nObject(m_aStructure[rDest.m_nStructElement].m_nObject);
2808
0
    if (nObject <= 0) // not emitted: NonStruct, or no structure at all
2809
0
        return false;
2810
2811
    // same as the page destination, bar the first entry
2812
0
    OStringBuffer aPageDest;
2813
0
    if (!appendDest(nDestID, aPageDest))
2814
0
        return false;
2815
0
    const OString aPage(aPageDest.makeStringAndClear());
2816
0
    const sal_Int32 nAfterPage(aPage.indexOf(" 0 R") + 4);
2817
2818
0
    rBuffer.append("[" + OString::number(nObject) + " 0 R" + aPage.subView(nAfterPage));
2819
0
    return true;
2820
0
}
2821
2822
void PDFWriterImpl::appendDestOrGoTo(sal_Int32 nDestID, OStringBuffer& rBuffer)
2823
3.71k
{
2824
3.71k
    OStringBuffer aStructure;
2825
3.71k
    if (!appendStructureDest(nDestID, aStructure))
2826
3.71k
    {
2827
3.71k
        rBuffer.append("/Dest");
2828
3.71k
        appendDest(nDestID, rBuffer);
2829
3.71k
        return;
2830
3.71k
    }
2831
2832
    // ISO 14289-2 8.8: a destination inside the document shall be a structure destination
2833
0
    rBuffer.append("/A<</Type/Action/S/GoTo/D");
2834
0
    appendDest(nDestID, rBuffer);
2835
0
    rBuffer.append("/SD" + aStructure.makeStringAndClear() + ">>");
2836
0
}
2837
2838
bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
2839
3.71k
{
2840
3.71k
    if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
2841
0
    {
2842
0
        SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << nDestID << " requested");
2843
0
        return false;
2844
0
    }
2845
2846
3.71k
    const PDFDest& rDest        = m_aDests[ nDestID ];
2847
3.71k
    const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
2848
2849
3.71k
    rBuffer.append( '[' );
2850
3.71k
    rBuffer.append( rDestPage.m_nPageObject );
2851
3.71k
    rBuffer.append( " 0 R" );
2852
2853
3.71k
    switch( rDest.m_eType )
2854
3.71k
    {
2855
3.71k
        case PDFWriter::DestAreaType::XYZ:
2856
3.71k
        default:
2857
3.71k
            rBuffer.append( "/XYZ " );
2858
3.71k
            appendFixedInt( rDest.m_aRect.Left(), rBuffer );
2859
3.71k
            rBuffer.append( ' ' );
2860
3.71k
            appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
2861
3.71k
            rBuffer.append( " 0" );
2862
3.71k
            break;
2863
0
        case PDFWriter::DestAreaType::FitRectangle:
2864
0
            rBuffer.append( "/FitR " );
2865
0
            appendFixedInt( rDest.m_aRect.Left(), rBuffer );
2866
0
            rBuffer.append( ' ' );
2867
0
            appendFixedInt( rDest.m_aRect.Top(), rBuffer );
2868
0
            rBuffer.append( ' ' );
2869
0
            appendFixedInt( rDest.m_aRect.Right(), rBuffer );
2870
0
            rBuffer.append( ' ' );
2871
0
            appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
2872
0
            break;
2873
3.71k
    }
2874
3.71k
    rBuffer.append( ']' );
2875
2876
3.71k
    return true;
2877
3.71k
}
2878
2879
void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream)
2880
0
{
2881
0
    if (m_nPDFA_Version == 1 || m_nPDFA_Version == 2)
2882
0
        return;
2883
2884
0
    sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType);
2885
0
    auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back();
2886
0
    rAttachedFile.maFilename = rFileName;
2887
0
    rAttachedFile.maMimeType = rMimeType;
2888
0
    rAttachedFile.maDescription = rDescription;
2889
0
    rAttachedFile.mnEmbeddedFileObjectId = nObjectID;
2890
0
    rAttachedFile.mnObjectId = createObject();
2891
0
}
2892
2893
sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType)
2894
0
{
2895
0
    sal_Int32 aObjectID = createObject();
2896
0
    auto& rEmbedded = m_aEmbeddedFiles.emplace_back();
2897
0
    rEmbedded.m_nObject = aObjectID;
2898
0
    rEmbedded.m_aSubType = rMimeType;
2899
0
    rEmbedded.m_pStream = std::move(rStream);
2900
0
    return aObjectID;
2901
0
}
2902
2903
sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer)
2904
0
{
2905
0
    sal_Int32 aObjectID = createObject();
2906
0
    m_aEmbeddedFiles.emplace_back();
2907
0
    m_aEmbeddedFiles.back().m_nObject = aObjectID;
2908
0
    m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
2909
0
    return aObjectID;
2910
0
}
2911
2912
bool PDFWriterImpl::emitScreenAnnotations()
2913
18.6k
{
2914
18.6k
    int nAnnots = m_aScreens.size();
2915
18.6k
    for (int i = 0; i < nAnnots; i++)
2916
0
    {
2917
0
        const PDFScreen& rScreen = m_aScreens[i];
2918
2919
0
        OStringBuffer aLine;
2920
0
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
2921
0
        bool bEmbed = false;
2922
0
        if (!rScreen.m_aTempFileURL.isEmpty())
2923
0
        {
2924
0
            bEmbed = true;
2925
0
            if (!updateObject(rScreen.m_nTempFileObject))
2926
0
                continue;
2927
2928
0
            SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
2929
0
            SvMemoryStream aMemoryStream;
2930
0
            aMemoryStream.WriteStream(aFileStream);
2931
2932
0
            aLine.append(OString::number(rScreen.m_nTempFileObject)
2933
0
                + " 0 obj\n<< /Type /EmbeddedFile /Length "
2934
0
                + OString::number(calculateStreamSize(aMemoryStream.GetSize()))
2935
0
                + " >>\nstream\n");
2936
0
            if (!writeBuffer(aLine))
2937
0
                return false;
2938
0
            aLine.setLength(0);
2939
2940
0
            checkAndEnableStreamEncryption(rScreen.m_nTempFileObject);
2941
0
            if (!writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()))
2942
0
                return false;
2943
0
            disableStreamEncryption();
2944
2945
0
            aLine.append("\nendstream\nendobj\n\n");
2946
0
            if (!writeBuffer(aLine))
2947
0
                return false;
2948
0
            aLine.setLength(0);
2949
0
        }
2950
2951
0
        if (!updateObject(rScreen.m_nObject))
2952
0
            continue;
2953
2954
        // Annot dictionary.
2955
0
        aLine.append(OString::number(rScreen.m_nObject)
2956
0
            + " 0 obj\n"
2957
0
              "<</Type/Annot"
2958
0
              "/Subtype/Screen/Rect[");
2959
0
        appendFixedInt(rScreen.m_aRect.Left(), aLine);
2960
0
        aLine.append(' ');
2961
0
        appendFixedInt(rScreen.m_aRect.Top(), aLine);
2962
0
        aLine.append(' ');
2963
0
        appendFixedInt(rScreen.m_aRect.Right(), aLine);
2964
0
        aLine.append(' ');
2965
0
        appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
2966
0
        aLine.append("]");
2967
2968
        // Action dictionary.
2969
0
        aLine.append("/A<</Type/Action /S/Rendition /AN "
2970
0
            + OString::number(rScreen.m_nObject)
2971
0
            + " 0 R ");
2972
2973
        // Rendition dictionary.
2974
0
        aLine.append("/R<</Type/Rendition /S/MR ");
2975
2976
        // MediaClip dictionary.
2977
0
        aLine.append("/C<</Type/MediaClip /S/MCD ");
2978
0
        if (bEmbed)
2979
0
        {
2980
0
            aLine.append("\n/D << /Type /Filespec ");
2981
0
            aWriter.writeKeyAndLiteralEncrypt("/F", "<embedded file>", rScreen.m_nObject);
2982
0
            if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
2983
0
            {   // ISO 14289-1:2014, Clause: 7.11
2984
0
                aWriter.writeKeyAndLiteralEncrypt("/UF", "<embedded file>", rScreen.m_nObject);
2985
0
            }
2986
0
            aLine.append("/EF << /F "
2987
0
                + OString::number(rScreen.m_nTempFileObject)
2988
0
                + " 0 R >>");
2989
0
        }
2990
0
        else
2991
0
        {
2992
            // Linked.
2993
0
            aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
2994
0
            aWriter.writeLiteralEncrypt(rScreen.m_aURL, rScreen.m_nObject, osl_getThreadTextEncoding());
2995
0
            if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
2996
0
            {   // ISO 14289-1:2014, Clause: 7.11
2997
0
                aLine.append("/UF ");
2998
0
                aWriter.writeUnicodeEncrypt(rScreen.m_aURL, rScreen.m_nObject);
2999
0
            }
3000
0
        }
3001
0
        if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
3002
0
            && !rScreen.m_AltText.isEmpty())
3003
0
        {   // ISO 14289-1:2014, Clause: 7.11
3004
0
            aLine.append("/Desc ");
3005
0
            aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3006
0
        }
3007
0
        aLine.append(" >>\n"); // end of /D
3008
        // Allow playing the video via a tempfile.
3009
0
        aLine.append("/P <<");
3010
0
        aWriter.writeKeyAndLiteralEncrypt("/TF", "TEMPACCESS", rScreen.m_nObject);
3011
0
        aLine.append(">>");
3012
        // ISO 14289-1:2014, Clause: 7.18.6.2
3013
0
        aLine.append("/CT ");
3014
0
        aWriter.writeLiteralEncrypt(rScreen.m_MimeType, rScreen.m_nObject);
3015
        // ISO 14289-1:2014, Clause: 7.18.6.2
3016
        // Alt text is a "Multi-language Text Array"
3017
0
        aLine.append(" /Alt [ ");
3018
0
        aWriter.writeLiteralEncrypt("", rScreen.m_nObject);
3019
0
        aLine.append(" ");
3020
0
        aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3021
0
        aLine.append(" ] "
3022
0
                     ">>");
3023
3024
        // End Rendition dictionary by requesting play/pause/stop controls.
3025
0
        aLine.append("/P<</BE<</C true >>>>"
3026
0
                     ">>");
3027
3028
        // End Action dictionary.
3029
0
        aLine.append("/OP 0 >>");
3030
3031
0
        if (-1 != rScreen.m_nStructParent)
3032
0
        {
3033
0
            aLine.append("\n/StructParent "
3034
0
                + OString::number(rScreen.m_nStructParent)
3035
0
                + "\n");
3036
0
        }
3037
3038
        // End Annot dictionary.
3039
0
        aLine.append("/P "
3040
0
            + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
3041
0
            + " 0 R\n>>\nendobj\n\n");
3042
0
        if (!writeBuffer(aLine))
3043
0
            return false;
3044
0
    }
3045
3046
18.6k
    return true;
3047
18.6k
}
3048
3049
bool PDFWriterImpl::emitLinkAnnotations()
3050
18.6k
{
3051
18.6k
    MARK("PDFWriterImpl::emitLinkAnnotations");
3052
18.6k
    int nAnnots = m_aLinks.size();
3053
21.6k
    for( int i = 0; i < nAnnots; i++ )
3054
3.03k
    {
3055
3.03k
        const PDFLink& rLink            = m_aLinks[i];
3056
3.03k
        if( ! updateObject( rLink.m_nObject ) )
3057
0
            continue;
3058
3.03k
        if( m_aContext.DefaultLinkAction == PDFWriter::RemoveExternalLinks && rLink.m_nDest < 0 )
3059
0
            continue;
3060
3061
3.03k
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
3062
3.03k
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3063
3.03k
        aLine.append( rLink.m_nObject );
3064
3.03k
        aLine.append( " 0 obj\n" );
3065
// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3066
// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3067
3.03k
        aLine.append( "<</Type/Annot" );
3068
3.03k
        if (m_nPDFA_Version > 0)
3069
0
            aLine.append( "/F 4" );
3070
3.03k
        aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3071
3072
3.03k
        appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3073
3.03k
        aLine.append( ' ' );
3074
3.03k
        appendFixedInt( rLink.m_aRect.Top(), aLine );
3075
3.03k
        aLine.append( ' ' );
3076
3.03k
        appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3077
3.03k
        aLine.append( ' ' );
3078
3.03k
        appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3079
3.03k
        aLine.append( "]" );
3080
        // ISO 14289-1:2014, Clause: 7.18.5
3081
3.03k
        OUString const& rContents(rLink.m_AltText.isEmpty() && rLink.m_nDest < 0 ? rLink.m_aURL
3082
3.03k
                                                                                 : rLink.m_AltText);
3083
3.03k
        if (!rContents.isEmpty())
3084
2.95k
        {
3085
2.95k
            aLine.append("/Contents");
3086
2.95k
            aWriter.writeUnicodeEncrypt(rContents, rLink.m_nObject);
3087
2.95k
        }
3088
3.03k
        if( rLink.m_nDest >= 0 )
3089
2.83k
        {
3090
2.83k
            appendDestOrGoTo(rLink.m_nDest, aLine);
3091
2.83k
        }
3092
200
        else
3093
200
        {
3094
/*
3095
destination is external to the document, so
3096
we check in the following sequence:
3097
3098
 if target type is neither .pdf, nor .od[tpgs], then
3099
          check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3100
                             end processing
3101
 else if target is .od[tpgs]: then
3102
      if conversion of type from od[tpgs]  to pdf is requested, convert it and this becomes the new target file
3103
      processing continue
3104
3105
 if (new)target is .pdf : then
3106
     if GotToR is requested, then
3107
           convert the target in GoToR where the fragment of the URI is
3108
           considered the named destination in the target file, set relative or absolute as requested
3109
     else strip the fragment from URL and then set URI or 'launch application' as requested
3110
*/
3111
3112
// FIXME: check if the decode mechanisms for URL processing throughout this implementation
3113
// are the correct one!!
3114
3115
// extract target file type
3116
200
            auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3117
3118
200
            INetURLObject aDocumentURL( m_aContext.BaseURL );
3119
200
            INetURLObject aTargetURL( url );
3120
200
            bool bSetGoToRMode = false;
3121
200
            bool    bTargetHasPDFExtension = false;
3122
200
            INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3123
200
            bool    bIsUNCPath = false;
3124
200
            bool    bUnparsedURI = false;
3125
3126
            // check if the protocol is a known one, or if there is no protocol at all (on target only)
3127
            // if there is no protocol, make the target relative to the current document directory
3128
            // getting the needed URL information from the current document path
3129
200
            if( eTargetProtocol == INetProtocol::NotValid )
3130
31
            {
3131
31
                if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3132
0
                {
3133
0
                    bIsUNCPath = true;
3134
0
                }
3135
31
                else
3136
31
                {
3137
                    //reassign the new target URL
3138
31
                    aTargetURL = INetURLObject(rtl::Uri::convertRelToAbs(
3139
31
                                                (m_aContext.BaseURL.getLength() > 0 ?
3140
0
                                                    m_aContext.BaseURL :
3141
                                                    //use dummy location if empty
3142
31
                                                    u"http://ahost.ax"_ustr),
3143
31
                                                url));
3144
3145
                    //recompute the target protocol, with the new URL
3146
                    //normal URL processing resumes
3147
31
                    eTargetProtocol = aTargetURL.GetProtocol();
3148
3149
31
                    bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
3150
31
                }
3151
31
            }
3152
3153
200
            OUString aFileExtension = aTargetURL.GetFileExtension();
3154
3155
            // Check if the URL ends in '/': if yes it's a directory,
3156
            // it will be forced to a URI link.
3157
            // possibly a malformed URI, leave it as it is, force as URI
3158
200
            if( aTargetURL.hasFinalSlash() )
3159
53
                m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3160
3161
200
            if( !aFileExtension.isEmpty() )
3162
73
            {
3163
73
                if( m_aContext.ConvertOOoTargetToPDFTarget )
3164
0
                {
3165
0
                    bool bChangeFileExtensionToPDF = false;
3166
                    //examine the file type (.odm .odt. .odp, odg, ods)
3167
0
                    if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3168
0
                        bChangeFileExtensionToPDF = true;
3169
0
                    if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3170
0
                        bChangeFileExtensionToPDF = true;
3171
0
                    else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3172
0
                        bChangeFileExtensionToPDF = true;
3173
0
                    else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3174
0
                        bChangeFileExtensionToPDF = true;
3175
0
                    else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3176
0
                        bChangeFileExtensionToPDF = true;
3177
0
                    if( bChangeFileExtensionToPDF )
3178
0
                        aTargetURL.setExtension(u"pdf" );
3179
0
                }
3180
                //check if extension is pdf, see if GoToR should be forced
3181
73
                bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3182
73
                if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3183
0
                    bSetGoToRMode = true;
3184
73
            }
3185
            //prepare the URL, if relative or not
3186
200
            INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3187
            //queue the string common to all types of actions
3188
200
            aLine.append( "/A<</Type/Action/S");
3189
200
            if( bIsUNCPath ) // handle Win UNC paths
3190
0
            {
3191
0
                aLine.append("/Launch");
3192
                // Entry /Win is deprecated in PDF 2.0
3193
0
                if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
3194
0
                {
3195
                    // So write /F directly. AFAICS it's up to PDF viewer to resolve this correctly
3196
0
                    aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3197
0
                }
3198
0
                else
3199
0
                {
3200
0
                    aLine.append("/Win");
3201
0
                    aWriter.startDict();
3202
                    // INetURLObject is not good with UNC paths, use original path
3203
0
                    aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3204
0
                    aWriter.endDict();
3205
0
                }
3206
0
            }
3207
200
            else
3208
200
            {
3209
200
                bool bSetRelative = false;
3210
200
                bool bFileSpec = false;
3211
                //check if relative file link is requested and if the protocol is 'file://'
3212
200
                if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3213
0
                    bSetRelative = true;
3214
3215
200
                OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3216
200
                if( !bSetGoToRMode )
3217
200
                {
3218
200
                    switch( m_aContext.DefaultLinkAction )
3219
200
                    {
3220
0
                    default:
3221
200
                    case PDFWriter::URIAction :
3222
200
                    case PDFWriter::URIActionDestination :
3223
200
                        aLine.append( "/URI/URI" );
3224
200
                        break;
3225
0
                    case PDFWriter::LaunchAction:
3226
                        // now:
3227
                        // if a launch action is requested and the hyperlink target has a fragment
3228
                        // and the target file does not have a pdf extension, or it's not a 'file:://'
3229
                        // protocol then force the uri action on it
3230
                        // This code will permit the correct opening of application on web pages,
3231
                        // the one that normally have fragments (but I may be wrong...)
3232
                        // and will force the use of URI when the protocol is not file:
3233
0
                        if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3234
0
                                        eTargetProtocol != INetProtocol::File )
3235
0
                        {
3236
0
                            aLine.append( "/URI/URI" );
3237
0
                        }
3238
0
                        else
3239
0
                        {
3240
0
                            aLine.append( "/Launch/F" );
3241
0
                            bFileSpec = true;
3242
0
                        }
3243
0
                        break;
3244
200
                    }
3245
200
                }
3246
3247
                //fragment are encoded in the same way as in the named destination processing
3248
200
                if( bSetGoToRMode )
3249
0
                {
3250
                    //add the fragment
3251
0
                    OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3252
0
                    aLine.append("/GoToR");
3253
0
                    aLine.append("/F");
3254
0
                    aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3255
0
                                                                                         INetURLObject::EncodeMechanism::WasEncoded,
3256
0
                                                                                         INetURLObject::DecodeMechanism::WithCharset ) :
3257
0
                                                                   aURLNoMark, rLink.m_nObject, osl_getThreadTextEncoding());
3258
0
                    if( !aFragment.isEmpty() )
3259
0
                    {
3260
0
                        aLine.append("/D/");
3261
0
                        appendDestinationName( aFragment , aLine );
3262
0
                    }
3263
0
                }
3264
200
                else
3265
200
                {
3266
                    // change the fragment to accommodate the bookmark (only if the file extension
3267
                    // is PDF and the requested action is of the correct type)
3268
200
                    if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3269
0
                               bTargetHasPDFExtension && !aFragment.isEmpty() )
3270
0
                    {
3271
0
                        OStringBuffer aLineLoc( PDFWRITER_IMPL_BUFFERSIZE );
3272
0
                        appendDestinationName( aFragment , aLineLoc );
3273
                        //substitute the fragment
3274
0
                        aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
3275
0
                    }
3276
200
                    OUString aURL = bUnparsedURI ? url :
3277
200
                                                   aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
3278
173
                                                                                      INetURLObject::DecodeMechanism::NONE );
3279
200
                    aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3280
0
                                                                                        INetURLObject::EncodeMechanism::WasEncoded,
3281
0
                                                                                            bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3282
0
                                                                                            ) :
3283
200
                                                                               aURL , rLink.m_nObject, osl_getThreadTextEncoding() );
3284
200
                }
3285
200
            }
3286
200
            aLine.append( ">>\n" );
3287
200
        }
3288
3.03k
        if (rLink.m_nStructParent != -1)
3289
0
        {
3290
0
            aLine.append( "/StructParent " );
3291
0
            aLine.append( rLink.m_nStructParent );
3292
0
        }
3293
3.03k
        aLine.append( ">>\nendobj\n\n" );
3294
3.03k
        if (!writeBuffer(aLine))
3295
0
            return false;
3296
3.03k
    }
3297
3298
18.6k
    return true;
3299
18.6k
}
3300
3301
namespace
3302
{
3303
3304
void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3305
136
{
3306
136
    aLine.append("/Rect [");
3307
136
    appendFixedInt(rRectangle.Left(), aLine);
3308
136
    aLine.append(' ');
3309
136
    appendFixedInt(rRectangle.Top(), aLine);
3310
136
    aLine.append(' ');
3311
136
    appendFixedInt(rRectangle.Right(), aLine);
3312
136
    aLine.append(' ');
3313
136
    appendFixedInt(rRectangle.Bottom(), aLine);
3314
136
    aLine.append("] ");
3315
136
}
3316
3317
void appendAnnotationColor(Color const& rColor, OStringBuffer & aLine)
3318
0
{
3319
0
    aLine.append("/C [");
3320
0
    appendColor(rColor, aLine, false);
3321
0
    aLine.append("] ");
3322
0
}
3323
3324
void appendAnnotationInteriorColor(Color const& rColor, OStringBuffer & aLine)
3325
0
{
3326
0
    aLine.append("/IC [");
3327
0
    appendColor(rColor, aLine, false);
3328
0
    aLine.append("] ");
3329
0
}
3330
3331
void appendPolygon(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3332
0
{
3333
0
    for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
3334
0
    {
3335
0
        appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
3336
0
        aLine.append(" ");
3337
0
        appendDouble(fPageHeight - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), aLine, nLog10Divisor);
3338
0
        aLine.append(" ");
3339
0
    }
3340
0
}
3341
3342
void appendVertices(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3343
0
{
3344
0
    aLine.append("/Vertices [");
3345
0
    appendPolygon(rPolygon, fPageHeight, aLine);
3346
0
    aLine.append("] ");
3347
3348
0
}
3349
3350
void appendAnnotationBorder(float fBorderWidth, OStringBuffer & aLine)
3351
0
{
3352
0
    aLine.append("/Border [0 0 ");
3353
0
    appendDouble(convertMm100ToPoint(fBorderWidth), aLine, nLog10Divisor);
3354
0
    aLine.append("] ");
3355
0
}
3356
3357
} // end anonymous namespace
3358
3359
void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3360
136
{
3361
136
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3362
3363
136
    appendObjectID(rNote.m_nObject, aLine);
3364
3365
136
    double fPageHeight = m_aPages[rNote.m_nPage].getHeight();
3366
3367
136
    aLine.append("<</Type /Annot ");
3368
3369
136
    appendAnnotationRect(rNote.m_aRect, aLine);
3370
3371
136
    aLine.append("/Subtype ");
3372
3373
136
    if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon ||
3374
136
        rNote.m_aContents.meType == PDFAnnotationSubType::Polyline)
3375
0
    {
3376
0
        auto const& rPolygon = rNote.m_aContents.maPolygons[0];
3377
0
        if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon)
3378
0
            aLine.append("/Polygon ");
3379
0
        else
3380
0
            aLine.append("/Polyline ");
3381
3382
0
        appendVertices(rPolygon, fPageHeight, aLine);
3383
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3384
3385
0
        if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon)
3386
0
            appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3387
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3388
0
    }
3389
136
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::Square)
3390
0
    {
3391
0
        aLine.append("/Square ");
3392
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3393
0
        appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3394
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3395
0
    }
3396
136
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::Circle)
3397
0
    {
3398
0
        aLine.append("/Circle ");
3399
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3400
0
        appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3401
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3402
0
    }
3403
136
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::Ink)
3404
0
    {
3405
0
        aLine.append("/Ink ");
3406
3407
0
        aLine.append("/InkList [");
3408
0
        for (auto const& rPolygon : rNote.m_aContents.maPolygons)
3409
0
        {
3410
0
            aLine.append("[");
3411
0
            appendPolygon(rPolygon, fPageHeight, aLine);
3412
0
            aLine.append("]");
3413
0
        }
3414
0
        aLine.append("] ");
3415
3416
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3417
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3418
3419
0
    }
3420
136
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::FreeText)
3421
0
    {
3422
0
        aLine.append("/FreeText ");
3423
0
    }
3424
136
    else
3425
136
    {
3426
136
        aLine.append("/Text ");
3427
136
    }
3428
3429
    // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3430
    // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3431
136
    if (m_nPDFA_Version > 0)
3432
0
        aLine.append("/F 4 ");
3433
3434
136
    aLine.append("/Popup ");
3435
136
    appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3436
3437
136
    auto & rDateTime = rNote.m_aContents.maModificationDate;
3438
3439
136
    OStringBuffer aDate;
3440
136
    appendPdfTimeDate(aDate, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours,
3441
136
                      rDateTime.Minutes, rDateTime.Seconds, 0);
3442
136
    aWriter.writeKeyAndLiteralEncrypt("/M", aDate, rNote.m_nObject);
3443
3444
    // contents of the note (type text string)
3445
136
    aLine.append("/Contents ");
3446
136
    aWriter.writeUnicodeEncrypt(rNote.m_aContents.maContents, rNote.m_nObject);
3447
136
    aLine.append("\n");
3448
3449
    // optional title
3450
136
    if (!rNote.m_aContents.maTitle.isEmpty())
3451
136
    {
3452
136
        aLine.append("/T ");
3453
136
        aWriter.writeUnicodeEncrypt(rNote.m_aContents.maTitle, rNote.m_nObject);
3454
136
        aLine.append("\n");
3455
136
    }
3456
3457
136
    if (-1 != rNote.m_nStructParent)
3458
0
    {
3459
0
        aLine.append("/StructParent ");
3460
0
        aLine.append(rNote.m_nStructParent);
3461
0
        aLine.append("\n");
3462
0
    }
3463
3464
136
    aLine.append(">>\n");
3465
136
    aLine.append("endobj\n\n");
3466
136
}
3467
3468
void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
3469
136
{
3470
136
    appendObjectID(rPopUp.m_nObject, aLine);
3471
136
    aLine.append("<</Type /Annot /Subtype /Popup ");
3472
136
    aLine.append("/Rect[");
3473
136
    appendFixedInt(rPopUp.m_aRect.Left(), aLine);
3474
136
    aLine.append(' ');
3475
136
    appendFixedInt(rPopUp.m_aRect.Top(), aLine);
3476
136
    aLine.append(' ');
3477
136
    appendFixedInt(rPopUp.m_aRect.Right(), aLine);
3478
136
    aLine.append(' ');
3479
136
    appendFixedInt(rPopUp.m_aRect.Bottom(), aLine);
3480
136
    aLine.append("]");
3481
136
    aLine.append("/Parent ");
3482
136
    appendObjectReference(rPopUp.m_nParentObject, aLine);
3483
136
    aLine.append(">>\n");
3484
136
    aLine.append("endobj\n\n");
3485
136
}
3486
3487
bool PDFWriterImpl::emitNoteAnnotations()
3488
18.6k
{
3489
    // emit note annotations
3490
18.6k
    int nAnnots = m_aNotes.size();
3491
18.7k
    for( int i = 0; i < nAnnots; i++ )
3492
136
    {
3493
136
        const PDFNoteEntry& rNote = m_aNotes[i];
3494
136
        const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
3495
3496
136
        {
3497
136
            if (!updateObject(rNote.m_nObject))
3498
0
                return false;
3499
3500
136
            OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
3501
3502
136
            emitTextAnnotationLine(aLine, rNote);
3503
3504
136
            if (!writeBuffer(aLine))
3505
0
                return false;
3506
136
        }
3507
3508
136
        {
3509
3510
136
            if (!updateObject(rPopUp.m_nObject))
3511
0
                return false;
3512
3513
136
            OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
3514
3515
136
            emitPopupAnnotationLine(aLine, rPopUp);
3516
3517
136
            if (!writeBuffer(aLine))
3518
0
                return false;
3519
136
        }
3520
136
    }
3521
18.6k
    return true;
3522
18.6k
}
3523
3524
Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font&  rAppSetFont )
3525
0
{
3526
0
    bool bAdjustSize = false;
3527
3528
0
    Font aFont( rControlFont );
3529
0
    if( aFont.GetFamilyName().isEmpty() )
3530
0
    {
3531
0
        aFont = rAppSetFont;
3532
0
        if( rControlFont.GetFontHeight() )
3533
0
            aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
3534
0
        else
3535
0
            bAdjustSize = true;
3536
0
        if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
3537
0
            aFont.SetItalic( rControlFont.GetItalic() );
3538
0
        if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
3539
0
            aFont.SetWeight( rControlFont.GetWeight() );
3540
0
    }
3541
0
    else if( ! aFont.GetFontHeight() )
3542
0
    {
3543
0
        aFont.SetFontSize( rAppSetFont.GetFontSize() );
3544
0
        bAdjustSize = true;
3545
0
    }
3546
0
    if( bAdjustSize )
3547
0
    {
3548
0
        Size aFontSize = aFont.GetFontSize();
3549
0
        OutputDevice* pDefDev = Application::GetDefaultDevice();
3550
0
        aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
3551
0
        aFont.SetFontSize( aFontSize );
3552
0
    }
3553
0
    return aFont;
3554
0
}
3555
3556
sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
3557
0
{
3558
0
    sal_Int32 nBest = 4; // default to Helvetica
3559
3560
0
    if (rFont.GetFamilyType() == FAMILY_ROMAN)
3561
0
    {
3562
        // Serif: default to Times-Roman.
3563
0
        nBest = 8;
3564
0
    }
3565
3566
0
    OUString aFontName( rFont.GetFamilyName() );
3567
0
    aFontName = aFontName.toAsciiLowerCase();
3568
3569
0
    if( aFontName.indexOf( "times" ) != -1 )
3570
0
        nBest = 8;
3571
0
    else if( aFontName.indexOf( "courier" ) != -1 )
3572
0
        nBest = 0;
3573
0
    else if( aFontName.indexOf( "dingbats" ) != -1 )
3574
0
        nBest = 13;
3575
0
    else if( aFontName.indexOf( "symbol" ) != -1 )
3576
0
        nBest = 12;
3577
0
    if( nBest < 12 )
3578
0
    {
3579
0
        if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
3580
0
            nBest += 1;
3581
0
        if( rFont.GetWeight() > WEIGHT_MEDIUM )
3582
0
            nBest += 2;
3583
0
    }
3584
3585
0
    if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
3586
0
        m_aBuildinFontToObjectMap[ nBest ] = createObject();
3587
3588
0
    return nBest;
3589
0
}
3590
3591
static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
3592
0
{
3593
0
    return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
3594
0
}
3595
3596
void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
3597
0
{
3598
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3599
3600
    // save graphics state
3601
0
    push( PushFlags::ALL );
3602
3603
    // transform relative to control's coordinates since an
3604
    // appearance stream is a form XObject
3605
    // this relies on the m_aRect member of rButton NOT already being transformed
3606
    // to default user space
3607
0
    if( rWidget.Background || rWidget.Border )
3608
0
    {
3609
0
        setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
3610
0
        setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
3611
0
        drawRectangle( rWidget.Location );
3612
0
    }
3613
    // prepare font to use
3614
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
3615
0
    setFont( aFont );
3616
0
    setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
3617
3618
0
    drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
3619
3620
    // create DA string while local mapmode is still in place
3621
    // (that is before endRedirect())
3622
0
    OStringBuffer aDA( 256 );
3623
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
3624
0
    Font aDummyFont( u"Helvetica"_ustr, aFont.GetFontSize() );
3625
0
    sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
3626
0
    aDA.append( ' ' );
3627
0
    aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
3628
0
    aDA.append( ' ' );
3629
0
    m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3630
0
    aDA.append( " Tf" );
3631
0
    rButton.m_aDAString = aDA.makeStringAndClear();
3632
3633
0
    pop();
3634
3635
0
    rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
3636
3637
    /* seems like a bad hack but at least works in both AR5 and 6:
3638
       we draw the button ourselves and tell AR
3639
       the button would be totally transparent with no text
3640
3641
       One would expect that simply setting a normal appearance
3642
       should suffice, but no, as soon as the user actually presses
3643
       the button and an action is tied to it (gasp! a button that
3644
       does something) the appearance gets replaced by some crap that AR
3645
       creates on the fly even if no DA or MK is given. On AR6 at least
3646
       the DA and MK work as expected, but on AR5 this creates a region
3647
       filled with the background color but nor text. Urgh.
3648
    */
3649
0
    rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr;
3650
0
    rButton.m_aMKDictCAString = ""_ostr;
3651
0
}
3652
3653
Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
3654
                                     const PDFWriter::AnyWidget& rWidget,
3655
                                     const StyleSettings& rSettings )
3656
0
{
3657
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
3658
3659
0
    if( rWidget.Background || rWidget.Border )
3660
0
    {
3661
0
        if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
3662
0
        {
3663
0
            sal_Int32 nDelta = GetDPIX() / 500;
3664
0
            if( nDelta < 1 )
3665
0
                nDelta = 1;
3666
0
            setLineColor( COL_TRANSPARENT );
3667
0
            tools::Rectangle aRect = rIntern.m_aRect;
3668
0
            setFillColor( rSettings.GetLightBorderColor() );
3669
0
            drawRectangle( aRect );
3670
0
            aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
3671
0
            aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
3672
0
            setFillColor( rSettings.GetFieldColor() );
3673
0
            drawRectangle( aRect );
3674
0
            setFillColor( rSettings.GetLightColor() );
3675
0
            drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
3676
0
            drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
3677
0
            setFillColor( rSettings.GetDarkShadowColor() );
3678
0
            drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
3679
0
            drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
3680
0
        }
3681
0
        else
3682
0
        {
3683
0
            setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
3684
0
            setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3685
0
            drawRectangle( rIntern.m_aRect );
3686
0
        }
3687
3688
0
        if( rWidget.Border )
3689
0
        {
3690
            // adjust edit area accounting for border
3691
0
            sal_Int32 nDelta = aFont.GetFontHeight()/4;
3692
0
            if( nDelta < 1 )
3693
0
                nDelta = 1;
3694
0
            rIntern.m_aRect.AdjustLeft(nDelta );
3695
0
            rIntern.m_aRect.AdjustTop(nDelta );
3696
0
            rIntern.m_aRect.AdjustRight( -nDelta );
3697
0
            rIntern.m_aRect.AdjustBottom( -nDelta );
3698
0
        }
3699
0
    }
3700
0
    return aFont;
3701
0
}
3702
3703
void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
3704
0
{
3705
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3706
0
    SvMemoryStream* pEditStream = new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE );
3707
3708
0
    push( PushFlags::ALL );
3709
3710
    // prepare font to use, draw field border
3711
0
    Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
3712
    // Get the built-in font which is closest to aFont.
3713
0
    sal_Int32 nBest = getBestBuildinFont(aFont);
3714
3715
    // prepare DA string
3716
0
    OStringBuffer aDA( 32 );
3717
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3718
0
    aDA.append( ' ' );
3719
0
    aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
3720
3721
0
    OStringBuffer aDR( 32 );
3722
0
    aDR.append( "/Font " );
3723
0
    aDR.append( getFontDictObject() );
3724
0
    aDR.append( " 0 R" );
3725
0
    rEdit.m_aDRDict = aDR.makeStringAndClear();
3726
0
    aDA.append( ' ' );
3727
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3728
0
    aDA.append( " Tf" );
3729
3730
    /*  create an empty appearance stream, let the viewer create
3731
        the appearance at runtime. This is because AR5 seems to
3732
        paint the widget appearance always, and a dynamically created
3733
        appearance on top of it. AR6 is well behaved in that regard, so
3734
        that behaviour seems to be a bug. Anyway this empty appearance
3735
        relies on /NeedAppearances in the AcroForm dictionary set to "true"
3736
     */
3737
0
    beginRedirect( pEditStream, rEdit.m_aRect );
3738
0
    writeBuffer( "/Tx BMC\nEMC\n" );
3739
3740
0
    endRedirect();
3741
0
    pop();
3742
3743
0
    rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream;
3744
3745
0
    rEdit.m_aDAString = aDA.makeStringAndClear();
3746
0
}
3747
3748
void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
3749
0
{
3750
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3751
0
    SvMemoryStream* pListBoxStream = new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE );
3752
3753
0
    push( PushFlags::ALL );
3754
3755
    // prepare font to use, draw field border
3756
0
    Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
3757
0
    sal_Int32 nBest = getSystemFont( aFont );
3758
3759
0
    setLineColor( COL_TRANSPARENT );
3760
0
    setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
3761
0
    drawRectangle( rBox.m_aRect );
3762
3763
0
    pop();
3764
3765
    // prepare DA string
3766
0
    OStringBuffer aDA( 256 );
3767
    // prepare DA string
3768
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3769
0
    aDA.append( ' ' );
3770
0
    aDA.append( "/F" );
3771
0
    aDA.append( nBest );
3772
3773
0
    OStringBuffer aDR( 32 );
3774
0
    aDR.append( "/Font " );
3775
0
    aDR.append( getFontDictObject() );
3776
0
    aDR.append( " 0 R" );
3777
0
    rBox.m_aDRDict = aDR.makeStringAndClear();
3778
0
    aDA.append( ' ' );
3779
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3780
0
    aDA.append( " Tf" );
3781
3782
0
    beginRedirect(pListBoxStream, rBox.m_aRect);
3783
    // empty appearance, see createDefaultEditAppearance for reference
3784
0
    writeBuffer("/Tx BMC\nEMC\n");
3785
0
    endRedirect();
3786
3787
0
    rBox.m_aAppearances["N"_ostr]["Standard"_ostr] = pListBoxStream;
3788
0
    rBox.m_aDAString = aDA.makeStringAndClear();
3789
0
}
3790
3791
void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
3792
0
{
3793
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3794
3795
    // save graphics state
3796
0
    push( PushFlags::ALL );
3797
3798
0
    if( rWidget.Background || rWidget.Border )
3799
0
    {
3800
0
        setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3801
0
        setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3802
0
        drawRectangle( rBox.m_aRect );
3803
0
    }
3804
3805
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3806
0
    setFont( aFont );
3807
0
    Size aFontSize = aFont.GetFontSize();
3808
0
    if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3809
0
        aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3810
0
    sal_Int32 nDelta = aFontSize.Height()/10;
3811
0
    if( nDelta < 1 )
3812
0
        nDelta = 1;
3813
3814
0
    tools::Rectangle aCheckRect, aTextRect;
3815
0
    {
3816
0
        aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3817
0
        aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3818
0
        aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3819
0
        aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3820
3821
        // #i74206# handle small controls without text area
3822
0
        while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3823
0
        {
3824
0
            aCheckRect.AdjustRight( -nDelta );
3825
0
            aCheckRect.AdjustTop(nDelta/2 );
3826
0
            aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3827
0
        }
3828
3829
0
        aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3830
0
        aTextRect.SetTop( rBox.m_aRect.Top() );
3831
0
        aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3832
0
        aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3833
0
    }
3834
0
    setLineColor( COL_BLACK );
3835
0
    setFillColor( COL_TRANSPARENT );
3836
0
    OStringBuffer aLW( 32 );
3837
0
    aLW.append( "q " );
3838
0
    m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
3839
0
    aLW.append( " w " );
3840
0
    writeBuffer( aLW );
3841
0
    drawRectangle( aCheckRect );
3842
0
    writeBuffer( " Q\n" );
3843
0
    setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3844
0
    drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3845
3846
0
    pop();
3847
3848
0
    OStringBuffer aDA( 256 );
3849
3850
    // tdf#93853 don't rely on Zapf (or any other 'standard' font)
3851
    // being present, but our own OpenSymbol - N.B. PDF/A for good
3852
    // reasons require even the standard PS fonts to be embedded!
3853
0
    Push();
3854
0
    SetFont( Font( u"OpenSymbol"_ustr, aFont.GetFontSize() ) );
3855
0
    const LogicalFontInstance* pFontInstance = GetFontInstance();
3856
0
    const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
3857
0
    Pop();
3858
3859
    // make sure OpenSymbol is embedded, and includes our checkmark
3860
0
    const sal_Unicode cMark=0x2713;
3861
0
    const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
3862
0
    const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
3863
3864
0
    sal_uInt8 nMappedGlyph;
3865
0
    sal_Int32 nMappedFontObject;
3866
0
    registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject);
3867
3868
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3869
0
    aDA.append( ' ' );
3870
0
    aDA.append( "/F" );
3871
0
    aDA.append( nMappedFontObject );
3872
0
    aDA.append( " 0 Tf" );
3873
3874
0
    OStringBuffer aDR( 32 );
3875
0
    aDR.append( "/Font " );
3876
0
    aDR.append( getFontDictObject() );
3877
0
    aDR.append( " 0 R" );
3878
0
    rBox.m_aDRDict = aDR.makeStringAndClear();
3879
0
    rBox.m_aDAString = aDA.makeStringAndClear();
3880
0
    rBox.m_aMKDict = "/CA"_ostr;
3881
0
    rBox.m_aMKDictCAString = "8"_ostr;
3882
0
    rBox.m_aRect = aCheckRect;
3883
3884
    // create appearance streams
3885
0
    sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
3886
0
    nCharXOffset *= aCheckRect.GetHeight();
3887
0
    nCharXOffset /= 2000;
3888
0
    sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
3889
0
    nCharYOffset *= aCheckRect.GetHeight();
3890
0
    nCharYOffset /= 2000;
3891
3892
    // write 'checked' appearance stream
3893
0
    SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3894
0
    beginRedirect( pCheckStream, aCheckRect );
3895
0
    aDA.append( "/Tx BMC\nq BT\n" );
3896
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3897
0
    aDA.append( ' ' );
3898
0
    aDA.append( "/F" );
3899
0
    aDA.append( nMappedFontObject );
3900
0
    aDA.append( ' ' );
3901
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3902
0
    aDA.append( " Tf\n" );
3903
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
3904
0
    aDA.append( " " );
3905
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
3906
0
    aDA.append( " Td <" );
3907
0
    COSWriter::appendHex( nMappedGlyph, aDA );
3908
0
    aDA.append( "> Tj\nET\nQ\nEMC\n" );
3909
0
    writeBuffer( aDA );
3910
0
    endRedirect();
3911
0
    rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
3912
3913
    // write 'unchecked' appearance stream
3914
0
    SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
3915
0
    beginRedirect( pUncheckStream, aCheckRect );
3916
0
    writeBuffer( "/Tx BMC\nEMC\n" );
3917
0
    endRedirect();
3918
0
    rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
3919
0
}
3920
3921
void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
3922
0
{
3923
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3924
3925
    // save graphics state
3926
0
    push( PushFlags::ALL );
3927
3928
0
    if( rWidget.Background || rWidget.Border )
3929
0
    {
3930
0
        setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3931
0
        setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3932
0
        drawRectangle( rBox.m_aRect );
3933
0
    }
3934
3935
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3936
0
    setFont( aFont );
3937
0
    Size aFontSize = aFont.GetFontSize();
3938
0
    if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3939
0
        aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3940
0
    sal_Int32 nDelta = aFontSize.Height()/10;
3941
0
    if( nDelta < 1 )
3942
0
        nDelta = 1;
3943
3944
0
    tools::Rectangle aCheckRect, aTextRect;
3945
0
    {
3946
0
        aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3947
0
        aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3948
0
        aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3949
0
        aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3950
3951
        // #i74206# handle small controls without text area
3952
0
        while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3953
0
        {
3954
0
            aCheckRect.AdjustRight( -nDelta );
3955
0
            aCheckRect.AdjustTop(nDelta/2 );
3956
0
            aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3957
0
        }
3958
3959
0
        aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3960
0
        aTextRect.SetTop( rBox.m_aRect.Top() );
3961
0
        aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3962
0
        aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3963
0
    }
3964
0
    setLineColor( COL_BLACK );
3965
0
    setFillColor( COL_TRANSPARENT );
3966
0
    OStringBuffer aLW( 32 );
3967
0
    aLW.append( "q " );
3968
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
3969
0
    aLW.append( " w " );
3970
0
    writeBuffer( aLW );
3971
0
    drawEllipse( aCheckRect );
3972
0
    writeBuffer( " Q\n" );
3973
0
    setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3974
0
    drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3975
3976
0
    pop();
3977
3978
    //to encrypt this (el)
3979
0
    rBox.m_aMKDict = "/CA"_ostr;
3980
    //after this assignment, to m_aMKDic cannot be added anything
3981
0
    rBox.m_aMKDictCAString = "l"_ostr;
3982
3983
0
    rBox.m_aRect = aCheckRect;
3984
3985
    // create appearance streams
3986
0
    push( PushFlags::ALL);
3987
0
    SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3988
3989
0
    beginRedirect( pCheckStream, aCheckRect );
3990
0
    OStringBuffer aDA( 256 );
3991
0
    aDA.append( "/Tx BMC\nq BT\n" );
3992
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3993
0
    aDA.append( ' ' );
3994
0
    m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3995
0
    aDA.append( " 0 0 Td\nET\nQ\n" );
3996
0
    writeBuffer( aDA );
3997
0
    setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3998
0
    setLineColor( COL_TRANSPARENT );
3999
0
    aCheckRect.AdjustLeft(3*nDelta );
4000
0
    aCheckRect.AdjustTop(3*nDelta );
4001
0
    aCheckRect.AdjustBottom( -(3*nDelta) );
4002
0
    aCheckRect.AdjustRight( -(3*nDelta) );
4003
0
    drawEllipse( aCheckRect );
4004
0
    writeBuffer( "\nEMC\n" );
4005
0
    endRedirect();
4006
4007
0
    pop();
4008
0
    rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
4009
4010
0
    SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4011
0
    beginRedirect( pUncheckStream, aCheckRect );
4012
0
    writeBuffer( "/Tx BMC\nEMC\n" );
4013
0
    endRedirect();
4014
0
    rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
4015
0
}
4016
4017
bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4018
0
{
4019
    // TODO: check and insert default streams
4020
0
    OString aStandardAppearance;
4021
0
    switch( rWidget.m_eType )
4022
0
    {
4023
0
        case PDFWriter::CheckBox:
4024
0
            aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4025
0
            break;
4026
0
        default:
4027
0
            break;
4028
0
    }
4029
4030
0
    if( !rWidget.m_aAppearances.empty() )
4031
0
    {
4032
0
        rAnnotDict.append( "/AP<<\n" );
4033
0
        for (auto & dict_item : rWidget.m_aAppearances)
4034
0
        {
4035
0
            rAnnotDict.append( "/" );
4036
0
            rAnnotDict.append( dict_item.first );
4037
0
            bool bUseSubDict = (dict_item.second.size() > 1);
4038
4039
            // PDF/A requires sub-dicts for /FT/Btn objects (clause
4040
            // 6.3.3)
4041
0
            if (m_nPDFA_Version > 0)
4042
0
            {
4043
0
                if( rWidget.m_eType == PDFWriter::RadioButton ||
4044
0
                    rWidget.m_eType == PDFWriter::CheckBox ||
4045
0
                    rWidget.m_eType == PDFWriter::PushButton )
4046
0
                {
4047
0
                    bUseSubDict = true;
4048
0
                }
4049
0
            }
4050
4051
0
            rAnnotDict.append( bUseSubDict ? "<<" : " " );
4052
4053
0
            for (auto const& stream_item : dict_item.second)
4054
0
            {
4055
0
                SvMemoryStream* pAppearanceStream = stream_item.second;
4056
0
                dict_item.second[ stream_item.first ] = nullptr;
4057
4058
0
                bool bDeflate = compressStream( pAppearanceStream );
4059
4060
0
                sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
4061
0
                pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4062
0
                sal_Int32 nObject = createObject();
4063
0
                if (!updateObject(nObject))
4064
0
                    return false;
4065
0
                if (g_bDebugDisableCompression)
4066
0
                {
4067
0
                    emitComment( "PDFWriterImpl::emitAppearances" );
4068
0
                }
4069
0
                OStringBuffer aLine;
4070
0
                aLine.append( nObject );
4071
4072
0
                aLine.append( " 0 obj\n"
4073
0
                              "<</Type/XObject\n"
4074
0
                              "/Subtype/Form\n"
4075
0
                              "/BBox[0 0 " );
4076
0
                appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4077
0
                aLine.append( " " );
4078
0
                appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4079
0
                aLine.append( "]\n"
4080
0
                              "/Resources " );
4081
0
                aLine.append( getResourceDictObj() );
4082
0
                aLine.append( " 0 R\n"
4083
0
                              "/Length " );
4084
0
                aLine.append(static_cast<sal_Int64>(calculateStreamSize(nStreamLen)));
4085
0
                aLine.append( "\n" );
4086
0
                if( bDeflate )
4087
0
                    aLine.append( "/Filter/FlateDecode\n" );
4088
0
                aLine.append( ">>\nstream\n" );
4089
0
                if (!writeBuffer(aLine)) return false;
4090
0
                checkAndEnableStreamEncryption( nObject );
4091
0
                if (!writeBufferBytes( pAppearanceStream->GetData(), nStreamLen)) return false;
4092
0
                disableStreamEncryption();
4093
0
                if (!writeBuffer("\nendstream\nendobj\n\n")) return false;
4094
4095
0
                if( bUseSubDict )
4096
0
                {
4097
0
                    rAnnotDict.append( " /" );
4098
0
                    rAnnotDict.append( stream_item.first );
4099
0
                    rAnnotDict.append( " " );
4100
0
                }
4101
0
                rAnnotDict.append( nObject );
4102
0
                rAnnotDict.append( " 0 R" );
4103
4104
0
                delete pAppearanceStream;
4105
0
            }
4106
4107
0
            rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4108
0
        }
4109
0
        rAnnotDict.append( ">>\n" );
4110
0
        if( !aStandardAppearance.isEmpty() )
4111
0
        {
4112
0
            rAnnotDict.append( "/AS /" );
4113
0
            rAnnotDict.append( aStandardAppearance );
4114
0
            rAnnotDict.append( "\n" );
4115
0
        }
4116
0
    }
4117
4118
0
    return true;
4119
0
}
4120
4121
bool PDFWriterImpl::emitWidgetAnnotations()
4122
18.6k
{
4123
18.6k
    ensureUniqueRadioOnValues();
4124
4125
18.6k
    int nAnnots = m_aWidgets.size();
4126
18.6k
    for( int a = 0; a < nAnnots; a++ )
4127
0
    {
4128
0
        PDFWidget& rWidget = m_aWidgets[a];
4129
4130
0
        if( rWidget.m_eType == PDFWriter::CheckBox )
4131
0
        {
4132
0
            if ( !rWidget.m_aOnValue.isEmpty() )
4133
0
            {
4134
0
                auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4135
0
                if( app_it != rWidget.m_aAppearances.end() )
4136
0
                {
4137
0
                    auto stream_it = app_it->second.find( "Yes"_ostr );
4138
0
                    if( stream_it != app_it->second.end() )
4139
0
                    {
4140
0
                        SvMemoryStream* pStream = stream_it->second;
4141
0
                        app_it->second.erase( stream_it );
4142
0
                        OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
4143
0
                        COSWriter::appendName( rWidget.m_aOnValue, aBuf );
4144
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4145
0
                    }
4146
0
                    else
4147
0
                        SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
4148
0
                }
4149
0
            }
4150
4151
0
            if ( !rWidget.m_aOffValue.isEmpty() )
4152
0
            {
4153
0
                auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4154
0
                if( app_it != rWidget.m_aAppearances.end() )
4155
0
                {
4156
0
                    auto stream_it = app_it->second.find( "Off"_ostr );
4157
0
                    if( stream_it != app_it->second.end() )
4158
0
                    {
4159
0
                        SvMemoryStream* pStream = stream_it->second;
4160
0
                        app_it->second.erase( stream_it );
4161
0
                        OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
4162
0
                        COSWriter::appendName( rWidget.m_aOffValue, aBuf );
4163
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4164
0
                    }
4165
0
                    else
4166
0
                        SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
4167
0
                }
4168
0
            }
4169
0
        }
4170
4171
0
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
4172
0
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4173
0
        OStringBuffer aValue( 256 );
4174
0
        COSWriter aValueWriter(aValue, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4175
0
        aLine.append( rWidget.m_nObject );
4176
0
        aLine.append( " 0 obj\n"
4177
0
                      "<<" );
4178
0
        if( rWidget.m_eType != PDFWriter::Hierarchy )
4179
0
        {
4180
            // emit widget annotation only for terminal fields
4181
0
            if( rWidget.m_aKids.empty() )
4182
0
            {
4183
0
                int iRectMargin;
4184
4185
0
                aLine.append( "/Type/Annot/Subtype/Widget/F " );
4186
4187
0
                if (rWidget.m_eType == PDFWriter::Signature)
4188
0
                {
4189
0
                    aLine.append( "132\n" ); // Print & Locked
4190
0
                    iRectMargin = 0;
4191
0
                }
4192
0
                else
4193
0
                {
4194
0
                    aLine.append( "4\n" );
4195
0
                    iRectMargin = 1;
4196
0
                }
4197
4198
0
                if (-1 != rWidget.m_nStructParent)
4199
0
                {
4200
0
                    aLine.append("/StructParent ");
4201
0
                    aLine.append(rWidget.m_nStructParent);
4202
0
                    aLine.append("\n");
4203
0
                }
4204
4205
0
                aLine.append("/Rect[" );
4206
0
                appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4207
0
                aLine.append( ' ' );
4208
0
                appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4209
0
                aLine.append( ' ' );
4210
0
                appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4211
0
                aLine.append( ' ' );
4212
0
                appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4213
0
                aLine.append( "]\n" );
4214
0
            }
4215
0
            aLine.append( "/FT/" );
4216
0
            switch( rWidget.m_eType )
4217
0
            {
4218
0
                case PDFWriter::RadioButton:
4219
0
                case PDFWriter::CheckBox:
4220
                    // for radio buttons only the RadioButton field, not the
4221
                    // CheckBox children should have a value, else acrobat reader
4222
                    // does not always check the right button
4223
                    // of course real check boxes (not belonging to a radio group)
4224
                    // need their values, too
4225
0
                    if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4226
0
                    {
4227
0
                        aValue.append( "/" );
4228
                        // check for radio group with all buttons unpressed
4229
0
                        if( rWidget.m_aValue.isEmpty() )
4230
0
                            aValue.append( "Off" );
4231
0
                        else
4232
0
                            COSWriter::appendName( rWidget.m_aValue, aValue );
4233
0
                    }
4234
0
                    [[fallthrough]];
4235
0
                case PDFWriter::PushButton:
4236
0
                    aLine.append( "Btn" );
4237
0
                    break;
4238
0
                case PDFWriter::ListBox:
4239
0
                    if( rWidget.m_nFlags & 0x200000 ) // multiselect
4240
0
                    {
4241
0
                        aValue.append( "[" );
4242
0
                        for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4243
0
                        {
4244
0
                            sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4245
0
                            if( nEntry >= 0
4246
0
                                && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
4247
0
                            {
4248
0
                                aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject);
4249
0
                            }
4250
0
                        }
4251
0
                        aValue.append( "]" );
4252
0
                    }
4253
0
                    else if( !rWidget.m_aSelectedEntries.empty() &&
4254
0
                             rWidget.m_aSelectedEntries[0] >= 0 &&
4255
0
                             o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
4256
0
                    {
4257
0
                        aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject);
4258
0
                    }
4259
0
                    else
4260
0
                        aValueWriter.writeUnicodeEncrypt( OUString(), rWidget.m_nObject);
4261
0
                    aLine.append( "Ch" );
4262
0
                    break;
4263
0
                case PDFWriter::ComboBox:
4264
0
                    aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4265
0
                    aLine.append( "Ch" );
4266
0
                    break;
4267
0
                case PDFWriter::Edit:
4268
0
                    aLine.append( "Tx" );
4269
0
                    aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4270
0
                    break;
4271
0
                case PDFWriter::Signature:
4272
0
                    aLine.append( "Sig" );
4273
0
                    aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4274
0
                    break;
4275
0
                case PDFWriter::Hierarchy: // make the compiler happy
4276
0
                    break;
4277
0
            }
4278
0
            aLine.append( "\n" );
4279
0
            aLine.append( "/P " );
4280
0
            aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4281
0
            aLine.append( " 0 R\n" );
4282
0
        }
4283
0
        if( rWidget.m_nParent )
4284
0
        {
4285
0
            aLine.append( "/Parent " );
4286
0
            aLine.append( rWidget.m_nParent );
4287
0
            aLine.append( " 0 R\n" );
4288
0
        }
4289
0
        if( !rWidget.m_aKids.empty() )
4290
0
        {
4291
0
            aLine.append( "/Kids[" );
4292
0
            for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4293
0
            {
4294
0
                aLine.append( rWidget.m_aKids[i] );
4295
0
                aLine.append( " 0 R" );
4296
0
                aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4297
0
            }
4298
0
            aLine.append( "]\n" );
4299
0
        }
4300
0
        if( !rWidget.m_aName.isEmpty() )
4301
0
        {
4302
0
            aLine.append( "/T" );
4303
0
            aWriter.writeLiteralEncrypt(rWidget.m_aName, rWidget.m_nObject);
4304
0
            aLine.append( "\n" );
4305
0
        }
4306
0
        if (!rWidget.m_aDescription.isEmpty())
4307
0
        {
4308
            // the alternate field name should be unicode able since it is
4309
            // supposed to be used in UI
4310
0
            aLine.append( "/TU" );
4311
0
            aWriter.writeUnicodeEncrypt(rWidget.m_aDescription, rWidget.m_nObject);
4312
0
            aLine.append( "\n" );
4313
0
        }
4314
4315
0
        if( rWidget.m_nFlags )
4316
0
        {
4317
0
            aLine.append( "/Ff " );
4318
0
            aLine.append( rWidget.m_nFlags );
4319
0
            aLine.append( "\n" );
4320
0
        }
4321
0
        if( !aValue.isEmpty() )
4322
0
        {
4323
0
            OString aVal = aValue.makeStringAndClear();
4324
0
            aLine.append( "/V " );
4325
0
            aLine.append( aVal );
4326
0
            aLine.append( "\n"
4327
0
                          "/DV " );
4328
0
            aLine.append( aVal );
4329
0
            aLine.append( "\n" );
4330
0
        }
4331
0
        if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4332
0
        {
4333
0
            sal_Int32 nTI = -1;
4334
0
            aLine.append( "/Opt[\n" );
4335
0
            sal_Int32 i = 0;
4336
0
            for (auto const& entry : rWidget.m_aListEntries)
4337
0
            {
4338
0
                aWriter.writeUnicodeEncrypt(entry, rWidget.m_nObject);
4339
0
                aLine.append( "\n" );
4340
0
                if( entry == rWidget.m_aValue )
4341
0
                    nTI = i;
4342
0
                ++i;
4343
0
            }
4344
0
            aLine.append( "]\n" );
4345
0
            if( nTI > 0 )
4346
0
            {
4347
0
                aLine.append( "/TI " );
4348
0
                aLine.append( nTI );
4349
0
                aLine.append( "\n" );
4350
0
                if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4351
0
                {
4352
0
                    aLine.append( "/I [" );
4353
0
                    aLine.append( nTI );
4354
0
                    aLine.append( "]\n" );
4355
0
                }
4356
0
            }
4357
0
        }
4358
0
        if( rWidget.m_eType == PDFWriter::Edit )
4359
0
        {
4360
0
            if ( rWidget.m_nMaxLen > 0 )
4361
0
            {
4362
0
                aLine.append( "/MaxLen " );
4363
0
                aLine.append( rWidget.m_nMaxLen );
4364
0
                aLine.append( "\n" );
4365
0
            }
4366
4367
0
            if ( rWidget.m_nFormat == PDFWriter::Number )
4368
0
            {
4369
0
                OString aHexText;
4370
4371
0
                if ( !rWidget.m_aCurrencySymbol.isEmpty() )
4372
0
                {
4373
                    // Get the hexadecimal code
4374
0
                    sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
4375
0
                    aHexText = "\\u" + OString::number(cChar, 16);
4376
0
                }
4377
4378
0
                const OString aArgs = OString::number(rWidget.m_nDecimalAccuracy) + ", 0, 0, 0, \""
4379
0
                                      + aHexText + "\","
4380
0
                                      + OString::boolean(rWidget.m_bPrependCurrencySymbol) + ");";
4381
0
                aLine.append("/AA<<\n");
4382
0
                aLine.append("/F<<");
4383
0
                const OString aFormatJS = "AFNumber_Format(" + aArgs;
4384
0
                aWriter.writeKeyAndLiteralEncrypt("/JS", aFormatJS, rWidget.m_nObject);
4385
0
                aLine.append("/S/JavaScript>>\n");
4386
0
                aLine.append("/K<<");
4387
0
                const OString aKeystrokeJS = "AFNumber_Keystroke(" + aArgs;
4388
0
                aWriter.writeKeyAndLiteralEncrypt("/JS", aKeystrokeJS, rWidget.m_nObject);
4389
0
                aLine.append("/S/JavaScript>>\n");
4390
0
                aLine.append(">>\n");
4391
0
            }
4392
0
            else if ( rWidget.m_nFormat == PDFWriter::Time )
4393
0
            {
4394
0
                const OString aArg
4395
0
                    = "(\"" + OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)
4396
0
                      + "\");";
4397
0
                aLine.append("/AA<<\n");
4398
0
                aLine.append("/F<<");
4399
0
                const OString aFormatJS = "AFTime_FormatEx" + aArg;
4400
0
                aWriter.writeKeyAndLiteralEncrypt("/JS", aFormatJS, rWidget.m_nObject);
4401
0
                aLine.append("/S/JavaScript>>\n");
4402
0
                aLine.append("/K<<");
4403
0
                const OString aKeystrokeJS = "AFTime_KeystrokeEx" + aArg;
4404
0
                aWriter.writeKeyAndLiteralEncrypt("/JS", aKeystrokeJS, rWidget.m_nObject);
4405
0
                aLine.append("/S/JavaScript>>\n");
4406
0
                aLine.append(">>\n");
4407
0
            }
4408
0
            else if ( rWidget.m_nFormat == PDFWriter::Date )
4409
0
            {
4410
0
                const OString aArg
4411
0
                    = "(\"" + OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)
4412
0
                      + "\");";
4413
0
                aLine.append("/AA<<\n");
4414
0
                aLine.append("/F<<");
4415
0
                const OString aFormatJS = "AFDate_FormatEx" + aArg;
4416
0
                aWriter.writeKeyAndLiteralEncrypt("/JS", aFormatJS, rWidget.m_nObject);
4417
0
                aLine.append("/S/JavaScript>>\n");
4418
0
                aLine.append("/K<<");
4419
0
                const OString aKeystrokeJS = "AFDate_KeystrokeEx" + aArg;
4420
0
                aWriter.writeKeyAndLiteralEncrypt("/JS", aKeystrokeJS, rWidget.m_nObject);
4421
0
                aLine.append("/S/JavaScript>>\n");
4422
0
                aLine.append(">>\n");
4423
0
            }
4424
0
        }
4425
0
        if( rWidget.m_eType == PDFWriter::PushButton )
4426
0
        {
4427
0
            if (!m_bIsPDF_A1)
4428
0
            {
4429
0
                OStringBuffer aDest;
4430
0
                if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4431
0
                {
4432
0
                    aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4433
0
                    aLine.append( aDest );
4434
0
                    aLine.append( ">>>>\n" );
4435
0
                }
4436
0
                else if( rWidget.m_aListEntries.empty() )
4437
0
                {
4438
0
                    if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4439
0
                    {
4440
                        // create a reset form action
4441
0
                        aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4442
0
                    }
4443
0
                }
4444
0
                else if( rWidget.m_bSubmit )
4445
0
                {
4446
                    // create a submit form action
4447
0
                    aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4448
0
                    aWriter.writeLiteralEncrypt(rWidget.m_aListEntries.front(), rWidget.m_nObject, osl_getThreadTextEncoding());
4449
0
                    aLine.append( "/Flags " );
4450
4451
0
                    sal_Int32 nFlags = 0;
4452
0
                    switch( m_aContext.SubmitFormat )
4453
0
                    {
4454
0
                    case PDFWriter::HTML:
4455
0
                        nFlags |= 4;
4456
0
                        break;
4457
0
                    case PDFWriter::XML:
4458
0
                        nFlags |= 32;
4459
0
                        break;
4460
0
                    case PDFWriter::PDF:
4461
0
                        nFlags |= 256;
4462
0
                        break;
4463
0
                    case PDFWriter::FDF:
4464
0
                    default:
4465
0
                        break;
4466
0
                    }
4467
0
                    if( rWidget.m_bSubmitGet )
4468
0
                        nFlags |= 8;
4469
0
                    aLine.append( nFlags );
4470
0
                    aLine.append( ">>>>\n" );
4471
0
                }
4472
0
                else
4473
0
                {
4474
                    // create a URI action
4475
0
                    aLine.append("/AA<</D<</Type/Action/S/URI/URI");
4476
0
                    aWriter.writeLiteralEncrypt(rWidget.m_aListEntries.front(), rWidget.m_nObject,
4477
0
                                                RTL_TEXTENCODING_ASCII_US);
4478
0
                    aLine.append(">>>>\n");
4479
0
                }
4480
0
            }
4481
0
            else
4482
0
                m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4483
0
        }
4484
0
        if( !rWidget.m_aDAString.isEmpty() )
4485
0
        {
4486
0
            if( !rWidget.m_aDRDict.isEmpty() )
4487
0
            {
4488
0
                aLine.append( "/DR<<" );
4489
0
                aLine.append( rWidget.m_aDRDict );
4490
0
                aLine.append( ">>\n" );
4491
0
            }
4492
0
            else
4493
0
            {
4494
0
                aLine.append( "/DR<</Font<<" );
4495
0
                appendBuildinFontsToDict( aLine );
4496
0
                aLine.append( ">>>>\n" );
4497
0
            }
4498
0
            aLine.append( "/DA" );
4499
0
            aWriter.writeLiteralEncrypt(rWidget.m_aDAString, rWidget.m_nObject);
4500
0
            aLine.append( "\n" );
4501
0
            if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4502
0
                aLine.append( "/Q 1\n" );
4503
0
            else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4504
0
                aLine.append( "/Q 2\n" );
4505
0
        }
4506
        // appearance characteristics for terminal fields
4507
        // which are supposed to have an appearance constructed
4508
        // by the viewer application
4509
0
        if( !rWidget.m_aMKDict.isEmpty() )
4510
0
        {
4511
0
            aLine.append( "/MK<<" );
4512
0
            aLine.append( rWidget.m_aMKDict );
4513
            //add the CA string, encrypting it
4514
0
            aWriter.writeLiteralEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject);
4515
0
            aLine.append( ">>\n" );
4516
0
        }
4517
4518
0
        if (!emitAppearances(rWidget, aLine)) return false;
4519
4520
0
        aLine.append( ">>\n"
4521
0
                      "endobj\n\n" );
4522
0
        if (!updateObject(rWidget.m_nObject)) return false;
4523
0
        if (!writeBuffer(aLine)) return false;
4524
0
    }
4525
18.6k
    return true;
4526
18.6k
}
4527
4528
bool PDFWriterImpl::emitAnnotations()
4529
18.6k
{
4530
18.6k
    if( m_aPages.empty() )
4531
0
        return false;
4532
4533
18.6k
    if (!emitLinkAnnotations()) return false;
4534
18.6k
    if (!emitScreenAnnotations()) return false;
4535
18.6k
    if (!emitNoteAnnotations()) return false;
4536
18.6k
    if (!emitWidgetAnnotations()) return false;
4537
4538
18.6k
    return true;
4539
18.6k
}
4540
4541
class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream >
4542
{
4543
    VclPtr<PDFWriterImpl>  m_pWriter;
4544
    bool            m_bWrite;
4545
    public:
4546
0
    explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
4547
4548
    virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override
4549
0
    {
4550
0
        if( m_bWrite && aData.hasElements() )
4551
0
        {
4552
0
            sal_Int32 nBytes = aData.getLength();
4553
0
            (void)m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
4554
0
        }
4555
0
    }
4556
0
    virtual void SAL_CALL flush() override {}
4557
    virtual void SAL_CALL closeOutput() override
4558
0
    {
4559
0
        m_bWrite = false;
4560
0
    }
4561
};
4562
4563
bool PDFWriterImpl::emitEmbeddedFiles()
4564
18.6k
{
4565
18.6k
    for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4566
0
    {
4567
0
        if (!updateObject(rEmbeddedFile.m_nObject))
4568
0
            continue;
4569
4570
0
        sal_Int32 nSizeObject = createObject();
4571
0
        sal_Int32 nParamsObject = createObject();
4572
4573
0
        OStringBuffer aLine;
4574
0
        aLine.append(rEmbeddedFile.m_nObject);
4575
0
        aLine.append(" 0 obj\n");
4576
0
        aLine.append("<< /Type /EmbeddedFile");
4577
0
        if (!rEmbeddedFile.m_aSubType.isEmpty())
4578
0
        {
4579
0
            aLine.append("/Subtype /");
4580
0
            COSWriter::appendName(rEmbeddedFile.m_aSubType, aLine);
4581
0
        }
4582
0
        aLine.append(" /Length ");
4583
0
        appendObjectReference(nSizeObject, aLine);
4584
0
        aLine.append(" /Params ");
4585
0
        appendObjectReference(nParamsObject, aLine);
4586
0
        aLine.append(">>\nstream\n");
4587
0
        if (!writeBuffer(aLine)) return false;
4588
0
        aLine.setLength(0);
4589
4590
0
        sal_Int64 nSize{};
4591
0
        if (!rEmbeddedFile.m_aDataContainer.isEmpty())
4592
0
        {
4593
0
            checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
4594
0
            sal_uInt64 const nStartPos{getCurrentFilePosition()};
4595
0
            if (!writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()))
4596
0
                return false;
4597
0
            disableStreamEncryption();
4598
0
            nSize = sal_Int64(getCurrentFilePosition() - nStartPos);
4599
0
        }
4600
0
        else if (rEmbeddedFile.m_pStream)
4601
0
        {
4602
0
            checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
4603
0
            sal_uInt64 nBegin = getCurrentFilePosition();
4604
0
            css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
4605
0
            rEmbeddedFile.m_pStream->write(xStream);
4606
0
            rEmbeddedFile.m_pStream.reset();
4607
0
            xStream.clear();
4608
0
            nSize = sal_Int64(getCurrentFilePosition() - nBegin);
4609
0
            disableStreamEncryption();
4610
0
        }
4611
0
        aLine.append("\nendstream\nendobj\n\n");
4612
0
        if (!writeBuffer(aLine)) return false;
4613
0
        aLine.setLength(0);
4614
4615
0
        if (!updateObject(nSizeObject))
4616
0
            return false;
4617
0
        aLine.append(nSizeObject);
4618
0
        aLine.append(" 0 obj\n");
4619
0
        aLine.append(nSize);
4620
0
        aLine.append("\nendobj\n\n");
4621
0
        if (!writeBuffer(aLine))
4622
0
            return false;
4623
0
        aLine.setLength(0);
4624
4625
0
        if (!updateObject(nParamsObject))
4626
0
            return false;
4627
0
        aLine.append(nParamsObject);
4628
0
        aLine.append(" 0 obj\n");
4629
0
        aLine.append("<<");
4630
0
        aLine.append("/Size ");
4631
0
        aLine.append(nSize);
4632
0
        aLine.append(">>");
4633
0
        aLine.append("\nendobj\n\n");
4634
0
        if (!writeBuffer(aLine))
4635
0
            return false;
4636
0
    }
4637
18.6k
    return true;
4638
18.6k
}
4639
4640
bool PDFWriterImpl::emitCatalog()
4641
18.6k
{
4642
    // build page tree
4643
    // currently there is only one node that contains all leaves
4644
4645
    // first create a page tree node id
4646
18.6k
    sal_Int32 nTreeNode = createObject();
4647
4648
    // emit global resource dictionary (page emit needs it)
4649
18.6k
    if (!emitResources()) return false;
4650
4651
    // emit all pages
4652
18.6k
    for (auto & page : m_aPages)
4653
96.2k
        if( ! page.emit( nTreeNode ) )
4654
0
            return false;
4655
4656
18.6k
    sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4657
4658
18.6k
    sal_Int32 nOutlineDict = emitOutline();
4659
4660
    // emit Output intent
4661
18.6k
    sal_Int32 nOutputIntentObject = emitOutputIntent();
4662
4663
    // emit metadata
4664
18.6k
    sal_Int32 nMetadataObject = emitDocumentMetadata();
4665
4666
18.6k
    emitNamespaces();
4667
4668
18.6k
    sal_Int32 nStructureDict = 0;
4669
18.6k
    if(m_aStructure.size() > 1)
4670
0
    {
4671
0
        removePlaceholderSE(m_aStructure, m_aStructure[0]);
4672
        // check if dummy structure containers are needed
4673
0
        addInternalStructureContainer(0);
4674
0
        nStructureDict = m_aStructure[0].m_nObject = createObject();
4675
0
        emitStructure( m_aStructure[ 0 ] );
4676
0
        if (!emitObjectStream())
4677
0
            return false;
4678
        // whatever put its strings in plain has to have landed in an object stream; that the
4679
        // stream is encrypted is asserted where it is written
4680
0
        assert(std::ranges::all_of(m_aPlainStringObjects, [this](sal_Int32 nPlain) {
4681
0
            return m_aObjects[nPlain - 1].isCompressed();
4682
0
        }));
4683
0
    }
4684
4685
    // adjust tree node file offset
4686
18.6k
    if( ! updateObject( nTreeNode ) )
4687
0
        return false;
4688
4689
    // emit tree node
4690
18.6k
    OStringBuffer aLine( 2048 );
4691
18.6k
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4692
18.6k
    aLine.append( nTreeNode );
4693
18.6k
    aLine.append( " 0 obj\n" );
4694
18.6k
    aLine.append( "<</Type/Pages\n" );
4695
18.6k
    aLine.append( "/Resources " );
4696
18.6k
    aLine.append( getResourceDictObj() );
4697
18.6k
    aLine.append( " 0 R\n" );
4698
4699
18.6k
    if( m_aPages.empty() ) // sanity check, this should not happen
4700
0
        aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt
4701
4702
18.6k
    aLine.append("/Kids[ ");
4703
18.6k
    unsigned int i = 0;
4704
18.6k
    for (const auto & page : m_aPages)
4705
96.2k
    {
4706
96.2k
        aLine.append( page.m_nPageObject );
4707
96.2k
        aLine.append( " 0 R" );
4708
96.2k
        aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4709
96.2k
        ++i;
4710
96.2k
    }
4711
18.6k
    aLine.append( "]\n"
4712
18.6k
                  "/Count " );
4713
18.6k
    aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
4714
18.6k
    aLine.append( ">>\n"
4715
18.6k
                  "endobj\n\n" );
4716
18.6k
    if (!writeBuffer(aLine)) return false;
4717
4718
    // emit annotation objects
4719
18.6k
    if (!emitAnnotations()) return false;
4720
18.6k
    if (!emitEmbeddedFiles()) return false;
4721
4722
    // emit attached files
4723
18.6k
    for (auto & rAttachedFile : m_aDocumentAttachedFiles)
4724
0
    {
4725
0
        if (!updateObject(rAttachedFile.mnObjectId))
4726
0
            return false;
4727
0
        aLine.setLength(0);
4728
4729
0
        aWriter.startObject(rAttachedFile.mnObjectId);
4730
0
        aWriter.startDict();
4731
0
        aWriter.write("/Type", "/Filespec");
4732
4733
        // Add associated files relationship (since PDF 2.0)
4734
0
        if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
4735
0
        {
4736
0
            aWriter.write("/AFRelationship", "/Source");
4737
0
        }
4738
4739
0
        aWriter.writeKeyAndUnicodeEncrypt("/F", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
4740
0
        if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
4741
0
        {
4742
0
            aWriter.writeKeyAndUnicodeEncrypt("/UF", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
4743
0
        }
4744
0
        if (!rAttachedFile.maDescription.isEmpty())
4745
0
        {
4746
0
            aWriter.writeKeyAndUnicodeEncrypt("/Desc", rAttachedFile.maDescription, rAttachedFile.mnObjectId);
4747
0
        }
4748
0
        aLine.append("/EF");
4749
0
        aWriter.startDict();
4750
0
        aWriter.writeKeyAndReference("/F", rAttachedFile.mnEmbeddedFileObjectId);
4751
0
        aWriter.endDict();
4752
0
        aWriter.endDict();
4753
0
        aWriter.endObject();
4754
0
        if (!writeBuffer(aLine)) return false;
4755
0
    }
4756
4757
    // emit Catalog
4758
18.6k
    m_nCatalogObject = createObject();
4759
18.6k
    if( ! updateObject( m_nCatalogObject ) )
4760
0
        return false;
4761
18.6k
    aLine.setLength( 0 );
4762
18.6k
    aLine.append( m_nCatalogObject );
4763
18.6k
    aLine.append( " 0 obj\n"
4764
18.6k
                  "<</Type/Catalog/Pages " );
4765
18.6k
    aLine.append( nTreeNode );
4766
18.6k
    aLine.append( " 0 R\n" );
4767
4768
    // check if there are named destinations to emit (root must be inside the catalog)
4769
18.6k
    if( nNamedDestinationsDictionary )
4770
0
    {
4771
0
        aLine.append("/Dests ");
4772
0
        aLine.append( nNamedDestinationsDictionary );
4773
0
        aLine.append( " 0 R\n" );
4774
0
    }
4775
4776
18.6k
    if (!m_aDocumentAttachedFiles.empty())
4777
0
    {
4778
        // Write the associated files catalog entry (since PDF 2.0)
4779
0
        if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
4780
0
        {
4781
0
            aLine.append("/AF");
4782
0
            aLine.append("[");
4783
0
            for (auto & rAttachedFile : m_aDocumentAttachedFiles)
4784
0
                aWriter.writeReference(rAttachedFile.mnObjectId);
4785
0
            aLine.append("]");
4786
0
        }
4787
4788
0
        aWriter.startDictWithKey("/Names");
4789
0
        aWriter.startDictWithKey("/EmbeddedFiles");
4790
0
        aLine.append("/Names [");
4791
0
        for (auto & rAttachedFile : m_aDocumentAttachedFiles)
4792
0
        {
4793
0
            aWriter.writeUnicodeEncrypt(rAttachedFile.maFilename, m_nCatalogObject);
4794
0
            aWriter.writeReference(rAttachedFile.mnObjectId);
4795
0
        }
4796
0
        aLine.append("]");
4797
0
        aWriter.endDict();
4798
0
        aWriter.endDict();
4799
0
    }
4800
4801
18.6k
    if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
4802
0
        switch(  m_aContext.PageLayout )
4803
0
        {
4804
0
        default :
4805
0
        case  PDFWriter::SinglePage :
4806
0
            aLine.append( "/PageLayout/SinglePage\n" );
4807
0
            break;
4808
0
        case  PDFWriter::Continuous :
4809
0
            aLine.append( "/PageLayout/OneColumn\n" );
4810
0
            break;
4811
0
        case  PDFWriter::ContinuousFacing :
4812
            // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
4813
0
            aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
4814
0
            break;
4815
0
        }
4816
18.6k
    if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
4817
0
        switch(  m_aContext.PDFDocumentMode )
4818
0
        {
4819
0
        default :
4820
0
            aLine.append( "/PageMode/UseNone\n" );
4821
0
            break;
4822
0
        case PDFWriter::UseOutlines :
4823
0
            aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
4824
0
            break;
4825
0
        case PDFWriter::UseThumbs :
4826
0
            aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
4827
0
            break;
4828
0
        }
4829
18.6k
    else if( m_aContext.OpenInFullScreenMode )
4830
0
        aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
4831
4832
18.6k
    OStringBuffer aInitPageRef;
4833
18.6k
    if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
4834
6.20k
    {
4835
6.20k
        aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
4836
6.20k
        aInitPageRef.append( " 0 R" );
4837
6.20k
    }
4838
12.4k
    else
4839
12.4k
        aInitPageRef.append( "0" );
4840
4841
18.6k
    switch( m_aContext.PDFDocumentAction )
4842
18.6k
    {
4843
18.6k
    case PDFWriter::ActionDefault :     //do nothing, this is the Acrobat default
4844
18.6k
    default:
4845
18.6k
        if ( aInitPageRef.getLength() > 1 && m_aContext.InitialPage > 0 )
4846
0
        {
4847
0
            aLine.append( "/OpenAction[" );
4848
0
            aLine.append( aInitPageRef );
4849
0
            aLine.append( " /XYZ null null 0]\n" );
4850
0
        }
4851
18.6k
        break;
4852
0
    case PDFWriter::FitInWindow :
4853
0
        aLine.append( "/OpenAction[" );
4854
0
        aLine.append( aInitPageRef );
4855
0
        aLine.append( " /Fit]\n" ); //Open fit page
4856
0
        break;
4857
0
    case PDFWriter::FitWidth :
4858
0
        aLine.append( "/OpenAction[" );
4859
0
        aLine.append( aInitPageRef );
4860
0
        aLine.append( " /FitH null]\n" ); //Open fit width
4861
0
        break;
4862
0
    case PDFWriter::FitVisible :
4863
0
        aLine.append( "/OpenAction[" );
4864
0
        aLine.append( aInitPageRef );
4865
0
        aLine.append( " /FitBH null]\n" ); //Open fit visible
4866
0
        break;
4867
0
    case PDFWriter::ActionZoom :
4868
0
        aLine.append( "/OpenAction[" );
4869
0
        aLine.append( aInitPageRef );
4870
0
        aLine.append( " /XYZ null null " );
4871
0
        if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
4872
0
            aLine.append( m_aContext.Zoom/100.0 );
4873
0
        else
4874
0
            aLine.append( "0" );
4875
0
        aLine.append( "]\n" );
4876
0
        break;
4877
18.6k
    }
4878
4879
    // viewer preferences, if we had some, then emit
4880
18.6k
    if (m_aContext.HideViewerToolbar ||
4881
18.6k
        (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) ||
4882
17.9k
        m_aContext.HideViewerMenubar ||
4883
17.9k
        m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
4884
17.9k
        m_aContext.CenterWindow || (m_aContext.FirstPageLeft  &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
4885
17.9k
        m_aContext.OpenInFullScreenMode ||
4886
17.9k
        m_bIsPDF_UA)
4887
665
    {
4888
665
        aLine.append( "/ViewerPreferences<<" );
4889
665
        if( m_aContext.HideViewerToolbar )
4890
0
            aLine.append( "/HideToolbar true\n" );
4891
665
        if( m_aContext.HideViewerMenubar )
4892
0
            aLine.append( "/HideMenubar true\n" );
4893
665
        if( m_aContext.HideViewerWindowControls )
4894
0
            aLine.append( "/HideWindowUI true\n" );
4895
665
        if( m_aContext.FitWindow )
4896
0
            aLine.append( "/FitWindow true\n" );
4897
665
        if( m_aContext.CenterWindow )
4898
0
            aLine.append( "/CenterWindow true\n" );
4899
        // PDF/UA-2 requires /DisplayDocTitle set to true
4900
665
        if ((!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_bIsPDF_UA)
4901
665
            aLine.append( "/DisplayDocTitle true\n" );
4902
665
        if( m_aContext.FirstPageLeft &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing )
4903
0
            aLine.append( "/Direction/R2L\n" );
4904
665
        if( m_aContext.OpenInFullScreenMode )
4905
0
            switch( m_aContext.PDFDocumentMode )
4906
0
            {
4907
0
            default :
4908
0
            case PDFWriter::ModeDefault :
4909
0
                aLine.append( "/NonFullScreenPageMode/UseNone\n" );
4910
0
                break;
4911
0
            case PDFWriter::UseOutlines :
4912
0
                aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
4913
0
                break;
4914
0
            case PDFWriter::UseThumbs :
4915
0
                aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
4916
0
                break;
4917
0
            }
4918
665
        aLine.append( ">>\n" );
4919
665
    }
4920
4921
18.6k
    if( nOutlineDict )
4922
627
    {
4923
627
        aLine.append( "/Outlines " );
4924
627
        aLine.append( nOutlineDict );
4925
627
        aLine.append( " 0 R\n" );
4926
627
    }
4927
18.6k
    if( nStructureDict )
4928
0
    {
4929
0
        aLine.append( "/StructTreeRoot " );
4930
0
        aLine.append( nStructureDict );
4931
0
        aLine.append( " 0 R\n" );
4932
0
    }
4933
18.6k
    if( !m_aContext.DocumentLocale.Language.isEmpty() )
4934
6.20k
    {
4935
        /* PDF allows only RFC 3066, see above in emitStructure(). */
4936
6.20k
        LanguageTag aLanguageTag( m_aContext.DocumentLocale);
4937
6.20k
        OUString aLanguage, aScript, aCountry;
4938
6.20k
        aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
4939
6.20k
        if (!aLanguage.isEmpty())
4940
6.20k
        {
4941
6.20k
            OUStringBuffer aLocBuf( 16 );
4942
6.20k
            aLocBuf.append( aLanguage );
4943
6.20k
            if( !aCountry.isEmpty() )
4944
538
            {
4945
538
                aLocBuf.append( '-' );
4946
538
                aLocBuf.append( aCountry );
4947
538
            }
4948
6.20k
            aLine.append( "/Lang" );
4949
6.20k
            aWriter.writeLiteralEncrypt(aLocBuf, m_nCatalogObject);
4950
6.20k
            aLine.append( "\n" );
4951
6.20k
        }
4952
6.20k
    }
4953
18.6k
    if (m_aContext.Tagged)
4954
0
    {
4955
0
        aLine.append( "/MarkInfo<</Marked true>>\n" );
4956
0
    }
4957
4958
18.6k
    if (!m_aWidgets.empty() || !m_aCopiedWidgets.empty())
4959
0
    {
4960
0
        aLine.append("/AcroForm");
4961
0
        aLine.append("<<");
4962
0
        aLine.append("/Fields");
4963
0
        aLine.append("[ ");
4964
4965
0
        for (auto const& rWidget : m_aWidgets)
4966
0
        {
4967
            // output only root fields
4968
0
            if (rWidget.m_nParent < 1)
4969
0
            {
4970
0
                appendObjectReference(rWidget.m_nObject, aLine);
4971
0
            }
4972
0
        }
4973
        // Add widgets that were copied from an external PDF
4974
0
        for (auto const& rCopiedWidget : m_aCopiedWidgets)
4975
0
        {
4976
0
            appendObjectReference(rCopiedWidget.m_nObject, aLine);
4977
0
        }
4978
0
        aLine.append(" ]\n");
4979
4980
0
        bool bSigned = false;
4981
#if HAVE_FEATURE_NSS
4982
        if (m_nSignatureObject != -1)
4983
        {
4984
            aLine.append("/SigFlags 3 ");
4985
            bSigned = true;
4986
        }
4987
#endif
4988
0
        aLine.append("/DR<</Font ");
4989
0
        appendObjectReference(getFontDictObject(), aLine);
4990
0
        aLine.append(">>");
4991
4992
        // NeedAppearances must not be used if PDF is signed, PDF/A is used or
4993
        // we have copied widgets (can't guarantee we have appearance streams in this case)
4994
0
        if (m_nPDFA_Version == 0 && !bSigned && m_aCopiedWidgets.empty())
4995
0
            aLine.append("/NeedAppearances true ");
4996
4997
0
        aLine.append(">>\n");
4998
0
    }
4999
5000
    //check if there is a Metadata object
5001
18.6k
    if( nOutputIntentObject )
5002
0
    {
5003
0
        aLine.append("/OutputIntents[");
5004
0
        aLine.append( nOutputIntentObject );
5005
0
        aLine.append( " 0 R]" );
5006
0
    }
5007
5008
18.6k
    if( nMetadataObject )
5009
18.6k
    {
5010
18.6k
        aWriter.writeKeyAndReference("/Metadata", nMetadataObject);
5011
18.6k
    }
5012
5013
18.6k
    aLine.append( ">>\n"
5014
18.6k
                  "endobj\n\n" );
5015
18.6k
    return writeBuffer( aLine );
5016
18.6k
}
5017
5018
#if HAVE_FEATURE_NSS
5019
5020
bool PDFWriterImpl::emitSignature()
5021
{
5022
    if( !updateObject( m_nSignatureObject ) )
5023
        return false;
5024
5025
    OStringBuffer aLine( 0x5000 );
5026
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5027
    aLine.append( m_nSignatureObject );
5028
    aLine.append( " 0 obj\n" );
5029
    aLine.append("<</Contents <" );
5030
5031
    sal_uInt64 nOffset = ~0U;
5032
    if (osl::File::E_None != m_aFile.getPos(nOffset))
5033
        return false;
5034
5035
    m_nSignatureContentOffset = nOffset + aLine.getLength();
5036
5037
    // reserve some space for the PKCS#7 object
5038
    aLine.append(RepeatedChar('0', MAX_SIGNATURE_CONTENT_LENGTH)
5039
                 + ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5040
5041
    if( !m_aContext.DocumentInfo.Author.isEmpty() )
5042
    {
5043
        aLine.append( "/Name" );
5044
        aWriter.writeUnicodeEncrypt(m_aContext.DocumentInfo.Author, m_nSignatureObject);
5045
    }
5046
5047
    aLine.append( " /M ");
5048
    aWriter.writeLiteralEncrypt(m_aCreationDateString, m_nSignatureObject);
5049
5050
    aLine.append( " /ByteRange [ 0 ");
5051
    aLine.append( m_nSignatureContentOffset - 1 );
5052
    aLine.append( " " );
5053
    aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5054
    aLine.append( " " );
5055
5056
    m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5057
5058
    // mark the last ByteRange no and add some space. Now, we don't know
5059
    // how many bytes we need for this ByteRange value
5060
    // The real value will be overwritten in the finalizeSignature method
5061
    aLine.append(RepeatedChar(' ', 100) + "  /Filter/Adobe.PPKMS");
5062
5063
    //emit reason, location and contactinfo
5064
    if ( !m_aContext.SignReason.isEmpty() )
5065
    {
5066
        aLine.append("/Reason");
5067
        aWriter.writeUnicodeEncrypt(m_aContext.SignReason, m_nSignatureObject);
5068
    }
5069
5070
    if ( !m_aContext.SignLocation.isEmpty() )
5071
    {
5072
        aLine.append("/Location");
5073
        aWriter.writeUnicodeEncrypt(m_aContext.SignLocation, m_nSignatureObject);
5074
    }
5075
5076
    if ( !m_aContext.SignContact.isEmpty() )
5077
    {
5078
        aLine.append("/ContactInfo");
5079
        aWriter.writeUnicodeEncrypt(m_aContext.SignContact, m_nSignatureObject);
5080
    }
5081
5082
    aLine.append(" >>\nendobj\n\n" );
5083
5084
    return writeBuffer( aLine );
5085
}
5086
5087
bool PDFWriterImpl::finalizeSignature()
5088
{
5089
    if (!m_aContext.SignCertificate.is())
5090
        return false;
5091
5092
    // 1- calculate last ByteRange value
5093
    sal_uInt64 nOffset = ~0U;
5094
    if (osl::File::E_None != m_aFile.getPos(nOffset))
5095
        return false;
5096
5097
    sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
5098
5099
    // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
5100
    sal_uInt64 nWritten = 0;
5101
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset))
5102
        return false;
5103
    OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
5104
5105
    if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
5106
    {
5107
        (void)m_aFile.setPos(osl_Pos_Absolut, nOffset);
5108
        return false;
5109
    }
5110
5111
    // 3- create the PKCS#7 object using NSS
5112
5113
    // Prepare buffer and calculate PDF file digest
5114
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, 0))
5115
        return false;
5116
5117
    std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
5118
    sal_uInt64 bytesRead1;
5119
5120
    //FIXME: Check if hash is calculated from the correct byterange
5121
    if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
5122
        bytesRead1 != o3tl::make_unsigned(m_nSignatureContentOffset) - 1)
5123
    {
5124
        SAL_WARN("vcl.pdfwriter", "First buffer read failed");
5125
        return false;
5126
    }
5127
5128
    std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
5129
    sal_uInt64 bytesRead2;
5130
5131
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
5132
        osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
5133
        bytesRead2 != o3tl::make_unsigned(nLastByteRangeNo))
5134
    {
5135
        SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
5136
        return false;
5137
    }
5138
5139
    OStringBuffer aCMSHexBuffer;
5140
    svl::crypto::SigningContext aSigningContext;
5141
    aSigningContext.m_xCertificate = m_aContext.SignCertificate;
5142
    svl::crypto::Signing aSigning(aSigningContext);
5143
    aSigning.AddDataRange(buffer1.get(), bytesRead1);
5144
    aSigning.AddDataRange(buffer2.get(), bytesRead2);
5145
    aSigning.SetSignTSA(m_aContext.SignTSA);
5146
    aSigning.SetSignPassword(m_aContext.SignPassword);
5147
    if (!aSigning.Sign(aCMSHexBuffer))
5148
    {
5149
        SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
5150
        return false;
5151
    }
5152
5153
    assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
5154
5155
    // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
5156
    nWritten = 0;
5157
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset))
5158
        return false;
5159
    m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
5160
5161
    return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
5162
}
5163
5164
#endif //HAVE_FEATURE_NSS
5165
5166
sal_Int32 PDFWriterImpl::emitInfoDict( )
5167
18.6k
{
5168
18.6k
    sal_Int32 nObject = createObject();
5169
5170
18.6k
    if (!updateObject(nObject))
5171
0
        return 0;
5172
5173
18.6k
    COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5174
18.6k
    aWriter.startObject(nObject);
5175
18.6k
    aWriter.startDict();
5176
5177
    // These entries are deprecated in PDF 2.0 (in favor of XMP metadata) and shouldn't be written.
5178
    // Exception: CreationDate and ModDate (which we don't write)
5179
18.6k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
5180
18.6k
    {
5181
18.6k
        if (!m_aContext.DocumentInfo.Title.isEmpty())
5182
665
        {
5183
665
            aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject);
5184
665
        }
5185
18.6k
        if (!m_aContext.DocumentInfo.Author.isEmpty())
5186
427
        {
5187
427
            aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject);
5188
427
        }
5189
18.6k
        if (!m_aContext.DocumentInfo.Subject.isEmpty())
5190
17
        {
5191
17
            aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject);
5192
17
        }
5193
18.6k
        if (!m_aContext.DocumentInfo.Keywords.isEmpty())
5194
312
        {
5195
312
            aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject);
5196
312
        }
5197
18.6k
        if (!m_aContext.DocumentInfo.Creator.isEmpty())
5198
6.20k
        {
5199
6.20k
            aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject);
5200
6.20k
        }
5201
18.6k
        if (!m_aContext.DocumentInfo.Producer.isEmpty())
5202
0
        {
5203
0
            aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject);
5204
0
        }
5205
18.6k
    }
5206
    // Allowed in PDF 2.0
5207
18.6k
    aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject);
5208
18.6k
    aWriter.endDict();
5209
18.6k
    aWriter.endObject();
5210
5211
18.6k
    if (!writeBuffer(aWriter.getLine()))
5212
0
        nObject = 0;
5213
5214
18.6k
    return nObject;
5215
18.6k
}
5216
5217
// Part of this function may be shared with method appendDest.
5218
sal_Int32 PDFWriterImpl::emitNamedDestinations()
5219
18.6k
{
5220
18.6k
    sal_Int32  nCount = m_aNamedDests.size();
5221
18.6k
    if( nCount <= 0 )
5222
18.6k
        return 0;//define internal error
5223
5224
    //get the object number for all the destinations
5225
0
    sal_Int32 nObject = createObject();
5226
5227
0
    if( updateObject( nObject ) )
5228
0
    {
5229
        //emit the dictionary
5230
0
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
5231
0
        aLine.append( nObject );
5232
0
        aLine.append( " 0 obj\n"
5233
0
                      "<<" );
5234
5235
0
        sal_Int32  nDestID;
5236
0
        for( nDestID = 0; nDestID < nCount; nDestID++ )
5237
0
        {
5238
0
            const PDFNamedDest& rDest   = m_aNamedDests[ nDestID ];
5239
            // In order to correctly function both under an Internet browser and
5240
            // directly with a reader (provided the reader has the feature) we
5241
            // need to set the name of the destination the same way it will be encoded
5242
            // in an Internet link
5243
0
            INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
5244
0
            aLocalURL.SetMark( rDest.m_aDestName );
5245
5246
0
            const OUString aName   = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5247
            // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5248
0
            const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
5249
5250
0
            aLine.append( '/' );
5251
0
            appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5252
0
            aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5253
                                 //maps the preceding character properly
5254
0
            aLine.append( rDestPage.m_nPageObject );
5255
0
            aLine.append( " 0 R" );
5256
5257
0
            switch( rDest.m_eType )
5258
0
            {
5259
0
            case PDFWriter::DestAreaType::XYZ:
5260
0
            default:
5261
0
                aLine.append( "/XYZ " );
5262
0
                appendFixedInt( rDest.m_aRect.Left(), aLine );
5263
0
                aLine.append( ' ' );
5264
0
                appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5265
0
                aLine.append( " 0" );
5266
0
                break;
5267
0
            case PDFWriter::DestAreaType::FitRectangle:
5268
0
                aLine.append( "/FitR " );
5269
0
                appendFixedInt( rDest.m_aRect.Left(), aLine );
5270
0
                aLine.append( ' ' );
5271
0
                appendFixedInt( rDest.m_aRect.Top(), aLine );
5272
0
                aLine.append( ' ' );
5273
0
                appendFixedInt( rDest.m_aRect.Right(), aLine );
5274
0
                aLine.append( ' ' );
5275
0
                appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5276
0
                break;
5277
0
            }
5278
0
            aLine.append( "]\n" );
5279
0
        }
5280
5281
        //close
5282
0
        aLine.append( ">>\nendobj\n\n" );
5283
0
        if( ! writeBuffer( aLine ) )
5284
0
            nObject = 0;
5285
0
    }
5286
0
    else
5287
0
        nObject = 0;
5288
5289
0
    return nObject;
5290
0
}
5291
5292
// emits the output intent dictionary
5293
sal_Int32 PDFWriterImpl::emitOutputIntent()
5294
18.6k
{
5295
18.6k
    if (m_nPDFA_Version == 0) // not PDFA
5296
18.6k
        return 0;
5297
5298
    //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5299
5300
0
    OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
5301
0
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5302
0
    sal_Int32 nICCObject = createObject();
5303
0
    sal_Int32 nStreamLengthObject = createObject();
5304
5305
0
    aLine.append( nICCObject );
5306
// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5307
0
    aLine.append( " 0 obj\n<</N 3/Length " );
5308
0
    aLine.append( nStreamLengthObject );
5309
0
    aLine.append( " 0 R" );
5310
0
    if (!g_bDebugDisableCompression)
5311
0
        aLine.append( "/Filter/FlateDecode" );
5312
0
    aLine.append( ">>\nstream\n" );
5313
0
    if ( !updateObject( nICCObject ) ) return 0;
5314
0
    if ( !writeBuffer( aLine ) ) return 0;
5315
    //get file position
5316
0
    sal_uInt64 nBeginStreamPos = 0;
5317
0
    if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5318
0
        return 0;
5319
0
    beginCompression();
5320
0
    checkAndEnableStreamEncryption( nICCObject );
5321
0
    cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5322
    //force ICC profile version 2.1
5323
0
    cmsSetProfileVersion(hProfile, 2.1);
5324
0
    cmsUInt32Number nBytesNeeded = 0;
5325
0
    cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5326
0
    if (!nBytesNeeded)
5327
0
      return 0;
5328
0
    std::vector<unsigned char> aBuffer(nBytesNeeded);
5329
0
    cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5330
0
    cmsCloseProfile(hProfile);
5331
0
    bool written = writeBufferBytes( aBuffer.data(), aBuffer.size() );
5332
0
    disableStreamEncryption();
5333
0
    endCompression();
5334
5335
0
    sal_uInt64 nEndStreamPos = 0;
5336
0
    if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5337
0
        return 0;
5338
5339
0
    if( !written )
5340
0
        return 0;
5341
0
    if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
5342
0
        return 0 ;
5343
0
    aLine.setLength( 0 );
5344
5345
    //emit the stream length   object
5346
0
    if ( !updateObject( nStreamLengthObject ) ) return 0;
5347
0
    aLine.setLength( 0 );
5348
0
    aLine.append( nStreamLengthObject );
5349
0
    aLine.append( " 0 obj\n" );
5350
0
    aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5351
0
    aLine.append( "\nendobj\n\n" );
5352
0
    if ( !writeBuffer( aLine ) ) return 0;
5353
0
    aLine.setLength( 0 );
5354
5355
    //emit the OutputIntent dictionary
5356
0
    sal_Int32 nOIObject = createObject();
5357
0
    if ( !updateObject( nOIObject ) ) return 0;
5358
0
    aLine.append( nOIObject );
5359
0
    aLine.append( " 0 obj\n"
5360
0
                  "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5361
5362
0
    aWriter.writeLiteralEncrypt(std::string_view("sRGB IEC61966-2.1"), nOIObject);
5363
0
    aLine.append("/DestOutputProfile ");
5364
0
    aLine.append( nICCObject );
5365
0
    aLine.append( " 0 R>>\nendobj\n\n" );
5366
0
    if ( !writeBuffer( aLine ) ) return 0;
5367
5368
0
    return nOIObject;
5369
0
}
5370
5371
static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
5372
205k
{
5373
205k
    if (!aValue.empty())
5374
7.62k
    {
5375
7.62k
        aMeta = OUStringToOString(aValue, RTL_TEXTENCODING_UTF8);
5376
7.62k
    }
5377
205k
}
5378
5379
static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta)
5380
55.9k
{
5381
55.9k
    if (!rValues.hasElements())
5382
55.9k
        return;
5383
5384
0
    std::vector<OString> aNewMetaVector;
5385
0
    aNewMetaVector.reserve(rValues.getLength());
5386
5387
0
    for (const OUString& rValue : rValues)
5388
0
    {
5389
0
        aNewMetaVector.emplace_back(
5390
0
            OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8));
5391
0
    }
5392
5393
0
    rMeta = std::move(aNewMetaVector);
5394
0
}
5395
5396
// emits the document metadata
5397
// Since in PDF 1.4
5398
sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5399
18.6k
{
5400
18.6k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4)
5401
0
        return 0;
5402
5403
    //get the object number for all the destinations
5404
18.6k
    sal_Int32 nObject = createObject();
5405
5406
18.6k
    if (!updateObject(nObject))
5407
0
        return 0;
5408
5409
18.6k
    pdf::XmpMetadata aMetadata;
5410
5411
18.6k
    if (m_nPDFA_Version > 0)
5412
0
        aMetadata.mnPDF_A = m_nPDFA_Version;
5413
5414
18.6k
    if (m_bIsPDF_UA)
5415
0
    {
5416
        // If PDF 2.0+ we need to use PDF/UA-2 otherwise PDF/UA-1
5417
0
        aMetadata.mnPDF_UA = (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) ? 2 : 1;
5418
0
    }
5419
5420
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
5421
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
5422
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
5423
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
5424
18.6k
    aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version);
5425
18.6k
    if (m_nPDFA_Version == 4)
5426
0
    {
5427
        // if we have embedded files we need to use conformance level "F"
5428
0
        aMetadata.msConformance = m_aEmbeddedFiles.empty() ? ""_ostr : "F"_ostr;
5429
0
    }
5430
18.6k
    else
5431
18.6k
    {
5432
18.6k
        aMetadata.msConformance = "B"_ostr;
5433
18.6k
    }
5434
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
5435
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor);
5436
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage);
5437
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier);
5438
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher);
5439
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation);
5440
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights);
5441
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource);
5442
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType);
5443
18.6k
    lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
5444
18.6k
    aMetadata.m_sCreateDate = m_aCreationMetaDateString;
5445
5446
    // metadata is always encrypted: V 2 has no /EncryptMetadata to leave it plain, and at V 5
5447
    // that entry defaults to true
5448
18.6k
    const bool bEncryptMetadata = m_pPDFEncryptor != nullptr;
5449
5450
18.6k
    {
5451
18.6k
        sal_uInt64 const nLength{bEncryptMetadata
5452
18.6k
                ? calculateStreamSize(aMetadata.getSize())
5453
18.6k
                : aMetadata.getSize()};
5454
18.6k
        COSWriter aWriter;
5455
18.6k
        aWriter.startObject(nObject);
5456
18.6k
        aWriter.startDict();
5457
18.6k
        aWriter.write("/Type", "/Metadata");
5458
18.6k
        aWriter.write("/Subtype", "/XML");
5459
18.6k
        aWriter.write("/Length", sal_Int32(nLength));
5460
18.6k
        aWriter.endDict();
5461
18.6k
        aWriter.startStream();
5462
18.6k
        if (!writeBuffer(aWriter.getLine()))
5463
0
            return 0;
5464
18.6k
    }
5465
5466
    //emit the stream
5467
18.6k
    if (bEncryptMetadata)
5468
0
        checkAndEnableStreamEncryption(nObject);
5469
5470
18.6k
    if (!writeBufferBytes(aMetadata.getData(), aMetadata.getSize()))
5471
0
        return 0;
5472
5473
18.6k
    if (bEncryptMetadata)
5474
0
        disableStreamEncryption();
5475
5476
18.6k
    {
5477
18.6k
        COSWriter aWriter;
5478
18.6k
        aWriter.endStream();
5479
18.6k
        aWriter.endObject();
5480
18.6k
        if (!writeBuffer(aWriter.getLine()))
5481
0
            return 0;
5482
18.6k
    }
5483
5484
18.6k
    return nObject;
5485
18.6k
}
5486
5487
sal_Int32 PDFWriterImpl::emitEncrypt()
5488
0
{
5489
    //emit the security information
5490
    //must be emitted as indirect dictionary object, since
5491
    //Acrobat Reader 5 works only with this kind of implementation
5492
5493
0
    sal_Int32 nObject = createObject();
5494
5495
0
    if (updateObject(nObject))
5496
0
    {
5497
0
        PDFEncryptionProperties& rProperties = m_aContext.Encryption;
5498
0
        COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5499
0
        aWriter.startObject(nObject);
5500
0
        aWriter.startDict();
5501
0
        aWriter.write("/Filter", "/Standard");
5502
0
        aWriter.write("/V", m_pPDFEncryptor->getVersion());
5503
0
        aWriter.write("/Length", m_pPDFEncryptor->getKeyLength() * 8);
5504
0
        aWriter.write("/R", m_pPDFEncryptor->getRevision());
5505
5506
0
        if (m_pPDFEncryptor->getVersion() == 5 && m_pPDFEncryptor->getRevision() == 6)
5507
0
        {
5508
            // emit the owner password, must not be encrypted
5509
0
            aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
5510
0
            aWriter.writeHexArray("/UE", rProperties.UE.data(), rProperties.UE.size());
5511
0
            aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
5512
0
            aWriter.writeHexArray("/OE", rProperties.OE.data(), rProperties.OE.size());
5513
5514
            // Encrypted perms
5515
0
            std::vector<sal_uInt8> aEncryptedPermissions = m_pPDFEncryptor->getEncryptedAccessPermissions(rProperties.EncryptionKey);
5516
0
            aWriter.writeHexArray("/Perms", aEncryptedPermissions.data(), aEncryptedPermissions.size());
5517
5518
            // Write content filter stuff - to select we want AESv3 256bit
5519
0
            aWriter.write("/CF", "<</StdCF <</CFM /AESV3 /Length 256>>>>");
5520
0
            aWriter.write("/StmF", "/StdCF");
5521
0
            aWriter.write("/StrF", "/StdCF");
5522
0
        }
5523
0
        else
5524
0
        {
5525
            // emit the owner password, must not be encrypted
5526
0
            aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
5527
0
            aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
5528
0
        }
5529
0
        aWriter.write("/P", m_pPDFEncryptor->getAccessPermissions());
5530
0
        aWriter.endDict();
5531
0
        aWriter.endObject();
5532
5533
0
        if (!writeBuffer(aWriter.getLine()))
5534
0
            nObject = 0;
5535
0
    }
5536
0
    else
5537
0
        nObject = 0;
5538
5539
0
    return nObject;
5540
0
}
5541
5542
bool PDFWriterImpl::emitTrailer()
5543
18.6k
{
5544
    // emit doc info
5545
18.6k
    sal_Int32 nDocInfoObject = 0;
5546
    // Deprecated in PDF 2.0, but still allowed if /PieceInfo is written (which we don't support)
5547
18.6k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
5548
18.6k
        nDocInfoObject = emitInfoDict();
5549
5550
18.6k
    sal_Int32 nSecObject = 0;
5551
5552
18.6k
    if (m_aContext.Encryption.canEncrypt())
5553
0
    {
5554
0
        nSecObject = emitEncrypt();
5555
0
    }
5556
    // a cross-reference table cannot address an object inside an object stream
5557
18.6k
    const bool bXRefStream = std::ranges::any_of(m_aObjects, &ObjectLocation::isCompressed);
5558
5559
18.6k
    sal_uInt64 nXRefOffset = 0;
5560
18.6k
    OStringBuffer aLine;
5561
18.6k
    if (!bXRefStream)
5562
18.6k
    {
5563
        // emit the xref table, remembering where it starts
5564
18.6k
        if (osl::File::E_None != m_aFile.getPos(nXRefOffset))
5565
0
            return false;
5566
18.6k
        if (!writeBuffer("xref\n"))
5567
0
            return false;
5568
5569
18.6k
        aLine.append("0 ");
5570
18.6k
        aLine.append(static_cast<sal_Int32>(m_aObjects.size() + 1));
5571
18.6k
        aLine.append("\n");
5572
18.6k
        aLine.append("0000000000 65535 f \n");
5573
18.6k
        if (!writeBuffer(aLine))
5574
0
            return false;
5575
5576
18.6k
        for (const auto& rLocation : m_aObjects)
5577
493k
        {
5578
493k
            if (rLocation.m_nOffset == SAL_MAX_UINT64)
5579
0
            {
5580
0
                SAL_WARN("vcl.pdfwriter", "object was created but never written");
5581
0
                if (!writeBuffer("0000000000 65535 f \n"))
5582
0
                    return false;
5583
0
                continue;
5584
0
            }
5585
493k
            auto aOffset(OString::number(rLocation.m_nOffset));
5586
493k
            aLine = RepeatedChar('0', 10 - aOffset.length) + aOffset + " 00000 n \n";
5587
493k
            SAL_WARN_IF(aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry");
5588
493k
            if (!writeBuffer(aLine))
5589
0
                return false;
5590
493k
        }
5591
18.6k
    }
5592
5593
    // prepare document checksum
5594
18.6k
    OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
5595
18.6k
    ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
5596
18.6k
    for (sal_uInt8 i : nMD5Sum)
5597
298k
        COSWriter::appendHex( i, aDocChecksum );
5598
    // document id set in setDocInfo method
5599
    // emit trailer
5600
18.6k
    aLine.setLength( 0 );
5601
18.6k
    aLine.append( "/Root " );
5602
18.6k
    aLine.append( m_nCatalogObject );
5603
18.6k
    aLine.append( " 0 R\n" );
5604
18.6k
    if( nSecObject )
5605
0
    {
5606
0
        aLine.append( "/Encrypt ");
5607
0
        aLine.append( nSecObject );
5608
0
        aLine.append( " 0 R\n" );
5609
0
    }
5610
18.6k
    if( nDocInfoObject )
5611
18.6k
    {
5612
18.6k
        aLine.append( "/Info " );
5613
18.6k
        aLine.append( nDocInfoObject );
5614
18.6k
        aLine.append( " 0 R\n" );
5615
18.6k
    }
5616
18.6k
    if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
5617
18.6k
    {
5618
18.6k
        aLine.append( "/ID [ <" );
5619
18.6k
        for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5620
298k
        {
5621
298k
            COSWriter::appendHex( sal_Int8(item), aLine );
5622
298k
        }
5623
18.6k
        aLine.append( ">\n"
5624
18.6k
                      "<" );
5625
18.6k
        for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5626
298k
        {
5627
298k
            COSWriter::appendHex( sal_Int8(item), aLine );
5628
298k
        }
5629
18.6k
        aLine.append( "> ]\n" );
5630
18.6k
    }
5631
5632
    // Writes the /DocChecksum - hash of the PDF stream
5633
    // This entry is not defined in the standard, so don't write it if we
5634
    // are using PDF/UA or PDF/A as the compliance checkers will complain.
5635
    // Actually we shouldn't write it at all...
5636
18.6k
    if (!aDocChecksum.isEmpty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
5637
18.6k
    {
5638
18.6k
        aLine.append( "/DocChecksum /" );
5639
18.6k
        aLine.append( aDocChecksum );
5640
18.6k
        aLine.append( "\n" );
5641
18.6k
    }
5642
5643
    // Writes the /AdditionalStreams - writes the embedded / attached files into the PDF
5644
    // This entry is not defined in the standard, so don't write it if we
5645
    // are using PDF/UA or PDF/A as the compliance checkers will complain.
5646
18.6k
    if (!m_aDocumentAttachedFiles.empty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
5647
0
    {
5648
0
        aLine.append( "/AdditionalStreams [" );
5649
0
        for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
5650
0
        {
5651
0
            aLine.append( "/" );
5652
0
            COSWriter::appendName(rAttachedFile.maMimeType, aLine);
5653
0
            aLine.append(" ");
5654
0
            appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
5655
0
            aLine.append("\n");
5656
0
        }
5657
0
        aLine.append( "]\n" );
5658
0
    }
5659
5660
    // After calling m_DocDigest.finalize(), we need to initialize the hash again,
5661
    // otherwise, m_DocDigest.update() inside writeBuffer will fail with
5662
    // Assertion failure: rv == SECSuccess, at sechash.c:140
5663
18.6k
    m_DocDigest.initialize();
5664
5665
18.6k
    if (bXRefStream)
5666
0
        return emitXRefStream(aLine);
5667
5668
18.6k
    return writeBuffer("trailer\n<</Size " + OString::number(m_aObjects.size() + 1))
5669
18.6k
           && writeBuffer(aLine)
5670
18.6k
           && writeBuffer(">>\nstartxref\n" + OString::number(static_cast<sal_Int64>(nXRefOffset))
5671
18.6k
                          + "\n%%EOF\n");
5672
18.6k
}
5673
5674
bool PDFWriterImpl::emitXRefStream(std::string_view aTrailerEntries)
5675
0
{
5676
    // ISO 32000-2 7.5.8.3 requires an entry for the stream itself
5677
0
    const sal_Int32 nXRefObject = createObject();
5678
0
    sal_uInt64 nXRefOffset = 0;
5679
0
    if (osl::File::E_None != m_aFile.getPos(nXRefOffset))
5680
0
        return false;
5681
0
    m_aObjects[nXRefObject - 1].m_nOffset = nXRefOffset;
5682
5683
    // the second field is a byte offset or an object stream number, so measure both
5684
0
    const auto aSecondField = [](const ObjectLocation& rLocation) -> sal_uInt64 {
5685
0
        if (rLocation.isCompressed())
5686
0
            return rLocation.m_nObjectStream;
5687
        // an unwritten object gets a free entry, which has no offset
5688
0
        return rLocation.m_nOffset == SAL_MAX_UINT64 ? 0 : rLocation.m_nOffset;
5689
0
    };
5690
0
    sal_Int32 nOffsetBytes = 1;
5691
0
    for (sal_uInt64 nWidest = std::ranges::max(m_aObjects | std::views::transform(aSecondField));
5692
0
         nWidest > 0xff; nWidest >>= 8)
5693
0
        ++nOffsetBytes;
5694
5695
0
    OStringBuffer aEntries(static_cast<sal_Int32>((3 + nOffsetBytes) * (m_aObjects.size() + 1)));
5696
0
    auto appendEntry
5697
0
        = [&aEntries, nOffsetBytes](sal_uInt8 nType, sal_uInt64 nSecond, sal_uInt16 nThird) {
5698
0
              aEntries.append(char(nType));
5699
0
              for (sal_Int32 nByte = nOffsetBytes; nByte-- > 0;)
5700
0
                  aEntries.append(char(nSecond >> (8 * nByte)));
5701
0
              aEntries.append(char(nThird >> 8));
5702
0
              aEntries.append(char(nThird));
5703
0
          };
5704
5705
    // object 0 heads the list of free objects, as it does in a table
5706
0
    appendEntry(0, 0, 0xffff);
5707
0
    for (const auto& rLocation : m_aObjects)
5708
0
    {
5709
0
        if (rLocation.isCompressed())
5710
0
            appendEntry(2, rLocation.m_nObjectStream, rLocation.m_nIndex);
5711
0
        else if (rLocation.m_nOffset == SAL_MAX_UINT64)
5712
0
            appendEntry(0, 0, 0xffff);
5713
0
        else
5714
0
            appendEntry(1, rLocation.m_nOffset, 0);
5715
0
    }
5716
5717
0
    const std::vector<sal_uInt8> aStream(deflateBuffer(aEntries));
5718
0
    if (aStream.empty())
5719
0
        return false;
5720
5721
0
    OStringBuffer aLine(512);
5722
    // 7.5.8.2: every entry of Table 17 shall be direct, and a reader cannot resolve an
5723
    // indirect /Length before it has read this stream
5724
0
    aLine.append(OString::number(nXRefObject) + " 0 obj\n<</Type/XRef/Size "
5725
0
                 + OString::number(static_cast<sal_Int32>(m_aObjects.size() + 1)) + "/W[1 "
5726
0
                 + OString::number(nOffsetBytes) + " 2]/Length "
5727
0
                 + OString::number(static_cast<sal_Int32>(aStream.size())));
5728
0
    if (!g_bDebugDisableCompression)
5729
0
        aLine.append("/Filter/FlateDecode");
5730
0
    aLine.append(aTrailerEntries);
5731
0
    aLine.append(">>\nstream\n");
5732
5733
    // 7.5.8.2 forbids encrypting this stream, so it goes out as built
5734
0
    return writeBuffer(aLine) && writeBufferBytes(aStream.data(), aStream.size())
5735
0
           && writeBuffer("\nendstream\nendobj\n\nstartxref\n"
5736
0
                          + OString::number(static_cast<sal_Int64>(nXRefOffset)) + "\n%%EOF\n");
5737
0
}
5738
5739
namespace {
5740
5741
struct AnnotationSortEntry
5742
{
5743
    sal_Int32 nTabOrder;
5744
    sal_Int32 nObject;
5745
    sal_Int32 nWidgetIndex;
5746
5747
    AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
5748
0
        nTabOrder( nTab ),
5749
0
        nObject( nObj ),
5750
0
        nWidgetIndex( nI )
5751
0
    {}
5752
};
5753
5754
struct AnnotSortContainer
5755
{
5756
    o3tl::sorted_vector< sal_Int32 >      aObjects;
5757
    std::vector< AnnotationSortEntry >    aSortedAnnots;
5758
};
5759
5760
struct AnnotSorterLess
5761
{
5762
    std::vector<PDFWidget>& m_rWidgets;
5763
5764
0
    explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
5765
5766
    bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
5767
0
    {
5768
0
        if( rLeft.nTabOrder < rRight.nTabOrder )
5769
0
            return true;
5770
0
        if( rRight.nTabOrder < rLeft.nTabOrder )
5771
0
            return false;
5772
0
        if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
5773
0
            return false;
5774
0
        if( rRight.nWidgetIndex < 0 )
5775
0
            return true;
5776
0
        if( rLeft.nWidgetIndex < 0 )
5777
0
            return false;
5778
        // remember: widget rects are in PDF coordinates, so they are ordered down up
5779
0
        if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
5780
0
            m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
5781
0
            return true;
5782
0
        if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
5783
0
            m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
5784
0
            return false;
5785
0
        if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
5786
0
            m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
5787
0
            return true;
5788
0
        return false;
5789
0
    }
5790
};
5791
5792
}
5793
5794
void PDFWriterImpl::sortWidgets()
5795
18.6k
{
5796
    // sort widget annotations on each page as per their
5797
    // TabOrder attribute
5798
18.6k
    std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
5799
18.6k
    int nWidgets = m_aWidgets.size();
5800
18.6k
    for( int nW = 0; nW < nWidgets; nW++ )
5801
0
    {
5802
0
        const PDFWidget& rWidget = m_aWidgets[nW];
5803
0
        if( rWidget.m_nPage >= 0 )
5804
0
        {
5805
0
            AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
5806
            // optimize vector allocation
5807
0
            if( rCont.aSortedAnnots.empty() )
5808
0
                rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
5809
            // insert widget to tab sorter
5810
            // RadioButtons are not page annotations, only their individual check boxes are
5811
0
            if( rWidget.m_eType != PDFWriter::RadioButton )
5812
0
            {
5813
0
                rCont.aObjects.insert( rWidget.m_nObject );
5814
0
                rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
5815
0
            }
5816
0
        }
5817
0
    }
5818
18.6k
    for (auto & item : sorted)
5819
0
    {
5820
        // append entries for non widget annotations
5821
0
        PDFPage& rPage = m_aPages[ item.first ];
5822
0
        unsigned int nAnnots = rPage.m_aAnnotations.size();
5823
0
        for( unsigned int nA = 0; nA < nAnnots; nA++ )
5824
0
            if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
5825
0
                item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
5826
5827
0
        AnnotSorterLess aLess( m_aWidgets );
5828
0
        std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
5829
        // sanity check
5830
0
        if( item.second.aSortedAnnots.size() == nAnnots)
5831
0
        {
5832
0
            for( unsigned int nA = 0; nA < nAnnots; nA++ )
5833
0
                rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
5834
0
        }
5835
0
        else
5836
0
        {
5837
0
            SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
5838
0
            SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
5839
0
                     "on page nr " << item.first << ", " <<
5840
0
                     item.second.aSortedAnnots.size() << " sorted and " <<
5841
0
                     nAnnots << " unsorted");
5842
0
        }
5843
0
    }
5844
5845
    // FIXME: implement tab order in structure tree for PDF 1.5
5846
18.6k
}
5847
5848
bool PDFWriterImpl::emit()
5849
18.6k
{
5850
18.6k
    endPage();
5851
5852
    // resort structure tree and annotations if necessary
5853
    // needed for widget tab order
5854
18.6k
    sortWidgets();
5855
5856
#if HAVE_FEATURE_NSS
5857
    if( m_aContext.SignPDF )
5858
    {
5859
        // sign the document
5860
        PDFWriter::SignatureWidget aSignature;
5861
        aSignature.Name = "Signature1";
5862
        createControl( aSignature, 0 );
5863
    }
5864
#endif
5865
5866
    // emit catalog
5867
18.6k
    if (!emitCatalog())
5868
0
        return false;
5869
5870
#if HAVE_FEATURE_NSS
5871
    if (m_nSignatureObject != -1) // if document is signed, emit sigdict
5872
    {
5873
        if( !emitSignature() )
5874
        {
5875
            m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5876
            return false;
5877
        }
5878
    }
5879
#endif
5880
5881
    // emit trailer
5882
18.6k
    if (!emitTrailer())
5883
0
        return false;
5884
5885
#if HAVE_FEATURE_NSS
5886
    if (m_nSignatureObject != -1) // finalize the signature
5887
    {
5888
        if( !finalizeSignature() )
5889
        {
5890
            m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5891
            return false;
5892
        }
5893
    }
5894
#endif
5895
5896
18.6k
    m_aFile.close();
5897
18.6k
    m_bOpen = false;
5898
5899
18.6k
    return true;
5900
18.6k
}
5901
5902
5903
sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
5904
0
{
5905
0
    auto popIt = ScopedPush();
5906
5907
0
    SetFont( i_rFont );
5908
5909
0
    const LogicalFontInstance* pFontInstance = GetFontInstance();
5910
0
    const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
5911
0
    sal_Int32 nFontID = 0;
5912
0
    auto it = m_aSystemFonts.find( pFace );
5913
0
    if( it != m_aSystemFonts.end() )
5914
0
        nFontID = it->second.m_nNormalFontID;
5915
0
    else
5916
0
    {
5917
0
        nFontID = m_nNextFID++;
5918
0
        m_aSystemFonts[ pFace ] = EmbedFont();
5919
0
        m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
5920
0
        m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
5921
0
    }
5922
5923
0
    return nFontID;
5924
0
}
5925
5926
void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
5927
                                  const vcl::font::PhysicalFontFace* pFace,
5928
                                  const LogicalFontInstance* pFont,
5929
                                  const std::vector<sal_Ucs>& rCodeUnits,
5930
                                  sal_Int32 nGlyphWidth,
5931
                                  sal_uInt8& nMappedGlyph,
5932
                                  sal_Int32& nMappedFontObject)
5933
17.3M
{
5934
17.3M
    FontSubset& rSubset = m_aSubsets[ { pFace, pFont } ];
5935
5936
    // search for font specific glyphID
5937
17.3M
    auto it = rSubset.m_aMapping.find( nFontGlyphId );
5938
17.3M
    if( it != rSubset.m_aMapping.end() )
5939
16.9M
    {
5940
16.9M
        nMappedFontObject = it->second.m_nFontID;
5941
16.9M
        nMappedGlyph = it->second.m_nSubsetGlyphID;
5942
16.9M
    }
5943
406k
    else
5944
406k
    {
5945
        // create new subset if necessary
5946
406k
        if( rSubset.m_aSubsets.empty()
5947
386k
        || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
5948
20.2k
        {
5949
20.2k
            rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
5950
20.2k
        }
5951
5952
        // copy font id
5953
406k
        nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
5954
        // create new glyph in subset
5955
406k
        sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
5956
406k
        nMappedGlyph = nNewId;
5957
5958
        // add new glyph to emitted font subset
5959
406k
        GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
5960
406k
        rNewGlyphEmit.setGlyphId( nNewId );
5961
406k
        rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth));
5962
406k
        for (const auto nCode : rCodeUnits)
5963
383k
            rNewGlyphEmit.addCode(nCode);
5964
5965
        // add new glyph to font mapping
5966
406k
        Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
5967
406k
        rNewGlyph.m_nFontID = nMappedFontObject;
5968
406k
        rNewGlyph.m_nSubsetGlyphID = nNewId;
5969
406k
    }
5970
17.3M
}
5971
5972
void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId,
5973
                                  const vcl::font::PhysicalFontFace* pFace,
5974
                                  const LogicalFontInstance* pFont,
5975
                                  const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
5976
                                  sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
5977
17.3M
{
5978
17.3M
    if (pFace->IsColorFont())
5979
1.94k
    {
5980
        // Font has colors, check if this glyph has color layers or bitmap.
5981
1.94k
        tools::Rectangle aRect;
5982
1.94k
        auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
5983
1.94k
        auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
5984
        // tdf#173919: Some color fonts (e.g. old Noto Color Emoji with CBDT) have no outlines, but
5985
        // only color bitmaps, so their glyphs without bitmaps (e.g. space) must also go into the
5986
        // Type 3 font, otherwise they will go into a regular font and we will create a subset with
5987
        // no outline tables, which will be rejected by PDF readers.
5988
1.94k
        if (!aLayers.empty() || !aBitmap.empty() || !pFace->HasOutlines())
5989
1.72k
        {
5990
1.72k
            auto& rSubset = m_aType3Fonts[pFace];
5991
1.72k
            auto it = rSubset.m_aMapping.find(nFontGlyphId);
5992
1.72k
            if (it != rSubset.m_aMapping.end())
5993
1.69k
            {
5994
1.69k
                nMappedFontObject = it->second.m_nFontID;
5995
1.69k
                nMappedGlyph = it->second.m_nSubsetGlyphID;
5996
1.69k
            }
5997
32
            else
5998
32
            {
5999
                // create new subset if necessary
6000
32
                if (rSubset.m_aSubsets.empty()
6001
0
                    || (rSubset.m_aSubsets.back().m_aMapping.size() > 254))
6002
32
                {
6003
32
                    rSubset.m_aSubsets.emplace_back(m_nNextFID++);
6004
32
                }
6005
6006
                // copy font id
6007
32
                nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6008
                // create new glyph in subset
6009
32
                sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(
6010
32
                    rSubset.m_aSubsets.back().m_aMapping.size() + 1);
6011
32
                nMappedGlyph = nNewId;
6012
6013
                // add new glyph to emitted font subset
6014
32
                auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
6015
32
                rNewGlyphEmit.setGlyphId(nNewId);
6016
32
                rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
6017
32
                for (const auto nCode : rCodeUnits)
6018
32
                    rNewGlyphEmit.addCode(nCode);
6019
6020
                // add color layers to the glyphs
6021
32
                if (!aLayers.empty())
6022
0
                {
6023
0
                    for (const auto& aLayer : aLayers)
6024
0
                    {
6025
0
                        sal_uInt8 nLayerGlyph;
6026
0
                        sal_Int32 nLayerFontID;
6027
0
                        registerSimpleGlyph(aLayer.nGlyphIndex, pFace, pFont, rCodeUnits,
6028
0
                                            nGlyphWidth, nLayerGlyph, nLayerFontID);
6029
6030
0
                        rNewGlyphEmit.addColorLayer(
6031
0
                            { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
6032
0
                    }
6033
0
                }
6034
32
                else if (!aBitmap.empty())
6035
0
                    rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
6036
6037
                // add new glyph to font mapping
6038
32
                Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
6039
32
                rNewGlyph.m_nFontID = nMappedFontObject;
6040
32
                rNewGlyph.m_nSubsetGlyphID = nNewId;
6041
32
            }
6042
1.72k
            return;
6043
1.72k
        }
6044
1.94k
    }
6045
6046
    // If we reach here then the glyph has no color layers.
6047
17.3M
    registerSimpleGlyph(nFontGlyphId, pFace, pFont, rCodeUnits, nGlyphWidth, nMappedGlyph,
6048
17.3M
                        nMappedFontObject);
6049
17.3M
}
6050
6051
void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6052
0
{
6053
0
    push( PushFlags::ALL );
6054
6055
0
    FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
6056
6057
0
    Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
6058
0
    Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6059
0
    Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6060
0
    Color aReliefColor( COL_LIGHTGRAY );
6061
0
    if( aTextColor == COL_BLACK )
6062
0
        aTextColor = COL_WHITE;
6063
0
    if( aTextLineColor == COL_BLACK )
6064
0
        aTextLineColor = COL_WHITE;
6065
0
    if( aOverlineColor == COL_BLACK )
6066
0
        aOverlineColor = COL_WHITE;
6067
    // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
6068
0
    if( aTextColor == COL_WHITE )
6069
0
        aReliefColor = COL_BLACK;
6070
6071
0
    Font aSetFont = m_aCurrentPDFState.m_aFont;
6072
0
    aSetFont.SetRelief( FontRelief::NONE );
6073
0
    aSetFont.SetShadow( false );
6074
6075
0
    aSetFont.SetColor( aReliefColor );
6076
0
    setTextLineColor( aReliefColor );
6077
0
    setOverlineColor( aReliefColor );
6078
0
    setFont( aSetFont );
6079
0
    tools::Long nOff = 1 + GetDPIX()/300;
6080
0
    if( eRelief == FontRelief::Engraved )
6081
0
        nOff = -nOff;
6082
6083
0
    auto aPrevOffset = rLayout.DrawOffset();
6084
0
    rLayout.DrawOffset()
6085
0
        += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) };
6086
0
    updateGraphicsState();
6087
0
    drawLayout( rLayout, rText, bTextLines );
6088
6089
0
    rLayout.DrawOffset() = aPrevOffset;
6090
0
    setTextLineColor( aTextLineColor );
6091
0
    setOverlineColor( aOverlineColor );
6092
0
    aSetFont.SetColor( aTextColor );
6093
0
    setFont( aSetFont );
6094
0
    updateGraphicsState();
6095
0
    drawLayout( rLayout, rText, bTextLines );
6096
6097
    // clean up the mess
6098
0
    pop();
6099
0
}
6100
6101
void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6102
658
{
6103
658
    Font aSaveFont = m_aCurrentPDFState.m_aFont;
6104
658
    Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6105
658
    Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6106
6107
658
    Font& rFont = m_aCurrentPDFState.m_aFont;
6108
658
    if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
6109
222
        rFont.SetColor( COL_LIGHTGRAY );
6110
436
    else
6111
436
        rFont.SetColor( COL_BLACK );
6112
658
    rFont.SetShadow( false );
6113
658
    rFont.SetOutline( false );
6114
658
    setFont( rFont );
6115
658
    setTextLineColor( rFont.GetColor() );
6116
658
    setOverlineColor( rFont.GetColor() );
6117
658
    updateGraphicsState();
6118
6119
658
    tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
6120
658
    if( rFont.IsOutline() )
6121
0
        nOff++;
6122
658
    rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff);
6123
658
    drawLayout( rLayout, rText, bTextLines );
6124
658
    rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff);
6125
6126
658
    setFont( aSaveFont );
6127
658
    setTextLineColor( aSaveTextLineColor );
6128
658
    setOverlineColor( aSaveOverlineColor );
6129
658
    updateGraphicsState();
6130
658
}
6131
6132
void PDFWriterImpl::drawVerticalGlyphs(
6133
        const std::vector<PDFGlyph>& rGlyphs,
6134
        OStringBuffer& rLine,
6135
        const Point& rAlignOffset,
6136
        const Matrix3& rRotScale,
6137
        double fAngle,
6138
        double fXScale,
6139
        sal_Int32 nFontHeight)
6140
0
{
6141
0
    double nXOffset = 0;
6142
0
    Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
6143
0
    aCurPos += rAlignOffset;
6144
0
    for( size_t i = 0; i < rGlyphs.size(); i++ )
6145
0
    {
6146
        // have to emit each glyph on its own
6147
0
        double fDeltaAngle = 0.0;
6148
0
        double fYScale = 1.0;
6149
0
        double fTempXScale = fXScale;
6150
6151
0
        Point aDeltaPos;
6152
0
        if (rGlyphs[i].m_pGlyph->IsVertical())
6153
0
        {
6154
0
            fDeltaAngle = M_PI/2.0;
6155
0
            fYScale = fXScale;
6156
0
            fTempXScale = 1.0;
6157
0
        }
6158
0
        aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
6159
0
        if( i < rGlyphs.size()-1 )
6160
        // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
6161
0
        {
6162
0
            double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
6163
0
            double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
6164
0
            nXOffset += std::hypot(nOffsetX, nOffsetY);
6165
0
        }
6166
0
        if (!rGlyphs[i].m_pGlyph->glyphId())
6167
0
            continue;
6168
6169
0
        aDeltaPos = rRotScale.transform( aDeltaPos );
6170
6171
0
        Matrix3 aMat;
6172
        // perform artificial italics if necessary
6173
0
        if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
6174
0
            aMat.skew(0.0, ARTIFICIAL_ITALIC_SKEW);
6175
0
        aMat.scale( fTempXScale, fYScale );
6176
0
        aMat.rotate( fAngle+fDeltaAngle );
6177
0
        aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
6178
0
        m_aPages.back().appendMatrix3(aMat, rLine);
6179
0
        rLine.append( " Tm" );
6180
0
        if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
6181
0
        {
6182
0
            rLine.append( " /F" );
6183
0
            rLine.append( rGlyphs[i].m_nMappedFontId );
6184
0
            rLine.append( ' ' );
6185
0
            m_aPages.back().appendMappedLength( nFontHeight, rLine );
6186
0
            rLine.append( " Tf" );
6187
0
        }
6188
0
        rLine.append( "<" );
6189
0
        COSWriter::appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
6190
0
        rLine.append( ">Tj\n" );
6191
0
    }
6192
0
}
6193
6194
void PDFWriterImpl::drawHorizontalGlyphs(
6195
        const std::vector<PDFGlyph>& rGlyphs,
6196
        OStringBuffer& rLine,
6197
        const Point& rAlignOffset,
6198
        bool bFirst,
6199
        double fAngle,
6200
        double fXScale,
6201
        sal_Int32 nFontHeight,
6202
        sal_Int32 nPixelFontHeight)
6203
1.19M
{
6204
    // horizontal (= normal) case
6205
6206
    // fill in  run end indices
6207
    // end is marked by index of the first glyph of the next run
6208
    // a run is marked by same mapped font id and same Y position
6209
1.19M
    std::vector< sal_uInt32 > aRunEnds;
6210
1.19M
    aRunEnds.reserve( rGlyphs.size() );
6211
17.3M
    for( size_t i = 1; i < rGlyphs.size(); i++ )
6212
16.1M
    {
6213
16.1M
        if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6214
16.1M
            rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont ||
6215
16.1M
            rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
6216
824
        {
6217
824
            aRunEnds.push_back(i);
6218
824
        }
6219
16.1M
    }
6220
    // last run ends at last glyph
6221
1.19M
    aRunEnds.push_back( rGlyphs.size() );
6222
6223
    // loop over runs of the same font
6224
1.19M
    sal_uInt32 nBeginRun = 0;
6225
2.38M
    for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6226
1.19M
    {
6227
        // setup text matrix back transformed to current coordinate system
6228
1.19M
        Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
6229
1.19M
        aCurPos += rAlignOffset;
6230
6231
        // perform artificial italics if necessary
6232
1.19M
        double fSkew = 0.0;
6233
1.19M
        if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
6234
0
            fSkew = ARTIFICIAL_ITALIC_SKEW;
6235
6236
        // the first run can be set with "Td" operator
6237
        // subsequent use of that operator would move
6238
        // the textline matrix relative to what was set before
6239
        // making use of that would drive us into rounding issues
6240
1.19M
        Matrix3 aMat;
6241
1.19M
        if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6242
704k
        {
6243
704k
            m_aPages.back().appendPoint( aCurPos, rLine );
6244
704k
            rLine.append( " Td " );
6245
704k
        }
6246
487k
        else
6247
487k
        {
6248
487k
            if( fSkew != 0.0 )
6249
0
                aMat.skew( 0.0, fSkew );
6250
487k
            aMat.scale( fXScale, 1.0 );
6251
487k
            aMat.rotate( fAngle );
6252
487k
            aMat.translate( aCurPos.X(), aCurPos.Y() );
6253
487k
            m_aPages.back().appendMatrix3(aMat, rLine);
6254
487k
            rLine.append( " Tm\n" );
6255
487k
        }
6256
        // set up correct font
6257
1.19M
        rLine.append( "/F" );
6258
1.19M
        rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6259
1.19M
        rLine.append( ' ' );
6260
1.19M
        m_aPages.back().appendMappedLength( nFontHeight, rLine );
6261
1.19M
        rLine.append( " Tf" );
6262
6263
        // output glyphs using Tj or TJ
6264
1.19M
        OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6265
1.19M
        aKernedLine.append( "[<" );
6266
1.19M
        aUnkernedLine.append( '<' );
6267
1.19M
        COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6268
1.19M
        COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6269
6270
1.19M
        aMat.invert();
6271
1.19M
        bool bNeedKern = false;
6272
17.3M
        for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6273
16.1M
        {
6274
16.1M
            COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6275
            // check if default glyph positioning is sufficient
6276
16.1M
            const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6277
16.1M
            const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6278
16.1M
            double fAdvance = aThisPos.getX() - aPrevPos.getX();
6279
16.1M
            fAdvance *= 1000.0 / nPixelFontHeight;
6280
16.1M
            const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6281
16.1M
            SAL_WARN_IF(
6282
16.1M
                fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6283
16.1M
                "adjustment " << fAdjustment << " outside 32-bit int");
6284
16.1M
            const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6285
16.1M
                std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6286
16.1M
            if( nAdjustment != 0 )
6287
9.66M
            {
6288
                // apply individual glyph positioning
6289
9.66M
                bNeedKern = true;
6290
9.66M
                aKernedLine.append( ">" );
6291
9.66M
                aKernedLine.append( nAdjustment );
6292
9.66M
                aKernedLine.append( "<" );
6293
9.66M
            }
6294
16.1M
            COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6295
16.1M
        }
6296
1.19M
        aKernedLine.append( ">]TJ\n" );
6297
1.19M
        aUnkernedLine.append( ">Tj\n" );
6298
1.19M
        rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
6299
6300
        // set beginning of next run
6301
1.19M
        nBeginRun = aRunEnds[nRun];
6302
1.19M
    }
6303
1.19M
}
6304
6305
void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6306
704k
{
6307
    // relief takes precedence over shadow (see outdev3.cxx)
6308
704k
    if(  m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6309
0
    {
6310
0
        drawRelief( rLayout, rText, bTextLines );
6311
0
        return;
6312
0
    }
6313
704k
    else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6314
658
        drawShadow( rLayout, rText, bTextLines );
6315
6316
704k
    OStringBuffer aLine( 512 );
6317
6318
704k
    const int nMaxGlyphs = 256;
6319
6320
704k
    std::vector<sal_Ucs> aCodeUnits;
6321
704k
    bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6322
704k
    int nIndex = 0;
6323
704k
    double fXScale = 1.0;
6324
704k
    sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6325
704k
    TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6326
6327
    // transform font height back to current units
6328
    // note: the layout calculates in outdevs device pixel !!
6329
704k
    sal_Int32 nFontHeight = DevicePixelToLogicHeight(nPixelFontHeight);
6330
704k
    if( m_aCurrentPDFState.m_aFont.GetFontWidth() )
6331
0
    {
6332
0
        Font aFont( m_aCurrentPDFState.m_aFont );
6333
0
        aFont.SetFontWidth( 0 );
6334
0
        FontMetric aMetric = GetFontMetric( aFont );
6335
0
        if( aMetric.GetFontWidth() != m_aCurrentPDFState.m_aFont.GetFontWidth() )
6336
0
        {
6337
0
            fXScale =
6338
0
                static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontWidth()) /
6339
0
                static_cast<double>(aMetric.GetFontWidth());
6340
0
        }
6341
0
    }
6342
6343
    // if the mapmode is distorted we need to adjust for that also
6344
704k
    if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6345
21
    {
6346
21
        fXScale *= m_aCurrentPDFState.m_aMapMode.GetScaleX() / m_aCurrentPDFState.m_aMapMode.GetScaleY();
6347
21
    }
6348
6349
704k
    Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6350
    // normalize angles
6351
704k
    while( nAngle < 0_deg10 )
6352
2
        nAngle += 3600_deg10;
6353
704k
    nAngle = nAngle % 3600_deg10;
6354
704k
    double fAngle = toRadians(nAngle);
6355
6356
704k
    Matrix3 aRotScale;
6357
704k
    aRotScale.scale( fXScale, 1.0 );
6358
704k
    if( fAngle != 0.0 )
6359
2
        aRotScale.rotate( -fAngle );
6360
6361
704k
    bool bPop = false;
6362
704k
    bool bABold = false;
6363
    // artificial bold necessary ?
6364
704k
    if (GetFontInstance()->NeedsArtificialBold())
6365
0
    {
6366
0
        aLine.append("q ");
6367
0
        bPop = true;
6368
0
        bABold = true;
6369
0
    }
6370
    // setup text colors (if necessary)
6371
704k
    Color aStrokeColor( COL_TRANSPARENT );
6372
704k
    Color aNonStrokeColor( COL_TRANSPARENT );
6373
6374
704k
    if( m_aCurrentPDFState.m_aFont.IsOutline() )
6375
73
    {
6376
73
        aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6377
73
        aNonStrokeColor = COL_WHITE;
6378
73
    }
6379
704k
    else
6380
704k
        aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6381
704k
    if( bABold )
6382
0
        aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6383
6384
704k
    if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6385
73
    {
6386
73
        if( ! bPop )
6387
73
            aLine.append( "q " );
6388
73
        bPop = true;
6389
73
        appendStrokingColor( aStrokeColor, aLine );
6390
73
        aLine.append( "\n" );
6391
73
    }
6392
704k
    if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6393
695k
    {
6394
695k
        if( ! bPop )
6395
695k
            aLine.append( "q " );
6396
695k
        bPop = true;
6397
695k
        appendNonStrokingColor( aNonStrokeColor, aLine );
6398
695k
        aLine.append( "\n" );
6399
695k
    }
6400
6401
    // begin text object
6402
704k
    aLine.append( "BT\n" );
6403
    // outline attribute ?
6404
704k
    if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6405
73
    {
6406
        // set correct text mode, set stroke width
6407
73
        aLine.append( "2 Tr " ); // fill, then stroke
6408
6409
73
        if( m_aCurrentPDFState.m_aFont.IsOutline() )
6410
73
        {
6411
            // unclear what to do in case of outline and artificial bold
6412
            // for the time being outline wins
6413
73
            aLine.append( "0.25 w \n" );
6414
73
        }
6415
0
        else
6416
0
        {
6417
0
            double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6418
0
            m_aPages.back().appendMappedLength( fW, aLine );
6419
0
            aLine.append ( " w\n" );
6420
0
        }
6421
73
    }
6422
6423
704k
    FontMetric aRefDevFontMetric = GetFontMetric();
6424
704k
    const GlyphItem* pGlyph = nullptr;
6425
704k
    const LogicalFontInstance* pGlyphFont = nullptr;
6426
6427
    // collect the glyphs into a single array
6428
704k
    std::vector< PDFGlyph > aGlyphs;
6429
704k
    aGlyphs.reserve( nMaxGlyphs );
6430
    // first get all the glyphs and register them; coordinates still in Pixel
6431
704k
    basegfx::B2DPoint aPos;
6432
18.0M
    while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6433
17.3M
    {
6434
17.3M
        const auto* pFace = pGlyphFont->GetFontFace();
6435
6436
17.3M
        aCodeUnits.clear();
6437
6438
        // tdf#66597, tdf#115117
6439
        //
6440
        // Here is how we embed textual content in PDF files, to allow for
6441
        // better text extraction for complex and typography-rich text.
6442
        //
6443
        // * If there is many to one or many to many mapping, use an
6444
        //   ActualText span embedding the original string, since ToUnicode
6445
        //   can't handle these.
6446
        // * If the one glyph is used for several Unicode code points, also
6447
        //   use ActualText since ToUnicode can map each glyph in the font
6448
        //   only once.
6449
        // * Limit ActualText to single cluster at a time, since using it
6450
        //   for whole words or sentences breaks text selection and
6451
        //   highlighting in PDF viewers (there will be no way to tell
6452
        //   which glyphs belong to which characters).
6453
6454
17.3M
        assert(pGlyph->charCount() >= 0);
6455
32.6M
        for (int n = 0; n < pGlyph->charCount(); n++)
6456
15.3M
            aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6457
6458
17.3M
        bool bUseActualText = false;
6459
6460
        // If this is a start of complex cluster, use ActualText.
6461
17.3M
        if (pGlyph->IsClusterStart())
6462
12.8k
            bUseActualText = true;
6463
6464
17.3M
        const auto nGlyphId = pGlyph->glyphId();
6465
6466
        // A glyph can't have more than one ToUnicode entry, use ActualText
6467
        // instead.
6468
17.3M
        if (!aCodeUnits.empty() && !bUseActualText)
6469
15.2M
        {
6470
15.2M
            for (const auto& rSubset : m_aSubsets[{ pFace, pGlyphFont }].m_aSubsets)
6471
15.2M
            {
6472
15.2M
                const auto it = rSubset.m_aMapping.find(nGlyphId);
6473
15.2M
                if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6474
456k
                    bUseActualText = true;
6475
15.2M
            }
6476
15.2M
        }
6477
6478
        // If we are using ActualText, then ToUnicode mapping is broken, drop it.
6479
17.3M
        if (bUseActualText)
6480
469k
            aCodeUnits.clear();
6481
6482
        // tdf#157390: The width stored by registerGlyph() must be the actual glyph width.
6483
        // This must be obtained by calling GetGlyphWidth(vertical=false), otherwise an incorrect
6484
        // width value will be returned for CJK characters.
6485
17.3M
        auto nMappedGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ false, false);
6486
17.3M
        auto nGlyphWidth = nMappedGlyphWidth;
6487
17.3M
        if (pGlyph->IsVertical())
6488
0
        {
6489
0
            nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ true, false);
6490
0
        }
6491
6492
17.3M
        sal_uInt8 nMappedGlyph;
6493
17.3M
        sal_Int32 nMappedFontObject;
6494
17.3M
        registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nMappedGlyphWidth, nMappedGlyph,
6495
17.3M
                      nMappedFontObject);
6496
6497
17.3M
        int nCharPos = -1;
6498
17.3M
        if (bUseActualText || pGlyph->IsInCluster())
6499
2.54M
            nCharPos = pGlyph->charPos();
6500
6501
17.3M
        aGlyphs.emplace_back(aPos,
6502
17.3M
                             pGlyph,
6503
17.3M
                             pGlyphFont,
6504
17.3M
                             XUnits(pFace->UnitsPerEm(), nGlyphWidth),
6505
17.3M
                             nMappedFontObject,
6506
17.3M
                             nMappedGlyph,
6507
17.3M
                             nCharPos);
6508
17.3M
    }
6509
6510
    // Avoid fill color when map mode is in pixels, the below code assumes
6511
    // logic map mode.
6512
704k
    bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6513
704k
    if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6514
0
    {
6515
        // PDF doesn't have a text fill color, so draw a rectangle before
6516
        // drawing the actual text.
6517
0
        push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6518
0
        setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6519
        // Avoid border around the rectangle for Writer shape text.
6520
0
        setLineColor(COL_TRANSPARENT);
6521
6522
        // The rectangle is the bounding box of the text, but also includes
6523
        // ascent / descent to match the on-screen rendering.
6524
        // This is the top left of the text without ascent / descent.
6525
0
        basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
6526
0
        tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
6527
0
                                    Size(DevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
6528
0
        aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
6529
        // This includes ascent / descent.
6530
0
        aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6531
6532
0
        const LogicalFontInstance* pFontInstance = GetFontInstance();
6533
0
        if (pFontInstance->mnOrientation)
6534
0
        {
6535
            // Adapt rectangle for rotated text.
6536
0
            tools::Polygon aPolygon(aRectangle);
6537
0
            aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation);
6538
0
            drawPolygon(aPolygon);
6539
0
        }
6540
0
        else
6541
0
            drawRectangle(aRectangle);
6542
6543
0
        pop();
6544
0
    }
6545
6546
704k
    Point aAlignOffset;
6547
704k
    if ( eAlign == ALIGN_BOTTOM )
6548
0
        aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6549
704k
    else if ( eAlign == ALIGN_TOP )
6550
8.97k
        aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6551
704k
    if( aAlignOffset.X() || aAlignOffset.Y() )
6552
3.62k
        aAlignOffset = aRotScale.transform( aAlignOffset );
6553
6554
    /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6555
       string contained only one of the UTF16 BOMs
6556
    */
6557
704k
    if( ! aGlyphs.empty() )
6558
704k
    {
6559
704k
        size_t nStart = 0;
6560
704k
        size_t nEnd = 0;
6561
1.89M
        while (nStart < aGlyphs.size())
6562
1.19M
        {
6563
18.5M
            while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6564
17.3M
                nEnd++;
6565
6566
1.19M
            std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6567
6568
1.19M
            int nCharPos, nCharCount;
6569
1.19M
            if (!aRun.front().m_pGlyph->IsRTLGlyph())
6570
1.12M
            {
6571
1.12M
                nCharPos = aRun.front().m_nCharPos;
6572
1.12M
                nCharCount = aRun.front().m_pGlyph->charCount();
6573
1.12M
            }
6574
66.1k
            else
6575
66.1k
            {
6576
66.1k
                nCharPos = aRun.back().m_nCharPos;
6577
66.1k
                nCharCount = aRun.back().m_pGlyph->charCount();
6578
66.1k
            }
6579
6580
1.19M
            if (nCharPos >= 0 && nCharCount)
6581
469k
            {
6582
469k
                aLine.append("/Span<</ActualText<FEFF");
6583
950k
                for (int i = 0; i < nCharCount; i++)
6584
480k
                {
6585
480k
                    sal_Unicode aChar = rText[nCharPos + i];
6586
480k
                    COSWriter::appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6587
480k
                    COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6588
480k
                }
6589
469k
                aLine.append( ">>>\nBDC\n" );
6590
469k
            }
6591
6592
1.19M
            if (bVertical)
6593
0
                drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight);
6594
1.19M
            else
6595
1.19M
                drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight);
6596
6597
1.19M
            if (nCharPos >= 0 && nCharCount)
6598
469k
                aLine.append( "EMC\n" );
6599
6600
1.19M
            nStart = nEnd;
6601
1.19M
        }
6602
704k
    }
6603
6604
    // end textobject
6605
704k
    aLine.append( "ET\n" );
6606
704k
    if( bPop )
6607
695k
        aLine.append( "Q\n" );
6608
6609
704k
    writeBuffer( aLine );
6610
6611
    // draw eventual textlines
6612
704k
    FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6613
704k
    FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6614
704k
    FontLineStyle eOverline  = m_aCurrentPDFState.m_aFont.GetOverline();
6615
704k
    if( bTextLines &&
6616
704k
        (
6617
704k
         ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6618
701k
         ( eOverline  != LINESTYLE_NONE && eOverline  != LINESTYLE_DONTKNOW ) ||
6619
701k
         ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6620
704k
         )
6621
704k
        )
6622
3.34k
    {
6623
3.34k
        bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
6624
3.34k
        if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6625
116
        {
6626
116
            basegfx::B2DPoint aStartPt;
6627
116
            double nWidth = 0;
6628
116
            nIndex = 0;
6629
3.22k
            while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6630
3.11k
            {
6631
3.11k
                if (!pGlyph->IsSpacing())
6632
2.69k
                {
6633
2.69k
                    if( !nWidth )
6634
482
                        aStartPt = aPos;
6635
6636
2.69k
                    nWidth += pGlyph->newWidth();
6637
2.69k
                }
6638
422
                else if( nWidth > 0 )
6639
405
                {
6640
405
                    drawTextLine( SubPixelToLogic(aStartPt),
6641
405
                                  DevicePixelToLogicWidth(nWidth),
6642
405
                                  eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6643
405
                    nWidth = 0;
6644
405
                }
6645
3.11k
            }
6646
6647
116
            if( nWidth > 0 )
6648
77
            {
6649
77
                drawTextLine( SubPixelToLogic(aStartPt),
6650
77
                              DevicePixelToLogicWidth(nWidth),
6651
77
                              eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6652
77
            }
6653
116
        }
6654
3.22k
        else
6655
3.22k
        {
6656
3.22k
            basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
6657
3.22k
            int nWidth = rLayout.GetTextWidth();
6658
3.22k
            drawTextLine( SubPixelToLogic(aStartPt),
6659
3.22k
                          DevicePixelToLogicWidth(nWidth),
6660
3.22k
                          eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6661
3.22k
        }
6662
3.34k
    }
6663
6664
    // write eventual emphasis marks
6665
704k
    if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
6666
704k
        return;
6667
6668
0
    push( PushFlags::ALL );
6669
6670
0
    aLine.setLength( 0 );
6671
0
    aLine.append( "q\n" );
6672
6673
0
    FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
6674
6675
0
    tools::Long nEmphHeight;
6676
0
    if ( nEmphMark & FontEmphasisMark::PosBelow )
6677
0
        nEmphHeight = GetEmphasisDescent();
6678
0
    else
6679
0
        nEmphHeight = GetEmphasisAscent();
6680
6681
0
    vcl::font::EmphasisMark aEmphasisMark(nEmphMark, DevicePixelToLogicWidth(nEmphHeight), GetDPIY());
6682
0
    if ( aEmphasisMark.IsShapePolyLine() )
6683
0
    {
6684
0
        setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
6685
0
        setFillColor( COL_TRANSPARENT );
6686
0
    }
6687
0
    else
6688
0
    {
6689
0
        setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
6690
0
        setLineColor( COL_TRANSPARENT );
6691
0
    }
6692
6693
0
    writeBuffer( aLine );
6694
6695
0
    Point aOffset(0,0);
6696
0
    Point aOffsetVert(0,0);
6697
6698
0
    if ( nEmphMark & FontEmphasisMark::PosBelow )
6699
0
    {
6700
0
        aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
6701
0
        aOffsetVert = aOffset;
6702
0
    }
6703
0
    else
6704
0
    {
6705
0
        aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
6706
        // Todo: use ideographic em-box or ideographic character face information.
6707
0
        aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
6708
0
                    GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
6709
0
    }
6710
6711
0
    tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2;
6712
0
    tools::Long nEmphHeight2 = nEmphHeight / 2;
6713
0
    aOffset += Point( nEmphWidth2, nEmphHeight2 );
6714
6715
0
    if ( eAlign == ALIGN_BOTTOM )
6716
0
        aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
6717
0
    else if ( eAlign == ALIGN_TOP )
6718
0
        aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
6719
6720
0
    basegfx::B2DRectangle aRectangle;
6721
0
    nIndex = 0;
6722
0
    while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6723
0
    {
6724
0
        if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
6725
0
            continue;
6726
6727
0
        if (!pGlyph->IsSpacing())
6728
0
        {
6729
0
            basegfx::B2DPoint aAdjOffset;
6730
0
            if (pGlyph->IsVertical())
6731
0
            {
6732
0
                aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
6733
0
                aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
6734
0
            }
6735
0
            else
6736
0
            {
6737
0
                aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
6738
0
                aAdjOffset.adjustX(aRectangle.getMinX() + (aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 );
6739
0
            }
6740
6741
0
            aAdjOffset = aRotScale.transform( aAdjOffset );
6742
6743
0
            aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
6744
6745
0
            basegfx::B2DPoint aMarkDevPos(aPos);
6746
0
            aMarkDevPos += aAdjOffset;
6747
0
            Point aMarkPos = SubPixelToLogic(aMarkDevPos);
6748
0
            drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
6749
0
                              aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
6750
0
                              aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
6751
0
        }
6752
0
    }
6753
6754
0
    writeBuffer( "Q\n" );
6755
0
    pop();
6756
6757
0
}
6758
6759
void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
6760
                                      const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
6761
                                      const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
6762
0
{
6763
    // TODO: pass nWidth as width of this mark
6764
    // long nWidth = 0;
6765
6766
0
    if ( rPolyPoly.Count() )
6767
0
    {
6768
0
        if ( bPolyLine )
6769
0
        {
6770
0
            tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
6771
0
            aPoly.Move( nX, nY );
6772
0
            drawPolyLine( aPoly );
6773
0
        }
6774
0
        else
6775
0
        {
6776
0
            tools::PolyPolygon aPolyPoly = rPolyPoly;
6777
0
            aPolyPoly.Move( nX, nY );
6778
0
            drawPolyPolygon( aPolyPoly );
6779
0
        }
6780
0
    }
6781
6782
0
    if ( !rRect1.IsEmpty() )
6783
0
    {
6784
0
        tools::Rectangle aRect( Point( nX+rRect1.Left(),
6785
0
                                nY+rRect1.Top() ), rRect1.GetSize() );
6786
0
        drawRectangle( aRect );
6787
0
    }
6788
6789
0
    if ( !rRect2.IsEmpty() )
6790
0
    {
6791
0
        tools::Rectangle aRect( Point( nX+rRect2.Left(),
6792
0
                                nY+rRect2.Top() ), rRect2.GetSize() );
6793
6794
0
        drawRectangle( aRect );
6795
0
    }
6796
0
}
6797
6798
void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
6799
12.4k
{
6800
12.4k
    MARK( "drawText" );
6801
6802
12.4k
    updateGraphicsState();
6803
6804
    // get a layout from the OutputDevice's SalGraphics
6805
    // this also enforces font substitution and sets the font on SalGraphics
6806
12.4k
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
6807
12.4k
        GetLayoutGlyphs( this, rText, nIndex, nLen );
6808
12.4k
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
6809
12.4k
        0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
6810
12.4k
    if( pLayout )
6811
8.97k
    {
6812
8.97k
        drawLayout( *pLayout, rText, bTextLines );
6813
8.97k
    }
6814
12.4k
}
6815
6816
void PDFWriterImpl::drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray,
6817
                                  std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
6818
                                  sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
6819
                                  sal_Int32 nLayoutContextLen)
6820
693k
{
6821
693k
    MARK( "drawText with array" );
6822
6823
693k
    updateGraphicsState();
6824
6825
    // get a layout from the OutputDevice's SalGraphics
6826
    // this also enforces font substitution and sets the font on SalGraphics
6827
693k
    std::unique_ptr<SalLayout> pLayout;
6828
693k
    if (nLayoutContextIndex >= 0)
6829
526k
    {
6830
526k
        const auto* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
6831
526k
            this, rText, nLayoutContextIndex, nLayoutContextLen, nIndex, nIndex + nLen);
6832
526k
        pLayout = ImplLayout(rText, nLayoutContextIndex, nLayoutContextLen, rPos, 0, pDXArray,
6833
526k
                             pKashidaArray, SalLayoutFlags::UnclusteredGlyphs, nullptr,
6834
526k
                             layoutGlyphs, /*nDrawOriginCluster=*/nIndex,
6835
526k
                             /*nDrawMinCharPos=*/nIndex, /*nDrawEndCharPos=*/nIndex + nLen);
6836
526k
    }
6837
166k
    else
6838
166k
    {
6839
166k
        const auto* layoutGlyphs
6840
166k
            = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(this, rText, nIndex, nLen);
6841
166k
        pLayout = ImplLayout(rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
6842
166k
                             SalLayoutFlags::NONE, nullptr, layoutGlyphs);
6843
166k
    }
6844
6845
693k
    if( pLayout )
6846
693k
    {
6847
693k
        drawLayout( *pLayout, rText, true );
6848
693k
    }
6849
693k
}
6850
6851
void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
6852
1.87k
{
6853
1.87k
    MARK( "drawStretchText" );
6854
6855
1.87k
    updateGraphicsState();
6856
6857
    // get a layout from the OutputDevice's SalGraphics
6858
    // this also enforces font substitution and sets the font on SalGraphics
6859
1.87k
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
6860
1.87k
        GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
6861
1.87k
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
6862
1.87k
        {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
6863
1.87k
    if( pLayout )
6864
1.87k
    {
6865
1.87k
        drawLayout( *pLayout, rText, true );
6866
1.87k
    }
6867
1.87k
}
6868
6869
void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
6870
0
{
6871
0
    tools::Long        nWidth          = rRect.GetWidth();
6872
0
    tools::Long        nHeight         = rRect.GetHeight();
6873
6874
0
    if ( nWidth <= 0 || nHeight <= 0 )
6875
0
        return;
6876
6877
0
    MARK( "drawText with rectangle" );
6878
6879
0
    updateGraphicsState();
6880
6881
    // clip with rectangle
6882
0
    OStringBuffer aLine;
6883
0
    aLine.append( "q " );
6884
0
    m_aPages.back().appendRect( rRect, aLine );
6885
0
    aLine.append( " W* n\n" );
6886
0
    writeBuffer( aLine );
6887
6888
    // if disabled text is needed, put in here
6889
6890
0
    Point       aPos            = rRect.TopLeft();
6891
6892
0
    tools::Long        nTextHeight     = GetTextHeight();
6893
6894
0
    OUString aStr = rOrigStr;
6895
0
    if ( nStyle & DrawTextFlags::Mnemonic )
6896
0
        aStr = removeMnemonicFromString(aStr);
6897
6898
    // multiline text
6899
0
    if ( nStyle & DrawTextFlags::MultiLine )
6900
0
    {
6901
0
        ImplMultiTextLineInfo   aMultiLineInfo;
6902
0
        sal_Int32               i;
6903
0
        sal_Int32               nFormatLines;
6904
6905
0
        if ( nTextHeight )
6906
0
        {
6907
0
            vcl::DefaultTextLayout aLayout( *this );
6908
0
            OUString               aLastLine;
6909
0
            aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle );
6910
0
            sal_Int32              nLines = nHeight/nTextHeight;
6911
0
            nFormatLines = aMultiLineInfo.Count();
6912
0
            if ( !nLines )
6913
0
                nLines = 1;
6914
0
            if ( nFormatLines > nLines )
6915
0
            {
6916
0
                if ( nStyle & DrawTextFlags::EndEllipsis )
6917
0
                {
6918
                    // handle last line
6919
0
                    nFormatLines = nLines-1;
6920
6921
0
                    ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
6922
0
                    aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
6923
                    // replace line feed by space
6924
0
                    aLastLine = aLastLine.replace('\n', ' ');
6925
0
                    aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
6926
0
                    nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
6927
0
                    nStyle |= DrawTextFlags::Top;
6928
0
                }
6929
0
            }
6930
6931
            // vertical alignment
6932
0
            if ( nStyle & DrawTextFlags::Bottom )
6933
0
                aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
6934
0
            else if ( nStyle & DrawTextFlags::VCenter )
6935
0
                aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
6936
6937
            // draw all lines excluding the last
6938
0
            for ( i = 0; i < nFormatLines; i++ )
6939
0
            {
6940
0
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
6941
0
                if ( nStyle & DrawTextFlags::Right )
6942
0
                    aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
6943
0
                else if ( nStyle & DrawTextFlags::Center )
6944
0
                    aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
6945
0
                sal_Int32 nIndex = rLineInfo.GetIndex();
6946
0
                sal_Int32 nLineLen = rLineInfo.GetLen();
6947
0
                drawText( aPos, aStr, nIndex, nLineLen );
6948
                // mnemonics should not appear in documents,
6949
                // if the need arises, put them in here
6950
0
                aPos.AdjustY(nTextHeight );
6951
0
                aPos.setX( rRect.Left() );
6952
0
            }
6953
6954
            // output last line left adjusted since it was shortened
6955
0
            if (!aLastLine.isEmpty())
6956
0
                drawText( aPos, aLastLine, 0, aLastLine.getLength() );
6957
0
        }
6958
0
    }
6959
0
    else
6960
0
    {
6961
0
        tools::Long nTextWidth = GetTextWidth( aStr );
6962
6963
        // Evt. Text kuerzen
6964
0
        if ( nTextWidth > nWidth )
6965
0
        {
6966
0
            if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
6967
0
            {
6968
0
                aStr = GetEllipsisString( aStr, nWidth, nStyle );
6969
0
                nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
6970
0
                nStyle |= DrawTextFlags::Left;
6971
0
                nTextWidth = GetTextWidth( aStr );
6972
0
            }
6973
0
        }
6974
6975
        // vertical alignment
6976
0
        if ( nStyle & DrawTextFlags::Right )
6977
0
            aPos.AdjustX(nWidth-nTextWidth );
6978
0
        else if ( nStyle & DrawTextFlags::Center )
6979
0
            aPos.AdjustX((nWidth-nTextWidth)/2 );
6980
6981
0
        if ( nStyle & DrawTextFlags::Bottom )
6982
0
            aPos.AdjustY(nHeight-nTextHeight );
6983
0
        else if ( nStyle & DrawTextFlags::VCenter )
6984
0
            aPos.AdjustY((nHeight-nTextHeight)/2 );
6985
6986
        // mnemonics should be inserted here if the need arises
6987
6988
        // draw the actual text
6989
0
        drawText( aPos, aStr, 0, aStr.getLength() );
6990
0
    }
6991
6992
    // reset clip region to original value
6993
0
    aLine.setLength( 0 );
6994
0
    aLine.append( "Q\n" );
6995
0
    writeBuffer( aLine );
6996
0
}
6997
6998
void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
6999
0
{
7000
0
    MARK( "drawLine" );
7001
7002
0
    updateGraphicsState();
7003
7004
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7005
0
        return;
7006
7007
0
    OStringBuffer aLine;
7008
0
    m_aPages.back().appendPoint( rStart, aLine );
7009
0
    aLine.append( " m " );
7010
0
    m_aPages.back().appendPoint( rStop, aLine );
7011
0
    aLine.append( " l S\n" );
7012
7013
0
    writeBuffer( aLine );
7014
0
}
7015
7016
void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
7017
0
{
7018
0
    MARK( "drawLine with LineInfo" );
7019
0
    updateGraphicsState();
7020
7021
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7022
0
        return;
7023
7024
0
    if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
7025
0
    {
7026
0
        drawLine( rStart, rStop );
7027
0
        return;
7028
0
    }
7029
7030
0
    OStringBuffer aLine;
7031
7032
0
    aLine.append( "q " );
7033
0
    if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7034
0
    {
7035
0
        m_aPages.back().appendPoint( rStart, aLine );
7036
0
        aLine.append( " m " );
7037
0
        m_aPages.back().appendPoint( rStop, aLine );
7038
0
        aLine.append( " l S Q\n" );
7039
7040
0
        writeBuffer( aLine );
7041
0
    }
7042
0
    else
7043
0
    {
7044
0
        PDFWriter::ExtLineInfo aInfo;
7045
0
        convertLineInfoToExtLineInfo( rInfo, aInfo );
7046
0
        Point aPolyPoints[2] = { rStart, rStop };
7047
0
        tools::Polygon aPoly( 2, aPolyPoints );
7048
0
        drawPolyLine( aPoly, aInfo );
7049
0
    }
7050
0
}
7051
7052
11.1k
#define HCONV(x) DevicePixelToLogicHeight(x)
7053
7054
void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7055
0
{
7056
    // note: units in pFontInstance are ref device pixel
7057
0
    const LogicalFontInstance*  pFontInstance = GetFontInstance();
7058
0
    tools::Long            nLineHeight = 0;
7059
0
    tools::Long            nLinePos = 0;
7060
7061
0
    appendStrokingColor( aColor, aLine );
7062
0
    aLine.append( "\n" );
7063
7064
0
    if ( bIsAbove )
7065
0
    {
7066
0
        if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
7067
0
            ImplInitAboveTextLineSize();
7068
0
        nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
7069
0
        nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
7070
0
    }
7071
0
    else
7072
0
    {
7073
0
        if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
7074
0
            ImplInitTextLineSize();
7075
0
        nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
7076
0
        nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
7077
0
    }
7078
0
    if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
7079
0
        nLineHeight = 3;
7080
7081
0
    tools::Long nLineWidth = GetDPIX()/450;
7082
0
    if ( ! nLineWidth )
7083
0
        nLineWidth = 1;
7084
7085
0
    if ( eTextLine == LINESTYLE_BOLDWAVE )
7086
0
        nLineWidth = 3*nLineWidth;
7087
7088
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
7089
0
    aLine.append( " w " );
7090
7091
0
    if ( eTextLine == LINESTYLE_DOUBLEWAVE )
7092
0
    {
7093
0
        tools::Long nOrgLineHeight = nLineHeight;
7094
0
        nLineHeight /= 3;
7095
0
        if ( nLineHeight < 2 )
7096
0
        {
7097
0
            if ( nOrgLineHeight > 1 )
7098
0
                nLineHeight = 2;
7099
0
            else
7100
0
                nLineHeight = 1;
7101
0
        }
7102
0
        tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
7103
0
        if ( nLineDY < nLineWidth )
7104
0
            nLineDY = nLineWidth;
7105
0
        tools::Long nLineDY2 = nLineDY/2;
7106
0
        if ( !nLineDY2 )
7107
0
            nLineDY2 = 1;
7108
7109
0
        nLinePos -= nLineWidth-nLineDY2;
7110
7111
0
        m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7112
7113
0
        nLinePos += nLineWidth+nLineDY;
7114
0
        m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7115
0
    }
7116
0
    else
7117
0
    {
7118
0
        if ( eTextLine != LINESTYLE_BOLDWAVE )
7119
0
            nLinePos -= nLineWidth/2;
7120
0
        m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
7121
0
    }
7122
0
}
7123
7124
void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7125
7.41k
{
7126
    // note: units in pFontInstance are ref device pixel
7127
7.41k
    const LogicalFontInstance*  pFontInstance = GetFontInstance();
7128
7.41k
    tools::Long            nLineHeight = 0;
7129
7.41k
    tools::Long            nLinePos  = 0;
7130
7.41k
    tools::Long            nLinePos2 = 0;
7131
7132
7.41k
    if ( eTextLine > LINESTYLE_BOLDWAVE )
7133
0
        eTextLine = LINESTYLE_SINGLE;
7134
7135
7.41k
    switch ( eTextLine )
7136
7.41k
    {
7137
1.74k
        case LINESTYLE_SINGLE:
7138
1.74k
        case LINESTYLE_DOTTED:
7139
1.74k
        case LINESTYLE_DASH:
7140
1.74k
        case LINESTYLE_LONGDASH:
7141
1.74k
        case LINESTYLE_DASHDOT:
7142
1.74k
        case LINESTYLE_DASHDOTDOT:
7143
1.74k
            if ( bIsAbove )
7144
0
            {
7145
0
                if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
7146
0
                    ImplInitAboveTextLineSize();
7147
0
                nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
7148
0
                nLinePos    = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
7149
0
            }
7150
1.74k
            else
7151
1.74k
            {
7152
1.74k
                if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
7153
0
                    ImplInitTextLineSize();
7154
1.74k
                nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
7155
1.74k
                nLinePos    = pFontInstance->mxFontMetric->GetUnderlineOffset();
7156
1.74k
            }
7157
1.74k
            break;
7158
0
        case LINESTYLE_BOLD:
7159
0
        case LINESTYLE_BOLDDOTTED:
7160
0
        case LINESTYLE_BOLDDASH:
7161
0
        case LINESTYLE_BOLDLONGDASH:
7162
0
        case LINESTYLE_BOLDDASHDOT:
7163
0
        case LINESTYLE_BOLDDASHDOTDOT:
7164
0
            if ( bIsAbove )
7165
0
            {
7166
0
                if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
7167
0
                    ImplInitAboveTextLineSize();
7168
0
                nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
7169
0
                nLinePos    = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
7170
0
            }
7171
0
            else
7172
0
            {
7173
0
                if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
7174
0
                    ImplInitTextLineSize();
7175
0
                nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
7176
0
                nLinePos    = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
7177
0
            }
7178
0
            break;
7179
1.82k
        case LINESTYLE_DOUBLE:
7180
1.82k
            if ( bIsAbove )
7181
0
            {
7182
0
                if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
7183
0
                    ImplInitAboveTextLineSize();
7184
0
                nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
7185
0
                nLinePos    = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
7186
0
                nLinePos2   = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
7187
0
            }
7188
1.82k
            else
7189
1.82k
            {
7190
1.82k
                if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
7191
0
                    ImplInitTextLineSize();
7192
1.82k
                nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
7193
1.82k
                nLinePos    = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
7194
1.82k
                nLinePos2   = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
7195
1.82k
            }
7196
1.82k
            break;
7197
3.83k
        default:
7198
3.83k
            break;
7199
7.41k
    }
7200
7201
7.41k
    if ( !nLineHeight )
7202
3.83k
        return;
7203
7204
    // tdf#154235
7205
    // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
7206
    // while in PDF we stroke the line so the position is to the middle of the
7207
    // line, we add half of nLineHeight to account for that.
7208
3.57k
    auto nOffset = nLineHeight / 2;
7209
3.57k
    if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7210
0
    {
7211
        // Except when outlining, as now we are drawing a rectangle, so
7212
        // nLinePos is the bottom of the rectangle, so need to add nLineHeight
7213
        // to it.
7214
0
        nOffset = nLineHeight;
7215
0
    }
7216
7217
3.57k
    nLineHeight = HCONV(nLineHeight);
7218
3.57k
    nLinePos = HCONV(nLinePos + nOffset);
7219
3.57k
    nLinePos2 = HCONV(nLinePos2 + nOffset);
7220
7221
    // outline attribute ?
7222
3.57k
    if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7223
0
    {
7224
0
        appendStrokingColor(aColor, aLine); // stroke with text color
7225
0
        aLine.append( " " );
7226
0
        appendNonStrokingColor(COL_WHITE, aLine); // fill with white
7227
0
        aLine.append( "\n" );
7228
0
        aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
7229
7230
        // draw rectangle instead
7231
0
        aLine.append( "0 " );
7232
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7233
0
        aLine.append( " " );
7234
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7235
0
        aLine.append( ' ' );
7236
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7237
0
        aLine.append( " re h B\n" );
7238
0
        return;
7239
0
    }
7240
7241
7242
3.57k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7243
3.57k
    aLine.append( " w " );
7244
3.57k
    appendStrokingColor( aColor, aLine );
7245
3.57k
    aLine.append( "\n" );
7246
7247
3.57k
    switch ( eTextLine )
7248
3.57k
    {
7249
0
        case LINESTYLE_DOTTED:
7250
0
        case LINESTYLE_BOLDDOTTED:
7251
0
            aLine.append( "[ " );
7252
0
            m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7253
0
            aLine.append( " ] 0 d\n" );
7254
0
            break;
7255
0
        case LINESTYLE_DASH:
7256
0
        case LINESTYLE_LONGDASH:
7257
0
        case LINESTYLE_BOLDDASH:
7258
0
        case LINESTYLE_BOLDLONGDASH:
7259
0
            {
7260
0
                sal_Int32 nDashLength = 4*nLineHeight;
7261
0
                sal_Int32 nVoidLength = 2*nLineHeight;
7262
0
                if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7263
0
                    nDashLength = 8*nLineHeight;
7264
7265
0
                aLine.append( "[ " );
7266
0
                m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7267
0
                aLine.append( ' ' );
7268
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7269
0
                aLine.append( " ] 0 d\n" );
7270
0
            }
7271
0
            break;
7272
0
        case LINESTYLE_DASHDOT:
7273
0
        case LINESTYLE_BOLDDASHDOT:
7274
0
            {
7275
0
                sal_Int32 nDashLength = 4*nLineHeight;
7276
0
                sal_Int32 nVoidLength = 2*nLineHeight;
7277
0
                aLine.append( "[ " );
7278
0
                m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7279
0
                aLine.append( ' ' );
7280
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7281
0
                aLine.append( ' ' );
7282
0
                m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7283
0
                aLine.append( ' ' );
7284
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7285
0
                aLine.append( " ] 0 d\n" );
7286
0
            }
7287
0
            break;
7288
0
        case LINESTYLE_DASHDOTDOT:
7289
0
        case LINESTYLE_BOLDDASHDOTDOT:
7290
0
            {
7291
0
                sal_Int32 nDashLength = 4*nLineHeight;
7292
0
                sal_Int32 nVoidLength = 2*nLineHeight;
7293
0
                aLine.append( "[ " );
7294
0
                m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7295
0
                aLine.append( ' ' );
7296
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7297
0
                aLine.append( ' ' );
7298
0
                m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7299
0
                aLine.append( ' ' );
7300
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7301
0
                aLine.append( ' ' );
7302
0
                m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7303
0
                aLine.append( ' ' );
7304
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7305
0
                aLine.append( " ] 0 d\n" );
7306
0
            }
7307
0
            break;
7308
3.57k
        default:
7309
3.57k
            break;
7310
3.57k
    }
7311
7312
3.57k
    aLine.append( "0 " );
7313
3.57k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7314
3.57k
    aLine.append( " m " );
7315
3.57k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7316
3.57k
    aLine.append( ' ' );
7317
3.57k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7318
3.57k
    aLine.append( " l S\n" );
7319
3.57k
    if ( eTextLine == LINESTYLE_DOUBLE )
7320
1.82k
    {
7321
1.82k
        aLine.append( "0 " );
7322
1.82k
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7323
1.82k
        aLine.append( " m " );
7324
1.82k
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7325
1.82k
        aLine.append( ' ' );
7326
1.82k
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7327
1.82k
        aLine.append( " l S\n" );
7328
1.82k
    }
7329
7330
3.57k
}
7331
7332
void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
7333
3.70k
{
7334
    // note: units in pFontInstance are ref device pixel
7335
3.70k
    const LogicalFontInstance*  pFontInstance = GetFontInstance();
7336
3.70k
    tools::Long            nLineHeight = 0;
7337
3.70k
    tools::Long            nLinePos  = 0;
7338
3.70k
    tools::Long            nLinePos2 = 0;
7339
7340
3.70k
    if ( eStrikeout > STRIKEOUT_X )
7341
0
        eStrikeout = STRIKEOUT_SINGLE;
7342
7343
3.70k
    switch ( eStrikeout )
7344
3.70k
    {
7345
130
        case STRIKEOUT_SINGLE:
7346
130
            if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7347
0
                ImplInitTextLineSize();
7348
130
            nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
7349
130
            nLinePos    = pFontInstance->mxFontMetric->GetStrikeoutOffset();
7350
130
            break;
7351
0
        case STRIKEOUT_BOLD:
7352
0
            if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7353
0
                ImplInitTextLineSize();
7354
0
            nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
7355
0
            nLinePos    = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
7356
0
            break;
7357
0
        case STRIKEOUT_DOUBLE:
7358
0
            if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7359
0
                ImplInitTextLineSize();
7360
0
            nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
7361
0
            nLinePos    = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
7362
0
            nLinePos2   = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
7363
0
            break;
7364
3.57k
        default:
7365
3.57k
            break;
7366
3.70k
    }
7367
7368
3.70k
    if ( !nLineHeight )
7369
3.57k
        return;
7370
7371
    // tdf#154235
7372
    // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
7373
    // while in PDF we stroke the line so the position is to the middle of the
7374
    // line, we add half of nLineHeight to account for that.
7375
130
    nLinePos = HCONV(nLinePos + nLineHeight / 2);
7376
130
    nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
7377
130
    nLineHeight = HCONV(nLineHeight);
7378
7379
130
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7380
130
    aLine.append( " w " );
7381
130
    appendStrokingColor( aColor, aLine );
7382
130
    aLine.append( "\n" );
7383
7384
130
    aLine.append( "0 " );
7385
130
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7386
130
    aLine.append( " m " );
7387
130
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7388
130
    aLine.append( ' ' );
7389
130
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7390
130
    aLine.append( " l S\n" );
7391
7392
130
    if ( eStrikeout == STRIKEOUT_DOUBLE )
7393
0
    {
7394
0
        aLine.append( "0 " );
7395
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7396
0
        aLine.append( " m " );
7397
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7398
0
        aLine.append( ' ' );
7399
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7400
0
        aLine.append( " l S\n" );
7401
0
    }
7402
7403
130
}
7404
7405
void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
7406
0
{
7407
    //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7408
    //to tweak this
7409
7410
0
    OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? u"/"_ustr : u"X"_ustr;
7411
0
    OUString aStrikeout = aStrikeoutChar;
7412
0
    while( GetTextWidth( aStrikeout ) < nWidth )
7413
0
        aStrikeout += aStrikeout;
7414
7415
    // do not get broader than nWidth modulo 1 character
7416
0
    while( GetTextWidth( aStrikeout ) >= nWidth )
7417
0
        aStrikeout = aStrikeout.copy(1);
7418
0
    aStrikeout += aStrikeoutChar;
7419
0
    bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7420
0
    if ( bShadow )
7421
0
    {
7422
0
        Font aFont = m_aCurrentPDFState.m_aFont;
7423
0
        aFont.SetShadow( false );
7424
0
        setFont( aFont );
7425
0
        updateGraphicsState();
7426
0
    }
7427
7428
0
    push(PushFlags::TEXTLAYOUTMODE | PushFlags::CLIPREGION);
7429
7430
    // strikeout string is left aligned non-CTL text
7431
0
    setLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
7432
7433
0
    FontMetric aRefDevFontMetric = GetFontMetric();
7434
0
    tools::Rectangle aRect;
7435
0
    aRect.SetLeft( rPos.X() );
7436
0
    aRect.SetRight( aRect.Left()+nWidth );
7437
0
    aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7438
0
    aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7439
7440
0
    const LogicalFontInstance* pFontInstance = GetFontInstance();
7441
0
    if (pFontInstance->mnOrientation)
7442
0
    {
7443
0
        tools::Polygon aPoly( aRect );
7444
0
        aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7445
0
        aRect = aPoly.GetBoundRect();
7446
0
    }
7447
7448
0
    intersectClipRegion( aRect );
7449
0
    drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7450
0
    pop();
7451
7452
0
    if ( bShadow )
7453
0
    {
7454
0
        Font aFont = m_aCurrentPDFState.m_aFont;
7455
0
        aFont.SetShadow( true );
7456
0
        setFont( aFont );
7457
0
        updateGraphicsState();
7458
0
    }
7459
0
}
7460
7461
void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7462
3.70k
{
7463
3.70k
    if ( !nWidth ||
7464
3.70k
         ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7465
3.57k
           ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7466
0
           ((eOverline  == LINESTYLE_NONE)||(eOverline  == LINESTYLE_DONTKNOW)) ) )
7467
1
        return;
7468
7469
3.70k
    MARK( "drawTextLine" );
7470
3.70k
    updateGraphicsState();
7471
7472
    // note: units in pFontInstance are ref device pixel
7473
3.70k
    const LogicalFontInstance* pFontInstance = GetFontInstance();
7474
3.70k
    Color           aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7475
3.70k
    Color           aOverlineColor  = m_aCurrentPDFState.m_aOverlineColor;
7476
3.70k
    Color           aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7477
3.70k
    bool            bStrikeoutDone = false;
7478
3.70k
    bool            bUnderlineDone = false;
7479
3.70k
    bool            bOverlineDone  = false;
7480
7481
3.70k
    if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7482
0
    {
7483
0
        drawStrikeoutChar( rPos, nWidth, eStrikeout );
7484
0
        bStrikeoutDone = true;
7485
0
    }
7486
7487
3.70k
    Point aPos( rPos );
7488
3.70k
    TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7489
3.70k
    if( eAlign == ALIGN_TOP )
7490
0
        aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7491
3.70k
    else if( eAlign == ALIGN_BOTTOM )
7492
0
        aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7493
7494
3.70k
    OStringBuffer aLine( 512 );
7495
    // save GS
7496
3.70k
    aLine.append( "q " );
7497
7498
    // rotate and translate matrix
7499
3.70k
    double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
7500
3.70k
    Matrix3 aMat;
7501
3.70k
    aMat.rotate( fAngle );
7502
3.70k
    aMat.translate( aPos.X(), aPos.Y() );
7503
3.70k
    m_aPages.back().appendMatrix3(aMat, aLine);
7504
3.70k
    aLine.append( " cm\n" );
7505
7506
3.70k
    if ( aUnderlineColor.IsTransparent() )
7507
3.69k
        aUnderlineColor = aStrikeoutColor;
7508
7509
3.70k
    if ( aOverlineColor.IsTransparent() )
7510
3.69k
        aOverlineColor = aStrikeoutColor;
7511
7512
3.70k
    if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7513
3.70k
         (eUnderline == LINESTYLE_WAVE) ||
7514
3.70k
         (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7515
3.70k
         (eUnderline == LINESTYLE_BOLDWAVE) )
7516
0
    {
7517
0
        drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7518
0
        bUnderlineDone = true;
7519
0
    }
7520
7521
3.70k
    if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7522
3.70k
         (eOverline == LINESTYLE_WAVE) ||
7523
3.70k
         (eOverline == LINESTYLE_DOUBLEWAVE) ||
7524
3.70k
         (eOverline == LINESTYLE_BOLDWAVE) )
7525
0
    {
7526
0
        drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7527
0
        bOverlineDone = true;
7528
0
    }
7529
7530
3.70k
    if ( !bUnderlineDone )
7531
3.70k
    {
7532
3.70k
        drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7533
3.70k
    }
7534
7535
3.70k
    if ( !bOverlineDone )
7536
3.70k
    {
7537
3.70k
        drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7538
3.70k
    }
7539
7540
3.70k
    if ( !bStrikeoutDone )
7541
3.70k
    {
7542
3.70k
        drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7543
3.70k
    }
7544
7545
3.70k
    aLine.append( "Q\n" );
7546
3.70k
    writeBuffer( aLine );
7547
3.70k
}
7548
7549
void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7550
0
{
7551
0
    MARK( "drawPolygon" );
7552
7553
0
    updateGraphicsState();
7554
7555
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7556
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7557
0
        return;
7558
7559
0
    int nPoints = rPoly.GetSize();
7560
0
    OStringBuffer aLine( 20 * nPoints );
7561
0
    m_aPages.back().appendPolygon( rPoly, aLine );
7562
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7563
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7564
0
        aLine.append( "B*\n" );
7565
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7566
0
        aLine.append( "S\n" );
7567
0
    else
7568
0
        aLine.append( "f*\n" );
7569
7570
0
    writeBuffer( aLine );
7571
0
}
7572
7573
void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7574
4.46k
{
7575
4.46k
    MARK( "drawPolyPolygon" );
7576
7577
4.46k
    updateGraphicsState();
7578
7579
4.46k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7580
1.26k
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7581
0
        return;
7582
7583
4.46k
    int nPolygons = rPolyPoly.Count();
7584
7585
4.46k
    OStringBuffer aLine( 40 * nPolygons );
7586
4.46k
    m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7587
4.46k
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7588
3.20k
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7589
3.20k
        aLine.append( "B*\n" );
7590
1.26k
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7591
0
        aLine.append( "S\n" );
7592
1.26k
    else
7593
1.26k
        aLine.append( "f*\n" );
7594
7595
4.46k
    writeBuffer( aLine );
7596
4.46k
}
7597
7598
void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7599
0
{
7600
0
    SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7601
0
    nTransparentPercent = nTransparentPercent % 100;
7602
7603
0
    MARK( "drawTransparent" );
7604
7605
0
    updateGraphicsState();
7606
7607
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7608
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7609
0
        return;
7610
7611
0
    if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7612
0
    {
7613
0
        m_aErrors.insert( m_bIsPDF_A1 ?
7614
0
                          PDFWriter::Warning_Transparency_Omitted_PDFA :
7615
0
                          PDFWriter::Warning_Transparency_Omitted_PDF13 );
7616
7617
0
        drawPolyPolygon( rPolyPoly );
7618
0
        return;
7619
0
    }
7620
7621
    // create XObject
7622
0
    m_aTransparentObjects.emplace_back( );
7623
    // FIXME: polygons with beziers may yield incorrect bound rect
7624
0
    m_aTransparentObjects.back().m_aBoundRect     = rPolyPoly.GetBoundRect();
7625
    // convert rectangle to default user space
7626
0
    m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7627
0
    m_aTransparentObjects.back().m_nObject          = createObject();
7628
0
    m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7629
0
    m_aTransparentObjects.back().m_fAlpha           = (100-nTransparentPercent) / 100.0;
7630
0
    m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7631
    // create XObject's content stream
7632
0
    OStringBuffer aContent( 256 );
7633
0
    m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7634
0
    if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7635
0
        m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7636
0
        aContent.append( " B*\n" );
7637
0
    else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7638
0
        aContent.append( " S\n" );
7639
0
    else
7640
0
        aContent.append( " f*\n" );
7641
0
    m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7642
0
        aContent.getStr(), aContent.getLength() );
7643
7644
0
    OStringBuffer aObjName( 16 );
7645
0
    aObjName.append( "Tr" );
7646
0
    aObjName.append( m_aTransparentObjects.back().m_nObject );
7647
0
    OString aTrName( aObjName.makeStringAndClear() );
7648
0
    aObjName.append( "EGS" );
7649
0
    aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7650
0
    OString aExtName( aObjName.makeStringAndClear() );
7651
7652
0
    OString aLine =
7653
    // insert XObject
7654
0
        "q /" +
7655
0
        aExtName +
7656
0
        " gs /" +
7657
0
        aTrName +
7658
0
        " Do Q\n";
7659
0
    writeBuffer( aLine );
7660
7661
0
    pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
7662
0
    pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7663
0
}
7664
7665
void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
7666
12
{
7667
12
    if( nObject < 0 )
7668
0
        return;
7669
7670
12
    switch( eKind )
7671
12
    {
7672
6
        case ResourceKind::XObject:
7673
6
            rResourceDict.m_aXObjects[rResource] = nObject;
7674
6
            if (!rOutputStreams.empty())
7675
0
                rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
7676
6
            break;
7677
6
        case ResourceKind::ExtGState:
7678
6
            rResourceDict.m_aExtGStates[rResource] = nObject;
7679
6
            if (!rOutputStreams.empty())
7680
0
                rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
7681
6
            break;
7682
0
        case ResourceKind::Shading:
7683
0
            rResourceDict.m_aShadings[rResource] = nObject;
7684
0
            if (!rOutputStreams.empty())
7685
0
                rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
7686
0
            break;
7687
0
        case ResourceKind::Pattern:
7688
0
            rResourceDict.m_aPatterns[rResource] = nObject;
7689
0
            if (!rOutputStreams.empty())
7690
0
                rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
7691
0
            break;
7692
12
    }
7693
12
}
7694
7695
void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
7696
12
{
7697
12
    pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
7698
12
}
7699
7700
void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
7701
6
{
7702
6
    push( PushFlags::ALL );
7703
7704
    // force reemitting clip region inside the new stream, and
7705
    // prevent emitting an unbalanced "Q" at the start
7706
6
    clearClipRegion();
7707
    // this is needed to point m_aCurrentPDFState at the pushed state
7708
    // ... but it's pointless to actually write into the "outer" stream here!
7709
6
    updateGraphicsState(Mode::NOWRITE);
7710
7711
6
    m_aOutputStreams.push_front( StreamRedirect() );
7712
6
    m_aOutputStreams.front().m_pStream = pStream;
7713
6
    m_aOutputStreams.front().m_aMapMode = m_aMapMode;
7714
7715
6
    if( !rTargetRect.IsEmpty() )
7716
0
    {
7717
0
        m_aOutputStreams.front().m_aTargetRect =
7718
0
            convert( m_aGraphicsStack.front().m_aMapMode,
7719
0
                         m_aMapMode,
7720
0
                         this,
7721
0
                         rTargetRect );
7722
0
        Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
7723
0
        tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
7724
0
        aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
7725
0
        m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
7726
0
    }
7727
7728
    // setup graphics state for independent object stream
7729
7730
    // force reemitting colors
7731
6
    m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7732
6
    m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7733
6
}
7734
7735
SvStream* PDFWriterImpl::endRedirect()
7736
6
{
7737
6
    SvStream* pStream = nullptr;
7738
6
    if( ! m_aOutputStreams.empty() )
7739
6
    {
7740
6
        pStream     = m_aOutputStreams.front().m_pStream;
7741
6
        m_aMapMode  = m_aOutputStreams.front().m_aMapMode;
7742
6
        m_aOutputStreams.pop_front();
7743
6
    }
7744
7745
6
    pop();
7746
7747
6
    m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7748
6
    m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7749
7750
    // needed after pop() to set m_aCurrentPDFState
7751
6
    updateGraphicsState(Mode::NOWRITE);
7752
7753
6
    return pStream;
7754
6
}
7755
7756
void PDFWriterImpl::beginTransparencyGroup()
7757
6
{
7758
6
    updateGraphicsState();
7759
6
    if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
7760
6
        beginRedirect( new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE ), tools::Rectangle() );
7761
6
}
7762
7763
void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
7764
6
{
7765
6
    SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7766
6
    nTransparentPercent = nTransparentPercent % 100;
7767
7768
6
    if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7769
0
        return;
7770
7771
    // create XObject
7772
6
    m_aTransparentObjects.emplace_back( );
7773
6
    m_aTransparentObjects.back().m_aBoundRect   = rBoundingBox;
7774
    // convert rectangle to default user space
7775
6
    m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7776
6
    m_aTransparentObjects.back().m_nObject      = createObject();
7777
6
    m_aTransparentObjects.back().m_fAlpha       = (100-nTransparentPercent) / 100.0;
7778
    // get XObject's content stream
7779
6
    m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
7780
6
    m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7781
7782
6
    OStringBuffer aObjName( 16 );
7783
6
    aObjName.append( "Tr" );
7784
6
    aObjName.append( m_aTransparentObjects.back().m_nObject );
7785
6
    OString aTrName( aObjName.makeStringAndClear() );
7786
6
    aObjName.append( "EGS" );
7787
6
    aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7788
6
    OString aExtName( aObjName.makeStringAndClear() );
7789
7790
6
    OString aLine =
7791
    // insert XObject
7792
6
        "q /" +
7793
6
        aExtName +
7794
6
        " gs /" +
7795
6
        aTrName +
7796
6
        " Do Q\n";
7797
6
    writeBuffer( aLine );
7798
7799
6
    pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
7800
6
    pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7801
7802
6
}
7803
7804
void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
7805
84.4k
{
7806
84.4k
    MARK( "drawRectangle" );
7807
7808
84.4k
    updateGraphicsState();
7809
7810
84.4k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7811
84.4k
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7812
83.6k
        return;
7813
7814
761
    OStringBuffer aLine( 40 );
7815
761
    m_aPages.back().appendRect( rRect, aLine );
7816
7817
761
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7818
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7819
0
        aLine.append( " B*\n" );
7820
761
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7821
0
        aLine.append( " S\n" );
7822
761
    else
7823
761
        aLine.append( " f*\n" );
7824
7825
761
    writeBuffer( aLine );
7826
761
}
7827
7828
void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
7829
0
{
7830
0
    MARK( "drawRectangle with rounded edges" );
7831
7832
0
    if( !nHorzRound && !nVertRound )
7833
0
        drawRectangle( rRect );
7834
7835
0
    updateGraphicsState();
7836
7837
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7838
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7839
0
        return;
7840
7841
0
    if( nHorzRound > o3tl::make_unsigned(rRect.GetWidth())/2 )
7842
0
        nHorzRound = rRect.GetWidth()/2;
7843
0
    if( nVertRound > o3tl::make_unsigned(rRect.GetHeight())/2 )
7844
0
        nVertRound = rRect.GetHeight()/2;
7845
7846
0
    Point aPoints[16];
7847
0
    const double kappa = 0.5522847498;
7848
0
    const sal_uInt32 kx = (kappa*nHorzRound)+0.5;
7849
0
    const sal_uInt32 ky = (kappa*nVertRound)+0.5;
7850
7851
0
    aPoints[1]  = Point( rRect.Left() + nHorzRound, rRect.Top() );
7852
0
    aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7853
0
    aPoints[2]  = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
7854
0
    aPoints[3]  = Point( aPoints[2].X()+kx, aPoints[2].Y() );
7855
7856
0
    aPoints[5]  = Point( rRect.Right()+1, rRect.Top()+nVertRound );
7857
0
    aPoints[4]  = Point( aPoints[5].X(), aPoints[5].Y()-ky );
7858
0
    aPoints[6]  = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
7859
0
    aPoints[7]  = Point( aPoints[6].X(), aPoints[6].Y()+ky );
7860
7861
0
    aPoints[9]  = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
7862
0
    aPoints[8]  = Point( aPoints[9].X()+kx, aPoints[9].Y() );
7863
0
    aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
7864
0
    aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
7865
7866
0
    aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
7867
0
    aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
7868
0
    aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
7869
0
    aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
7870
7871
0
    OStringBuffer aLine( 80 );
7872
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7873
0
    aLine.append( " m " );
7874
0
    m_aPages.back().appendPoint( aPoints[2], aLine );
7875
0
    aLine.append( " l " );
7876
0
    m_aPages.back().appendPoint( aPoints[3], aLine );
7877
0
    aLine.append( ' ' );
7878
0
    m_aPages.back().appendPoint( aPoints[4], aLine );
7879
0
    aLine.append( ' ' );
7880
0
    m_aPages.back().appendPoint( aPoints[5], aLine );
7881
0
    aLine.append( " c\n" );
7882
0
    m_aPages.back().appendPoint( aPoints[6], aLine );
7883
0
    aLine.append( " l " );
7884
0
    m_aPages.back().appendPoint( aPoints[7], aLine );
7885
0
    aLine.append( ' ' );
7886
0
    m_aPages.back().appendPoint( aPoints[8], aLine );
7887
0
    aLine.append( ' ' );
7888
0
    m_aPages.back().appendPoint( aPoints[9], aLine );
7889
0
    aLine.append( " c\n" );
7890
0
    m_aPages.back().appendPoint( aPoints[10], aLine );
7891
0
    aLine.append( " l " );
7892
0
    m_aPages.back().appendPoint( aPoints[11], aLine );
7893
0
    aLine.append( ' ' );
7894
0
    m_aPages.back().appendPoint( aPoints[12], aLine );
7895
0
    aLine.append( ' ' );
7896
0
    m_aPages.back().appendPoint( aPoints[13], aLine );
7897
0
    aLine.append( " c\n" );
7898
0
    m_aPages.back().appendPoint( aPoints[14], aLine );
7899
0
    aLine.append( " l " );
7900
0
    m_aPages.back().appendPoint( aPoints[15], aLine );
7901
0
    aLine.append( ' ' );
7902
0
    m_aPages.back().appendPoint( aPoints[0], aLine );
7903
0
    aLine.append( ' ' );
7904
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7905
0
    aLine.append( " c " );
7906
7907
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7908
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7909
0
        aLine.append( "b*\n" );
7910
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7911
0
        aLine.append( "s\n" );
7912
0
    else
7913
0
        aLine.append( "f*\n" );
7914
7915
0
    writeBuffer( aLine );
7916
0
}
7917
7918
void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
7919
0
{
7920
0
    MARK( "drawEllipse" );
7921
7922
0
    updateGraphicsState();
7923
7924
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7925
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7926
0
        return;
7927
7928
0
    Point aPoints[12];
7929
0
    const double kappa = 0.5522847498;
7930
0
    const sal_uInt32 kx = (kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5;
7931
0
    const sal_uInt32 ky = (kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5;
7932
7933
0
    aPoints[1]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
7934
0
    aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7935
0
    aPoints[2]  = Point( aPoints[1].X() + kx, aPoints[1].Y() );
7936
7937
0
    aPoints[4]  = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
7938
0
    aPoints[3]  = Point( aPoints[4].X(), aPoints[4].Y() - ky );
7939
0
    aPoints[5]  = Point( aPoints[4].X(), aPoints[4].Y() + ky );
7940
7941
0
    aPoints[7]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
7942
0
    aPoints[6]  = Point( aPoints[7].X() + kx, aPoints[7].Y() );
7943
0
    aPoints[8]  = Point( aPoints[7].X() - kx, aPoints[7].Y() );
7944
7945
0
    aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
7946
0
    aPoints[9]  = Point( aPoints[10].X(), aPoints[10].Y() + ky );
7947
0
    aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
7948
7949
0
    OStringBuffer aLine( 80 );
7950
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7951
0
    aLine.append( " m " );
7952
0
    m_aPages.back().appendPoint( aPoints[2], aLine );
7953
0
    aLine.append( ' ' );
7954
0
    m_aPages.back().appendPoint( aPoints[3], aLine );
7955
0
    aLine.append( ' ' );
7956
0
    m_aPages.back().appendPoint( aPoints[4], aLine );
7957
0
    aLine.append( " c\n" );
7958
0
    m_aPages.back().appendPoint( aPoints[5], aLine );
7959
0
    aLine.append( ' ' );
7960
0
    m_aPages.back().appendPoint( aPoints[6], aLine );
7961
0
    aLine.append( ' ' );
7962
0
    m_aPages.back().appendPoint( aPoints[7], aLine );
7963
0
    aLine.append( " c\n" );
7964
0
    m_aPages.back().appendPoint( aPoints[8], aLine );
7965
0
    aLine.append( ' ' );
7966
0
    m_aPages.back().appendPoint( aPoints[9], aLine );
7967
0
    aLine.append( ' ' );
7968
0
    m_aPages.back().appendPoint( aPoints[10], aLine );
7969
0
    aLine.append( " c\n" );
7970
0
    m_aPages.back().appendPoint( aPoints[11], aLine );
7971
0
    aLine.append( ' ' );
7972
0
    m_aPages.back().appendPoint( aPoints[0], aLine );
7973
0
    aLine.append( ' ' );
7974
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7975
0
    aLine.append( " c " );
7976
7977
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7978
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7979
0
        aLine.append( "b*\n" );
7980
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7981
0
        aLine.append( "s\n" );
7982
0
    else
7983
0
        aLine.append( "f*\n" );
7984
7985
0
    writeBuffer( aLine );
7986
0
}
7987
7988
static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
7989
0
{
7990
0
    Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
7991
0
                  (rRect.Top()+rRect.Bottom()+1)/2);
7992
0
    Point aPoint = rPoint - aOrigin;
7993
7994
0
    double fX = static_cast<double>(aPoint.X());
7995
0
    double fY = static_cast<double>(-aPoint.Y());
7996
7997
0
    if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
7998
0
        throw o3tl::divide_by_zero();
7999
8000
0
    if( rRect.GetWidth() > rRect.GetHeight() )
8001
0
        fY *= static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight());
8002
0
    else if( rRect.GetHeight() > rRect.GetWidth() )
8003
0
        fX *= static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth());
8004
0
    return atan2( fY, fX );
8005
0
}
8006
8007
void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
8008
0
{
8009
0
    MARK( "drawArc" );
8010
8011
0
    updateGraphicsState();
8012
8013
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8014
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8015
0
        return;
8016
8017
    // calculate start and stop angles
8018
0
    const double fStartAngle = calcAngle( rRect, rStart );
8019
0
    double fStopAngle  = calcAngle( rRect, rStop );
8020
0
    while( fStopAngle < fStartAngle )
8021
0
        fStopAngle += 2.0*M_PI;
8022
0
    const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
8023
0
    const double fFragmentDelta = (fStopAngle-fStartAngle)/nFragments;
8024
0
    const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
8025
0
    const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
8026
0
    const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
8027
8028
0
    const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
8029
0
                         (rRect.Top()+rRect.Bottom()+1)/2 );
8030
8031
0
    OStringBuffer aLine( 30*nFragments );
8032
0
    Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
8033
0
                  -static_cast<int>(halfHeight * sin(fStartAngle) ) );
8034
0
    aPoint += aCenter;
8035
0
    m_aPages.back().appendPoint( aPoint, aLine );
8036
0
    aLine.append( " m " );
8037
0
    if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
8038
0
    {
8039
0
        for( int i = 0; i < nFragments; i++ )
8040
0
        {
8041
0
            const double fStartFragment = fStartAngle + i*fFragmentDelta;
8042
0
            const double fStopFragment = fStartFragment + fFragmentDelta;
8043
0
            aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
8044
0
                            -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
8045
0
            aPoint += aCenter;
8046
0
            m_aPages.back().appendPoint( aPoint, aLine );
8047
0
            aLine.append( ' ' );
8048
8049
0
            aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
8050
0
                            -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
8051
0
            aPoint += aCenter;
8052
0
            m_aPages.back().appendPoint( aPoint, aLine );
8053
0
            aLine.append( ' ' );
8054
8055
0
            aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
8056
0
                            -static_cast<int>(halfHeight * sin(fStopFragment) ) );
8057
0
            aPoint += aCenter;
8058
0
            m_aPages.back().appendPoint( aPoint, aLine );
8059
0
            aLine.append( " c\n" );
8060
0
        }
8061
0
    }
8062
0
    if( bWithChord || bWithPie )
8063
0
    {
8064
0
        if( bWithPie )
8065
0
        {
8066
0
            m_aPages.back().appendPoint( aCenter, aLine );
8067
0
            aLine.append( " l " );
8068
0
        }
8069
0
        aLine.append( "h " );
8070
0
    }
8071
0
    if( ! bWithChord && ! bWithPie )
8072
0
        aLine.append( "S\n" );
8073
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8074
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8075
0
        aLine.append( "B*\n" );
8076
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8077
0
        aLine.append( "S\n" );
8078
0
    else
8079
0
        aLine.append( "f*\n" );
8080
8081
0
    writeBuffer( aLine );
8082
0
}
8083
8084
void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
8085
13.3k
{
8086
13.3k
    MARK( "drawPolyLine" );
8087
8088
13.3k
    sal_uInt16 nPoints = rPoly.GetSize();
8089
13.3k
    if( nPoints < 2 )
8090
0
        return;
8091
8092
13.3k
    updateGraphicsState();
8093
8094
13.3k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8095
0
        return;
8096
8097
13.3k
    OStringBuffer aLine( 20 * nPoints );
8098
13.3k
    m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
8099
13.3k
    aLine.append( "S\n" );
8100
8101
13.3k
    writeBuffer( aLine );
8102
13.3k
}
8103
8104
void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
8105
0
{
8106
0
    MARK( "drawPolyLine with LineInfo" );
8107
8108
0
    updateGraphicsState();
8109
8110
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8111
0
        return;
8112
8113
0
    OStringBuffer aLine;
8114
0
    aLine.append( "q " );
8115
0
    if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
8116
0
    {
8117
0
        writeBuffer( aLine );
8118
0
        drawPolyLine( rPoly );
8119
0
        writeBuffer( "Q\n" );
8120
0
    }
8121
0
    else
8122
0
    {
8123
0
        PDFWriter::ExtLineInfo aInfo;
8124
0
        convertLineInfoToExtLineInfo( rInfo, aInfo );
8125
0
        drawPolyLine( rPoly, aInfo );
8126
0
    }
8127
0
}
8128
8129
void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
8130
0
{
8131
0
    SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
8132
0
    rOut.m_fLineWidth           = rIn.GetWidth();
8133
0
    rOut.m_fTransparency        = 0.0;
8134
0
    rOut.m_eCap                 = PDFWriter::capButt;
8135
0
    rOut.m_eJoin                = PDFWriter::joinMiter;
8136
0
    rOut.m_fMiterLimit          = 10;
8137
0
    rOut.m_aDashArray           = rIn.GetDotDashArray();
8138
8139
    // add LineJoin
8140
0
    switch(rIn.GetLineJoin())
8141
0
    {
8142
0
        case basegfx::B2DLineJoin::Bevel :
8143
0
        {
8144
0
            rOut.m_eJoin = PDFWriter::joinBevel;
8145
0
            break;
8146
0
        }
8147
        // Pdf has no 'none' lineJoin, default is miter
8148
0
        case basegfx::B2DLineJoin::NONE :
8149
0
        case basegfx::B2DLineJoin::Miter :
8150
0
        {
8151
0
            rOut.m_eJoin = PDFWriter::joinMiter;
8152
0
            break;
8153
0
        }
8154
0
        case basegfx::B2DLineJoin::Round :
8155
0
        {
8156
0
            rOut.m_eJoin = PDFWriter::joinRound;
8157
0
            break;
8158
0
        }
8159
0
    }
8160
8161
    // add LineCap
8162
0
    switch(rIn.GetLineCap())
8163
0
    {
8164
0
        default: /* css::drawing::LineCap_BUTT */
8165
0
        {
8166
0
            rOut.m_eCap = PDFWriter::capButt;
8167
0
            break;
8168
0
        }
8169
0
        case css::drawing::LineCap_ROUND:
8170
0
        {
8171
0
            rOut.m_eCap = PDFWriter::capRound;
8172
0
            break;
8173
0
        }
8174
0
        case css::drawing::LineCap_SQUARE:
8175
0
        {
8176
0
            rOut.m_eCap = PDFWriter::capSquare;
8177
0
            break;
8178
0
        }
8179
0
    }
8180
0
}
8181
8182
void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
8183
13.3k
{
8184
13.3k
    MARK( "drawPolyLine with ExtLineInfo" );
8185
8186
13.3k
    updateGraphicsState();
8187
8188
13.3k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8189
0
        return;
8190
8191
13.3k
    if( rInfo.m_fTransparency >= 1.0 )
8192
0
        return;
8193
8194
13.3k
    if( rInfo.m_fTransparency != 0.0 )
8195
0
        beginTransparencyGroup();
8196
8197
13.3k
    OStringBuffer aLine;
8198
13.3k
    aLine.append( "q " );
8199
13.3k
    m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
8200
13.3k
    aLine.append( " w" );
8201
13.3k
    if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
8202
13.3k
    {
8203
13.3k
        switch( rInfo.m_eCap )
8204
13.3k
        {
8205
0
            default:
8206
13.3k
            case PDFWriter::capButt:   aLine.append( " 0 J" );break;
8207
3
            case PDFWriter::capRound:  aLine.append( " 1 J" );break;
8208
0
            case PDFWriter::capSquare: aLine.append( " 2 J" );break;
8209
13.3k
        }
8210
13.3k
        switch( rInfo.m_eJoin )
8211
13.3k
        {
8212
0
            default:
8213
15
            case PDFWriter::joinMiter:
8214
15
            {
8215
15
                double fLimit = rInfo.m_fMiterLimit;
8216
15
                if (rInfo.m_fLineWidth < rInfo.m_fMiterLimit)
8217
15
                {
8218
15
                    if (rInfo.m_fLineWidth != 0.0)
8219
15
                        fLimit = fLimit / rInfo.m_fLineWidth;
8220
0
                    else
8221
0
                    {
8222
0
                        SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::drawPolyLine divide by zero attempt");
8223
0
                        fLimit = 1.0;
8224
0
                    }
8225
15
                }
8226
15
                if( fLimit < 1.0 )
8227
0
                    fLimit = 1.0;
8228
15
                aLine.append( " 0 j " );
8229
15
                appendDouble( fLimit, aLine );
8230
15
                aLine.append( " M" );
8231
15
            }
8232
0
            break;
8233
13.3k
            case PDFWriter::joinRound:  aLine.append( " 1 j" );break;
8234
0
            case PDFWriter::joinBevel:  aLine.append( " 2 j" );break;
8235
13.3k
        }
8236
13.3k
        if( !rInfo.m_aDashArray.empty() )
8237
0
        {
8238
0
            aLine.append( " [ " );
8239
0
            for (auto const& dash : rInfo.m_aDashArray)
8240
0
            {
8241
0
                m_aPages.back().appendMappedLength( dash, aLine );
8242
0
                aLine.append( ' ' );
8243
0
            }
8244
0
            aLine.append( "] 0 d" );
8245
0
        }
8246
13.3k
        aLine.append( "\n" );
8247
13.3k
        writeBuffer( aLine );
8248
13.3k
        drawPolyLine( rPoly );
8249
13.3k
    }
8250
0
    else
8251
0
    {
8252
0
        basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8253
0
        basegfx::B2DPolyPolygon aPolyPoly;
8254
8255
0
        basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8256
8257
        // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8258
        // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8259
        // this line needs to be removed and the loop below adapted accordingly
8260
0
        aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8261
8262
0
        const sal_uInt32 nPolygonCount(aPolyPoly.count());
8263
8264
0
        for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8265
0
        {
8266
0
            aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8267
0
            aPoly = aPolyPoly.getB2DPolygon( nPoly );
8268
0
            const sal_uInt32 nPointCount(aPoly.count());
8269
8270
0
            if(nPointCount)
8271
0
            {
8272
0
                const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8273
0
                basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8274
8275
0
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
8276
0
                {
8277
0
                    if( a > 0 )
8278
0
                        aLine.append( " " );
8279
0
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8280
0
                    const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8281
8282
0
                    m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aCurrent.getX()),
8283
0
                                                        basegfx::fround<tools::Long>(aCurrent.getY()) ),
8284
0
                                                 aLine );
8285
0
                    aLine.append( " m " );
8286
0
                    m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aNext.getX()),
8287
0
                                                        basegfx::fround<tools::Long>(aNext.getY()) ),
8288
0
                                                 aLine );
8289
0
                    aLine.append( " l" );
8290
8291
                    // prepare next edge
8292
0
                    aCurrent = aNext;
8293
0
                }
8294
0
            }
8295
0
        }
8296
0
        aLine.append( " S " );
8297
0
        writeBuffer( aLine );
8298
0
    }
8299
13.3k
    writeBuffer( "Q\n" );
8300
8301
13.3k
    if( rInfo.m_fTransparency == 0.0 )
8302
13.3k
        return;
8303
8304
    // FIXME: actually this may be incorrect with bezier polygons
8305
0
    tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8306
    // avoid clipping with thick lines
8307
0
    if( rInfo.m_fLineWidth > 0.0 )
8308
0
    {
8309
0
        sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8310
0
        aBoundRect.AdjustTop( -nLW );
8311
0
        aBoundRect.AdjustLeft( -nLW );
8312
0
        aBoundRect.AdjustRight(nLW );
8313
0
        aBoundRect.AdjustBottom(nLW );
8314
0
    }
8315
0
    endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8316
0
}
8317
8318
void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8319
0
{
8320
0
    MARK( "drawPixel" );
8321
8322
0
    Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8323
8324
0
    if( aColor == COL_TRANSPARENT )
8325
0
        return;
8326
8327
    // pixels are drawn in line color, so have to set
8328
    // the nonstroking color to line color
8329
0
    Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8330
0
    setFillColor( aColor );
8331
8332
0
    updateGraphicsState();
8333
8334
0
    OStringBuffer aLine( 20 );
8335
0
    m_aPages.back().appendPoint( rPoint, aLine );
8336
0
    aLine.append( ' ' );
8337
0
    appendDouble( 1.0/GetDPIX(), aLine );
8338
0
    aLine.append( ' ' );
8339
0
    appendDouble( 1.0/GetDPIY(), aLine );
8340
0
    aLine.append( " re f\n" );
8341
0
    writeBuffer( aLine );
8342
8343
0
    setFillColor( aOldFillColor );
8344
0
}
8345
8346
void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8347
6
{
8348
6
    if (!updateObject(rObject.m_nObject))
8349
0
        return;
8350
8351
6
    bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8352
6
    sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
8353
6
    rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8354
6
    if (g_bDebugDisableCompression)
8355
0
    {
8356
0
        emitComment( "PDFWriterImpl::writeTransparentObject" );
8357
0
    }
8358
6
    OStringBuffer aLine( 512 );
8359
6
    if (!updateObject(rObject.m_nObject))
8360
0
        return;
8361
6
    aLine.append( rObject.m_nObject );
8362
6
    aLine.append( " 0 obj\n"
8363
6
                  "<</Type/XObject\n"
8364
6
                  "/Subtype/Form\n"
8365
6
                  "/BBox[ " );
8366
6
    appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8367
6
    aLine.append( ' ' );
8368
6
    appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8369
6
    aLine.append( ' ' );
8370
6
    appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8371
6
    aLine.append( ' ' );
8372
6
    appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8373
6
    aLine.append( " ]\n" );
8374
6
    if( ! m_bIsPDF_A1 )
8375
6
    {
8376
        // 7.8.3 Resource dicts are required for content streams
8377
6
        aLine.append( "/Resources " );
8378
6
        aLine.append( getResourceDictObj() );
8379
6
        aLine.append( " 0 R\n" );
8380
8381
6
        aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8382
6
    }
8383
8384
6
    aLine.append( "/Length " );
8385
6
    aLine.append(static_cast<sal_Int64>(calculateStreamSize(nSize)));
8386
6
    aLine.append( "\n" );
8387
6
    if( bFlateFilter )
8388
6
        aLine.append( "/Filter/FlateDecode\n" );
8389
6
    aLine.append( ">>\n"
8390
6
                  "stream\n" );
8391
6
    if (!writeBuffer(aLine))
8392
0
        return;
8393
6
    checkAndEnableStreamEncryption( rObject.m_nObject );
8394
6
    if (!writeBufferBytes(rObject.m_pContentStream->GetData(), nSize))
8395
0
        return;
8396
6
    disableStreamEncryption();
8397
6
    aLine.setLength( 0 );
8398
6
    aLine.append( "\n"
8399
6
                  "endstream\n"
8400
6
                  "endobj\n\n" );
8401
6
    if (!writeBuffer(aLine))
8402
0
        return;
8403
8404
    // write ExtGState dict for this XObject
8405
6
    aLine.setLength( 0 );
8406
6
    aLine.append( rObject.m_nExtGStateObject );
8407
6
    aLine.append( " 0 obj\n"
8408
6
                  "<<" );
8409
8410
6
    if( m_bIsPDF_A1 )
8411
0
    {
8412
0
        aLine.append( "/CA 1.0/ca 1.0" );
8413
0
        m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8414
0
    }
8415
6
    else
8416
6
    {
8417
6
        aLine.append(  "/CA " );
8418
6
        appendDouble( rObject.m_fAlpha, aLine );
8419
6
        aLine.append( "\n"
8420
6
                          "   /ca " );
8421
6
        appendDouble( rObject.m_fAlpha, aLine );
8422
6
    }
8423
6
    aLine.append( "\n" );
8424
8425
6
    aLine.append( ">>\n"
8426
6
                  "endobj\n\n" );
8427
6
    if (!updateObject(rObject.m_nExtGStateObject)) return;
8428
6
    if (!writeBuffer(aLine)) return;
8429
6
}
8430
8431
bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8432
0
{
8433
    // LO internal gradient -> PDF shading type:
8434
    //  * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
8435
    //                          [t=0:colorStart, t=1:colorEnd]
8436
    //  * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples
8437
    //                          [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8438
    //  * other styles: function shading with aSize.Width() * aSize.Height() samples
8439
0
    sal_Int32 nFunctionObject = createObject();
8440
0
    if (!updateObject(nFunctionObject))
8441
0
        return false;
8442
8443
0
    ScopedVclPtrInstance< VirtualDevice > aDev;
8444
0
    aDev->SetOutputSizePixel( rObject.m_aSize );
8445
0
    aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8446
0
    if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8447
0
        aDev->SetDrawMode( aDev->GetDrawMode() |
8448
0
                          ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8449
0
                            DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8450
0
    aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8451
8452
0
    Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8453
0
    BitmapScopedReadAccess pAccess(aSample);
8454
8455
0
    Size aSize = aSample.GetSizePixel();
8456
8457
0
    sal_Int32 nStreamLengthObject = createObject();
8458
0
    if (g_bDebugDisableCompression)
8459
0
    {
8460
0
        emitComment( "PDFWriterImpl::writeGradientFunction" );
8461
0
    }
8462
0
    OStringBuffer aLine( 120 );
8463
0
    aLine.append( nFunctionObject );
8464
0
    aLine.append( " 0 obj\n"
8465
0
                  "<</FunctionType 0\n");
8466
0
    switch (rObject.m_aGradient.GetStyle())
8467
0
    {
8468
0
        case css::awt::GradientStyle_LINEAR:
8469
0
        case css::awt::GradientStyle_AXIAL:
8470
0
            aLine.append("/Domain[ 0 1]\n");
8471
0
            break;
8472
0
        default:
8473
0
            aLine.append("/Domain[ 0 1 0 1]\n");
8474
0
    }
8475
0
    aLine.append("/Size[ " );
8476
0
    switch (rObject.m_aGradient.GetStyle())
8477
0
    {
8478
0
        case css::awt::GradientStyle_LINEAR:
8479
0
            aLine.append('2');
8480
0
            break;
8481
0
        case css::awt::GradientStyle_AXIAL:
8482
0
            aLine.append('3');
8483
0
            break;
8484
0
        default:
8485
0
            aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8486
0
            aLine.append( ' ' );
8487
0
            aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8488
0
    }
8489
0
    aLine.append( " ]\n"
8490
0
                  "/BitsPerSample 8\n"
8491
0
                  "/Range[ 0 1 0 1 0 1 ]\n"
8492
0
                  "/Order 3\n"
8493
0
                  "/Length " );
8494
0
    aLine.append( nStreamLengthObject );
8495
0
    if (!g_bDebugDisableCompression)
8496
0
        aLine.append( " 0 R\n"
8497
0
                      "/Filter/FlateDecode"
8498
0
                      ">>\n"
8499
0
                      "stream\n" );
8500
0
    else
8501
0
        aLine.append( " 0 R\n"
8502
0
                      ">>\n"
8503
0
                      "stream\n" );
8504
0
    if (!writeBuffer(aLine))
8505
0
        return false;
8506
8507
0
    sal_uInt64 nStartStreamPos = 0;
8508
0
    if (osl::File::E_None != m_aFile.getPos(nStartStreamPos))
8509
0
        return false;
8510
8511
0
    checkAndEnableStreamEncryption( nFunctionObject );
8512
0
    beginCompression();
8513
0
    sal_uInt8 aCol[3];
8514
0
    switch (rObject.m_aGradient.GetStyle())
8515
0
    {
8516
0
        case css::awt::GradientStyle_AXIAL:
8517
0
            aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8518
0
            aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8519
0
            aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8520
0
            if (!writeBufferBytes(aCol, 3))
8521
0
                return false;
8522
0
            [[fallthrough]];
8523
0
        case css::awt::GradientStyle_LINEAR:
8524
0
        {
8525
0
            aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8526
0
            aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8527
0
            aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8528
0
            if (!writeBufferBytes(aCol, 3))
8529
0
                return false;
8530
8531
0
            aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8532
0
            aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8533
0
            aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8534
0
            if (!writeBufferBytes(aCol, 3))
8535
0
                return false;
8536
0
            break;
8537
0
        }
8538
0
        default:
8539
0
            for( int y = aSize.Height()-1; y >= 0; y-- )
8540
0
            {
8541
0
                for( tools::Long x = 0; x < aSize.Width(); x++ )
8542
0
                {
8543
0
                    BitmapColor aColor = pAccess->GetColor( y, x );
8544
0
                    aCol[0] = aColor.GetRed();
8545
0
                    aCol[1] = aColor.GetGreen();
8546
0
                    aCol[2] = aColor.GetBlue();
8547
0
                    if (!writeBufferBytes(aCol, 3))
8548
0
                        return false;
8549
0
                }
8550
0
            }
8551
0
    }
8552
0
    endCompression();
8553
0
    disableStreamEncryption();
8554
8555
0
    sal_uInt64 nEndStreamPos = 0;
8556
0
    if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
8557
0
        return false;
8558
8559
0
    aLine.setLength( 0 );
8560
0
    aLine.append( "\nendstream\nendobj\n\n" );
8561
0
    if (!writeBuffer(aLine)) return false;
8562
8563
    // write stream length
8564
0
    if (!updateObject(nStreamLengthObject)) return false;
8565
0
    aLine.setLength( 0 );
8566
0
    aLine.append( nStreamLengthObject );
8567
0
    aLine.append( " 0 obj\n" );
8568
0
    aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8569
0
    aLine.append( "\nendobj\n\n" );
8570
0
    if (!writeBuffer(aLine)) return false;
8571
8572
0
    if (!updateObject(rObject.m_nObject)) return false;
8573
0
    aLine.setLength( 0 );
8574
0
    aLine.append( rObject.m_nObject );
8575
0
    aLine.append( " 0 obj\n");
8576
0
    switch (rObject.m_aGradient.GetStyle())
8577
0
    {
8578
0
        case css::awt::GradientStyle_LINEAR:
8579
0
        case css::awt::GradientStyle_AXIAL:
8580
0
            aLine.append("<</ShadingType 2\n");
8581
0
            break;
8582
0
        default:
8583
0
            aLine.append("<</ShadingType 1\n");
8584
0
    }
8585
0
    aLine.append("/ColorSpace/DeviceRGB\n"
8586
0
                  "/AntiAlias true\n");
8587
8588
    // Determination of shading axis
8589
    // See: OutputDevice::ImplDrawLinearGradient for reference
8590
0
    tools::Rectangle aRect;
8591
0
    aRect.SetLeft(0);
8592
0
    aRect.SetTop(0);
8593
0
    aRect.SetRight( aSize.Width() );
8594
0
    aRect.SetBottom( aSize.Height() );
8595
8596
0
    tools::Rectangle aBoundRect;
8597
0
    Point     aCenter;
8598
0
    Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
8599
0
    rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8600
8601
0
    const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
8602
0
    double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8603
0
    if ( !bLinear )
8604
0
    {
8605
0
        fBorder /= 2.0;
8606
0
    }
8607
8608
0
    aBoundRect.AdjustBottom( -fBorder );
8609
0
    if (!bLinear)
8610
0
    {
8611
0
        aBoundRect.AdjustTop(fBorder );
8612
0
    }
8613
8614
0
    switch (rObject.m_aGradient.GetStyle())
8615
0
    {
8616
0
        case css::awt::GradientStyle_LINEAR:
8617
0
        case css::awt::GradientStyle_AXIAL:
8618
0
        {
8619
0
            aLine.append("/Domain[ 0 1 ]\n"
8620
0
                    "/Coords[ " );
8621
0
            tools::Polygon aPoly( 2 );
8622
0
            aPoly[0] = aBoundRect.BottomCenter();
8623
0
            aPoly[1] = aBoundRect.TopCenter();
8624
0
            aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
8625
8626
0
            aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8627
0
            aLine.append( " " );
8628
0
            aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8629
0
            aLine.append( " " );
8630
0
            aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8631
0
            aLine.append( " ");
8632
0
            aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8633
0
            aLine.append( " ]\n");
8634
0
            aLine.append("/Extend [true true]\n");
8635
0
            break;
8636
0
        }
8637
0
        default:
8638
0
            aLine.append("/Domain[ 0 1 0 1 ]\n"
8639
0
                    "/Matrix[ " );
8640
0
            aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8641
0
            aLine.append( " 0 0 " );
8642
0
            aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8643
0
            aLine.append( " 0 0 ]\n");
8644
0
    }
8645
0
    aLine.append("/Function " );
8646
0
    aLine.append( nFunctionObject );
8647
0
    aLine.append( " 0 R\n"
8648
0
                  ">>\n"
8649
0
                  "endobj\n\n" );
8650
0
    return writeBuffer( aLine );
8651
0
}
8652
8653
void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
8654
0
{
8655
0
    if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
8656
0
    {
8657
0
        writeReferenceXObject(rObject.m_aReferenceXObject);
8658
0
        return;
8659
0
    }
8660
8661
0
    if (!rObject.m_pStream) return;
8662
0
    if (!updateObject(rObject.m_nObject)) return;
8663
8664
0
    sal_uInt64 nLength = rObject.m_pStream->TellEnd();
8665
0
    rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8666
8667
0
    sal_Int32 nMaskObject = 0;
8668
0
    if( !rObject.m_aAlphaMask.IsEmpty() )
8669
0
    {
8670
0
        if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
8671
0
                && !m_bIsPDF_A1)
8672
0
        {
8673
0
            nMaskObject = createObject();
8674
0
        }
8675
0
        else if( m_bIsPDF_A1 )
8676
0
            m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8677
0
        else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8678
0
            m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8679
8680
0
    }
8681
0
    if (g_bDebugDisableCompression)
8682
0
    {
8683
0
        emitComment( "PDFWriterImpl::writeJPG" );
8684
0
    }
8685
8686
0
    OStringBuffer aLine(200);
8687
0
    aLine.append( rObject.m_nObject );
8688
0
    aLine.append( " 0 obj\n"
8689
0
                  "<</Type/XObject/Subtype/Image/Width " );
8690
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
8691
0
    aLine.append( " /Height " );
8692
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
8693
0
    aLine.append( " /BitsPerComponent 8 " );
8694
0
    if( rObject.m_bTrueColor )
8695
0
        aLine.append( "/ColorSpace/DeviceRGB" );
8696
0
    else
8697
0
        aLine.append( "/ColorSpace/DeviceGray" );
8698
0
    aLine.append( "/Filter/DCTDecode/Length " );
8699
0
    aLine.append(static_cast<sal_Int64>(calculateStreamSize(nLength)));
8700
0
    if( nMaskObject )
8701
0
    {
8702
0
        aLine.append(" /SMask ");
8703
0
        aLine.append( nMaskObject );
8704
0
        aLine.append( " 0 R " );
8705
0
    }
8706
0
    aLine.append( ">>\nstream\n" );
8707
0
    if (!writeBuffer(aLine))
8708
0
        return;
8709
8710
0
    checkAndEnableStreamEncryption( rObject.m_nObject );
8711
0
    if (!writeBufferBytes(rObject.m_pStream->GetData(), nLength))
8712
0
        return;
8713
0
    disableStreamEncryption();
8714
8715
0
    aLine.setLength( 0 );
8716
0
    if (!writeBuffer("\nendstream\nendobj\n\n"))
8717
0
        return;
8718
8719
0
    if( nMaskObject )
8720
0
        writeBitmapMaskObject( nMaskObject, rObject.m_aAlphaMask );
8721
8722
0
    writeReferenceXObject(rObject.m_aReferenceXObject);
8723
0
}
8724
8725
void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
8726
0
{
8727
0
    if (rEmit.m_nFormObject <= 0)
8728
0
        return;
8729
8730
    // Count /Matrix and /BBox.
8731
    // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
8732
    // rendering DPI so we have to take into account that here too.
8733
0
    static const double fResolutionDPI = getDefaultPdfResolutionDpi();
8734
0
    static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR;
8735
8736
0
    sal_Int32 nOldDPIX = GetDPIX();
8737
0
    sal_Int32 nOldDPIY = GetDPIY();
8738
0
    SetDPIX(fResolutionDPI);
8739
0
    SetDPIY(fResolutionDPI);
8740
0
    Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
8741
0
    SetDPIX(nOldDPIX);
8742
0
    SetDPIY(nOldDPIY);
8743
0
    double fScaleX = 1.0 / aSize.Width();
8744
0
    double fScaleY = 1.0 / aSize.Height();
8745
8746
0
    sal_Int32 nWrappedFormObject = 0;
8747
0
    if (!m_aContext.UseReferenceXObject)
8748
0
    {
8749
        // tdf#156842 increase scale for external PDF data
8750
        // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS
8751
        // that scale all images by this number.
8752
        // This fix also allows the CppunitTest_vcl_pdfexport to run
8753
        // successfully on macOS.
8754
0
        fScaleX = fMagicScaleFactor / aSize.Width();
8755
0
        fScaleY = fMagicScaleFactor / aSize.Height();
8756
8757
        // Parse the PDF data, we need that to write the PDF dictionary of our
8758
        // object.
8759
0
        if (rEmit.m_nExternalPDFDataIndex < 0)
8760
0
            return;
8761
0
        auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
8762
0
        auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
8763
0
        if (!pPDFDocument)
8764
0
        {
8765
            // Couldn't parse the document and can't continue
8766
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
8767
0
            return;
8768
0
        }
8769
8770
0
        std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
8771
0
        if (aPages.empty())
8772
0
        {
8773
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
8774
0
            return;
8775
0
        }
8776
8777
0
        size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
8778
8779
0
        filter::PDFObjectElement* pPage = aPages[nPageIndex];
8780
0
        if (!pPage)
8781
0
        {
8782
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
8783
0
            return;
8784
0
        }
8785
8786
        // Get the copied resource map, so we can use that to skip objects we already copied
8787
0
        auto& rCopiedResourcesMap = rExternalPDFStream.getCopiedResources();
8788
8789
        // Add page mapping to the copied resources map.
8790
        // Needed if we reference the current page and we want to prevent copying the page
8791
        // if it is referenced.
8792
0
        rCopiedResourcesMap.emplace(pPage->GetObjectValue(), m_aPages.back().m_nPageObject);
8793
8794
0
        double aOrigin[2] = { 0.0, 0.0 };
8795
8796
        // tdf#160714 use crop box for bounds of embedded PDF object
8797
        // If there is no crop box, fallback to the media box just to be safe.
8798
0
        auto* pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("CropBox"_ostr));
8799
0
        if (!pBoundsArray)
8800
0
            pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr));
8801
0
        if (pBoundsArray)
8802
0
        {
8803
0
            const auto& rElements = pBoundsArray->GetElements();
8804
0
            if (rElements.size() >= 4)
8805
0
            {
8806
                // get x1, y1 of the rectangle.
8807
0
                for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
8808
0
                {
8809
0
                    if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
8810
0
                        aOrigin[nIdx] = pNumElement->GetValue();
8811
0
                }
8812
0
            }
8813
0
        }
8814
8815
0
        std::vector<filter::PDFObjectElement*> aContentStreams;
8816
0
        if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr))
8817
0
            aContentStreams.push_back(pContentStream);
8818
0
        else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr)))
8819
0
        {
8820
0
            for (const auto pElement : pArray->GetElements())
8821
0
            {
8822
0
                auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8823
0
                if (!pReference)
8824
0
                    continue;
8825
8826
0
                filter::PDFObjectElement* pObject = pReference->LookupObject();
8827
0
                if (!pObject)
8828
0
                    continue;
8829
8830
0
                aContentStreams.push_back(pObject);
8831
0
            }
8832
0
        }
8833
8834
0
        if (aContentStreams.empty())
8835
0
        {
8836
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
8837
0
            return;
8838
0
        }
8839
8840
        // Merge link and widget annotations from pPage to our page.
8841
0
        mergeAnnotationsFromExternalPage(pPage, rCopiedResourcesMap);
8842
8843
0
        nWrappedFormObject = createObject();
8844
        // Write the form XObject wrapped below. This is a separate object from
8845
        // the wrapper, this way there is no need to alter the stream contents.
8846
8847
0
        OStringBuffer aLine;
8848
0
        aLine.append(nWrappedFormObject);
8849
0
        aLine.append(" 0 obj\n");
8850
0
        aLine.append("<< /Type /XObject");
8851
0
        aLine.append(" /Subtype /Form");
8852
8853
0
        tools::Long nWidth = aSize.Width();
8854
0
        tools::Long nHeight = aSize.Height();
8855
0
        basegfx::B2DRange aBBox(0, 0, aSize.Width(),  aSize.Height());
8856
0
        if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr)))
8857
0
        {
8858
            // The original page was rotated, then construct a transformation matrix which does the
8859
            // same with our form object.
8860
0
            sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
8861
            // /Rotate is clockwise, matrix rotate is counter-clockwise.
8862
0
            sal_Int32 nAngle = -1 * nRotAngle;
8863
8864
            // The bounding box just rotates.
8865
0
            basegfx::B2DHomMatrix aBBoxMat;
8866
0
            aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
8867
0
            aBBox.transform(aBBoxMat);
8868
8869
            // Now transform the object: rotate around the center and make sure that the rotation
8870
            // doesn't affect the aspect ratio.
8871
0
            basegfx::B2DHomMatrix aMat;
8872
0
            aMat.translate((-0.5 * aBBox.getWidth() / fMagicScaleFactor) - aOrigin[0], (-0.5 * aBBox.getHeight() / fMagicScaleFactor) - aOrigin[1]);
8873
0
            aMat.rotate(basegfx::deg2rad(nAngle));
8874
0
            aMat.translate(0.5 * nWidth / fMagicScaleFactor, 0.5 * nHeight / fMagicScaleFactor);
8875
8876
0
            aLine.append(" /Matrix [ ");
8877
0
            aLine.append(aMat.a());
8878
0
            aLine.append(" ");
8879
0
            aLine.append(aMat.b());
8880
0
            aLine.append(" ");
8881
0
            aLine.append(aMat.c());
8882
0
            aLine.append(" ");
8883
0
            aLine.append(aMat.d());
8884
0
            aLine.append(" ");
8885
0
            aLine.append(aMat.e());
8886
0
            aLine.append(" ");
8887
0
            aLine.append(aMat.f());
8888
0
            aLine.append(" ] ");
8889
0
        }
8890
8891
0
        PDFObjectCopier aCopier(*this);
8892
0
        aCopier.copyPageResources(pPage, aLine, rCopiedResourcesMap);
8893
8894
0
        aLine.append(" /BBox [ ");
8895
0
        aLine.append(aOrigin[0]);
8896
0
        aLine.append(' ');
8897
0
        aLine.append(aOrigin[1]);
8898
0
        aLine.append(' ');
8899
0
        aLine.append(aBBox.getWidth() + aOrigin[0]);
8900
0
        aLine.append(' ');
8901
0
        aLine.append(aBBox.getHeight() + aOrigin[1]);
8902
0
        aLine.append(" ]");
8903
8904
0
        if (!g_bDebugDisableCompression)
8905
0
            aLine.append(" /Filter/FlateDecode");
8906
0
        aLine.append(" /Length ");
8907
8908
0
        SvMemoryStream aStream;
8909
0
        bool bCompressed = false;
8910
0
        bool bIsTaggedNonReferenceXObject = m_aContext.Tagged && !m_aContext.UseReferenceXObject;
8911
0
        sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed,
8912
0
                                                             bIsTaggedNonReferenceXObject);
8913
0
        aLine.append(static_cast<sal_Int64>(calculateStreamSize(nLength)));
8914
8915
0
        aLine.append(">>\nstream\n");
8916
0
        if (g_bDebugDisableCompression)
8917
0
        {
8918
0
            emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
8919
0
        }
8920
0
        if (!updateObject(nWrappedFormObject))
8921
0
            return;
8922
0
        if (!writeBuffer(aLine))
8923
0
            return;
8924
0
        aLine.setLength(0);
8925
8926
0
        checkAndEnableStreamEncryption(nWrappedFormObject);
8927
        // Copy the original page streams to the form XObject stream.
8928
0
        aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
8929
0
        if (!writeBuffer(aLine))
8930
0
            return;
8931
0
        aLine.setLength(0);
8932
0
        disableStreamEncryption();
8933
8934
0
        aLine.append("\nendstream\nendobj\n\n");
8935
0
        if (!writeBuffer(aLine))
8936
0
            return;
8937
0
    }
8938
8939
0
    OStringBuffer aLine;
8940
0
    if (g_bDebugDisableCompression)
8941
0
    {
8942
0
        emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
8943
0
    }
8944
0
    if (!updateObject(rEmit.m_nFormObject))
8945
0
        return;
8946
8947
    // Now have all the info to write the form XObject.
8948
0
    aLine.append(rEmit.m_nFormObject);
8949
0
    aLine.append(" 0 obj\n");
8950
0
    aLine.append("<< /Type /XObject");
8951
0
    aLine.append(" /Subtype /Form");
8952
0
    aLine.append(" /Resources << /XObject<<");
8953
8954
0
    sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
8955
0
    aLine.append(" /Im");
8956
0
    aLine.append(nObject);
8957
0
    aLine.append(" ");
8958
0
    aLine.append(nObject);
8959
0
    aLine.append(" 0 R");
8960
8961
0
    aLine.append(">> >>");
8962
0
    aLine.append(" /Matrix [ ");
8963
0
    appendDouble(fScaleX, aLine);
8964
0
    aLine.append(" 0 0 ");
8965
0
    appendDouble(fScaleY, aLine);
8966
0
    aLine.append(" 0 0 ]");
8967
0
    aLine.append(" /BBox [ 0 0 ");
8968
    // tdf#157680 reduce size by magic scale factor in /BBox
8969
0
    aLine.append(aSize.Width() / fMagicScaleFactor);
8970
0
    aLine.append(" ");
8971
    // tdf#157680 reduce size by magic scale factor in /BBox
8972
0
    aLine.append(aSize.Height() / fMagicScaleFactor);
8973
0
    aLine.append(" ]\n");
8974
8975
0
    if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
8976
0
    {
8977
        // Write the reference dictionary.
8978
0
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
8979
0
        aLine.append("/Ref<< /F << /Type /Filespec ");
8980
0
        aWriter.writeKeyAndLiteralEncrypt("/F", "<embedded file>", rEmit.m_nFormObject);
8981
0
        if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
8982
0
        {   // ISO 14289-1:2014, Clause: 7.11
8983
0
            aWriter.writeKeyAndLiteralEncrypt("/UF", "<embedded file>", rEmit.m_nFormObject);
8984
0
        }
8985
0
        aLine.append("/EF << /F ");
8986
0
        aLine.append(rEmit.m_nEmbeddedObject);
8987
0
        aLine.append(" 0 R >> >> /Page 0 >>\n");
8988
0
    }
8989
8990
0
    aLine.append("/Length ");
8991
8992
0
    OStringBuffer aStream;
8993
0
    aStream.append("q ");
8994
0
    if (m_aContext.UseReferenceXObject)
8995
0
    {
8996
        // Reference XObject markup is used, just refer to the fallback bitmap
8997
        // here.
8998
0
        aStream.append(aSize.Width());
8999
0
        aStream.append(" 0 0 ");
9000
0
        aStream.append(aSize.Height());
9001
0
        aStream.append(" 0 0 cm\n");
9002
0
        aStream.append("/Im");
9003
0
        aStream.append(rEmit.m_nBitmapObject);
9004
0
        aStream.append(" Do\n");
9005
0
    }
9006
0
    else
9007
0
    {
9008
        // Reset line width to the default.
9009
0
        aStream.append(" 1 w\n");
9010
9011
        // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
9012
        // consistent with that.
9013
0
        aStream.append("1 1 1 rg\n");
9014
0
        aStream.append("0 0 ");
9015
0
        aStream.append(aSize.Width());
9016
0
        aStream.append(" ");
9017
0
        aStream.append(aSize.Height());
9018
0
        aStream.append(" re\n");
9019
0
        aStream.append("f*\n");
9020
9021
        // Reset non-stroking color in case the XObject uses the default
9022
0
        aStream.append("0 0 0 rg\n");
9023
        // No reference XObject, draw the form XObject containing the original
9024
        // page streams.
9025
0
        aStream.append("/Im");
9026
0
        aStream.append(nWrappedFormObject);
9027
0
        aStream.append(" Do\n");
9028
0
    }
9029
0
    aStream.append("Q");
9030
0
    aLine.append(static_cast<sal_Int64>(calculateStreamSize(aStream.getLength())));
9031
9032
0
    aLine.append(">>\nstream\n");
9033
0
    if (!writeBuffer(aLine))
9034
0
        return;
9035
0
    aLine.setLength(0);
9036
9037
0
    checkAndEnableStreamEncryption(rEmit.m_nFormObject);
9038
0
    aLine.append(aStream.getStr());
9039
0
    if (!writeBuffer(aLine))
9040
0
        return;
9041
0
    aLine.setLength(0);
9042
0
    disableStreamEncryption();
9043
9044
0
    aLine.append("\nendstream\nendobj\n\n");
9045
0
    if (!writeBuffer(aLine))
9046
0
        return;
9047
0
}
9048
9049
namespace
9050
{
9051
9052
sal_Int32 getRootParent(filter::PDFObjectElement* pObject)
9053
0
{
9054
0
    auto* pReference = dynamic_cast<filter::PDFReferenceElement*>(pObject->Lookup("Parent"_ostr));
9055
0
    if (!pReference)
9056
0
        return pObject->GetObjectValue();
9057
9058
0
    auto* pParent = pReference->LookupObject();
9059
0
    return getRootParent(pParent);
9060
0
}
9061
9062
} // end anonymous
9063
9064
void PDFWriterImpl::mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage,  std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap)
9065
0
{
9066
0
    auto* pResult = pPage->Lookup("Annots"_ostr);
9067
0
    filter::PDFArrayElement* pArray = nullptr;
9068
    // If the Annots array is a reference - get the array from the referenced object
9069
0
    auto pAnnotsReference = dynamic_cast<filter::PDFReferenceElement*>(pResult);
9070
0
    if (pAnnotsReference)
9071
0
    {
9072
0
        filter::PDFObjectElement* pObject = pAnnotsReference->LookupObject();
9073
0
        pArray = pObject->GetArray();
9074
0
    }
9075
0
    else
9076
0
    {
9077
        // Not a reference so is it an array
9078
0
        pArray = dynamic_cast<filter::PDFArrayElement*>(pResult);
9079
0
    }
9080
9081
    // Have we found our /Annots array?
9082
0
    if (!pArray)
9083
0
        return;
9084
9085
0
    std::unordered_set<sal_Int32> aAlreadyCopied;
9086
0
    PDFObjectCopier aCopier(*this);
9087
0
    SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
9088
9089
0
    for (const auto pElement : pArray->GetElements())
9090
0
    {
9091
0
        auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9092
0
        if (!pReference)
9093
0
            continue;
9094
9095
0
        filter::PDFObjectElement* pObject = pReference->LookupObject();
9096
0
        if (!pObject)
9097
0
            continue;
9098
9099
        // Get the /Type and the /Subtype
9100
0
        auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
9101
0
        auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
9102
9103
        // Is it a /Annot we want to copy?
9104
0
        if (pType && pType->GetValue() == "Annot" && pSubtype)
9105
0
        {
9106
0
            bool bIsLink = pSubtype->GetValue() == "Link";
9107
0
            bool bIsWidget = pSubtype->GetValue() == "Widget";
9108
9109
            // is link or widget
9110
0
            if (!bIsLink && !bIsWidget)
9111
0
                continue;
9112
9113
            // Copy over the annotation and refer to its new id.
9114
0
            sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pObject, rCopiedResourcesMap);
9115
0
            m_aPages.back().m_aAnnotations.push_back(nNewId);
9116
9117
0
            if (!bIsWidget)
9118
0
                continue;
9119
9120
            // Find the root
9121
0
            sal_Int32 nRootID = getRootParent(pObject);
9122
9123
0
            auto aIterator = rCopiedResourcesMap.find(nRootID);
9124
0
            if (aIterator == rCopiedResourcesMap.end()) // Can't find the mapped ID ?
9125
0
                continue;
9126
9127
0
            nNewId = aIterator->second;
9128
9129
            // Ignore if we added the ID already
9130
0
            if (aAlreadyCopied.find(nNewId) == aAlreadyCopied.end())
9131
0
            {
9132
                // Add new entry into copied widgets vector
9133
0
                auto& rCopiedWidget = m_aCopiedWidgets.emplace_back();
9134
0
                rCopiedWidget.m_nObject = nNewId;
9135
0
                aAlreadyCopied.emplace(nNewId);
9136
0
            }
9137
0
        }
9138
0
    }
9139
9140
0
}
9141
9142
bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject )
9143
0
{
9144
0
    if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9145
0
    {
9146
0
        writeReferenceXObject(rObject.m_aReferenceXObject);
9147
0
        return true;
9148
0
    }
9149
9150
0
    if (!updateObject(rObject.m_nObject))
9151
0
        return false;
9152
9153
0
    bool    bWriteMask = false;
9154
0
    if( rObject.m_aBitmap.HasAlpha() )
9155
0
    {
9156
0
        if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9157
0
            bWriteMask = true;
9158
        // else draw without alpha channel
9159
0
    }
9160
9161
0
    BitmapScopedReadAccess pAccess(rObject.m_aBitmap);
9162
9163
0
    bool bTrueColor = true;
9164
0
    sal_Int32 nBitsPerComponent = 0;
9165
0
    auto const ePixelFormat = rObject.m_aBitmap.getPixelFormat();
9166
0
    switch (ePixelFormat)
9167
0
    {
9168
0
        case vcl::PixelFormat::N8_BPP:
9169
0
            bTrueColor = false;
9170
0
            nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
9171
0
            break;
9172
0
        case vcl::PixelFormat::N24_BPP:
9173
0
        case vcl::PixelFormat::N32_BPP:
9174
0
            bTrueColor = true;
9175
0
            nBitsPerComponent = 8;
9176
0
            break;
9177
0
        case vcl::PixelFormat::INVALID:
9178
0
            return false;
9179
0
    }
9180
9181
0
    sal_Int32 nStreamLengthObject   = createObject();
9182
0
    sal_Int32 nMaskObject           = 0;
9183
9184
0
    if (g_bDebugDisableCompression)
9185
0
    {
9186
0
        emitComment( "PDFWriterImpl::writeBitmapObject" );
9187
0
    }
9188
0
    OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
9189
0
    aLine.append( rObject.m_nObject );
9190
0
    aLine.append( " 0 obj\n"
9191
0
                  "<</Type/XObject/Subtype/Image/Width " );
9192
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9193
0
    aLine.append( "/Height " );
9194
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Height()) );
9195
0
    aLine.append( "/BitsPerComponent " );
9196
0
    aLine.append( nBitsPerComponent );
9197
0
    aLine.append( "/Length " );
9198
0
    aLine.append( nStreamLengthObject );
9199
0
    aLine.append( " 0 R\n" );
9200
0
    if (!g_bDebugDisableCompression)
9201
0
    {
9202
0
        if( nBitsPerComponent != 1 )
9203
0
        {
9204
0
            aLine.append( "/Filter/FlateDecode" );
9205
0
        }
9206
0
        else
9207
0
        {
9208
0
            aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9209
0
            aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9210
0
            aLine.append( ">>\n" );
9211
0
        }
9212
0
    }
9213
0
    aLine.append( "/ColorSpace" );
9214
0
    if( bTrueColor )
9215
0
        aLine.append( "/DeviceRGB\n" );
9216
0
    else
9217
0
    {
9218
0
        aLine.append( "[ /Indexed/DeviceRGB " );
9219
0
        aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9220
0
        aLine.append( "\n<" );
9221
0
        if (m_aContext.Encryption.canEncrypt())
9222
0
        {
9223
0
            enableStringEncryption(rObject.m_nObject);
9224
            //check encryption buffer size
9225
0
            m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9226
0
            int nChar = 0;
9227
            //fill the encryption buffer
9228
0
            for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9229
0
            {
9230
0
                const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9231
0
                m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9232
0
                m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9233
0
                m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9234
0
            }
9235
            //encrypt the colorspace lookup table
9236
0
            std::vector<sal_uInt8> aOutputBuffer(nChar);
9237
0
            m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, aOutputBuffer, nChar);
9238
            //now queue the data for output
9239
0
            COSWriter::appendHexArray(aOutputBuffer.data(), aOutputBuffer.size(), aLine);
9240
0
        }
9241
0
        else //no encryption requested (PDF/A-1a program flow drops here)
9242
0
        {
9243
0
            for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9244
0
            {
9245
0
                const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9246
0
                COSWriter::appendHex( rColor.GetRed(), aLine );
9247
0
                COSWriter::appendHex( rColor.GetGreen(), aLine );
9248
0
                COSWriter::appendHex( rColor.GetBlue(), aLine );
9249
0
            }
9250
0
        }
9251
0
        aLine.append( ">\n]\n" );
9252
0
    }
9253
9254
0
    if (!m_bIsPDF_A1)
9255
0
    {
9256
0
        if( bWriteMask )
9257
0
        {
9258
0
            nMaskObject = createObject();
9259
0
            aLine.append( "/SMask " );
9260
0
            aLine.append( nMaskObject );
9261
0
            aLine.append( " 0 R\n" );
9262
0
        }
9263
0
    }
9264
0
    else if( m_bIsPDF_A1 && bWriteMask )
9265
0
        m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9266
9267
0
    aLine.append( ">>\n"
9268
0
                  "stream\n" );
9269
0
    if (!writeBuffer(aLine)) return false;
9270
0
    sal_uInt64 nStartPos = 0;
9271
0
    if (osl::File::E_None != m_aFile.getPos(nStartPos))
9272
0
        return false;
9273
9274
0
    checkAndEnableStreamEncryption( rObject.m_nObject );
9275
0
    {
9276
0
        beginCompression();
9277
0
        if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9278
0
        {
9279
            //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9280
0
            const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9281
9282
0
            for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9283
0
            {
9284
0
                if (!writeBufferBytes(pAccess->GetScanline(i), nScanLineBytes))
9285
0
                    return false;
9286
0
            }
9287
0
        }
9288
0
        else
9289
0
        {
9290
0
            const int nScanLineBytes = pAccess->Width()*3;
9291
0
            std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9292
0
            for( tools::Long y = 0, nHeight = pAccess->Height(); y < nHeight; y++ )
9293
0
            {
9294
0
                for( tools::Long x = 0, nWidth = pAccess->Width(); x < nWidth; x++ )
9295
0
                {
9296
0
                    BitmapColor aColor = pAccess->GetColor( y, x );
9297
0
                    xCol[3*x+0] = aColor.GetRed();
9298
0
                    xCol[3*x+1] = aColor.GetGreen();
9299
0
                    xCol[3*x+2] = aColor.GetBlue();
9300
0
                }
9301
0
                if (!writeBufferBytes(xCol.get(), nScanLineBytes))
9302
0
                    return false;
9303
0
            }
9304
0
        }
9305
0
        endCompression();
9306
0
    }
9307
0
    disableStreamEncryption();
9308
9309
0
    sal_uInt64 nEndPos = 0;
9310
0
    if (osl::File::E_None != m_aFile.getPos(nEndPos))
9311
0
        return false;
9312
0
    aLine.setLength( 0 );
9313
0
    aLine.append( "\nendstream\nendobj\n\n" );
9314
0
    if (!writeBuffer(aLine)) return false;
9315
0
    if (!updateObject(nStreamLengthObject)) return false;
9316
0
    aLine.setLength( 0 );
9317
0
    aLine.append( nStreamLengthObject );
9318
0
    aLine.append( " 0 obj\n" );
9319
0
    aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9320
0
    aLine.append( "\nendobj\n\n" );
9321
0
    if (!writeBuffer(aLine)) return false;
9322
9323
0
    if( nMaskObject )
9324
0
        return writeBitmapMaskObject( nMaskObject, rObject.m_aBitmap.CreateAlphaMask() );
9325
9326
0
    writeReferenceXObject(rObject.m_aReferenceXObject);
9327
9328
0
    return true;
9329
0
}
9330
9331
bool PDFWriterImpl::writeBitmapMaskObject( sal_Int32 nMaskObject, const AlphaMask& rAlphaMask )
9332
0
{
9333
0
    assert( rAlphaMask.GetBitmap().getPixelFormat() == vcl::PixelFormat::N8_BPP );
9334
9335
0
    if (!updateObject(nMaskObject))
9336
0
        return false;
9337
9338
0
    Bitmap  aBitmap;
9339
0
    if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9340
0
    {
9341
0
        aBitmap = rAlphaMask.GetBitmap();
9342
0
        aBitmap.Convert( BmpConversion::N1BitThreshold );
9343
0
    }
9344
0
    else
9345
0
    {
9346
0
        aBitmap = rAlphaMask.GetBitmap();
9347
0
    }
9348
9349
0
    const sal_Int32 nBitsPerComponent = 8;
9350
9351
0
    sal_Int32 nStreamLengthObject   = createObject();
9352
9353
0
    if (g_bDebugDisableCompression)
9354
0
    {
9355
0
        emitComment( "PDFWriterImpl::writeBitmapObject" );
9356
0
    }
9357
0
    OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
9358
0
    aLine.append( nMaskObject );
9359
0
    aLine.append( " 0 obj\n"
9360
0
                  "<</Type/XObject/Subtype/Image/Width " );
9361
0
    aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9362
0
    aLine.append( "/Height " );
9363
0
    aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9364
0
    aLine.append( "/BitsPerComponent " );
9365
0
    aLine.append( nBitsPerComponent );
9366
0
    aLine.append( "/Length " );
9367
0
    aLine.append( nStreamLengthObject );
9368
0
    aLine.append( " 0 R\n" );
9369
0
    if (!g_bDebugDisableCompression)
9370
0
    {
9371
0
        aLine.append( "/Filter/FlateDecode" );
9372
0
    }
9373
0
    aLine.append( "/ColorSpace/DeviceGray\n"
9374
0
                  "/Decode [ 1 0 ]\n" );
9375
9376
0
    aLine.append( ">>\n"
9377
0
                  "stream\n" );
9378
0
    if (!writeBuffer(aLine)) return false;
9379
0
    sal_uInt64 nStartPos = 0;
9380
0
    if (osl::File::E_None != m_aFile.getPos(nStartPos))
9381
0
        return false;
9382
9383
0
    checkAndEnableStreamEncryption( nMaskObject );
9384
0
    beginCompression();
9385
0
    BitmapScopedReadAccess pAccess(aBitmap);
9386
    //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9387
0
    const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9388
    // we have alpha, but we want to output transparency, so we need to invert the data
9389
0
    std::unique_ptr<sal_uInt8[]> pInvertedBytes = std::make_unique<sal_uInt8[]>(nScanLineBytes);
9390
0
    for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9391
0
    {
9392
0
        const Scanline pScanline = pAccess->GetScanline(i);
9393
0
        std::copy(pScanline, pScanline + nScanLineBytes, pInvertedBytes.get());
9394
0
        for (auto p = pInvertedBytes.get(); p < pInvertedBytes.get() + nScanLineBytes; ++p)
9395
0
            *p = ~(*p);
9396
0
        if (!writeBufferBytes(pInvertedBytes.get(), nScanLineBytes))
9397
0
            return false;
9398
0
    }
9399
0
    endCompression();
9400
0
    disableStreamEncryption();
9401
9402
0
    sal_uInt64 nEndPos = 0;
9403
0
    if (osl::File::E_None != m_aFile.getPos(nEndPos))
9404
0
        return false;
9405
0
    aLine.setLength( 0 );
9406
0
    aLine.append( "\nendstream\nendobj\n\n" );
9407
0
    if (!writeBuffer(aLine)) return false;
9408
0
    if (!updateObject(nStreamLengthObject)) return false;
9409
0
    aLine.setLength( 0 );
9410
0
    aLine.append( nStreamLengthObject );
9411
0
    aLine.append( " 0 obj\n" );
9412
0
    aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9413
0
    aLine.append( "\nendobj\n\n" );
9414
0
    if (!writeBuffer(aLine)) return false;
9415
9416
0
    return true;
9417
0
}
9418
9419
void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9420
0
{
9421
    // The bitmap object is always a valid identifier, even if the graphic has
9422
    // no pdf data.
9423
0
    rEmit.m_nBitmapObject = nBitmapObject;
9424
9425
0
    if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
9426
0
        return;
9427
9428
0
    BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
9429
9430
0
    if (m_aContext.UseReferenceXObject)
9431
0
    {
9432
        // Store the original PDF data as an embedded file.
9433
0
        auto nObjectID = addEmbeddedFile(rDataContainer);
9434
0
        rEmit.m_nEmbeddedObject = nObjectID;
9435
0
    }
9436
0
    else
9437
0
    {
9438
0
        sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
9439
0
        rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
9440
0
        rEmit.m_nExternalPDFDataIndex = aIndex;
9441
0
    }
9442
9443
0
    rEmit.m_nFormObject = createObject();
9444
0
    rEmit.m_aPixelSize = rGraphic.GetSizePixel();
9445
0
}
9446
9447
void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
9448
0
{
9449
0
    MARK( "drawJPGBitmap" );
9450
9451
0
    OStringBuffer aLine( 80 );
9452
0
    updateGraphicsState();
9453
9454
    // #i40055# sanity check
9455
0
    if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9456
0
        return;
9457
0
    if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9458
0
        return;
9459
9460
0
    rDCTData.Seek( 0 );
9461
0
    if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9462
0
    {
9463
        // need to convert to grayscale;
9464
        // load stream to bitmap and draw the bitmap instead
9465
0
        Graphic aGraphic;
9466
0
        GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9467
0
        if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
9468
0
        {
9469
0
            Bitmap aBmp( aGraphic.GetBitmap().CreateColorBitmap() );
9470
0
            Bitmap aBmp2( aBmp, rAlphaMask );
9471
0
            drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmp2 );
9472
0
        }
9473
0
        else
9474
0
            drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmap() );
9475
0
        return;
9476
0
    }
9477
9478
0
    std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9479
0
    pStream->WriteStream( rDCTData );
9480
0
    pStream->Seek( STREAM_SEEK_TO_END );
9481
9482
0
    BitmapID aID;
9483
0
    aID.m_aPixelSize    = rSizePixel;
9484
0
    aID.m_nSize         = pStream->Tell();
9485
0
    pStream->Seek( STREAM_SEEK_TO_BEGIN );
9486
0
    aID.m_nChecksum     = rtl_crc32( 0, pStream->GetData(), aID.m_nSize );
9487
0
    if( ! rAlphaMask.IsEmpty() )
9488
0
        aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
9489
9490
0
    std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9491
0
                                             [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9492
0
    if( it == m_aJPGs.end() )
9493
0
    {
9494
0
        m_aJPGs.emplace( m_aJPGs.begin() );
9495
0
        JPGEmit& rEmit = m_aJPGs.front();
9496
0
        if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9497
0
            rEmit.m_nObject = createObject();
9498
0
        rEmit.m_aID         = aID;
9499
0
        rEmit.m_pStream = std::move( pStream );
9500
0
        rEmit.m_bTrueColor  = bIsTrueColor;
9501
0
        if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
9502
0
            rEmit.m_aAlphaMask = rAlphaMask;
9503
0
        createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9504
9505
0
        it = m_aJPGs.begin();
9506
0
    }
9507
9508
0
    aLine.append( "q " );
9509
0
    sal_Int32 nCheckWidth = 0;
9510
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9511
0
    aLine.append( " 0 0 " );
9512
0
    sal_Int32 nCheckHeight = 0;
9513
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9514
0
    aLine.append( ' ' );
9515
0
    m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9516
0
    aLine.append( " cm\n/Im" );
9517
0
    sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9518
0
    aLine.append(nObject);
9519
0
    aLine.append( " Do Q\n" );
9520
0
    if( nCheckWidth == 0 || nCheckHeight == 0 )
9521
0
    {
9522
        // #i97512# avoid invalid current matrix
9523
0
        aLine.setLength( 0 );
9524
0
        aLine.append( "\n%jpeg image /Im" );
9525
0
        aLine.append( it->m_nObject );
9526
0
        aLine.append( " scaled to zero size, omitted\n" );
9527
0
    }
9528
0
    writeBuffer( aLine );
9529
9530
0
    OString aObjName = "Im" + OString::number(nObject);
9531
0
    pushResource( ResourceKind::XObject, aObjName, nObject );
9532
9533
0
}
9534
9535
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9536
0
{
9537
0
    OStringBuffer& rLine = drawBitmapLine;
9538
0
    rLine.setLength(0);
9539
0
    updateGraphicsState();
9540
9541
0
    rLine.append( "q " );
9542
0
    if( rFillColor != COL_TRANSPARENT )
9543
0
    {
9544
0
        appendNonStrokingColor( rFillColor, rLine );
9545
0
        rLine.append( ' ' );
9546
0
    }
9547
0
    sal_Int32 nCheckWidth = 0;
9548
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), rLine, false, &nCheckWidth );
9549
0
    rLine.append( " 0 0 " );
9550
0
    sal_Int32 nCheckHeight = 0;
9551
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), rLine, true, &nCheckHeight );
9552
0
    rLine.append( ' ' );
9553
0
    m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), rLine );
9554
0
    rLine.append( " cm\n/Im" );
9555
0
    sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9556
0
    rLine.append(nObject);
9557
0
    rLine.append( " Do Q\n" );
9558
0
    if( nCheckWidth == 0 || nCheckHeight == 0 )
9559
0
    {
9560
        // #i97512# avoid invalid current matrix
9561
0
        rLine.setLength( 0 );
9562
0
        rLine.append( "\n%bitmap image /Im" );
9563
0
        rLine.append( rBitmap.m_nObject );
9564
0
        rLine.append( " scaled to zero size, omitted\n" );
9565
0
    }
9566
0
    writeBuffer( rLine );
9567
0
}
9568
9569
const BitmapEmit& PDFWriterImpl::createBitmapEmit(const Bitmap& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
9570
0
{
9571
0
    Bitmap aBitmap( i_rBitmap );
9572
0
    auto ePixelFormat = aBitmap.getPixelFormat();
9573
0
    if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9574
0
        aBitmap.Convert(BmpConversion::N8BitGreys);
9575
0
    BitmapID aID;
9576
0
    aID.m_aPixelSize        = aBitmap.GetSizePixel();
9577
0
    aID.m_nSize             = vcl::pixelFormatBitCount(ePixelFormat);
9578
0
    if( aBitmap.HasAlpha() )
9579
0
    {
9580
0
        std::pair<Bitmap, AlphaMask> aPair = aBitmap.SplitIntoColorAndAlpha();
9581
0
        aID.m_nChecksum     = aPair.first.GetChecksum();
9582
0
        aID.m_nMaskChecksum = aPair.second.GetChecksum();
9583
0
    }
9584
0
    else
9585
0
    {
9586
0
        aID.m_nChecksum         = aBitmap.GetChecksum();
9587
0
        aID.m_nMaskChecksum     = 0;
9588
0
    }
9589
0
    std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
9590
0
                                             [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9591
0
    if (it == rBitmaps.end())
9592
0
    {
9593
0
        rBitmaps.push_front(BitmapEmit());
9594
0
        rBitmaps.front().m_aID = aID;
9595
0
        rBitmaps.front().m_aBitmap = std::move(aBitmap);
9596
0
        if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9597
0
            rBitmaps.front().m_nObject = createObject();
9598
0
        createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
9599
0
        it = rBitmaps.begin();
9600
0
    }
9601
9602
0
    sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9603
0
    OString aObjName = "Im" + OString::number(nObject);
9604
0
    pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
9605
9606
0
    return *it;
9607
0
}
9608
9609
const BitmapEmit& PDFWriterImpl::createBitmapEmit( const Bitmap& i_rBitmap, const Graphic& rGraphic )
9610
0
{
9611
0
    return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
9612
0
}
9613
9614
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9615
0
{
9616
0
    MARK( "drawBitmap (Bitmap)" );
9617
9618
    // #i40055# sanity check
9619
0
    if( ! (rDestSize.Width() && rDestSize.Height()) )
9620
0
        return;
9621
9622
0
    const BitmapEmit& rEmit = createBitmapEmit( rBitmap, rGraphic );
9623
0
    drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9624
0
}
9625
9626
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap )
9627
0
{
9628
0
    MARK( "drawBitmap (Bitmap)" );
9629
9630
    // #i40055# sanity check
9631
0
    if( ! (rDestSize.Width() && rDestSize.Height()) )
9632
0
        return;
9633
9634
0
    const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9635
0
    drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9636
0
}
9637
9638
sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9639
0
{
9640
0
    Size aPtSize( convert( m_aGraphicsStack.front().m_aMapMode,
9641
0
                               MapMode( MapUnit::MapPoint ),
9642
0
                               this,
9643
0
                               rSize ) );
9644
    // check if we already have this gradient
9645
    // rounding to point will generally lose some pixels
9646
    // round up to point boundary
9647
0
    aPtSize.AdjustWidth( 1 );
9648
0
    aPtSize.AdjustHeight( 1 );
9649
0
    std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9650
0
                                             [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9651
9652
0
    if( it == m_aGradients.end() )
9653
0
    {
9654
0
        m_aGradients.push_front( GradientEmit() );
9655
0
        m_aGradients.front().m_aGradient    = rGradient;
9656
0
        m_aGradients.front().m_nObject      = createObject();
9657
0
        m_aGradients.front().m_aSize        = aPtSize;
9658
0
        it = m_aGradients.begin();
9659
0
    }
9660
9661
0
    OStringBuffer aObjName( 16 );
9662
0
    aObjName.append( 'P' );
9663
0
    aObjName.append( it->m_nObject );
9664
0
    pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
9665
9666
0
    return it->m_nObject;
9667
0
}
9668
9669
void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9670
0
{
9671
0
    MARK( "drawGradient (Rectangle)" );
9672
9673
0
    sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9674
9675
0
    Point aTranslate( rRect.BottomLeft() );
9676
0
    aTranslate += Point( 0, 1 );
9677
9678
0
    updateGraphicsState();
9679
9680
0
    OStringBuffer aLine( 80 );
9681
0
    aLine.append( "q 1 0 0 1 " );
9682
0
    m_aPages.back().appendPoint( aTranslate, aLine );
9683
0
    aLine.append( " cm " );
9684
    // if a stroke is appended reset the clip region before stroke
9685
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9686
0
        aLine.append( "q " );
9687
0
    aLine.append( "0 0 " );
9688
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9689
0
    aLine.append( ' ' );
9690
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9691
0
    aLine.append( " re W n\n" );
9692
9693
0
    aLine.append( "/P" );
9694
0
    aLine.append( nGradient );
9695
0
    aLine.append( " sh " );
9696
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9697
0
    {
9698
0
        aLine.append( "Q 0 0 " );
9699
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9700
0
        aLine.append( ' ' );
9701
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9702
0
        aLine.append( " re S " );
9703
0
    }
9704
0
    aLine.append( "Q\n" );
9705
0
    writeBuffer( aLine );
9706
0
}
9707
9708
void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
9709
0
{
9710
0
    MARK( "drawHatch" );
9711
9712
0
    updateGraphicsState();
9713
9714
0
    if( rPolyPoly.Count() )
9715
0
    {
9716
0
        tools::PolyPolygon     aPolyPoly( rPolyPoly );
9717
9718
0
        aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
9719
0
        push( PushFlags::LINECOLOR );
9720
0
        setLineColor( rHatch.GetColor() );
9721
0
        DrawHatch( aPolyPoly, rHatch, false );
9722
0
        pop();
9723
0
    }
9724
0
}
9725
9726
void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
9727
0
{
9728
0
    MARK( "drawWallpaper" );
9729
9730
0
    bool bDrawColor         = false;
9731
0
    bool bDrawGradient      = false;
9732
0
    bool bDrawBitmap        = false;
9733
9734
0
    Bitmap aBitmap;
9735
0
    Point aBmpPos = rRect.TopLeft();
9736
0
    Size aBmpSize;
9737
0
    if( rWall.IsBitmap() )
9738
0
    {
9739
0
        aBitmap = rWall.GetBitmap();
9740
0
        aBmpSize = convert( aBitmap.GetPrefMapMode(),
9741
0
                                getMapMode(),
9742
0
                                this,
9743
0
                                aBitmap.GetPrefSize() );
9744
0
        tools::Rectangle aRect( rRect );
9745
0
        if( rWall.IsRect() )
9746
0
        {
9747
0
            aRect = rWall.GetRect();
9748
0
            aBmpPos = aRect.TopLeft();
9749
0
            aBmpSize = aRect.GetSize();
9750
0
        }
9751
0
        if( rWall.GetStyle() != WallpaperStyle::Scale )
9752
0
        {
9753
0
            if( rWall.GetStyle() != WallpaperStyle::Tile )
9754
0
            {
9755
0
                bDrawBitmap     = true;
9756
0
                if( rWall.IsGradient() )
9757
0
                    bDrawGradient = true;
9758
0
                else
9759
0
                    bDrawColor = true;
9760
0
                switch( rWall.GetStyle() )
9761
0
                {
9762
0
                    case WallpaperStyle::TopLeft:
9763
0
                        break;
9764
0
                    case WallpaperStyle::Top:
9765
0
                        aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9766
0
                        break;
9767
0
                    case WallpaperStyle::Left:
9768
0
                        aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9769
0
                        break;
9770
0
                    case WallpaperStyle::TopRight:
9771
0
                        aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9772
0
                        break;
9773
0
                    case WallpaperStyle::Center:
9774
0
                        aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9775
0
                        aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9776
0
                        break;
9777
0
                    case WallpaperStyle::Right:
9778
0
                        aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9779
0
                        aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9780
0
                        break;
9781
0
                    case WallpaperStyle::BottomLeft:
9782
0
                        aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9783
0
                        break;
9784
0
                    case WallpaperStyle::Bottom:
9785
0
                        aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9786
0
                        aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9787
0
                        break;
9788
0
                    case WallpaperStyle::BottomRight:
9789
0
                        aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9790
0
                        aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9791
0
                        break;
9792
0
                    default: ;
9793
0
                }
9794
0
            }
9795
0
            else
9796
0
            {
9797
                // push the bitmap
9798
0
                const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
9799
9800
                // convert to page coordinates; this needs to be done here
9801
                // since the emit does not know the page anymore
9802
0
                tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
9803
0
                m_aPages.back().convertRect( aConvertRect );
9804
9805
0
                OString aImageName = "Im" + OString::number( rEmit.m_nObject );
9806
9807
                // push the pattern
9808
0
                OStringBuffer aTilingStream( 32 );
9809
0
                appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
9810
0
                aTilingStream.append( " 0 0 " );
9811
0
                appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
9812
0
                aTilingStream.append( " 0 0 cm\n/" );
9813
0
                aTilingStream.append( aImageName );
9814
0
                aTilingStream.append( " Do\n" );
9815
9816
0
                m_aTilings.emplace_back( );
9817
0
                m_aTilings.back().m_nObject         = createObject();
9818
0
                m_aTilings.back().m_aRectangle      = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
9819
0
                m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
9820
0
                m_aTilings.back().m_pTilingStream->WriteBytes(
9821
0
                    aTilingStream.getStr(), aTilingStream.getLength() );
9822
                // phase the tiling so wallpaper begins on upper left
9823
0
                if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
9824
0
                    throw o3tl::divide_by_zero();
9825
0
                m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
9826
0
                m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
9827
0
                m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
9828
9829
0
                updateGraphicsState();
9830
9831
0
                OStringBuffer aObjName( 16 );
9832
0
                aObjName.append( 'P' );
9833
0
                aObjName.append( m_aTilings.back().m_nObject );
9834
0
                OString aPatternName( aObjName.makeStringAndClear() );
9835
0
                pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
9836
9837
                // fill a rRect with the pattern
9838
0
                OStringBuffer aLine( 100 );
9839
0
                aLine.append( "q /Pattern cs /" );
9840
0
                aLine.append( aPatternName );
9841
0
                aLine.append( " scn " );
9842
0
                m_aPages.back().appendRect( rRect, aLine );
9843
0
                aLine.append( " f Q\n" );
9844
0
                writeBuffer( aLine );
9845
0
            }
9846
0
        }
9847
0
        else
9848
0
        {
9849
0
            aBmpPos     = aRect.TopLeft();
9850
0
            aBmpSize    = aRect.GetSize();
9851
0
            bDrawBitmap = true;
9852
0
        }
9853
9854
0
        if( aBitmap.HasAlpha() )
9855
0
        {
9856
0
            if( rWall.IsGradient() )
9857
0
                bDrawGradient = true;
9858
0
            else
9859
0
                bDrawColor = true;
9860
0
        }
9861
0
    }
9862
0
    else if( rWall.IsGradient() )
9863
0
        bDrawGradient = true;
9864
0
    else
9865
0
        bDrawColor = true;
9866
9867
0
    if( bDrawGradient )
9868
0
    {
9869
0
        drawGradient( rRect, rWall.GetGradient() );
9870
0
    }
9871
0
    if( bDrawColor )
9872
0
    {
9873
0
        Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
9874
0
        Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
9875
0
        setLineColor( COL_TRANSPARENT );
9876
0
        setFillColor( rWall.GetColor() );
9877
0
        drawRectangle( rRect );
9878
0
        setLineColor( aOldLineColor );
9879
0
        setFillColor( aOldFillColor );
9880
0
    }
9881
0
    if( bDrawBitmap )
9882
0
    {
9883
        // set temporary clip region since aBmpPos and aBmpSize
9884
        // may be outside rRect
9885
0
        OStringBuffer aLine( 20 );
9886
0
        aLine.append( "q " );
9887
0
        m_aPages.back().appendRect( rRect, aLine );
9888
0
        aLine.append( " W n\n" );
9889
0
        writeBuffer( aLine );
9890
0
        drawBitmap( aBmpPos, aBmpSize, aBitmap );
9891
0
        writeBuffer( "Q\n" );
9892
0
    }
9893
0
}
9894
9895
 void PDFWriterImpl::updateGraphicsState(Mode const mode)
9896
924k
{
9897
924k
    OStringBuffer& rLine = updateGraphicsStateLine;
9898
924k
    rLine.setLength(0);
9899
924k
    GraphicsState& rNewState = m_aGraphicsStack.front();
9900
    // first set clip region since it might invalidate everything else
9901
9902
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
9903
304k
    {
9904
304k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
9905
9906
304k
        if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
9907
135k
            ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
9908
247k
        {
9909
247k
            if( m_aCurrentPDFState.m_bClipRegion )
9910
163k
            {
9911
163k
                rLine.append( "Q " );
9912
                // invalidate everything but the clip region
9913
163k
                m_aCurrentPDFState = GraphicsState();
9914
163k
                rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
9915
163k
            }
9916
247k
            if( rNewState.m_bClipRegion )
9917
163k
            {
9918
                // clip region is always stored in private PDF mapmode
9919
163k
                MapMode aNewMapMode = std::move(rNewState.m_aMapMode);
9920
163k
                rNewState.m_aMapMode = m_aMapMode;
9921
163k
                SetMapMode( rNewState.m_aMapMode );
9922
163k
                m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9923
9924
163k
                rLine.append("q ");
9925
163k
                if ( rNewState.m_aClipRegion.count() )
9926
163k
                {
9927
163k
                    m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, rLine );
9928
163k
                }
9929
0
                else
9930
0
                {
9931
                    // tdf#130150 Need to revert tdf#99680, that breaks the
9932
                    // rule that an set but empty clip-region clips everything
9933
                    // aka draws nothing -> nothing is in an empty clip-region
9934
0
                    rLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
9935
0
                }
9936
163k
                rLine.append( "W* n\n" );
9937
9938
163k
                rNewState.m_aMapMode = std::move(aNewMapMode);
9939
163k
                SetMapMode( rNewState.m_aMapMode );
9940
163k
                m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9941
163k
            }
9942
247k
        }
9943
304k
    }
9944
9945
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
9946
226k
    {
9947
226k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
9948
226k
        SetMapMode( rNewState.m_aMapMode );
9949
226k
    }
9950
9951
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
9952
781k
    {
9953
781k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
9954
781k
        SetFont( rNewState.m_aFont );
9955
781k
    }
9956
9957
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
9958
389k
    {
9959
389k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
9960
389k
        SetLayoutMode( rNewState.m_nLayoutMode );
9961
389k
    }
9962
9963
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
9964
827k
    {
9965
827k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
9966
827k
        SetDigitLanguage( rNewState.m_aDigitLanguage );
9967
827k
    }
9968
9969
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
9970
305k
    {
9971
305k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
9972
305k
        if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
9973
4.87k
            rNewState.m_aLineColor != COL_TRANSPARENT )
9974
4.48k
        {
9975
4.48k
            appendStrokingColor( rNewState.m_aLineColor, rLine );
9976
4.48k
            rLine.append( "\n" );
9977
4.48k
        }
9978
305k
    }
9979
9980
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
9981
305k
    {
9982
305k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
9983
305k
        if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
9984
4.88k
            rNewState.m_aFillColor != COL_TRANSPARENT )
9985
4.10k
        {
9986
4.10k
            appendNonStrokingColor( rNewState.m_aFillColor, rLine );
9987
4.10k
            rLine.append( "\n" );
9988
4.10k
        }
9989
305k
    }
9990
9991
924k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
9992
226k
    {
9993
226k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
9994
226k
        if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9995
226k
        {
9996
            // TODO: switch extended graphicsstate
9997
226k
        }
9998
226k
    }
9999
10000
    // everything is up to date now
10001
924k
    m_aCurrentPDFState = m_aGraphicsStack.front();
10002
924k
    if ((mode != Mode::NOWRITE) &&  !rLine.isEmpty())
10003
249k
        writeBuffer( rLine );
10004
924k
}
10005
10006
/* #i47544# imitate OutputDevice behaviour:
10007
*  if a font with a nontransparent color is set, it overwrites the current
10008
*  text color. OTOH setting the text color will overwrite the color of the font.
10009
*/
10010
void PDFWriterImpl::setFont( const vcl::Font& rFont )
10011
2.14M
{
10012
2.14M
    Color aColor = rFont.GetColor();
10013
2.14M
    if( aColor == COL_TRANSPARENT )
10014
1.42M
        aColor = m_aGraphicsStack.front().m_aFont.GetColor();
10015
2.14M
    m_aGraphicsStack.front().m_aFont = rFont;
10016
2.14M
    m_aGraphicsStack.front().m_aFont.SetColor( aColor );
10017
2.14M
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
10018
2.14M
}
10019
10020
void PDFWriterImpl::push( PushFlags nFlags )
10021
796k
{
10022
796k
    OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
10023
796k
    m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
10024
796k
    m_aGraphicsStack.front().m_nFlags = nFlags;
10025
796k
}
10026
10027
void PDFWriterImpl::pop()
10028
796k
{
10029
796k
    OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
10030
796k
    if( m_aGraphicsStack.size() < 2 )
10031
0
        return;
10032
10033
796k
    GraphicsState aState = m_aGraphicsStack.front();
10034
796k
    m_aGraphicsStack.pop_front();
10035
796k
    GraphicsState& rOld = m_aGraphicsStack.front();
10036
10037
    // move those parameters back that were not pushed
10038
    // in the first place
10039
796k
    if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
10040
275k
        setLineColor( aState.m_aLineColor );
10041
796k
    if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
10042
447k
        setFillColor( aState.m_aFillColor );
10043
796k
    if( ! (aState.m_nFlags & PushFlags::FONT) )
10044
637k
        setFont( aState.m_aFont );
10045
796k
    if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
10046
637k
        setTextColor( aState.m_aFont.GetColor() );
10047
796k
    if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
10048
617k
        setMapMode( aState.m_aMapMode );
10049
796k
    if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
10050
380k
    {
10051
        // do not use setClipRegion here
10052
        // it would convert again assuming the current mapmode
10053
380k
        rOld.m_aClipRegion = aState.m_aClipRegion;
10054
380k
        rOld.m_bClipRegion = aState.m_bClipRegion;
10055
380k
    }
10056
796k
    if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
10057
637k
        setTextLineColor( aState.m_aTextLineColor );
10058
796k
    if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
10059
637k
        setOverlineColor( aState.m_aOverlineColor );
10060
796k
    if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
10061
637k
        setTextAlign( aState.m_aFont.GetAlignment() );
10062
796k
    if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
10063
637k
        setTextFillColor( aState.m_aFont.GetFillColor() );
10064
796k
    if (!(aState.m_nFlags & PushFlags::TEXTLAYOUTMODE))
10065
637k
    {
10066
637k
        setLayoutMode(aState.m_nLayoutMode);
10067
637k
    }
10068
796k
    if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10069
637k
    {
10070
        // what ?
10071
637k
    }
10072
    // invalidate graphics state
10073
796k
    m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10074
796k
}
10075
10076
void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10077
1.07M
{
10078
1.07M
    m_aGraphicsStack.front().m_aMapMode = rMapMode;
10079
1.07M
    SetMapMode( rMapMode );
10080
1.07M
    m_aCurrentPDFState.m_aMapMode = rMapMode;
10081
1.07M
}
10082
10083
void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10084
161k
{
10085
    // tdf#130150 improve coordinate manipulations to double precision transformations
10086
161k
    const basegfx::B2DHomMatrix aCurrentTransform(
10087
161k
        GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10088
161k
    basegfx::B2DPolyPolygon aRegion(rRegion);
10089
10090
161k
    aRegion.transform(aCurrentTransform);
10091
161k
    m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10092
161k
    m_aGraphicsStack.front().m_bClipRegion = true;
10093
161k
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10094
161k
}
10095
10096
void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10097
0
{
10098
0
    if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
10099
0
        return;
10100
10101
    // tdf#130150 improve coordinate manipulations to double precision transformations
10102
0
    basegfx::B2DHomMatrix aConvertA;
10103
10104
0
    if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
10105
0
    {
10106
0
        aConvertA = GetInverseViewTransformation(m_aMapMode);
10107
0
    }
10108
0
    else
10109
0
    {
10110
0
        aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
10111
0
    }
10112
10113
0
    basegfx::B2DPoint aB2DPointA(nX, nY);
10114
0
    basegfx::B2DPoint aB2DPointB(0.0, 0.0);
10115
0
    aB2DPointA *= aConvertA;
10116
0
    aB2DPointB *= aConvertA;
10117
0
    aB2DPointA -= aB2DPointB;
10118
0
    basegfx::B2DHomMatrix aMat;
10119
10120
0
    aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
10121
0
    m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10122
0
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10123
0
}
10124
10125
void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10126
199
{
10127
199
    basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10128
199
                                    vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10129
199
    intersectClipRegion( aRect );
10130
199
}
10131
10132
void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10133
255k
{
10134
    // tdf#130150 improve coordinate manipulations to double precision transformations
10135
255k
    const basegfx::B2DHomMatrix aCurrentTransform(
10136
255k
        GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10137
255k
    basegfx::B2DPolyPolygon aRegion(rRegion);
10138
10139
255k
    aRegion.transform(aCurrentTransform);
10140
255k
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10141
10142
255k
    if( m_aGraphicsStack.front().m_bClipRegion )
10143
254k
    {
10144
254k
        basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10145
254k
        aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10146
254k
        m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10147
254k
    }
10148
199
    else
10149
199
    {
10150
199
        m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10151
199
        m_aGraphicsStack.front().m_bClipRegion = true;
10152
199
    }
10153
255k
}
10154
10155
sal_Int32 PDFWriterImpl::createNote(const tools::Rectangle& rRect,
10156
                                    const tools::Rectangle& rPopupRect, const pdf::PDFNote& rNote,
10157
                                    sal_Int32 nPageNr)
10158
136
{
10159
136
    if (nPageNr < 0)
10160
0
        nPageNr = m_nCurrentPage;
10161
10162
136
    if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10163
0
        return -1;
10164
10165
136
    sal_Int32 nRet = m_aNotes.size();
10166
10167
136
    m_aNotes.emplace_back();
10168
136
    auto & rNoteEntry = m_aNotes.back();
10169
136
    rNoteEntry.m_nObject = createObject();
10170
136
    rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
10171
136
    rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
10172
136
    rNoteEntry.m_aPopUpAnnotation.m_aRect = rPopupRect;
10173
136
    rNoteEntry.m_aContents = rNote;
10174
136
    rNoteEntry.m_aRect = rRect;
10175
136
    rNoteEntry.m_nPage = nPageNr;
10176
    // convert to default user space now, since the mapmode may change
10177
136
    m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
10178
136
    m_aPages[nPageNr].convertRect(rNoteEntry.m_aPopUpAnnotation.m_aRect);
10179
10180
    // insert note to page's annotation list
10181
136
    m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
10182
136
    m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
10183
10184
136
    return nRet;
10185
136
}
10186
10187
sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
10188
3.03k
{
10189
3.03k
    if( nPageNr < 0 )
10190
0
        nPageNr = m_nCurrentPage;
10191
10192
3.03k
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10193
0
        return -1;
10194
10195
3.03k
    sal_Int32 nRet = m_aLinks.size();
10196
10197
3.03k
    m_aLinks.emplace_back(rAltText);
10198
3.03k
    m_aLinks.back().m_nObject   = createObject();
10199
3.03k
    m_aLinks.back().m_nPage     = nPageNr;
10200
3.03k
    m_aLinks.back().m_aRect     = rRect;
10201
    // convert to default user space now, since the mapmode may change
10202
3.03k
    m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10203
10204
    // insert link to page's annotation list
10205
3.03k
    m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10206
10207
3.03k
    return nRet;
10208
3.03k
}
10209
10210
sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
10211
0
{
10212
0
    if (nPageNr < 0)
10213
0
        nPageNr = m_nCurrentPage;
10214
10215
0
    if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10216
0
        return -1;
10217
10218
0
    sal_Int32 nRet = m_aScreens.size();
10219
10220
0
    m_aScreens.emplace_back(rAltText, rMimeType);
10221
0
    m_aScreens.back().m_nObject = createObject();
10222
0
    m_aScreens.back().m_nPage = nPageNr;
10223
0
    m_aScreens.back().m_aRect = rRect;
10224
    // Convert to default user space now, since the mapmode may change.
10225
0
    m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10226
10227
    // Insert link to page's annotation list.
10228
0
    m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10229
10230
0
    return nRet;
10231
0
}
10232
10233
sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10234
0
{
10235
0
    if( nPageNr < 0 )
10236
0
        nPageNr = m_nCurrentPage;
10237
10238
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10239
0
        return -1;
10240
10241
0
    sal_Int32 nRet = m_aNamedDests.size();
10242
10243
0
    m_aNamedDests.emplace_back( );
10244
0
    m_aNamedDests.back().m_aDestName = sDestName;
10245
0
    m_aNamedDests.back().m_nPage = nPageNr;
10246
0
    m_aNamedDests.back().m_eType = eType;
10247
0
    m_aNamedDests.back().m_aRect = rRect;
10248
    // convert to default user space now, since the mapmode may change
10249
0
    m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10250
10251
0
    return nRet;
10252
0
}
10253
10254
sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10255
3.71k
{
10256
3.71k
    if( nPageNr < 0 )
10257
0
        nPageNr = m_nCurrentPage;
10258
10259
3.71k
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10260
0
        return -1;
10261
10262
3.71k
    sal_Int32 nRet = m_aDests.size();
10263
10264
3.71k
    m_aDests.emplace_back( );
10265
3.71k
    m_aDests.back().m_nPage = nPageNr;
10266
3.71k
    m_aDests.back().m_eType = eType;
10267
3.71k
    m_aDests.back().m_aRect = rRect;
10268
    // convert to default user space now, since the mapmode may change
10269
3.71k
    m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10270
10271
3.71k
    return nRet;
10272
3.71k
}
10273
10274
sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10275
0
{
10276
0
    m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10277
0
    return m_aDestinationIdTranslation[ nDestId ];
10278
0
}
10279
10280
void PDFWriterImpl::setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId)
10281
0
{
10282
0
    if (nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size())
10283
0
        return;
10284
10285
0
    m_aDests[nDestId].m_nStructElement = nStructElementId;
10286
0
}
10287
10288
void PDFWriterImpl::addStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId)
10289
0
{
10290
0
    if (nElementId < 0 || o3tl::make_unsigned(nElementId) >= m_aStructure.size())
10291
0
        return;
10292
0
    if (nRefElementId < 0 || o3tl::make_unsigned(nRefElementId) >= m_aStructure.size())
10293
0
        return;
10294
    // an element left out of the tree has no object to point at
10295
0
    if (m_aStructure[nRefElementId].m_nObject <= 0)
10296
0
        return;
10297
    // ISO 32000-2 added Ref to a structure element, and PDF 1.7 has no entry of that name
10298
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10299
0
        return;
10300
10301
0
    m_aStructure[nElementId].m_RefElements.push_back(nRefElementId);
10302
0
}
10303
10304
void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10305
2.83k
{
10306
2.83k
    if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10307
0
        return;
10308
2.83k
    if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
10309
0
        return;
10310
10311
2.83k
    m_aLinks[ nLinkId ].m_nDest = nDestId;
10312
2.83k
}
10313
10314
void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10315
200
{
10316
200
    if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10317
0
        return;
10318
10319
200
    m_aLinks[ nLinkId ].m_nDest = -1;
10320
10321
200
    using namespace ::com::sun::star;
10322
10323
200
    if (!m_xTrans.is())
10324
83
    {
10325
83
        const uno::Reference< uno::XComponentContext >& xContext( comphelper::getProcessComponentContext() );
10326
83
        m_xTrans = util::URLTransformer::create(xContext);
10327
83
    }
10328
10329
200
    util::URL aURL;
10330
200
    aURL.Complete = rURL;
10331
10332
200
    m_xTrans->parseStrict( aURL );
10333
10334
200
    m_aLinks[ nLinkId ].m_aURL  = aURL.Complete;
10335
200
}
10336
10337
void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10338
0
{
10339
0
    if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10340
0
        return;
10341
10342
0
    m_aScreens[nScreenId].m_aURL = rURL;
10343
0
}
10344
10345
void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10346
0
{
10347
0
    if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10348
0
        return;
10349
10350
0
    m_aScreens[nScreenId].m_aTempFileURL = rURL;
10351
0
    m_aScreens[nScreenId].m_nTempFileObject = createObject();
10352
0
}
10353
10354
void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10355
3.17k
{
10356
3.17k
    m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10357
3.17k
}
10358
10359
sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
10360
876
{
10361
    // create new item
10362
876
    sal_Int32 nNewItem = m_aOutline.size();
10363
876
    m_aOutline.emplace_back( );
10364
10365
    // set item attributes
10366
876
    setOutlineItemParent( nNewItem, nParent );
10367
876
    setOutlineItemText( nNewItem, rText );
10368
876
    setOutlineItemDest( nNewItem, nDestID );
10369
10370
876
    return nNewItem;
10371
876
}
10372
10373
void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10374
876
{
10375
876
    if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10376
0
        return;
10377
10378
876
    if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
10379
870
    {
10380
870
        nNewParent = 0;
10381
870
    }
10382
    // insert item to new parent's list of children
10383
876
    m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10384
876
}
10385
10386
void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
10387
876
{
10388
876
    if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10389
0
        return;
10390
10391
876
    m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10392
876
}
10393
10394
void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10395
876
{
10396
876
    if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
10397
0
        return;
10398
876
    if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
10399
0
        return;
10400
876
    m_aOutline[nItem].m_nDestID = nDestID;
10401
876
}
10402
10403
const char* PDFWriterImpl::getStructureTag(StructElement eType)
10404
0
{
10405
    // First handle fallbacks for elements that were added in a certain PDF version
10406
10407
    // PDF 1.5 fallbacks
10408
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5 && eType == StructElement::Annot)
10409
0
        eType = StructElement::Figure;
10410
10411
    // PDF 2.0 fallbacks
10412
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10413
0
    {
10414
0
        switch (eType)
10415
0
        {
10416
0
            case StructElement::Title:
10417
0
                eType = StructElement::Paragraph; break;
10418
0
            case StructElement::Emphasis:
10419
0
                eType = StructElement::Span; break;
10420
0
            case StructElement::Strong:
10421
0
                eType = StructElement::Span; break;
10422
0
            case StructElement::FENote:
10423
0
                eType = StructElement::Note; break;
10424
0
            default:
10425
0
                break;
10426
0
        }
10427
0
    }
10428
10429
0
    auto iterator = constTagStrings.find(eType);
10430
10431
0
    if (iterator == constTagStrings.end())
10432
0
        return "Div";
10433
10434
0
    return iterator->second;
10435
0
}
10436
10437
bool PDFWriterImpl::isStandardStructureName(std::string_view aName)
10438
0
{
10439
    // below 2.0 getStructureTag returns P for Title, so a style may take that name
10440
0
    const auto it(std::ranges::find_if(constTagStrings,
10441
0
                                       [aName](const auto& rTag) { return aName == rTag.second; }));
10442
0
    if ((it != constTagStrings.end() && aName == getStructureTag(it->first))
10443
0
        || std::ranges::find(constUnusedTagStrings, aName) != std::end(constUnusedTagStrings))
10444
0
        return true;
10445
10446
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10447
0
        return false;
10448
10449
0
    return isDeepHeading(aName)
10450
0
           || std::ranges::find(constUnusedTagStrings20, aName)
10451
0
                  != std::end(constUnusedTagStrings20);
10452
0
}
10453
10454
OString PDFWriterImpl::claimRoleName(const OString& rAlias, StructElement eType)
10455
0
{
10456
0
    const OString aTag(getStructureTag(eType));
10457
    // For PDF/UA it's not allowed to map an alias with the same name.
10458
    // Not aware of a reason for doing it in any case, so just don't do it.
10459
0
    if (rAlias == aTag)
10460
0
        return rAlias;
10461
10462
    // a heading deeper than the six this writer emits: PDF 2.0 knows the name, so the element
10463
    // says the level it is and needs no entry
10464
0
    if (isDeepHeading(rAlias) && StructElement::H1 <= eType && eType <= StructElement::H6
10465
0
        && PDFWriter::PDFVersion::PDF_2_0 <= m_aContext.Version)
10466
0
        return rAlias;
10467
10468
0
    OString aName(rAlias);
10469
0
    for (sal_Int32 nIndex(1);; ++nIndex)
10470
0
    {
10471
        // a standard type's name cannot be made to mean another type
10472
0
        if (!isStandardStructureName(aName))
10473
0
        {
10474
            // any other name belongs to the alias that asked for it first
10475
0
            const auto[it, bInserted](
10476
0
                m_aRoleMap.emplace(aName, RoleMapEntry{ .m_aTag = aTag, .m_aAsked = rAlias }));
10477
0
            if (bInserted || (it->second.m_aTag == aTag && it->second.m_aAsked == rAlias))
10478
0
                return aName;
10479
0
        }
10480
10481
        // name taken, try the next index
10482
0
        aName = rAlias + "-" + OString::number(nIndex);
10483
0
    }
10484
0
}
10485
10486
void PDFWriterImpl::beginStructureElementMCSeq()
10487
941k
{
10488
941k
    assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
10489
941k
    if( m_bEmitStructure &&
10490
941k
        m_nCurrentStructElement > 0 && // StructTreeRoot
10491
        // Document = SwPageFrame => this is not *inside* the page content
10492
        // stream so do not emit MCID!
10493
0
        m_aStructure[m_nCurrentStructElement].m_oType &&
10494
0
        *m_aStructure[m_nCurrentStructElement].m_oType != StructElement::Document &&
10495
0
        ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10496
941k
        )
10497
0
    {
10498
0
        PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10499
0
        OStringBuffer aLine( 128 );
10500
0
        sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10501
0
        aLine.append( "/" );
10502
0
        if( !rEle.m_aAlias.isEmpty() )
10503
0
            aLine.append( rEle.m_aAlias );
10504
0
        else
10505
0
            aLine.append( getStructureTag(*rEle.m_oType) );
10506
0
        aLine.append( "<</MCID " );
10507
0
        aLine.append( nMCID );
10508
0
        aLine.append( ">>BDC\n" );
10509
0
        writeBuffer( aLine );
10510
10511
        // update the element's content list
10512
0
        SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10513
0
                 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10514
0
                 << rEle.m_nFirstPageObject);
10515
0
        rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID});
10516
        // update the page's mcid parent list
10517
0
        m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10518
        // mark element MC sequence as open
10519
0
        rEle.m_bOpenMCSeq = true;
10520
0
    }
10521
    // handle artifacts
10522
941k
    else if( ! m_bEmitStructure && m_aContext.Tagged &&
10523
0
               m_nCurrentStructElement > 0 &&
10524
0
               m_aStructure[m_nCurrentStructElement].m_oType &&
10525
0
               *m_aStructure[m_nCurrentStructElement].m_oType == StructElement::NonStructElement &&
10526
0
             ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10527
941k
             )
10528
0
    {
10529
0
        OString aLine = "/Artifact "_ostr;
10530
0
        writeBuffer( aLine );
10531
        // emit property list if requested
10532
0
        OStringBuffer buf;
10533
0
        for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
10534
0
        {
10535
0
            appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
10536
0
        }
10537
0
        if (buf.isEmpty())
10538
0
        {
10539
0
            writeBuffer("BMC\n");
10540
0
        }
10541
0
        else
10542
0
        {
10543
0
            writeBuffer("<<");
10544
0
            writeBuffer(buf);
10545
0
            writeBuffer(">> BDC\n");
10546
0
        }
10547
        // mark element MC sequence as open
10548
0
        m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10549
0
    }
10550
941k
}
10551
10552
void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
10553
96.2k
{
10554
96.2k
    if (m_nCurrentStructElement > 0 // not StructTreeRoot
10555
0
        && m_aStructure[m_nCurrentStructElement].m_oType
10556
0
        && (m_bEmitStructure
10557
0
            || (endMode != EndMode::OnlyStruct
10558
0
                && m_aStructure[m_nCurrentStructElement].m_oType == StructElement::NonStructElement))
10559
0
        && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
10560
0
    {
10561
0
        writeBuffer( "EMC\n" );
10562
0
        m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10563
0
    }
10564
96.2k
}
10565
10566
bool PDFWriterImpl::checkEmitStructure()
10567
0
{
10568
0
    bool bEmit = false;
10569
0
    if( m_aContext.Tagged )
10570
0
    {
10571
0
        bEmit = true;
10572
0
        sal_Int32 nEle = m_nCurrentStructElement;
10573
0
        while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
10574
0
        {
10575
0
            if (m_aStructure[nEle].m_oType
10576
0
                && *m_aStructure[nEle].m_oType == StructElement::NonStructElement)
10577
0
            {
10578
0
                bEmit = false;
10579
0
                break;
10580
0
            }
10581
0
            nEle = m_aStructure[ nEle ].m_nParentElement;
10582
0
        }
10583
0
    }
10584
0
    return bEmit;
10585
0
}
10586
10587
sal_Int32 PDFWriterImpl::ensureStructureElement()
10588
28
{
10589
28
    if( ! m_aContext.Tagged )
10590
28
        return -1;
10591
10592
0
    sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10593
10594
    // use m_nCurrentStructElement as temporary parent
10595
0
    m_aStructure.emplace_back(nNewId, m_nCurrentStructElement, m_aPages[m_nCurrentPage].m_nPageObject);
10596
10597
0
    m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10598
0
    return nNewId;
10599
28
}
10600
10601
void PDFWriterImpl::initStructureElement(sal_Int32 const id,
10602
        StructElement const eType, std::u16string_view const rAlias)
10603
28
{
10604
28
    if( ! m_aContext.Tagged )
10605
28
        return;
10606
10607
0
    if( m_nCurrentStructElement == 0 &&
10608
0
        eType != StructElement::Document && eType != StructElement::NonStructElement )
10609
0
    {
10610
        // struct tree root hit, but not beginning document
10611
        // this might happen with setCurrentStructureElement
10612
        // silently insert structure into document again if one properly exists
10613
0
        if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10614
0
        {
10615
0
            const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10616
0
            auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
10617
0
                [&](sal_Int32 nElement) {
10618
0
                    return m_aStructure[nElement].m_oType
10619
0
                        && *m_aStructure[nElement].m_oType == StructElement::Document; });
10620
0
            if( it != rRootChildren.end() )
10621
0
            {
10622
0
                m_nCurrentStructElement = *it;
10623
0
                SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10624
0
            }
10625
0
            else {
10626
0
                OSL_FAIL( "document structure in disorder !" );
10627
0
            }
10628
0
        }
10629
0
        else {
10630
0
            OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10631
0
        }
10632
0
    }
10633
10634
0
    PDFStructureElement& rEle = m_aStructure[id];
10635
0
    assert(!rEle.m_oType);
10636
0
    rEle.m_oType.emplace(eType);
10637
    // remove it from its possibly placeholder parent; append to real parent
10638
0
    auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
10639
0
        m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
10640
0
    assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
10641
0
    m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
10642
0
    rEle.m_nParentElement   = m_nCurrentStructElement;
10643
0
    rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10644
0
    m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
10645
10646
    // handle alias names
10647
0
    if( !rAlias.empty() && eType != StructElement::NonStructElement )
10648
0
    {
10649
0
        OStringBuffer aNameBuf( rAlias.size() );
10650
0
        COSWriter::appendName( rAlias, aNameBuf );
10651
0
        OString aAliasName( aNameBuf.makeStringAndClear() );
10652
0
        rEle.m_aAlias = claimRoleName(aAliasName, eType);
10653
0
    }
10654
10655
0
    if (m_bEmitStructure && eType != StructElement::NonStructElement) // don't create nonexistent objects
10656
0
    {
10657
0
        rEle.m_nObject      = createObject();
10658
        // update parent's kids list
10659
0
        m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject});
10660
        // ISO 14289-1:2014, Clause: 7.9, for the element it names Note
10661
0
        if (*rEle.m_oType == StructElement::FENote)
10662
0
        {
10663
0
            m_StructElemObjsWithID.insert(rEle.m_nObject);
10664
0
        }
10665
0
    }
10666
0
}
10667
10668
void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
10669
28
{
10670
28
    if( m_nCurrentPage < 0 )
10671
0
        return;
10672
10673
28
    if( ! m_aContext.Tagged )
10674
28
        return;
10675
10676
28
    assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
10677
10678
    // close eventual current MC sequence
10679
0
    endStructureElementMCSeq(EndMode::OnlyStruct);
10680
10681
0
    PDFStructureElement& rEle = m_aStructure[id];
10682
0
    m_StructElementStack.push(m_nCurrentStructElement);
10683
0
    m_nCurrentStructElement = id;
10684
10685
0
    if (g_bDebugDisableCompression)
10686
0
    {
10687
0
        OStringBuffer aLine( "beginStructureElement " );
10688
0
        aLine.append( m_nCurrentStructElement );
10689
0
        aLine.append( ": " );
10690
0
        aLine.append( rEle.m_oType
10691
0
            ? getStructureTag(*rEle.m_oType)
10692
0
            : "<placeholder>" );
10693
0
        if( !rEle.m_aAlias.isEmpty() )
10694
0
        {
10695
0
            aLine.append( " aliased as \"" );
10696
0
            aLine.append( rEle.m_aAlias );
10697
0
            aLine.append( '\"' );
10698
0
        }
10699
0
        emitComment( aLine.getStr() );
10700
0
    }
10701
10702
    // check whether to emit structure henceforth
10703
0
    m_bEmitStructure = checkEmitStructure();
10704
0
}
10705
10706
void PDFWriterImpl::endStructureElement()
10707
28
{
10708
28
    if( m_nCurrentPage < 0 )
10709
0
        return;
10710
10711
28
    if( ! m_aContext.Tagged )
10712
28
        return;
10713
10714
0
    if( m_nCurrentStructElement == 0 )
10715
0
    {
10716
        // hit the struct tree root, that means there is an endStructureElement
10717
        // without corresponding beginStructureElement
10718
0
        return;
10719
0
    }
10720
10721
    // end the marked content sequence
10722
0
    endStructureElementMCSeq();
10723
10724
0
    OStringBuffer aLine;
10725
0
    if (g_bDebugDisableCompression)
10726
0
    {
10727
0
        aLine.append( "endStructureElement " );
10728
0
        aLine.append( m_nCurrentStructElement );
10729
0
        aLine.append( ": " );
10730
0
        aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
10731
0
            ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
10732
0
            : "<placeholder>" );
10733
0
        if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10734
0
        {
10735
0
            aLine.append( " aliased as \"" );
10736
0
            aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10737
0
            aLine.append( '\"' );
10738
0
        }
10739
0
    }
10740
10741
    // "end" the structure element, the parent becomes current element
10742
0
    m_nCurrentStructElement = m_StructElementStack.top();
10743
0
    m_StructElementStack.pop();
10744
10745
    // check whether to emit structure henceforth
10746
0
    m_bEmitStructure = checkEmitStructure();
10747
10748
0
    if (g_bDebugDisableCompression && m_bEmitStructure)
10749
0
    {
10750
0
        emitComment( aLine.getStr() );
10751
0
    }
10752
0
}
10753
10754
namespace {
10755
10756
void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
10757
        std::vector<sal_Int32>::iterator & rParentIt)
10758
0
{
10759
0
    PDFStructureElement& rEle(rStructure[*rParentIt]);
10760
0
    removePlaceholderSE(rStructure, rEle);
10761
10762
0
    if (!rEle.m_oType)
10763
0
    {
10764
        // Placeholder was not initialised - should not happen when printing
10765
        // a full page, but might if a selection is printed, which can be only
10766
        // a shape without its anchor.
10767
        // Handle this by moving the children to the parent SE.
10768
0
        PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
10769
0
        rParentIt = rParent.m_aChildren.erase(rParentIt);
10770
0
        std::vector<sal_Int32> children;
10771
0
        for (auto const child : rEle.m_aChildren)
10772
0
        {
10773
0
            PDFStructureElement& rChild = rStructure[child];
10774
0
            rChild.m_nParentElement = rEle.m_nParentElement;
10775
0
            children.push_back(rChild.m_nOwnElement);
10776
0
        }
10777
0
        rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
10778
0
            + children.size();
10779
0
    }
10780
0
    else
10781
0
    {
10782
0
        ++rParentIt;
10783
0
    }
10784
10785
0
}
10786
10787
void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
10788
0
{
10789
0
    for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
10790
0
    {
10791
0
        removePlaceholderSEImpl(rStructure, it);
10792
0
    }
10793
0
}
10794
10795
} // end anonymous namespace
10796
10797
/*
10798
 * This function adds an internal structure list container to overcome the 8191 elements array limitation
10799
 * in kids element emission.
10800
 * Recursive function
10801
 *
10802
 */
10803
void PDFWriterImpl::addInternalStructureContainer(const sal_Int32 nEle)
10804
0
{
10805
    // m_aStructure grows below, so this addresses elements by index throughout: any
10806
    // reference into the vector dies at the next push_back
10807
0
    if (m_aStructure[nEle].m_nOwnElement != m_aStructure[nEle].m_nParentElement
10808
0
        && *m_aStructure[nEle].m_oType == StructElement::NonStructElement)
10809
0
    {
10810
0
        return;
10811
0
    }
10812
10813
0
    for (size_t i = 0; i < m_aStructure[nEle].m_aChildren.size(); ++i)
10814
0
    {
10815
0
        const sal_Int32 nChild = m_aStructure[nEle].m_aChildren[i];
10816
0
        assert(nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size());
10817
0
        if (nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size())
10818
0
        {
10819
0
            if (*m_aStructure[nChild].m_oType != StructElement::NonStructElement)
10820
0
            {
10821
                //triggered when a child of the nEle element is found
10822
0
                assert(m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement);
10823
0
                if (m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement)
10824
0
                    addInternalStructureContainer(nChild); //examine the child
10825
0
                else
10826
0
                {
10827
0
                    OSL_FAIL("PDFWriterImpl::addInternalStructureContainer: invalid child "
10828
0
                             "structure element");
10829
0
                    SAL_INFO("vcl.pdfwriter",
10830
0
                             "PDFWriterImpl::addInternalStructureContainer: invalid child "
10831
0
                             "structure element with id "
10832
0
                                 << nChild);
10833
0
                }
10834
0
            }
10835
0
        }
10836
0
        else
10837
0
        {
10838
0
            OSL_FAIL("PDFWriterImpl::emitStructure: invalid child structure id");
10839
0
            SAL_INFO("vcl.pdfwriter",
10840
0
                     "PDFWriterImpl::addInternalStructureContainer: invalid child structure id "
10841
0
                         << nChild);
10842
0
        }
10843
0
    }
10844
10845
0
    if (m_aStructure[nEle].m_nOwnElement == m_aStructure[nEle].m_nParentElement)
10846
0
        return;
10847
10848
0
    if (m_aStructure[nEle].m_aKids.size() <= ncMaxPDFArraySize)
10849
0
        return;
10850
10851
    //then we need to add the containers for the kids elements
10852
    // a list to be used for the new kid element
10853
0
    std::list<PDFStructureElementKid> aNewKids;
10854
0
    std::vector<sal_Int32> aNewChildren;
10855
10856
0
    while (m_aStructure[nEle].m_aKids.size() > ncMaxPDFArraySize)
10857
0
    {
10858
0
        const sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10859
0
        {
10860
            //inherit the same page as the first child to be reparented
10861
0
            const sal_Int32 nPage(
10862
0
                m_aStructure[m_aStructure[nEle].m_aChildren.front()].m_nFirstPageObject);
10863
0
            PDFStructureElement aNew(nNewId, nEle, nPage);
10864
0
            aNew.m_oType.emplace(StructElement::Division); // a new Div type container
10865
0
            aNew.m_nObject = createObject(); //assign a PDF object number
10866
0
            m_aStructure.push_back(std::move(aNew));
10867
0
        }
10868
        //add the object to the kid list of the parent
10869
0
        aNewKids.emplace_back(ObjReference{ m_aStructure[nNewId].m_nObject });
10870
0
        aNewChildren.push_back(nNewId);
10871
10872
        // m_aKids and m_aChildren are not parallel: every element gets a child entry, only an
10873
        // emitted one gets a kid, and marked content adds kids of its own. Cut the two at
10874
        // corresponding points, not at the same count.
10875
0
        const auto aKidEndIt(std::next(m_aStructure[nEle].m_aKids.begin(), ncMaxPDFArraySize));
10876
0
        size_t nMovedChildren(0);
10877
0
        for (auto it = m_aStructure[nEle].m_aKids.begin(); it != aKidEndIt; ++it)
10878
0
        {
10879
0
            if (std::holds_alternative<ObjReference>(*it))
10880
0
                ++nMovedChildren;
10881
0
        }
10882
0
        auto aChildEndIt(m_aStructure[nEle].m_aChildren.begin());
10883
0
        for (size_t nSeen = 0;
10884
0
             nSeen < nMovedChildren && aChildEndIt != m_aStructure[nEle].m_aChildren.end();
10885
0
             ++aChildEndIt)
10886
0
        {
10887
0
            if (m_aStructure[*aChildEndIt].m_nObject != 0) // an emitted child has a kid entry
10888
0
                ++nSeen;
10889
0
        }
10890
10891
0
        m_aStructure[nNewId].m_aKids.splice(m_aStructure[nNewId].m_aKids.begin(),
10892
0
                                            m_aStructure[nEle].m_aKids,
10893
0
                                            m_aStructure[nEle].m_aKids.begin(), aKidEndIt);
10894
0
        m_aStructure[nNewId].m_aChildren.insert(m_aStructure[nNewId].m_aChildren.begin(),
10895
0
                                                m_aStructure[nEle].m_aChildren.begin(),
10896
0
                                                aChildEndIt);
10897
0
        m_aStructure[nEle].m_aChildren.erase(m_aStructure[nEle].m_aChildren.begin(), aChildEndIt);
10898
10899
        // set the kid's new parent
10900
0
        for (const auto nMoved : m_aStructure[nNewId].m_aChildren)
10901
0
        {
10902
0
            m_aStructure[nMoved].m_nParentElement = nNewId;
10903
0
        }
10904
0
    }
10905
    //finally add the new kids resulting from the container added
10906
0
    m_aStructure[nEle].m_aKids.insert(m_aStructure[nEle].m_aKids.begin(), aNewKids.begin(),
10907
0
                                      aNewKids.end());
10908
0
    m_aStructure[nEle].m_aChildren.insert(m_aStructure[nEle].m_aChildren.begin(),
10909
0
                                          aNewChildren.begin(), aNewChildren.end());
10910
0
}
10911
10912
bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
10913
0
{
10914
0
    bool bSuccess = false;
10915
10916
0
    if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
10917
0
    {
10918
        // end eventual previous marked content sequence
10919
0
        endStructureElementMCSeq();
10920
10921
0
        m_nCurrentStructElement = nEle;
10922
0
        m_bEmitStructure = checkEmitStructure();
10923
0
        if (g_bDebugDisableCompression)
10924
0
        {
10925
0
            OStringBuffer aLine( "setCurrentStructureElement " );
10926
0
            aLine.append( m_nCurrentStructElement );
10927
0
            aLine.append( ": " );
10928
0
            aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
10929
0
                ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
10930
0
                : "<placeholder>" );
10931
0
            if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10932
0
            {
10933
0
                aLine.append( " aliased as \"" );
10934
0
                aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10935
0
                aLine.append( '\"' );
10936
0
            }
10937
0
            if( ! m_bEmitStructure )
10938
0
                aLine.append( " (inside NonStruct)" );
10939
0
            emitComment( aLine.getStr() );
10940
0
        }
10941
0
        bSuccess = true;
10942
0
    }
10943
10944
0
    return bSuccess;
10945
0
}
10946
10947
bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
10948
0
{
10949
0
    if( !m_aContext.Tagged )
10950
0
        return false;
10951
10952
0
    assert(m_aStructure[m_nCurrentStructElement].m_oType);
10953
0
    bool bInsert = false;
10954
0
    if (m_nCurrentStructElement > 0
10955
0
        && (m_bEmitStructure
10956
            // allow it for topmost non-structured element
10957
0
            || (m_aContext.Tagged
10958
0
                && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
10959
0
                    || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
10960
0
                    || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != StructElement::NonStructElement))))
10961
0
    {
10962
0
        StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
10963
0
        switch( eAttr )
10964
0
        {
10965
0
            case PDFWriter::Placement:
10966
0
                if( eVal == PDFWriter::Block        ||
10967
0
                    eVal == PDFWriter::Inline       ||
10968
0
                    eVal == PDFWriter::Before       ||
10969
0
                    eVal == PDFWriter::Start        ||
10970
0
                    eVal == PDFWriter::End )
10971
0
                    bInsert = true;
10972
0
                break;
10973
0
            case PDFWriter::WritingMode:
10974
0
                if( eVal == PDFWriter::LrTb         ||
10975
0
                    eVal == PDFWriter::RlTb         ||
10976
0
                    eVal == PDFWriter::TbRl )
10977
0
                {
10978
0
                    bInsert = true;
10979
0
                }
10980
0
                break;
10981
0
            case PDFWriter::TextAlign:
10982
0
                if( eVal == PDFWriter::Start        ||
10983
0
                    eVal == PDFWriter::Center       ||
10984
0
                    eVal == PDFWriter::End          ||
10985
0
                    eVal == PDFWriter::Justify )
10986
0
                {
10987
0
                    if (eType == StructElement::Paragraph   ||
10988
0
                        eType == StructElement::Title ||
10989
0
                        eType == StructElement::Heading     ||
10990
0
                        eType == StructElement::H1          ||
10991
0
                        eType == StructElement::H2          ||
10992
0
                        eType == StructElement::H3          ||
10993
0
                        eType == StructElement::H4          ||
10994
0
                        eType == StructElement::H5          ||
10995
0
                        eType == StructElement::H6          ||
10996
0
                        eType == StructElement::List        ||
10997
0
                        eType == StructElement::ListItem    ||
10998
0
                        eType == StructElement::LILabel     ||
10999
0
                        eType == StructElement::LIBody      ||
11000
0
                        eType == StructElement::Table       ||
11001
0
                        eType == StructElement::TableRow    ||
11002
0
                        eType == StructElement::TableHeader ||
11003
0
                        eType == StructElement::TableData)
11004
0
                    {
11005
0
                        bInsert = true;
11006
0
                    }
11007
0
                }
11008
0
                break;
11009
0
            case PDFWriter::Width:
11010
0
            case PDFWriter::Height:
11011
0
                if( eVal == PDFWriter::Auto )
11012
0
                {
11013
0
                    if (eType == StructElement::Figure      ||
11014
0
                        eType == StructElement::Formula     ||
11015
0
                        eType == StructElement::Form        ||
11016
0
                        eType == StructElement::Table       ||
11017
0
                        eType == StructElement::TableHeader ||
11018
0
                        eType == StructElement::TableData)
11019
0
                    {
11020
0
                        bInsert = true;
11021
0
                    }
11022
0
                }
11023
0
                break;
11024
0
            case PDFWriter::BlockAlign:
11025
0
                if( eVal == PDFWriter::Before       ||
11026
0
                    eVal == PDFWriter::Middle       ||
11027
0
                    eVal == PDFWriter::After        ||
11028
0
                    eVal == PDFWriter::Justify )
11029
0
                {
11030
0
                    if (eType == StructElement::TableHeader ||
11031
0
                        eType == StructElement::TableData)
11032
0
                    {
11033
0
                        bInsert = true;
11034
0
                    }
11035
0
                }
11036
0
                break;
11037
0
            case PDFWriter::InlineAlign:
11038
0
                if( eVal == PDFWriter::Start        ||
11039
0
                    eVal == PDFWriter::Center       ||
11040
0
                    eVal == PDFWriter::End )
11041
0
                {
11042
0
                    if (eType == StructElement::TableHeader ||
11043
0
                        eType == StructElement::TableData)
11044
0
                    {
11045
0
                        bInsert = true;
11046
0
                    }
11047
0
                }
11048
0
                break;
11049
0
            case PDFWriter::LineHeight:
11050
0
                if( eVal == PDFWriter::Normal       ||
11051
0
                    eVal == PDFWriter::Auto )
11052
0
                {
11053
                    // only for ILSE and BLSE
11054
0
                    if (eType == StructElement::Paragraph   ||
11055
0
                        eType == StructElement::Title ||
11056
0
                        eType == StructElement::Heading     ||
11057
0
                        eType == StructElement::H1          ||
11058
0
                        eType == StructElement::H2          ||
11059
0
                        eType == StructElement::H3          ||
11060
0
                        eType == StructElement::H4          ||
11061
0
                        eType == StructElement::H5          ||
11062
0
                        eType == StructElement::H6          ||
11063
0
                        eType == StructElement::List        ||
11064
0
                        eType == StructElement::ListItem    ||
11065
0
                        eType == StructElement::LILabel     ||
11066
0
                        eType == StructElement::LIBody      ||
11067
0
                        eType == StructElement::Table       ||
11068
0
                        eType == StructElement::TableRow    ||
11069
0
                        eType == StructElement::TableHeader ||
11070
0
                        eType == StructElement::TableData   ||
11071
0
                        eType == StructElement::Span        ||
11072
0
                        eType == StructElement::Quote       ||
11073
0
                        eType == StructElement::Emphasis ||
11074
0
                        eType == StructElement::Strong ||
11075
0
                        eType == StructElement::Note        ||
11076
0
                        eType == StructElement::FENote      ||
11077
0
                        eType == StructElement::Reference   ||
11078
0
                        eType == StructElement::BibEntry    ||
11079
0
                        eType == StructElement::Code        ||
11080
0
                        eType == StructElement::Link)
11081
0
                    {
11082
0
                        bInsert = true;
11083
0
                    }
11084
0
                }
11085
0
                break;
11086
0
            case PDFWriter::TextDecorationType:
11087
0
                if( eVal == PDFWriter::NONE         ||
11088
0
                    eVal == PDFWriter::Underline    ||
11089
0
                    eVal == PDFWriter::Overline     ||
11090
0
                    eVal == PDFWriter::LineThrough )
11091
0
                {
11092
                    // only for ILSE and BLSE
11093
0
                    if (eType == StructElement::Paragraph   ||
11094
0
                        eType == StructElement::Title ||
11095
0
                        eType == StructElement::Heading     ||
11096
0
                        eType == StructElement::H1          ||
11097
0
                        eType == StructElement::H2          ||
11098
0
                        eType == StructElement::H3          ||
11099
0
                        eType == StructElement::H4          ||
11100
0
                        eType == StructElement::H5          ||
11101
0
                        eType == StructElement::H6          ||
11102
0
                        eType == StructElement::List        ||
11103
0
                        eType == StructElement::ListItem    ||
11104
0
                        eType == StructElement::LILabel     ||
11105
0
                        eType == StructElement::LIBody      ||
11106
0
                        eType == StructElement::Table       ||
11107
0
                        eType == StructElement::TableRow    ||
11108
0
                        eType == StructElement::TableHeader ||
11109
0
                        eType == StructElement::TableData   ||
11110
0
                        eType == StructElement::Span        ||
11111
0
                        eType == StructElement::Quote       ||
11112
0
                        eType == StructElement::Emphasis ||
11113
0
                        eType == StructElement::Strong ||
11114
0
                        eType == StructElement::Note        ||
11115
0
                        eType == StructElement::FENote      ||
11116
0
                        eType == StructElement::Reference   ||
11117
0
                        eType == StructElement::BibEntry    ||
11118
0
                        eType == StructElement::Code        ||
11119
0
                        eType == StructElement::Link)
11120
0
                    {
11121
0
                        bInsert = true;
11122
0
                    }
11123
0
                }
11124
0
                break;
11125
0
            case PDFWriter::Scope:
11126
0
                if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
11127
0
                {
11128
                    // The Scope attribute on TH was introduced in PDF 1.5, but it
11129
                    // is also mandatory for PDF/UA-1 (clause 7.5). When PDF/UA is
11130
                    // requested with a PDF/A-1 (PDF 1.4) target, accept Scope so
11131
                    // the resulting file can satisfy the PDF/UA structure rules.
11132
0
                    if (eType == StructElement::TableHeader
11133
0
                        && (PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version
11134
0
                            || m_aContext.UniversalAccessibilityCompliance))
11135
0
                    {
11136
0
                        bInsert = true;
11137
0
                    }
11138
0
                }
11139
0
                break;
11140
0
            case PDFWriter::Type:
11141
0
                if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
11142
                    // + Background for PDF >= 1.7
11143
0
                {
11144
0
                    if (eType == StructElement::NonStructElement)
11145
0
                    {
11146
0
                        bInsert = true;
11147
0
                    }
11148
0
                }
11149
0
                break;
11150
0
            case PDFWriter::Subtype:
11151
0
                if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
11152
0
                {
11153
0
                    if (eType == StructElement::NonStructElement
11154
0
                        && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11155
0
                    {
11156
0
                        bInsert = true;
11157
0
                    }
11158
0
                }
11159
0
                break;
11160
0
            case PDFWriter::Role:
11161
0
                if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
11162
0
                {
11163
0
                    if (eType == StructElement::Form
11164
0
                        && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11165
0
                    {
11166
0
                        bInsert = true;
11167
0
                    }
11168
0
                }
11169
0
                break;
11170
0
            case PDFWriter::RubyAlign:
11171
0
                if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute)
11172
0
                {
11173
0
                    if (eType == StructElement::RT
11174
0
                        && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11175
0
                    {
11176
0
                        bInsert = true;
11177
0
                    }
11178
0
                }
11179
0
                break;
11180
0
            case PDFWriter::RubyPosition:
11181
0
                if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline)
11182
0
                {
11183
0
                    if (eType == StructElement::RT
11184
0
                        && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11185
0
                    {
11186
0
                        bInsert = true;
11187
0
                    }
11188
0
                }
11189
0
                break;
11190
0
            case PDFWriter::ListNumbering:
11191
0
                if( eVal == PDFWriter::NONE         ||
11192
0
                    eVal == PDFWriter::Disc         ||
11193
0
                    eVal == PDFWriter::Circle       ||
11194
0
                    eVal == PDFWriter::Square       ||
11195
0
                    eVal == PDFWriter::Decimal      ||
11196
0
                    eVal == PDFWriter::UpperRoman   ||
11197
0
                    eVal == PDFWriter::LowerRoman   ||
11198
0
                    eVal == PDFWriter::UpperAlpha   ||
11199
0
                    eVal == PDFWriter::LowerAlpha )
11200
0
                {
11201
0
                    if( eType == StructElement::List )
11202
0
                        bInsert = true;
11203
0
                }
11204
0
                break;
11205
0
            default: break;
11206
0
        }
11207
0
    }
11208
11209
0
    if( bInsert )
11210
0
        m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
11211
0
    else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11212
0
        SAL_INFO("vcl.pdfwriter",
11213
0
                 "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
11214
0
                 << ", " << getAttributeValueTag( eVal )
11215
0
                 << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11216
0
                 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11217
0
                 << ") element");
11218
11219
0
    return bInsert;
11220
0
}
11221
11222
sal_Int32 PDFWriterImpl::convertStructureAttribute(enum PDFWriter::StructAttribute eAttr,
11223
                                                   sal_Int32 nValue)
11224
0
{
11225
0
    if (!isLengthAttribute(eAttr))
11226
0
        return nValue;
11227
11228
0
    const Size aSize(
11229
0
        convert(m_aGraphicsStack.front().m_aMapMode, m_aMapMode, this, Size(nValue, nValue)));
11230
    // the map mode can scale X and Y differently
11231
0
    const bool bHorizontal(eAttr == PDFWriter::StartIndent || eAttr == PDFWriter::EndIndent
11232
0
                           || eAttr == PDFWriter::TextIndent || eAttr == PDFWriter::Width);
11233
11234
0
    return bHorizontal ? aSize.Width() : aSize.Height();
11235
0
}
11236
11237
bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
11238
0
{
11239
0
    if( ! m_aContext.Tagged )
11240
0
        return false;
11241
11242
0
    assert(m_aStructure[m_nCurrentStructElement].m_oType);
11243
0
    bool bInsert = false;
11244
0
    if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11245
0
    {
11246
0
        if( eAttr == PDFWriter::Language )
11247
0
        {
11248
0
            m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
11249
0
            return true;
11250
0
        }
11251
11252
0
        StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11253
0
        switch( eAttr )
11254
0
        {
11255
0
            case PDFWriter::SpaceBefore:
11256
0
            case PDFWriter::SpaceAfter:
11257
0
            case PDFWriter::StartIndent:
11258
0
            case PDFWriter::EndIndent:
11259
                // just for BLSE
11260
0
                if (eType == StructElement::Paragraph   ||
11261
0
                    eType == StructElement::Title ||
11262
0
                    eType == StructElement::Heading     ||
11263
0
                    eType == StructElement::H1          ||
11264
0
                    eType == StructElement::H2          ||
11265
0
                    eType == StructElement::H3          ||
11266
0
                    eType == StructElement::H4          ||
11267
0
                    eType == StructElement::H5          ||
11268
0
                    eType == StructElement::H6          ||
11269
0
                    eType == StructElement::List        ||
11270
0
                    eType == StructElement::ListItem    ||
11271
0
                    eType == StructElement::LILabel     ||
11272
0
                    eType == StructElement::LIBody      ||
11273
0
                    eType == StructElement::Table       ||
11274
0
                    eType == StructElement::TableRow    ||
11275
0
                    eType == StructElement::TableHeader ||
11276
0
                    eType == StructElement::TableData)
11277
0
                {
11278
0
                    bInsert = true;
11279
0
                }
11280
0
                break;
11281
0
            case PDFWriter::TextIndent:
11282
                // paragraph like BLSE and additional elements
11283
0
                if (eType == StructElement::Paragraph   ||
11284
0
                    eType == StructElement::Title ||
11285
0
                    eType == StructElement::Heading     ||
11286
0
                    eType == StructElement::H1          ||
11287
0
                    eType == StructElement::H2          ||
11288
0
                    eType == StructElement::H3          ||
11289
0
                    eType == StructElement::H4          ||
11290
0
                    eType == StructElement::H5          ||
11291
0
                    eType == StructElement::H6          ||
11292
0
                    eType == StructElement::LILabel     ||
11293
0
                    eType == StructElement::LIBody      ||
11294
0
                    eType == StructElement::TableHeader ||
11295
0
                    eType == StructElement::TableData)
11296
0
                {
11297
0
                    bInsert = true;
11298
0
                }
11299
0
                break;
11300
0
            case PDFWriter::Width:
11301
0
            case PDFWriter::Height:
11302
0
                if (eType == StructElement::Figure      ||
11303
0
                    eType == StructElement::Formula     ||
11304
0
                    eType == StructElement::Form        ||
11305
0
                    eType == StructElement::Table       ||
11306
0
                    eType == StructElement::TableHeader ||
11307
0
                    eType == StructElement::TableData)
11308
0
                {
11309
0
                    bInsert = true;
11310
0
                }
11311
0
                break;
11312
0
            case PDFWriter::LineHeight:
11313
0
            case PDFWriter::BaselineShift:
11314
                // only for ILSE and BLSE
11315
0
                if (eType == StructElement::Paragraph   ||
11316
0
                    eType == StructElement::Title ||
11317
0
                    eType == StructElement::Heading     ||
11318
0
                    eType == StructElement::H1          ||
11319
0
                    eType == StructElement::H2          ||
11320
0
                    eType == StructElement::H3          ||
11321
0
                    eType == StructElement::H4          ||
11322
0
                    eType == StructElement::H5          ||
11323
0
                    eType == StructElement::H6          ||
11324
0
                    eType == StructElement::List        ||
11325
0
                    eType == StructElement::ListItem    ||
11326
0
                    eType == StructElement::LILabel     ||
11327
0
                    eType == StructElement::LIBody      ||
11328
0
                    eType == StructElement::Table       ||
11329
0
                    eType == StructElement::TableRow    ||
11330
0
                    eType == StructElement::TableHeader ||
11331
0
                    eType == StructElement::TableData   ||
11332
0
                    eType == StructElement::Span        ||
11333
0
                    eType == StructElement::Quote       ||
11334
0
                    eType == StructElement::Emphasis ||
11335
0
                    eType == StructElement::Strong ||
11336
0
                    eType == StructElement::Note        ||
11337
0
                    eType == StructElement::FENote      ||
11338
0
                    eType == StructElement::Reference   ||
11339
0
                    eType == StructElement::BibEntry    ||
11340
0
                    eType == StructElement::Code        ||
11341
0
                    eType == StructElement::Link)
11342
0
                {
11343
0
                        bInsert = true;
11344
0
                }
11345
0
                break;
11346
0
            case PDFWriter::RowSpan:
11347
0
            case PDFWriter::ColSpan:
11348
                // only for table cells
11349
0
                if (eType == StructElement::TableHeader ||
11350
0
                    eType == StructElement::TableData)
11351
0
                {
11352
0
                    bInsert = true;
11353
0
                }
11354
0
                break;
11355
0
            case PDFWriter::LinkAnnotation:
11356
0
                if (eType == StructElement::Link)
11357
0
                    bInsert = true;
11358
0
                break;
11359
0
            case PDFWriter::NoteAnnotation:
11360
0
                if (eType == StructElement::Annot)
11361
0
                    bInsert = true;
11362
0
                break;
11363
0
            default: break;
11364
0
        }
11365
0
    }
11366
11367
0
    if (bInsert)
11368
0
    {
11369
0
        if (eAttr == PDFWriter::LinkAnnotation)
11370
0
        {
11371
0
            m_aStructure[m_nCurrentStructElement].m_LinkAnnotIds.push_back(nValue);
11372
0
        }
11373
0
        else
11374
0
        {
11375
0
            m_aStructure[m_nCurrentStructElement].m_aAttributes[eAttr]
11376
0
                = PDFStructureAttribute(convertStructureAttribute(eAttr, nValue));
11377
0
        }
11378
0
    }
11379
0
    else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11380
0
        SAL_INFO("vcl.pdfwriter",
11381
0
                 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
11382
0
                 << ", " << nValue
11383
0
                 << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11384
0
                 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11385
0
                 << ") element");
11386
11387
0
    return bInsert;
11388
0
}
11389
11390
void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
11391
0
{
11392
0
    sal_Int32 nPageNr = m_nCurrentPage;
11393
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
11394
0
        return;
11395
11396
0
    if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
11397
0
        return;
11398
11399
0
    assert(m_aStructure[m_nCurrentStructElement].m_oType);
11400
0
    StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11401
0
    if (eType == StructElement::Figure ||
11402
0
        eType == StructElement::Formula ||
11403
0
        eType == StructElement::Form ||
11404
0
        eType == StructElement::Division ||
11405
0
        eType == StructElement::Table)
11406
0
    {
11407
0
        m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
11408
        // convert to default user space now, since the mapmode may change
11409
0
        m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11410
0
    }
11411
0
}
11412
11413
void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
11414
0
{
11415
0
    assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
11416
11417
0
    if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
11418
0
    {
11419
0
        return;
11420
0
    }
11421
11422
0
    m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
11423
0
}
11424
11425
void PDFWriterImpl::setActualText( const OUString& rText )
11426
0
{
11427
0
    if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11428
0
    {
11429
0
        m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11430
0
    }
11431
0
}
11432
11433
void PDFWriterImpl::setAlternateText( const OUString& rText )
11434
0
{
11435
0
    if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11436
0
    {
11437
0
        m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11438
0
    }
11439
0
}
11440
11441
void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11442
0
{
11443
0
    if( nPageNr < 0 )
11444
0
        nPageNr = m_nCurrentPage;
11445
11446
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11447
0
        return;
11448
11449
0
    m_aPages[ nPageNr ].m_eTransition   = eType;
11450
0
    m_aPages[ nPageNr ].m_nTransTime    = nMilliSec;
11451
0
}
11452
11453
void PDFWriterImpl::ensureUniqueRadioOnValues()
11454
18.6k
{
11455
    // loop over radio groups
11456
18.6k
    for (auto const& group : m_aRadioGroupWidgets)
11457
0
    {
11458
0
        PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11459
        // check whether all kids have a unique OnValue
11460
0
        std::unordered_map< OUString, sal_Int32 > aOnValues;
11461
0
        bool bIsUnique = true;
11462
0
        for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11463
0
        {
11464
0
            const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11465
0
            SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11466
0
            if( aOnValues.find( rVal ) == aOnValues.end() )
11467
0
            {
11468
0
                aOnValues[ rVal ] = 1;
11469
0
            }
11470
0
            else
11471
0
            {
11472
0
                bIsUnique = false;
11473
0
                break;
11474
0
            }
11475
0
        }
11476
0
        if( ! bIsUnique )
11477
0
        {
11478
0
            SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11479
            // make unique by using ascending OnValues
11480
0
            int nKid = 0;
11481
0
            for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11482
0
            {
11483
0
                PDFWidget& rKid = m_aWidgets[nKidIndex];
11484
0
                rKid.m_aOnValue = OUString::number( nKid+1 );
11485
0
                if( rKid.m_aValue != "Off" )
11486
0
                    rKid.m_aValue = rKid.m_aOnValue;
11487
0
                ++nKid;
11488
0
            }
11489
0
        }
11490
        // finally move the "Yes" appearance to the OnValue appearance
11491
0
        for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11492
0
        {
11493
0
            PDFWidget& rKid = m_aWidgets[nKidIndex];
11494
0
            if ( !rKid.m_aOnValue.isEmpty() )
11495
0
            {
11496
0
                auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11497
0
                if( app_it != rKid.m_aAppearances.end() )
11498
0
                {
11499
0
                    auto stream_it = app_it->second.find( "Yes"_ostr );
11500
0
                    if( stream_it != app_it->second.end() )
11501
0
                    {
11502
0
                        SvMemoryStream* pStream = stream_it->second;
11503
0
                        app_it->second.erase( stream_it );
11504
0
                        OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11505
0
                        COSWriter::appendName( rKid.m_aOnValue, aBuf );
11506
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11507
0
                    }
11508
0
                    else
11509
0
                        SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11510
0
                }
11511
0
            }
11512
11513
0
            if ( !rKid.m_aOffValue.isEmpty() )
11514
0
            {
11515
0
                auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11516
0
                if( app_it != rKid.m_aAppearances.end() )
11517
0
                {
11518
0
                    auto stream_it = app_it->second.find( "Off"_ostr );
11519
0
                    if( stream_it != app_it->second.end() )
11520
0
                    {
11521
0
                        SvMemoryStream* pStream = stream_it->second;
11522
0
                        app_it->second.erase( stream_it );
11523
0
                        OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
11524
0
                        COSWriter::appendName( rKid.m_aOffValue, aBuf );
11525
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11526
0
                    }
11527
0
                    else
11528
0
                        SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
11529
0
                }
11530
0
            }
11531
11532
            // update selected radio button
11533
0
            if( rKid.m_aValue != "Off" )
11534
0
            {
11535
0
                rGroupWidget.m_aValue = rKid.m_aValue;
11536
0
            }
11537
0
        }
11538
0
    }
11539
18.6k
}
11540
11541
sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11542
0
{
11543
0
    sal_Int32 nRadioGroupWidget = -1;
11544
11545
0
    std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11546
11547
0
    if( it == m_aRadioGroupWidgets.end() )
11548
0
    {
11549
0
        m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11550
0
            sal_Int32(m_aWidgets.size());
11551
11552
        // new group, insert the radiobutton
11553
0
        m_aWidgets.emplace_back( );
11554
0
        m_aWidgets.back().m_nObject     = createObject();
11555
0
        m_aWidgets.back().m_nPage       = m_nCurrentPage;
11556
0
        m_aWidgets.back().m_eType       = PDFWriter::RadioButton;
11557
0
        m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11558
0
        m_aWidgets.back().m_nFlags |= 0x0000C000;   // NoToggleToOff and Radio bits
11559
11560
0
        createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11561
0
    }
11562
0
    else
11563
0
        nRadioGroupWidget = it->second;
11564
11565
0
    return nRadioGroupWidget;
11566
0
}
11567
11568
sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11569
0
{
11570
0
    if( nPageNr < 0 )
11571
0
        nPageNr = m_nCurrentPage;
11572
11573
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11574
0
        return -1;
11575
11576
0
    sal_Int32 nNewWidget = m_aWidgets.size();
11577
0
    m_aWidgets.emplace_back( );
11578
11579
0
    m_aWidgets.back().m_nObject         = createObject();
11580
0
    m_aWidgets.back().m_aRect           = rControl.Location;
11581
0
    m_aWidgets.back().m_nPage           = nPageNr;
11582
0
    m_aWidgets.back().m_eType           = rControl.getType();
11583
11584
0
    sal_Int32 nRadioGroupWidget = -1;
11585
    // for unknown reasons the radio buttons of a radio group must not have a
11586
    // field name, else the buttons are in fact check boxes -
11587
    // that is multiple buttons of the radio group can be selected
11588
0
    if( rControl.getType() == PDFWriter::RadioButton )
11589
0
        nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11590
0
    else
11591
0
    {
11592
0
        createWidgetFieldName( nNewWidget, rControl );
11593
0
    }
11594
11595
    // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11596
0
    PDFWidget& rNewWidget           = m_aWidgets[nNewWidget];
11597
0
    rNewWidget.m_aDescription       = rControl.Description;
11598
0
    rNewWidget.m_aText              = rControl.Text;
11599
0
    rNewWidget.m_nTextStyle         = rControl.TextStyle &
11600
0
        (  DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11601
0
           DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11602
0
           DrawTextFlags::MultiLine | DrawTextFlags::WordBreak  );
11603
0
    rNewWidget.m_nTabOrder          = rControl.TabOrder;
11604
11605
    // various properties are set via the flags (/Ff) property of the field dict
11606
0
    if( rControl.ReadOnly )
11607
0
        rNewWidget.m_nFlags |= 1;
11608
0
    if( rControl.getType() == PDFWriter::PushButton )
11609
0
    {
11610
0
        const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11611
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11612
0
            rNewWidget.m_nTextStyle =
11613
0
                DrawTextFlags::Center | DrawTextFlags::VCenter |
11614
0
                DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11615
11616
0
        rNewWidget.m_nFlags |= 0x00010000;
11617
0
        if( !rBtn.URL.isEmpty() )
11618
0
            rNewWidget.m_aListEntries.push_back( rBtn.URL );
11619
0
        rNewWidget.m_bSubmit    = rBtn.Submit;
11620
0
        rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
11621
0
        rNewWidget.m_nDest      = rBtn.Dest;
11622
0
        createDefaultPushButtonAppearance( rNewWidget, rBtn );
11623
0
    }
11624
0
    else if( rControl.getType() == PDFWriter::RadioButton )
11625
0
    {
11626
0
        const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
11627
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11628
0
            rNewWidget.m_nTextStyle =
11629
0
                DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11630
        /*  PDF sees a RadioButton group as one radio button with
11631
         *  children which are in turn check boxes
11632
         *
11633
         *  so we need to create a radio button on demand for a new group
11634
         *  and insert a checkbox for each RadioButtonWidget as its child
11635
         */
11636
0
        rNewWidget.m_eType          = PDFWriter::CheckBox;
11637
0
        rNewWidget.m_nRadioGroup    = rBtn.RadioGroup;
11638
11639
0
        SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
11640
11641
0
        PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
11642
0
        rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
11643
0
        rRadioButton.m_aKidsIndex.push_back( nNewWidget );
11644
0
        rNewWidget.m_nParent = rRadioButton.m_nObject;
11645
11646
0
        rNewWidget.m_aValue     = "Off";
11647
0
        rNewWidget.m_aOnValue   = rBtn.OnValue;
11648
0
        rNewWidget.m_aOffValue   = rBtn.OffValue;
11649
0
        if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
11650
0
        {
11651
0
            rNewWidget.m_aValue     = rNewWidget.m_aOnValue;
11652
0
            rRadioButton.m_aValue   = rNewWidget.m_aOnValue;
11653
0
        }
11654
0
        createDefaultRadioButtonAppearance( rNewWidget, rBtn );
11655
11656
        // union rect of radio group
11657
0
        tools::Rectangle aRect = rNewWidget.m_aRect;
11658
0
        m_aPages[ nPageNr ].convertRect( aRect );
11659
0
        rRadioButton.m_aRect.Union( aRect );
11660
0
    }
11661
0
    else if( rControl.getType() == PDFWriter::CheckBox )
11662
0
    {
11663
0
        const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
11664
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11665
0
            rNewWidget.m_nTextStyle =
11666
0
                DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11667
11668
0
        rNewWidget.m_aValue
11669
0
            = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
11670
0
        rNewWidget.m_aOnValue   = rBox.OnValue;
11671
0
        rNewWidget.m_aOffValue   = rBox.OffValue;
11672
        // create default appearance before m_aRect gets transformed
11673
0
        createDefaultCheckBoxAppearance( rNewWidget, rBox );
11674
0
    }
11675
0
    else if( rControl.getType() == PDFWriter::ListBox )
11676
0
    {
11677
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11678
0
            rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11679
11680
0
        const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
11681
0
        rNewWidget.m_aListEntries     = rLstBox.Entries;
11682
0
        rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
11683
0
        rNewWidget.m_aValue           = rLstBox.Text;
11684
0
        if( rLstBox.DropDown )
11685
0
            rNewWidget.m_nFlags |= 0x00020000;
11686
0
        if (rLstBox.MultiSelect && !rLstBox.DropDown)
11687
0
            rNewWidget.m_nFlags |= 0x00200000;
11688
11689
0
        createDefaultListBoxAppearance( rNewWidget, rLstBox );
11690
0
    }
11691
0
    else if( rControl.getType() == PDFWriter::ComboBox )
11692
0
    {
11693
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11694
0
            rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11695
11696
0
        const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
11697
0
        rNewWidget.m_aValue         = rBox.Text;
11698
0
        rNewWidget.m_aListEntries   = rBox.Entries;
11699
0
        rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
11700
11701
0
        PDFWriter::ListBoxWidget aLBox;
11702
0
        aLBox.Name              = rBox.Name;
11703
0
        aLBox.Description       = rBox.Description;
11704
0
        aLBox.Text              = rBox.Text;
11705
0
        aLBox.TextStyle         = rBox.TextStyle;
11706
0
        aLBox.ReadOnly          = rBox.ReadOnly;
11707
0
        aLBox.Border            = rBox.Border;
11708
0
        aLBox.BorderColor       = rBox.BorderColor;
11709
0
        aLBox.Background        = rBox.Background;
11710
0
        aLBox.BackgroundColor   = rBox.BackgroundColor;
11711
0
        aLBox.TextFont          = rBox.TextFont;
11712
0
        aLBox.TextColor         = rBox.TextColor;
11713
0
        aLBox.DropDown          = true;
11714
0
        aLBox.MultiSelect       = false;
11715
0
        aLBox.Entries           = rBox.Entries;
11716
11717
0
        createDefaultListBoxAppearance( rNewWidget, aLBox );
11718
0
    }
11719
0
    else if( rControl.getType() == PDFWriter::Edit )
11720
0
    {
11721
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11722
0
            rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
11723
11724
0
        const PDFWriter::EditWidget& rEdit = static_cast<const  PDFWriter::EditWidget&>(rControl);
11725
0
        if( rEdit.MultiLine )
11726
0
        {
11727
0
            rNewWidget.m_nFlags |= 0x00001000;
11728
0
            rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11729
0
        }
11730
0
        if( rEdit.Password )
11731
0
            rNewWidget.m_nFlags |= 0x00002000;
11732
0
        if (rEdit.FileSelect)
11733
0
            rNewWidget.m_nFlags |= 0x00100000;
11734
0
        rNewWidget.m_nMaxLen = rEdit.MaxLen;
11735
0
        rNewWidget.m_nFormat = rEdit.Format;
11736
0
        rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
11737
0
        rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
11738
0
        rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
11739
0
        rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
11740
0
        rNewWidget.m_aDateFormat = rEdit.DateFormat;
11741
0
        rNewWidget.m_aValue = rEdit.Text;
11742
11743
0
        createDefaultEditAppearance( rNewWidget, rEdit );
11744
0
    }
11745
#if HAVE_FEATURE_NSS
11746
    else if( rControl.getType() == PDFWriter::Signature)
11747
    {
11748
        rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11749
11750
        m_nSignatureObject = createObject();
11751
        rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11752
        rNewWidget.m_aValue += " 0 R";
11753
        // let's add a fake appearance
11754
        rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
11755
    }
11756
#endif
11757
11758
    // if control is a signature, do not convert coordinates since we
11759
    // need /Rect [ 0 0 0 0 ]
11760
0
    if ( rControl.getType() != PDFWriter::Signature )
11761
0
    {
11762
        // convert to default user space now, since the mapmode may change
11763
        // note: create default appearances before m_aRect gets transformed
11764
0
        m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11765
0
    }
11766
11767
    // insert widget to page's annotation list
11768
0
    m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11769
11770
0
    return nNewWidget;
11771
0
}
11772
11773
void PDFWriterImpl::MARK( const char* pString )
11774
941k
{
11775
941k
    beginStructureElementMCSeq();
11776
941k
    if (g_bDebugDisableCompression)
11777
0
        emitComment( pString );
11778
941k
}
11779
11780
sal_Int32 ReferenceXObjectEmit::getObject() const
11781
0
{
11782
0
    if (m_nFormObject > 0)
11783
0
        return m_nFormObject;
11784
0
    else
11785
0
        return m_nBitmapObject;
11786
0
}
11787
}
11788
11789
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */