Coverage Report

Created: 2026-10-14 09:33

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
15.4k
{
419
15.4k
    if( rColor != COL_TRANSPARENT )
420
15.4k
    {
421
15.4k
        const bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
422
15.4k
        appendColor( rColor, rBuffer, bGrey );
423
15.4k
        rBuffer.append( bGrey ? " G" : " RG" );
424
15.4k
    }
425
15.4k
}
426
427
void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
428
669k
{
429
669k
    if( rColor != COL_TRANSPARENT )
430
669k
    {
431
669k
        const bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
432
669k
        appendColor( rColor, rBuffer, bGrey );
433
669k
        rBuffer.append( bGrey ? " g" : " rg" );
434
669k
    }
435
669k
}
436
437
PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
438
                               const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder,
439
                               PDFWriter& i_rOuterFace)
440
8.87k
        : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
441
8.87k
        m_aMapMode( MapUnit::MapPoint, Point(), 1.0 / pointToPixel(1), 1.0 / pointToPixel(1) ),
442
8.87k
        m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
443
8.87k
        m_nCurrentStructElement( 0 ),
444
8.87k
        m_bEmitStructure( true ),
445
8.87k
        m_nNextFID( 1 ),
446
8.87k
        m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
447
8.87k
            officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
448
8.87k
        m_nCurrentPage( -1 ),
449
8.87k
        m_nCatalogObject(0),
450
8.87k
        m_nSignatureObject( -1 ),
451
8.87k
        m_nSignatureContentOffset( 0 ),
452
8.87k
        m_nSignatureLastByteRangeNoOffset( 0 ),
453
8.87k
        m_nResourceDict( -1 ),
454
8.87k
        m_nFontDictObject( -1 ),
455
8.87k
        m_aContext(rContext),
456
8.87k
        m_aFile(m_aContext.URL),
457
8.87k
        m_bOpen(false),
458
8.87k
        m_DocDigest(::comphelper::HashType::MD5),
459
8.87k
        m_rOuterFace( i_rOuterFace )
460
8.87k
{
461
8.87k
    m_aStructure.emplace_back( );
462
8.87k
    m_aStructure[0].m_nOwnElement       = 0;
463
8.87k
    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
8.87k
    m_aWidgetStyleSettings.SetStandardStyles();
468
469
8.87k
    GraphicsState aState;
470
8.87k
    aState.m_aMapMode       = m_aMapMode;
471
8.87k
    aState.m_aFont.SetFamilyName( u"Times"_ustr );
472
8.87k
    aState.m_aFont.SetFontSize( Size( 0, 12 ) );
473
474
8.87k
    m_aGraphicsStack.push_front( aState );
475
476
8.87k
    osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
477
8.87k
    if (aError != osl::File::E_None)
478
8.87k
    {
479
8.87k
        if (aError == osl::File::E_EXIST)
480
8.87k
        {
481
8.87k
            aError = m_aFile.open(osl_File_OpenFlag_Write);
482
8.87k
            if (aError == osl::File::E_None)
483
8.87k
                aError = m_aFile.setSize(0);
484
8.87k
        }
485
8.87k
    }
486
8.87k
    if (aError != osl::File::E_None)
487
0
        return;
488
489
8.87k
    m_bOpen = true;
490
491
    // setup DocInfo
492
8.87k
    setupDocInfo();
493
494
8.87k
    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
8.87k
    if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
504
0
    {
505
0
        m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
506
0
    }
507
508
    // write header
509
8.87k
    if (!writeBuffer(Concat2View(OString::Concat("%PDF-") + getPDFVersionStr(m_aContext.Version)
510
8.87k
                             + "\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
8.87k
    m_aOutline.emplace_back( );
519
520
8.87k
    switch (m_aContext.Version)
521
8.87k
    {
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
8.87k
        default:
543
8.87k
            break;
544
8.87k
    }
545
546
    // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
547
8.87k
    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
8.87k
    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
8.87k
    if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
562
8.87k
        SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
563
0
    else
564
0
        SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
565
566
8.87k
    SetOutputSizePixel( Size( 640, 480 ) );
567
8.87k
    SetMapMode(MapMode(MapUnit::MapMM));
568
8.87k
}
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
8.87k
        : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
441
8.87k
        m_aMapMode( MapUnit::MapPoint, Point(), 1.0 / pointToPixel(1), 1.0 / pointToPixel(1) ),
442
8.87k
        m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
443
8.87k
        m_nCurrentStructElement( 0 ),
444
8.87k
        m_bEmitStructure( true ),
445
8.87k
        m_nNextFID( 1 ),
446
8.87k
        m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
447
8.87k
            officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
448
8.87k
        m_nCurrentPage( -1 ),
449
8.87k
        m_nCatalogObject(0),
450
8.87k
        m_nSignatureObject( -1 ),
451
8.87k
        m_nSignatureContentOffset( 0 ),
452
8.87k
        m_nSignatureLastByteRangeNoOffset( 0 ),
453
8.87k
        m_nResourceDict( -1 ),
454
8.87k
        m_nFontDictObject( -1 ),
455
8.87k
        m_aContext(rContext),
456
8.87k
        m_aFile(m_aContext.URL),
457
8.87k
        m_bOpen(false),
458
8.87k
        m_DocDigest(::comphelper::HashType::MD5),
459
8.87k
        m_rOuterFace( i_rOuterFace )
460
8.87k
{
461
8.87k
    m_aStructure.emplace_back( );
462
8.87k
    m_aStructure[0].m_nOwnElement       = 0;
463
8.87k
    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
8.87k
    m_aWidgetStyleSettings.SetStandardStyles();
468
469
8.87k
    GraphicsState aState;
470
8.87k
    aState.m_aMapMode       = m_aMapMode;
471
8.87k
    aState.m_aFont.SetFamilyName( u"Times"_ustr );
472
8.87k
    aState.m_aFont.SetFontSize( Size( 0, 12 ) );
473
474
8.87k
    m_aGraphicsStack.push_front( aState );
475
476
8.87k
    osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
477
8.87k
    if (aError != osl::File::E_None)
478
8.87k
    {
479
8.87k
        if (aError == osl::File::E_EXIST)
480
8.87k
        {
481
8.87k
            aError = m_aFile.open(osl_File_OpenFlag_Write);
482
8.87k
            if (aError == osl::File::E_None)
483
8.87k
                aError = m_aFile.setSize(0);
484
8.87k
        }
485
8.87k
    }
486
8.87k
    if (aError != osl::File::E_None)
487
0
        return;
488
489
8.87k
    m_bOpen = true;
490
491
    // setup DocInfo
492
8.87k
    setupDocInfo();
493
494
8.87k
    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
8.87k
    if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
504
0
    {
505
0
        m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
506
0
    }
507
508
    // write header
509
8.87k
    if (!writeBuffer(Concat2View(OString::Concat("%PDF-") + getPDFVersionStr(m_aContext.Version)
510
8.87k
                             + "\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
8.87k
    m_aOutline.emplace_back( );
519
520
8.87k
    switch (m_aContext.Version)
521
8.87k
    {
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
8.87k
        default:
543
8.87k
            break;
544
8.87k
    }
545
546
    // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
547
8.87k
    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
8.87k
    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
8.87k
    if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
562
8.87k
        SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
563
0
    else
564
0
        SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
565
566
8.87k
    SetOutputSizePixel( Size( 640, 480 ) );
567
8.87k
    SetMapMode(MapMode(MapUnit::MapMM));
568
8.87k
}
569
570
PDFWriterImpl::~PDFWriterImpl()
571
8.87k
{
572
8.87k
    disposeOnce();
573
8.87k
}
574
575
void PDFWriterImpl::dispose()
576
8.87k
{
577
8.87k
    m_aPages.clear();
578
8.87k
    VirtualDevice::dispose();
579
8.87k
}
580
581
bool PDFWriterImpl::ImplNewFont() const
582
6.03M
{
583
6.03M
    const ImplSVData* pSVData = ImplGetSVData();
584
585
6.03M
    if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
586
6.03M
        ||  mxFontCache == pSVData->maGDIData.mxScreenFontCache )
587
0
    {
588
0
        const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
589
0
    }
590
591
6.03M
    return OutputDevice::ImplNewFont();
592
6.03M
}
593
594
void PDFWriterImpl::setupDocInfo()
595
8.87k
{
596
8.87k
    std::vector< sal_uInt8 > aId;
597
8.87k
    m_aCreationDateString = PDFWriter::GetDateTime();
598
8.87k
    computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString);
599
8.87k
    if( m_aContext.Encryption.DocumentIdentifier.empty() )
600
8.87k
        m_aContext.Encryption.DocumentIdentifier = std::move(aId);
601
8.87k
}
602
603
OString PDFWriter::GetDateTime(svl::crypto::SigningContext* pSigningContext)
604
8.87k
{
605
8.87k
    OStringBuffer aRet;
606
607
8.87k
    TimeValue aTVal, aGMT;
608
8.87k
    oslDateTime aDT;
609
8.87k
    osl_getSystemTime(&aGMT);
610
611
8.87k
    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
8.87k
    osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
625
8.87k
    osl_getDateTimeFromTimeValue(&aTVal, &aDT);
626
627
8.87k
    sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
628
629
8.87k
    appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
630
631
8.87k
    aRet.append("'");
632
8.87k
    return aRet.makeStringAndClear();
633
8.87k
}
634
635
void PDFWriterImpl::emitComment( const char* pComment )
636
0
{
637
0
    OString aLine = OString::Concat("% ") + pComment + "\n";
638
0
    writeBuffer( aLine );
639
0
}
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
90.1k
{
665
90.1k
    if (!g_bDebugDisableCompression)
666
90.1k
    {
667
90.1k
        m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
668
90.1k
        m_pMemStream = std::make_unique<SvMemoryStream>();
669
90.1k
        m_pCodec->BeginCompression();
670
90.1k
    }
671
90.1k
}
672
673
void PDFWriterImpl::endCompression()
674
90.1k
{
675
90.1k
    if (!g_bDebugDisableCompression && m_pCodec)
676
90.1k
    {
677
90.1k
        m_pCodec->EndCompression();
678
90.1k
        m_pCodec.reset();
679
90.1k
        sal_uInt64 nLen = m_pMemStream->Tell();
680
90.1k
        m_pMemStream->Seek( 0 );
681
90.1k
        (void)writeBufferBytes( m_pMemStream->GetData(), nLen );
682
90.1k
        m_pMemStream.reset();
683
90.1k
    }
684
90.1k
}
685
686
bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes )
687
2.13M
{
688
2.13M
    if( ! m_bOpen ) // we are already down the drain
689
0
        return false;
690
691
2.13M
    if( ! nBytes ) // huh ?
692
0
        return true;
693
694
2.13M
    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.13M
    sal_uInt64 nWritten;
703
2.13M
    sal_uInt64 nActualSize = nBytes;
704
705
    // we are compressing the stream
706
2.13M
    if (m_pCodec)
707
1.03M
    {
708
1.03M
        m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), nBytes );
709
1.03M
        nWritten = nBytes;
710
1.03M
    }
711
1.10M
    else
712
1.10M
    {
713
        // is it encrypted?
714
1.10M
        bool bStreamEncryption = m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled();
715
1.10M
        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.10M
        const void* pWriteBuffer = bStreamEncryption ? m_vEncryptionBuffer.data() : pBuffer;
723
1.10M
        m_DocDigest.update(pWriteBuffer, sal_uInt32(nActualSize));
724
725
726
1.10M
        if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != osl::File::E_None)
727
0
            nWritten = 0;
728
729
1.10M
        if (nWritten != nActualSize)
730
0
        {
731
0
            m_aFile.close();
732
0
            m_bOpen = false;
733
0
            return false;
734
0
        }
735
1.10M
        return true;
736
1.10M
    }
737
738
1.03M
    return nWritten == nActualSize;
739
2.13M
}
740
741
void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
742
73.7k
{
743
73.7k
    endPage();
744
73.7k
    m_nCurrentPage = m_aPages.size();
745
73.7k
    m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
746
747
73.7k
    const double frac = double(m_aPages.back().m_nUserUnit) / pointToPixel(1);
748
73.7k
    m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
749
750
73.7k
    m_aPages.back().beginStream();
751
752
    // setup global graphics state
753
    // linewidth is "1 pixel" by default
754
73.7k
    OStringBuffer aBuf( 16 );
755
73.7k
    appendDouble( 72.0/GetDPIX(), aBuf );
756
73.7k
    aBuf.append( " w\n" );
757
73.7k
    writeBuffer( aBuf );
758
73.7k
}
759
760
void PDFWriterImpl::endPage()
761
82.6k
{
762
82.6k
    if( m_aPages.empty() )
763
8.85k
        return;
764
765
    // close eventual MC sequence
766
73.7k
    endStructureElementMCSeq();
767
768
    // sanity check
769
73.7k
    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
73.7k
    m_aGraphicsStack.clear();
777
73.7k
    m_aGraphicsStack.emplace_back( );
778
779
    // this should pop the PDF graphics stack if necessary
780
73.7k
    updateGraphicsState();
781
782
73.7k
    m_aPages.back().endStream();
783
784
    // reset the default font
785
73.7k
    Font aFont;
786
73.7k
    aFont.SetFamilyName( u"Times"_ustr );
787
73.7k
    aFont.SetFontSize( Size( 0, 12 ) );
788
789
73.7k
    m_aCurrentPDFState = m_aGraphicsStack.front();
790
73.7k
    m_aGraphicsStack.front().m_aFont = std::move(aFont);
791
792
73.7k
    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
73.7k
    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
73.7k
    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
73.7k
}
819
820
sal_Int32 PDFWriterImpl::createObject()
821
394k
{
822
394k
    m_aObjects.emplace_back();
823
394k
    return m_aObjects.size();
824
394k
}
825
826
bool PDFWriterImpl::updateObject( sal_Int32 n )
827
394k
{
828
394k
    if( ! m_bOpen )
829
0
        return false;
830
831
394k
    sal_uInt64 nOffset = SAL_MAX_UINT64;
832
394k
    osl::File::RC aError = m_aFile.getPos(nOffset);
833
394k
    SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
834
394k
    if (aError != osl::File::E_None)
835
0
    {
836
0
        m_aFile.close();
837
0
        m_bOpen = false;
838
0
    }
839
394k
    m_aObjects[n - 1].m_nOffset = nOffset;
840
394k
    return aError == osl::File::E_None;
841
394k
}
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
8.85k
{
1232
8.85k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
1233
8.85k
        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.writeKeyAndLiteral("/NS", sNamespace);
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
16.3k
{
1720
16.3k
    rCodeUnits.reserve(256);
1721
1722
    // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
1723
16.3k
    pWidths[0] = 0;
1724
1725
16.3k
    uint32_t nGlyphs = 1;
1726
16.3k
    for (auto const& item : rSubset.m_aMapping)
1727
489k
    {
1728
489k
        sal_uInt8 nEnc = item.second.getGlyphId();
1729
1730
489k
        SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
1731
489k
                    "duplicate glyph");
1732
489k
        SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
1733
1734
489k
        pGlyphIds[nEnc] = item.first;
1735
489k
        pEncoding[nEnc] = nEnc;
1736
489k
        pEncToUnicodeIndex[nEnc] = rCodeUnits.size();
1737
489k
        pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
1738
489k
        pWidths[nEnc] = item.second.getGlyphWidth();
1739
977k
        for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
1740
487k
            rCodeUnits.push_back(item.second.getCode(n));
1741
489k
        if (item.second.getCode(0))
1742
487k
            nToUnicodeStream = 1;
1743
489k
        if (nGlyphs < 256)
1744
489k
            nGlyphs++;
1745
0
        else
1746
489k
            OSL_FAIL("too many glyphs for subset");
1747
489k
    }
1748
1749
16.3k
    return nGlyphs;
1750
16.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
0
{
1757
0
    if (g_bDebugDisableCompression)
1758
0
        emitComment("PDFWriterImpl::emitType3Font");
1759
1760
0
    const auto& rColorPalettes = pFace->GetColorPalettes();
1761
1762
0
    FontSubsetInfo aSubsetInfo;
1763
0
    sal_GlyphId pTempGlyphIds[] = { 0 };
1764
0
    sal_uInt8 pTempEncoding[] = { 0 };
1765
0
    std::vector<sal_uInt8> aBuffer;
1766
0
    pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
1767
1768
0
    for (auto& rSubset : rType3Font.m_aSubsets)
1769
0
    {
1770
0
        sal_GlyphId pGlyphIds[256] = {};
1771
0
        sal_Int32 pWidths[256];
1772
0
        sal_uInt8 pEncoding[256] = {};
1773
0
        sal_Int32 pEncToUnicodeIndex[256] = {};
1774
0
        sal_Int32 pCodeUnitsPerGlyph[256] = {};
1775
0
        std::vector<sal_Ucs> aCodeUnits;
1776
0
        sal_Int32 nToUnicodeStream = 0;
1777
1778
        // fill arrays and prepare encoding index map
1779
0
        auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
1780
0
                                        pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
1781
1782
        // write font descriptor
1783
0
        sal_Int32 nFontDescriptor = 0;
1784
0
        if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
1785
0
            nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0, 0);
1786
1787
0
        if (nToUnicodeStream)
1788
0
            nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
1789
0
                                                   pEncToUnicodeIndex, nGlyphs);
1790
1791
        // write font object
1792
0
        sal_Int32 nFontObject = createObject();
1793
0
        if (!updateObject(nFontObject))
1794
0
            return false;
1795
1796
0
        OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
1797
0
        aLine.append(
1798
0
            OString::number(nFontObject)
1799
0
            + " 0 obj\n"
1800
0
              "<</Type/Font/Subtype/Type3/Name/");
1801
0
        COSWriter::appendName(aSubsetInfo.m_aPSName, aLine);
1802
1803
0
        aLine.append(
1804
0
            "\n/FontBBox["
1805
        // note: Top and Bottom are reversed in VCL and PDF rectangles
1806
0
            + OString::number(aSubsetInfo.m_aFontBBox.Left())
1807
0
            + " "
1808
0
            + OString::number(aSubsetInfo.m_aFontBBox.Top())
1809
0
            + " "
1810
0
            + OString::number(aSubsetInfo.m_aFontBBox.Right())
1811
0
            + " "
1812
0
            + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
1813
0
            + "]\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
0
        double fScale = 1000. / pFace->UnitsPerEm();
1821
1822
0
        aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n");
1823
1824
0
        sal_Int32 pGlyphStreams[256] = {};
1825
0
        aLine.append("/CharProcs<<\n");
1826
0
        for (auto i = 1u; i < nGlyphs; i++)
1827
0
        {
1828
0
            auto nStream = createObject();
1829
0
            aLine.append("/"
1830
0
                + pFace->GetGlyphName(pGlyphIds[i], true)
1831
0
                + " "
1832
0
                + OString::number(nStream)
1833
0
                + " 0 R\n");
1834
0
            pGlyphStreams[i] = nStream;
1835
0
        }
1836
0
        aLine.append(">>\n"
1837
1838
0
            "/Encoding<</Type/Encoding/Differences[1");
1839
0
        for (auto i = 1u; i < nGlyphs; i++)
1840
0
            aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
1841
0
        aLine.append("]>>\n"
1842
1843
0
            "/FirstChar 0\n"
1844
0
            "/LastChar "
1845
0
            + OString::number(nGlyphs - 1)
1846
0
            + "\n"
1847
1848
0
            "/Widths[");
1849
0
        for (auto i = 0u; i < nGlyphs; i++)
1850
0
        {
1851
0
            appendDouble(pWidths[i] * fScale, aLine);
1852
0
            aLine.append(" ");
1853
0
        }
1854
0
        aLine.append("]\n");
1855
1856
0
        if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
1857
0
        {
1858
0
            aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
1859
0
        }
1860
1861
0
        auto nResources = createObject();
1862
0
        aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
1863
1864
0
        if (nToUnicodeStream)
1865
0
        {
1866
0
            aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
1867
0
        }
1868
1869
0
        aLine.append(">>\n"
1870
0
                     "endobj\n\n");
1871
1872
0
        if (!writeBuffer(aLine))
1873
0
            return false;
1874
1875
0
        std::set<sal_Int32> aUsedFonts;
1876
0
        std::list<BitmapEmit> aUsedBitmaps;
1877
0
        std::map<sal_uInt8, sal_Int32> aUsedAlpha;
1878
0
        ResourceDict aResourceDict;
1879
0
        std::list<StreamRedirect> aOutputStreams;
1880
1881
0
        for (auto i = 1u; i < nGlyphs; i++)
1882
0
        {
1883
0
            auto nStream = pGlyphStreams[i];
1884
0
            if (!updateObject(nStream))
1885
0
                return false;
1886
0
            OStringBuffer aContents(PDFWRITER_IMPL_BUFFERSIZE);
1887
0
            appendDouble(pWidths[i] * fScale, aContents);
1888
0
            aContents.append(" 0 d0\n");
1889
1890
0
            const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
1891
0
            const auto& rLayers = rGlyph.getColorLayers();
1892
0
            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
0
            tools::Rectangle aRect;
1938
0
            const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
1939
0
            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
0
            assert(aContents[aContents.getLength() - 1] == '\n');
1964
0
            aContents.setLength(aContents.getLength() - 1);
1965
0
            assert(aContents[aContents.getLength() - 1] != '\n');
1966
0
            aLine.setLength(0);
1967
0
            aLine.append(OString::number(nStream)
1968
0
                + " 0 obj\n<</Length "
1969
0
                + OString::number(calculateStreamSize(aContents.getLength()))
1970
0
                + ">>\nstream\n");
1971
0
            if (!writeBuffer(aLine))
1972
0
                return false;
1973
0
            checkAndEnableStreamEncryption(nStream);
1974
0
            if (!writeBuffer(aContents))
1975
0
                return false;
1976
0
            disableStreamEncryption();
1977
0
            aLine.setLength(0);
1978
0
            aLine.append("\nendstream\nendobj\n\n");
1979
0
            if (!writeBuffer(aLine))
1980
0
                return false;
1981
0
        }
1982
1983
        // write font dict
1984
0
        sal_Int32 nFontDict = 0;
1985
0
        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
0
        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
0
        for (auto& aBitmap : aUsedBitmaps)
2034
0
            writeBitmapObject(aBitmap);
2035
2036
        // write resources dict
2037
0
        aLine.setLength(0);
2038
0
        aLine.append(OString::number(nResources) + " 0 obj\n");
2039
0
        aResourceDict.append(aLine, nFontDict, m_aContext.Version);
2040
0
        aLine.append("endobj\n\n");
2041
0
        if (!updateObject(nResources))
2042
0
            return false;
2043
0
        if (!writeBuffer(aLine))
2044
0
            return false;
2045
2046
0
        rFontIDToObject[rSubset.m_nFontID] = nFontObject;
2047
0
    }
2048
2049
0
    return true;
2050
0
}
2051
2052
static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
2053
48.9k
{
2054
48.9k
    if( nSubsetID )
2055
48.9k
    {
2056
342k
        for( int i = 0; i < 6; i++ )
2057
293k
        {
2058
293k
            int nOffset = nSubsetID % 26;
2059
293k
            nSubsetID /= 26;
2060
293k
            rBuffer.append( static_cast<char>('A'+nOffset) );
2061
293k
        }
2062
48.9k
        rBuffer.append( '+' );
2063
48.9k
    }
2064
48.9k
    COSWriter::appendName( rPSName, rBuffer );
2065
48.9k
}
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
16.3k
{
2073
16.3k
    int nMapped = 0;
2074
521k
    for (auto n = 0u; n < nGlyphs; ++n)
2075
505k
        if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2076
487k
            nMapped++;
2077
2078
16.3k
    if( nMapped == 0 )
2079
0
        return 0;
2080
2081
16.3k
    sal_Int32 nStream = createObject();
2082
16.3k
    if (!updateObject(nStream))
2083
0
        return 0;
2084
16.3k
    OStringBuffer aContents( PDFWRITER_IMPL_BUFFERSIZE );
2085
16.3k
    aContents.append(
2086
16.3k
                     "/CIDInit/ProcSet findresource begin\n"
2087
16.3k
                     "12 dict begin\n"
2088
16.3k
                     "begincmap\n"
2089
16.3k
                     "/CIDSystemInfo<<\n"
2090
16.3k
                     "/Registry (Adobe)\n"
2091
16.3k
                     "/Ordering (UCS)\n"
2092
16.3k
                     "/Supplement 0\n"
2093
16.3k
                     ">> def\n"
2094
16.3k
                     "/CMapName/Adobe-Identity-UCS def\n"
2095
16.3k
                     "/CMapType 2 def\n"
2096
16.3k
                     "1 begincodespacerange\n"
2097
16.3k
                     "<00> <FF>\n"
2098
16.3k
                     "endcodespacerange\n"
2099
16.3k
                     );
2100
16.3k
    int nCount = 0;
2101
521k
    for (auto n = 0u; n < nGlyphs; ++n)
2102
505k
    {
2103
505k
        if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2104
487k
        {
2105
487k
            if( (nCount % 100) == 0 )
2106
16.6k
            {
2107
16.6k
                if( nCount )
2108
359
                    aContents.append( "endbfchar\n" );
2109
16.6k
                aContents.append( OString::number(std::min(nMapped-nCount, 100) )
2110
16.6k
                    + " beginbfchar\n" );
2111
16.6k
            }
2112
487k
            aContents.append( '<' );
2113
487k
            COSWriter::appendHex(static_cast<sal_Int8>(pEncoding[n]), aContents);
2114
487k
            aContents.append( "> <" );
2115
            // TODO: handle code points>U+FFFF
2116
487k
            sal_Int32 nIndex = pEncToUnicodeIndex[n];
2117
975k
            for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2118
487k
            {
2119
487k
                COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents);
2120
487k
                COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents);
2121
487k
            }
2122
487k
            aContents.append( ">\n" );
2123
487k
            nCount++;
2124
487k
        }
2125
505k
    }
2126
16.3k
    aContents.append( "endbfchar\n"
2127
16.3k
                      "endcmap\n"
2128
16.3k
                      "CMapName currentdict /CMap defineresource pop\n"
2129
16.3k
                      "end\n"
2130
16.3k
                      "end\n" );
2131
16.3k
    SvMemoryStream aStream;
2132
16.3k
    if (!g_bDebugDisableCompression)
2133
16.3k
    {
2134
16.3k
        ZCodec aCodec( 0x4000, 0x4000 );
2135
16.3k
        aCodec.BeginCompression();
2136
16.3k
        aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2137
16.3k
        aCodec.EndCompression();
2138
16.3k
    }
2139
2140
16.3k
    if (g_bDebugDisableCompression)
2141
0
    {
2142
0
        emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2143
0
    }
2144
16.3k
    OStringBuffer aLine( 40 );
2145
2146
16.3k
    aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " );
2147
16.3k
    sal_uInt64 nLen = 0;
2148
16.3k
    if (!g_bDebugDisableCompression)
2149
16.3k
    {
2150
16.3k
        nLen = aStream.Tell();
2151
16.3k
        aStream.Seek( 0 );
2152
16.3k
        aLine.append(OString::number(calculateStreamSize(nLen)) + "/Filter/FlateDecode");
2153
16.3k
    }
2154
0
    else
2155
0
        aLine.append(OString::number(calculateStreamSize(aContents.getLength())));
2156
16.3k
    aLine.append( ">>\nstream\n" );
2157
16.3k
    if (!writeBuffer(aLine)) return 0;
2158
16.3k
    checkAndEnableStreamEncryption( nStream );
2159
16.3k
    if (!g_bDebugDisableCompression)
2160
16.3k
    {
2161
16.3k
        if(!writeBufferBytes(aStream.GetData(), nLen)) return 0;
2162
16.3k
    }
2163
0
    else
2164
0
    {
2165
0
        if (!writeBuffer(aContents)) return 0;
2166
0
    }
2167
16.3k
    disableStreamEncryption();
2168
16.3k
    aLine.setLength( 0 );
2169
16.3k
    aLine.append( "\nendstream\n"
2170
16.3k
                  "endobj\n\n" );
2171
16.3k
    if (!writeBuffer(aLine)) return 0;
2172
16.3k
    return nStream;
2173
16.3k
}
2174
2175
sal_Int32 PDFWriterImpl::emitCIDCMap(sal_Int32 nSubsetID, sal_uInt32 nGlyphs)
2176
16.3k
{
2177
16.3k
    sal_Int32 nStream = createObject();
2178
16.3k
    if (!updateObject(nStream))
2179
0
        return 0;
2180
2181
16.3k
    OStringBuffer aContents(PDFWRITER_IMPL_BUFFERSIZE);
2182
16.3k
    aContents.append("/CIDInit/ProcSet findresource begin\n"
2183
16.3k
                     "12 dict begin\n"
2184
16.3k
                     "begincmap\n"
2185
16.3k
                     "/CIDSystemInfo<<\n"
2186
16.3k
                     "/Registry (Adobe)\n"
2187
16.3k
                     "/Ordering (Identity)\n"
2188
16.3k
                     "/Supplement 0\n"
2189
16.3k
                     ">> def\n"
2190
16.3k
                     "/CMapName/Subset-"
2191
16.3k
                     + OString::number(nSubsetID)
2192
16.3k
                     + " def\n"
2193
16.3k
                       "/CMapType 1 def\n"
2194
16.3k
                       "1 begincodespacerange\n"
2195
16.3k
                       "<00> <FF>\n"
2196
16.3k
                       "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
16.3k
    aContents.append("1 begincidrange\n<00> <");
2201
16.3k
    COSWriter::appendHex(static_cast<sal_Int8>(nGlyphs - 1), aContents);
2202
16.3k
    aContents.append("> 0\nendcidrange\n");
2203
2204
16.3k
    aContents.append("endcmap\n"
2205
16.3k
                     "CMapName currentdict /CMap defineresource pop\n"
2206
16.3k
                     "end\n"
2207
16.3k
                     "end\n");
2208
2209
16.3k
    SvMemoryStream aStream;
2210
16.3k
    if (!g_bDebugDisableCompression)
2211
16.3k
    {
2212
16.3k
        ZCodec aCodec(0x4000, 0x4000);
2213
16.3k
        aCodec.BeginCompression();
2214
16.3k
        aCodec.Write(aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()),
2215
16.3k
                     aContents.getLength());
2216
16.3k
        aCodec.EndCompression();
2217
16.3k
    }
2218
2219
16.3k
    OStringBuffer aLine(80);
2220
16.3k
    aLine.append(OString::number(nStream)
2221
16.3k
                 + " 0 obj\n"
2222
16.3k
                   "<</Type/CMap"
2223
16.3k
                   "/CMapName/Subset-"
2224
16.3k
                 + OString::number(nSubsetID)
2225
16.3k
                 + "-CMap"
2226
16.3k
                   "/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>"
2227
16.3k
                   "/Length ");
2228
16.3k
    sal_uInt64 nLen = 0;
2229
16.3k
    if (!g_bDebugDisableCompression)
2230
16.3k
    {
2231
16.3k
        nLen = aStream.Tell();
2232
16.3k
        aStream.Seek(0);
2233
16.3k
        aLine.append(OString::number(nLen) + "/Filter/FlateDecode");
2234
16.3k
    }
2235
0
    else
2236
0
        aLine.append(aContents.getLength());
2237
16.3k
    aLine.append(">>\nstream\n");
2238
16.3k
    if (!writeBuffer(aLine))
2239
0
        return 0;
2240
16.3k
    checkAndEnableStreamEncryption(nStream);
2241
16.3k
    if (!g_bDebugDisableCompression)
2242
16.3k
    {
2243
16.3k
        if (!writeBufferBytes(aStream.GetData(), nLen))
2244
0
            return 0;
2245
16.3k
    }
2246
0
    else
2247
0
    {
2248
0
        if (!writeBuffer(aContents))
2249
0
            return 0;
2250
0
    }
2251
16.3k
    disableStreamEncryption();
2252
16.3k
    aLine.setLength(0);
2253
16.3k
    aLine.append("\nendstream\nendobj\n\n");
2254
16.3k
    if (!writeBuffer(aLine))
2255
0
        return 0;
2256
16.3k
    return nStream;
2257
16.3k
}
2258
2259
sal_Int32 PDFWriterImpl::emitCIDSet(sal_uInt32 nGlyphs)
2260
16.3k
{
2261
    // Only PDF/A-1 requires one, and PDF 2.0 deprecates it.
2262
16.3k
    if (m_nPDFA_Version != 1 || !nGlyphs)
2263
16.3k
        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
16.3k
{
2319
16.3k
    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
16.3k
    sal_Int32 nFontFlags = (1<<2);
2324
16.3k
    if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
2325
889
        nFontFlags |= (1 << 6);
2326
16.3k
    if( pFace->GetPitch() == PITCH_FIXED )
2327
0
        nFontFlags |= 1;
2328
16.3k
    if( pFace->GetFamilyType() == FAMILY_SCRIPT )
2329
0
        nFontFlags |= (1 << 3);
2330
16.3k
    else if( pFace->GetFamilyType() == FAMILY_ROMAN )
2331
0
        nFontFlags |= (1 << 1);
2332
2333
16.3k
    sal_Int32 nFontDescriptor = createObject();
2334
16.3k
    if (!updateObject(nFontDescriptor)) return 0;
2335
16.3k
    aLine.setLength( 0 );
2336
16.3k
    aLine.append(
2337
16.3k
        OString::number(nFontDescriptor)
2338
16.3k
        + " 0 obj\n"
2339
16.3k
          "<</Type/FontDescriptor/FontName/" );
2340
16.3k
    appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2341
16.3k
    aLine.append( "\n"
2342
16.3k
                  "/Flags "
2343
16.3k
        + OString::number( nFontFlags )
2344
16.3k
        + "\n"
2345
16.3k
          "/FontBBox["
2346
    // note: Top and Bottom are reversed in VCL and PDF rectangles
2347
16.3k
        + OString::number( rInfo.m_aFontBBox.Left() )
2348
16.3k
        + " "
2349
16.3k
        + OString::number( rInfo.m_aFontBBox.Top() )
2350
16.3k
        + " "
2351
16.3k
        + OString::number( rInfo.m_aFontBBox.Right() )
2352
16.3k
        + " "
2353
16.3k
        + OString::number( rInfo.m_aFontBBox.Bottom()+1 )
2354
16.3k
        +  "]/ItalicAngle " );
2355
16.3k
    if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
2356
889
        aLine.append( "-30" );
2357
15.4k
    else
2358
15.4k
        aLine.append( "0" );
2359
16.3k
    aLine.append( "\n"
2360
16.3k
                  "/Ascent "
2361
16.3k
        + OString::number( rInfo.m_nAscent )
2362
16.3k
        + "\n"
2363
16.3k
          "/Descent "
2364
16.3k
        + OString::number( -rInfo.m_nDescent )
2365
16.3k
        + "\n"
2366
16.3k
          "/CapHeight "
2367
16.3k
        + 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
16.3k
        + "\n"
2371
16.3k
          "/StemV 80\n" );
2372
16.3k
    if( nFontStream )
2373
16.3k
    {
2374
16.3k
        aLine.append( "/FontFile" );
2375
16.3k
        switch( rInfo.m_nFontType )
2376
16.3k
        {
2377
16.3k
            case FontType::SFNT_TTF:
2378
16.3k
                aLine.append( '2' );
2379
16.3k
                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
16.3k
        }
2387
16.3k
        aLine.append( " " + OString::number(nFontStream) + " 0 R\n" );
2388
16.3k
    }
2389
16.3k
    if( nCIDSet )
2390
0
        aLine.append( "/CIDSet " + OString::number(nCIDSet) + " 0 R\n" );
2391
16.3k
    aLine.append( ">>\n"
2392
16.3k
                  "endobj\n\n" );
2393
16.3k
    if (!writeBuffer(aLine)) return 0;
2394
2395
16.3k
    return nFontDescriptor;
2396
16.3k
}
2397
2398
void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2399
8.85k
{
2400
8.85k
    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
8.85k
}
2408
2409
bool PDFWriterImpl::emitFonts()
2410
8.85k
{
2411
8.85k
    OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
2412
2413
8.85k
    std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2414
2415
8.85k
    for (const auto & subset : m_aSubsets)
2416
16.3k
    {
2417
16.3k
        for (auto & s_subset :subset.second.m_aSubsets)
2418
16.3k
        {
2419
16.3k
            sal_GlyphId pGlyphIds[ 256 ] = {};
2420
16.3k
            sal_Int32 pWidths[ 256 ];
2421
16.3k
            sal_uInt8 pEncoding[ 256 ] = {};
2422
16.3k
            sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
2423
16.3k
            sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
2424
16.3k
            std::vector<sal_Ucs> aCodeUnits;
2425
16.3k
            sal_Int32 nToUnicodeStream = 0;
2426
2427
            // fill arrays and prepare encoding index map
2428
16.3k
            auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2429
16.3k
                                            pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2430
2431
16.3k
            std::vector<sal_uInt8> aBuffer;
2432
16.3k
            FontSubsetInfo aSubsetInfo;
2433
16.3k
            const auto* pFace = subset.first.m_pFace;
2434
16.3k
            if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo,
2435
16.3k
                                        subset.first.m_rVariations))
2436
16.3k
            {
2437
                // create font stream
2438
16.3k
                if (g_bDebugDisableCompression)
2439
0
                {
2440
0
                    emitComment( "PDFWriterImpl::emitFonts" );
2441
0
                }
2442
16.3k
                sal_Int32 nFontStream = createObject();
2443
16.3k
                sal_Int32 nStreamLengthObject = createObject();
2444
16.3k
                if ( !updateObject( nFontStream ) ) return false;
2445
16.3k
                aLine.setLength( 0 );
2446
16.3k
                aLine.append( OString::number(nFontStream)
2447
16.3k
                    + " 0 obj\n"
2448
16.3k
                      "<</Length "
2449
16.3k
                    + OString::number( nStreamLengthObject ) );
2450
16.3k
                if (!g_bDebugDisableCompression)
2451
16.3k
                    aLine.append( " 0 R"
2452
16.3k
                                 "/Filter/FlateDecode");
2453
0
                else
2454
0
                    aLine.append( " 0 R");
2455
2456
16.3k
                sal_uInt64 nStartPos = 0;
2457
16.3k
                if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
2458
16.3k
                {
2459
16.3k
                    aLine.append("/Length1 "
2460
16.3k
                        + OString::number(aBuffer.size())
2461
16.3k
                        + ">>\n"
2462
16.3k
                          "stream\n" );
2463
16.3k
                    if ( !writeBuffer( aLine ) ) return false;
2464
16.3k
                    if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2465
2466
                    // copy font file
2467
16.3k
                    beginCompression();
2468
16.3k
                    checkAndEnableStreamEncryption( nFontStream );
2469
16.3k
                    if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
2470
0
                        return false;
2471
16.3k
                }
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
16.3k
                endCompression();
2491
16.3k
                disableStreamEncryption();
2492
2493
16.3k
                sal_uInt64 nEndPos = 0;
2494
16.3k
                if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
2495
                // end the stream
2496
16.3k
                aLine.setLength( 0 );
2497
16.3k
                aLine.append( "\nendstream\nendobj\n\n" );
2498
16.3k
                if ( !writeBuffer( aLine ) ) return false;
2499
2500
                // emit stream length object
2501
16.3k
                if ( !updateObject( nStreamLengthObject ) ) return false;
2502
16.3k
                aLine.setLength( 0 );
2503
16.3k
                aLine.append( OString::number(nStreamLengthObject)
2504
16.3k
                    + " 0 obj\n"
2505
16.3k
                    + OString::number( nEndPos-nStartPos )
2506
16.3k
                    + "\nendobj\n\n" );
2507
16.3k
                if ( !writeBuffer( aLine ) ) return false;
2508
2509
                // PDF/A-1 wants the CIDs of a composite subset listed
2510
16.3k
                sal_Int32 nCIDSet = emitCIDSet(nGlyphs);
2511
2512
                // write font descriptor
2513
16.3k
                sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first.m_pFace, aSubsetInfo, s_subset.m_nFontID, nFontStream, nCIDSet );
2514
2515
16.3k
                if( nToUnicodeStream )
2516
16.3k
                    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
16.3k
                bool bTrueType = aSubsetInfo.m_nFontType == FontType::SFNT_TTF;
2521
16.3k
                sal_Int32 nCMapStream = emitCIDCMap(s_subset.m_nFontID, nGlyphs);
2522
16.3k
                if (!nCMapStream) return false;
2523
2524
16.3k
                sal_Int32 nCIDFontObject = createObject();
2525
16.3k
                if (!updateObject(nCIDFontObject)) return false;
2526
16.3k
                {
2527
16.3k
                    OStringBuffer aCIDLine;
2528
16.3k
                    aCIDLine.append(OString::number(nCIDFontObject) + " 0 obj\n"
2529
16.3k
                        "<</Type/Font/Subtype/");
2530
16.3k
                    aCIDLine.append(bTrueType ? "CIDFontType2" : "CIDFontType0");
2531
16.3k
                    aCIDLine.append("/BaseFont/");
2532
16.3k
                    appendSubsetName(s_subset.m_nFontID, aSubsetInfo.m_aPSName, aCIDLine);
2533
16.3k
                    aCIDLine.append("\n/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>\n"
2534
16.3k
                        "/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
2535
16.3k
                    if (bTrueType)
2536
16.3k
                        aCIDLine.append("/CIDToGIDMap/Identity\n");
2537
                    // /W is indexed by CID, which is the glyph ID here
2538
16.3k
                    aCIDLine.append("/W[0[");
2539
521k
                    for (auto i = 0u; i < nGlyphs; i++)
2540
505k
                    {
2541
505k
                        aCIDLine.append(pWidths[i]);
2542
505k
                        aCIDLine.append(((i & 15) == 15) ? "\n" : " ");
2543
505k
                    }
2544
16.3k
                    aCIDLine.append("]]\n>>\nendobj\n\n");
2545
16.3k
                    if (!writeBuffer(aCIDLine)) return false;
2546
16.3k
                }
2547
2548
16.3k
                sal_Int32 nFontObject = createObject();
2549
16.3k
                if ( !updateObject( nFontObject ) ) return false;
2550
16.3k
                aLine.setLength( 0 );
2551
16.3k
                aLine.append( OString::number(nFontObject) + " 0 obj\n"
2552
16.3k
                    "<</Type/Font/Subtype/Type0/BaseFont/");
2553
16.3k
                appendSubsetName(s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine);
2554
16.3k
                aLine.append("\n/Encoding " + OString::number(nCMapStream) + " 0 R\n"
2555
16.3k
                    "/DescendantFonts[" + OString::number(nCIDFontObject) + " 0 R]\n");
2556
16.3k
                if (nToUnicodeStream)
2557
16.3k
                {
2558
16.3k
                    aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
2559
16.3k
                }
2560
16.3k
                aLine.append(">>\nendobj\n\n");
2561
16.3k
                if ( !writeBuffer( aLine ) ) return false;
2562
2563
16.3k
                aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
2564
16.3k
            }
2565
0
            else
2566
0
            {
2567
0
                OStringBuffer aErrorComment( 256 );
2568
0
                aErrorComment.append( "CreateFontSubset failed for font \""
2569
0
                    + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
2570
0
                    + "\"" );
2571
0
                if( pFace->GetItalic() == ITALIC_NORMAL )
2572
0
                    aErrorComment.append( " italic" );
2573
0
                else if( pFace->GetItalic() == ITALIC_OBLIQUE )
2574
0
                    aErrorComment.append( " oblique" );
2575
0
                aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
2576
0
                emitComment( aErrorComment.getStr() );
2577
0
            }
2578
16.3k
        }
2579
16.3k
    }
2580
2581
    // emit system fonts
2582
8.85k
    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
8.85k
    for (auto const& it : m_aType3Fonts)
2594
0
    {
2595
0
        if (!emitType3Font(it.first, it.second, aFontIDToObject))
2596
0
            return false;
2597
0
    }
2598
2599
8.85k
    OStringBuffer aFontDict( PDFWRITER_IMPL_BUFFERSIZE );
2600
8.85k
    aFontDict.append( OString::number(getFontDictObject())
2601
8.85k
        + " 0 obj\n"
2602
8.85k
          "<<" );
2603
8.85k
    int ni = 0;
2604
8.85k
    for (auto const& itemMap : aFontIDToObject)
2605
16.3k
    {
2606
16.3k
        aFontDict.append( "/F"
2607
16.3k
            + OString::number( itemMap.first )
2608
16.3k
            + " "
2609
16.3k
            + OString::number( itemMap.second )
2610
16.3k
            + " 0 R" );
2611
16.3k
        if( ((++ni) & 7) == 0 )
2612
36
            aFontDict.append( '\n' );
2613
16.3k
    }
2614
    // emit builtin font for widget appearances / variable text
2615
8.85k
    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
8.85k
    appendBuildinFontsToDict(aFontDict);
2622
8.85k
    aFontDict.append( "\n>>\nendobj\n\n" );
2623
2624
8.85k
    if ( !updateObject( getFontDictObject() ) ) return false;
2625
8.85k
    if ( !writeBuffer( aFontDict ) ) return false;
2626
8.85k
    return true;
2627
8.85k
}
2628
2629
sal_Int32 PDFWriterImpl::emitResources()
2630
8.85k
{
2631
    // emit shadings
2632
8.85k
    if (!m_aGradients.empty())
2633
0
    {
2634
0
        if (!emitGradients()) return 0;
2635
0
    }
2636
    // emit tilings
2637
8.85k
    if (!m_aTilings.empty())
2638
0
    {
2639
0
        if(!emitTilings()) return 0;
2640
0
    }
2641
2642
    // emit font dict
2643
8.85k
    if (!emitFonts()) return 0;
2644
2645
    // emit Resource dict
2646
8.85k
    OStringBuffer aLine( 512 );
2647
8.85k
    sal_Int32 nResourceDict = getResourceDictObj();
2648
8.85k
    if (!updateObject(nResourceDict))
2649
0
        return 0;
2650
8.85k
    aLine.setLength( 0 );
2651
8.85k
    aLine.append( OString::number(nResourceDict)
2652
8.85k
        + " 0 obj\n" );
2653
8.85k
    m_aGlobalResourceDict.append(aLine, getFontDictObject(), m_aContext.Version);
2654
8.85k
    aLine.append( "endobj\n\n" );
2655
8.85k
    if (!writeBuffer(aLine)) return 0;
2656
8.85k
    return nResourceDict;
2657
8.85k
}
2658
2659
sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
2660
                                                 sal_Int32 nItemLevel,
2661
                                                 sal_Int32 nCurrentItemId )
2662
1.24k
{
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.24k
    sal_Int32 nCount = 0;
2671
2672
1.24k
    if( m_aContext.OpenBookmarkLevels < 0           || // all levels are visible
2673
0
        m_aContext.OpenBookmarkLevels >= nItemLevel    // this level is visible
2674
1.24k
      )
2675
1.24k
    {
2676
1.24k
        PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
2677
1.24k
        sal_Int32 nChildren = rItem.m_aChildren.size();
2678
1.96k
        for( sal_Int32 i = 0; i < nChildren; i++ )
2679
723
            nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
2680
1.24k
        rCounts[nCurrentItemId] = nCount;
2681
        // return 1 (this item) + visible sub items
2682
1.24k
        if( nCount < 0 )
2683
0
            nCount = 0;
2684
1.24k
        nCount++;
2685
1.24k
    }
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.24k
    return nCount;
2698
1.24k
}
2699
2700
sal_Int32 PDFWriterImpl::emitOutline()
2701
8.85k
{
2702
8.85k
    int i, nItems = m_aOutline.size();
2703
2704
    // do we have an outline at all ?
2705
8.85k
    if( nItems < 2 )
2706
8.33k
        return 0;
2707
2708
    // reserve object numbers for all outline items
2709
1.76k
    for( i = 0; i < nItems; ++i )
2710
1.24k
        m_aOutline[i].m_nObject = createObject();
2711
2712
    // update all parent, next and prev object ids
2713
1.76k
    for( i = 0; i < nItems; ++i )
2714
1.24k
    {
2715
1.24k
        PDFOutlineEntry& rItem = m_aOutline[i];
2716
1.24k
        int nChildren = rItem.m_aChildren.size();
2717
2718
1.24k
        if( nChildren )
2719
525
        {
2720
1.24k
            for( int n = 0; n < nChildren; ++n )
2721
723
            {
2722
723
                PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
2723
2724
723
                rChild.m_nParentObject = rItem.m_nObject;
2725
723
                rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
2726
723
                rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
2727
723
            }
2728
2729
525
        }
2730
1.24k
    }
2731
2732
    // calculate Count entries for all items
2733
523
    std::vector< sal_Int32 > aCounts( nItems );
2734
523
    updateOutlineItemCount( aCounts, 0, 0 );
2735
2736
    // emit hierarchy
2737
1.76k
    for( i = 0; i < nItems; ++i )
2738
1.24k
    {
2739
1.24k
        PDFOutlineEntry& rItem = m_aOutline[i];
2740
1.24k
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
2741
1.24k
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
2742
2743
1.24k
        if (!updateObject(rItem.m_nObject))
2744
0
            return 0;
2745
1.24k
        aLine.append( OString::number(rItem.m_nObject)
2746
1.24k
            + " 0 obj\n"
2747
1.24k
              "<<" );
2748
        // number of visible children (all levels)
2749
1.24k
        if( i > 0 || aCounts[0] > 0 )
2750
1.24k
        {
2751
1.24k
            aLine.append( "/Count " + OString::number( aCounts[i] ) );
2752
1.24k
        }
2753
1.24k
        if( ! rItem.m_aChildren.empty() )
2754
525
        {
2755
            // children list: First, Last
2756
525
            aLine.append( "/First "
2757
525
                + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
2758
525
                + " 0 R/Last "
2759
525
                + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
2760
525
                + " 0 R\n" );
2761
525
        }
2762
1.24k
        if( i > 0 )
2763
723
        {
2764
            // Title, Dest, Parent, Prev, Next
2765
723
            aLine.append( "/Title" );
2766
723
            aWriter.writeUnicodeEncrypt(rItem.m_aTitle, rItem.m_nObject);
2767
723
            aLine.append( "\n" );
2768
            // Dest is not required
2769
723
            if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
2770
723
            {
2771
723
                appendDestOrGoTo(rItem.m_nDestID, aLine);
2772
723
            }
2773
723
            aLine.append( "/Parent "
2774
723
                + OString::number( rItem.m_nParentObject )
2775
723
                + " 0 R" );
2776
723
            if( rItem.m_nPrevObject )
2777
198
            {
2778
198
                aLine.append( "/Prev "
2779
198
                    + OString::number( rItem.m_nPrevObject )
2780
198
                    + " 0 R" );
2781
198
            }
2782
723
            if( rItem.m_nNextObject )
2783
198
            {
2784
198
                aLine.append( "/Next "
2785
198
                    + OString::number( rItem.m_nNextObject )
2786
198
                    + " 0 R" );
2787
198
            }
2788
723
        }
2789
1.24k
        aLine.append( ">>\nendobj\n\n" );
2790
1.24k
        if (!writeBuffer(aLine))
2791
0
            return 0;
2792
1.24k
    }
2793
2794
523
    return m_aOutline[0].m_nObject;
2795
523
}
2796
2797
bool PDFWriterImpl::appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer)
2798
3.54k
{
2799
3.54k
    if (nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size())
2800
0
        return false;
2801
2802
3.54k
    const PDFDest& rDest = m_aDests[nDestID];
2803
3.54k
    if (rDest.m_nStructElement < 0
2804
0
        || o3tl::make_unsigned(rDest.m_nStructElement) >= m_aStructure.size())
2805
3.54k
        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.54k
{
2824
3.54k
    OStringBuffer aStructure;
2825
3.54k
    if (!appendStructureDest(nDestID, aStructure))
2826
3.54k
    {
2827
3.54k
        rBuffer.append("/Dest");
2828
3.54k
        appendDest(nDestID, rBuffer);
2829
3.54k
        return;
2830
3.54k
    }
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.54k
{
2840
3.54k
    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.54k
    const PDFDest& rDest        = m_aDests[ nDestID ];
2847
3.54k
    const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
2848
2849
3.54k
    rBuffer.append( '[' );
2850
3.54k
    rBuffer.append( rDestPage.m_nPageObject );
2851
3.54k
    rBuffer.append( " 0 R" );
2852
2853
3.54k
    switch( rDest.m_eType )
2854
3.54k
    {
2855
3.54k
        case PDFWriter::DestAreaType::XYZ:
2856
3.54k
        default:
2857
3.54k
            rBuffer.append( "/XYZ " );
2858
3.54k
            appendFixedInt( rDest.m_aRect.Left(), rBuffer );
2859
3.54k
            rBuffer.append( ' ' );
2860
3.54k
            appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
2861
3.54k
            rBuffer.append( " 0" );
2862
3.54k
            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.54k
    }
2874
3.54k
    rBuffer.append( ']' );
2875
2876
3.54k
    return true;
2877
3.54k
}
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
8.85k
{
2914
8.85k
    int nAnnots = m_aScreens.size();
2915
8.85k
    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 /F (<embedded file>) ");
2981
0
            if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
2982
0
            {   // ISO 14289-1:2014, Clause: 7.11
2983
0
                aLine.append("/UF (<embedded file>) ");
2984
0
            }
2985
0
            aLine.append("/EF << /F "
2986
0
                + OString::number(rScreen.m_nTempFileObject)
2987
0
                + " 0 R >>");
2988
0
        }
2989
0
        else
2990
0
        {
2991
            // Linked.
2992
0
            aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
2993
0
            aWriter.writeLiteralEncrypt(rScreen.m_aURL, rScreen.m_nObject, osl_getThreadTextEncoding());
2994
0
            if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
2995
0
            {   // ISO 14289-1:2014, Clause: 7.11
2996
0
                aLine.append("/UF ");
2997
0
                aWriter.writeUnicodeEncrypt(rScreen.m_aURL, rScreen.m_nObject);
2998
0
            }
2999
0
        }
3000
0
        if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
3001
0
            && !rScreen.m_AltText.isEmpty())
3002
0
        {   // ISO 14289-1:2014, Clause: 7.11
3003
0
            aLine.append("/Desc ");
3004
0
            aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3005
0
        }
3006
0
        aLine.append(" >>\n"); // end of /D
3007
        // Allow playing the video via a tempfile.
3008
0
        aLine.append("/P <</TF (TEMPACCESS)>>");
3009
        // ISO 14289-1:2014, Clause: 7.18.6.2
3010
0
        aLine.append("/CT ");
3011
0
        aWriter.writeLiteralEncrypt(rScreen.m_MimeType, rScreen.m_nObject);
3012
        // ISO 14289-1:2014, Clause: 7.18.6.2
3013
        // Alt text is a "Multi-language Text Array"
3014
0
        aLine.append(" /Alt [ () ");
3015
0
        aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3016
0
        aLine.append(" ] "
3017
0
                     ">>");
3018
3019
        // End Rendition dictionary by requesting play/pause/stop controls.
3020
0
        aLine.append("/P<</BE<</C true >>>>"
3021
0
                     ">>");
3022
3023
        // End Action dictionary.
3024
0
        aLine.append("/OP 0 >>");
3025
3026
0
        if (-1 != rScreen.m_nStructParent)
3027
0
        {
3028
0
            aLine.append("\n/StructParent "
3029
0
                + OString::number(rScreen.m_nStructParent)
3030
0
                + "\n");
3031
0
        }
3032
3033
        // End Annot dictionary.
3034
0
        aLine.append("/P "
3035
0
            + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
3036
0
            + " 0 R\n>>\nendobj\n\n");
3037
0
        if (!writeBuffer(aLine))
3038
0
            return false;
3039
0
    }
3040
3041
8.85k
    return true;
3042
8.85k
}
3043
3044
bool PDFWriterImpl::emitLinkAnnotations()
3045
8.85k
{
3046
8.85k
    MARK("PDFWriterImpl::emitLinkAnnotations");
3047
8.85k
    int nAnnots = m_aLinks.size();
3048
12.2k
    for( int i = 0; i < nAnnots; i++ )
3049
3.34k
    {
3050
3.34k
        const PDFLink& rLink            = m_aLinks[i];
3051
3.34k
        if( ! updateObject( rLink.m_nObject ) )
3052
0
            continue;
3053
3.34k
        if( m_aContext.DefaultLinkAction == PDFWriter::RemoveExternalLinks && rLink.m_nDest < 0 )
3054
0
            continue;
3055
3056
3.34k
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
3057
3.34k
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3058
3.34k
        aLine.append( rLink.m_nObject );
3059
3.34k
        aLine.append( " 0 obj\n" );
3060
// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3061
// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3062
3.34k
        aLine.append( "<</Type/Annot" );
3063
3.34k
        if (m_nPDFA_Version > 0)
3064
0
            aLine.append( "/F 4" );
3065
3.34k
        aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3066
3067
3.34k
        appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3068
3.34k
        aLine.append( ' ' );
3069
3.34k
        appendFixedInt( rLink.m_aRect.Top(), aLine );
3070
3.34k
        aLine.append( ' ' );
3071
3.34k
        appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3072
3.34k
        aLine.append( ' ' );
3073
3.34k
        appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3074
3.34k
        aLine.append( "]" );
3075
        // ISO 14289-1:2014, Clause: 7.18.5
3076
3.34k
        OUString const& rContents(rLink.m_AltText.isEmpty() && rLink.m_nDest < 0 ? rLink.m_aURL
3077
3.34k
                                                                                 : rLink.m_AltText);
3078
3.34k
        if (!rContents.isEmpty())
3079
3.26k
        {
3080
3.26k
            aLine.append("/Contents");
3081
3.26k
            aWriter.writeUnicodeEncrypt(rContents, rLink.m_nObject);
3082
3.26k
        }
3083
3.34k
        if( rLink.m_nDest >= 0 )
3084
2.82k
        {
3085
2.82k
            appendDestOrGoTo(rLink.m_nDest, aLine);
3086
2.82k
        }
3087
524
        else
3088
524
        {
3089
/*
3090
destination is external to the document, so
3091
we check in the following sequence:
3092
3093
 if target type is neither .pdf, nor .od[tpgs], then
3094
          check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3095
                             end processing
3096
 else if target is .od[tpgs]: then
3097
      if conversion of type from od[tpgs]  to pdf is requested, convert it and this becomes the new target file
3098
      processing continue
3099
3100
 if (new)target is .pdf : then
3101
     if GotToR is requested, then
3102
           convert the target in GoToR where the fragment of the URI is
3103
           considered the named destination in the target file, set relative or absolute as requested
3104
     else strip the fragment from URL and then set URI or 'launch application' as requested
3105
*/
3106
3107
// FIXME: check if the decode mechanisms for URL processing throughout this implementation
3108
// are the correct one!!
3109
3110
// extract target file type
3111
524
            auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3112
3113
524
            INetURLObject aDocumentURL( m_aContext.BaseURL );
3114
524
            INetURLObject aTargetURL( url );
3115
524
            bool bSetGoToRMode = false;
3116
524
            bool    bTargetHasPDFExtension = false;
3117
524
            INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3118
524
            bool    bIsUNCPath = false;
3119
524
            bool    bUnparsedURI = false;
3120
3121
            // check if the protocol is a known one, or if there is no protocol at all (on target only)
3122
            // if there is no protocol, make the target relative to the current document directory
3123
            // getting the needed URL information from the current document path
3124
524
            if( eTargetProtocol == INetProtocol::NotValid )
3125
100
            {
3126
100
                if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3127
0
                {
3128
0
                    bIsUNCPath = true;
3129
0
                }
3130
100
                else
3131
100
                {
3132
                    //reassign the new target URL
3133
100
                    aTargetURL = INetURLObject(rtl::Uri::convertRelToAbs(
3134
100
                                                (m_aContext.BaseURL.getLength() > 0 ?
3135
0
                                                    m_aContext.BaseURL :
3136
                                                    //use dummy location if empty
3137
100
                                                    u"http://ahost.ax"_ustr),
3138
100
                                                url));
3139
3140
                    //recompute the target protocol, with the new URL
3141
                    //normal URL processing resumes
3142
100
                    eTargetProtocol = aTargetURL.GetProtocol();
3143
3144
100
                    bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
3145
100
                }
3146
100
            }
3147
3148
524
            OUString aFileExtension = aTargetURL.GetFileExtension();
3149
3150
            // Check if the URL ends in '/': if yes it's a directory,
3151
            // it will be forced to a URI link.
3152
            // possibly a malformed URI, leave it as it is, force as URI
3153
524
            if( aTargetURL.hasFinalSlash() )
3154
151
                m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3155
3156
524
            if( !aFileExtension.isEmpty() )
3157
120
            {
3158
120
                if( m_aContext.ConvertOOoTargetToPDFTarget )
3159
0
                {
3160
0
                    bool bChangeFileExtensionToPDF = false;
3161
                    //examine the file type (.odm .odt. .odp, odg, ods)
3162
0
                    if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3163
0
                        bChangeFileExtensionToPDF = true;
3164
0
                    if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3165
0
                        bChangeFileExtensionToPDF = true;
3166
0
                    else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3167
0
                        bChangeFileExtensionToPDF = true;
3168
0
                    else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3169
0
                        bChangeFileExtensionToPDF = true;
3170
0
                    else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3171
0
                        bChangeFileExtensionToPDF = true;
3172
0
                    if( bChangeFileExtensionToPDF )
3173
0
                        aTargetURL.setExtension(u"pdf" );
3174
0
                }
3175
                //check if extension is pdf, see if GoToR should be forced
3176
120
                bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3177
120
                if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3178
0
                    bSetGoToRMode = true;
3179
120
            }
3180
            //prepare the URL, if relative or not
3181
524
            INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3182
            //queue the string common to all types of actions
3183
524
            aLine.append( "/A<</Type/Action/S");
3184
524
            if( bIsUNCPath ) // handle Win UNC paths
3185
0
            {
3186
0
                aLine.append("/Launch");
3187
                // Entry /Win is deprecated in PDF 2.0
3188
0
                if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
3189
0
                {
3190
                    // So write /F directly. AFAICS it's up to PDF viewer to resolve this correctly
3191
0
                    aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3192
0
                }
3193
0
                else
3194
0
                {
3195
0
                    aLine.append("/Win");
3196
0
                    aWriter.startDict();
3197
                    // INetURLObject is not good with UNC paths, use original path
3198
0
                    aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3199
0
                    aWriter.endDict();
3200
0
                }
3201
0
            }
3202
524
            else
3203
524
            {
3204
524
                bool bSetRelative = false;
3205
524
                bool bFileSpec = false;
3206
                //check if relative file link is requested and if the protocol is 'file://'
3207
524
                if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3208
0
                    bSetRelative = true;
3209
3210
524
                OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3211
524
                if( !bSetGoToRMode )
3212
524
                {
3213
524
                    switch( m_aContext.DefaultLinkAction )
3214
524
                    {
3215
0
                    default:
3216
524
                    case PDFWriter::URIAction :
3217
524
                    case PDFWriter::URIActionDestination :
3218
524
                        aLine.append( "/URI/URI" );
3219
524
                        break;
3220
0
                    case PDFWriter::LaunchAction:
3221
                        // now:
3222
                        // if a launch action is requested and the hyperlink target has a fragment
3223
                        // and the target file does not have a pdf extension, or it's not a 'file:://'
3224
                        // protocol then force the uri action on it
3225
                        // This code will permit the correct opening of application on web pages,
3226
                        // the one that normally have fragments (but I may be wrong...)
3227
                        // and will force the use of URI when the protocol is not file:
3228
0
                        if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3229
0
                                        eTargetProtocol != INetProtocol::File )
3230
0
                        {
3231
0
                            aLine.append( "/URI/URI" );
3232
0
                        }
3233
0
                        else
3234
0
                        {
3235
0
                            aLine.append( "/Launch/F" );
3236
0
                            bFileSpec = true;
3237
0
                        }
3238
0
                        break;
3239
524
                    }
3240
524
                }
3241
3242
                //fragment are encoded in the same way as in the named destination processing
3243
524
                if( bSetGoToRMode )
3244
0
                {
3245
                    //add the fragment
3246
0
                    OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3247
0
                    aLine.append("/GoToR");
3248
0
                    aLine.append("/F");
3249
0
                    aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3250
0
                                                                                         INetURLObject::EncodeMechanism::WasEncoded,
3251
0
                                                                                         INetURLObject::DecodeMechanism::WithCharset ) :
3252
0
                                                                   aURLNoMark, rLink.m_nObject, osl_getThreadTextEncoding());
3253
0
                    if( !aFragment.isEmpty() )
3254
0
                    {
3255
0
                        aLine.append("/D/");
3256
0
                        appendDestinationName( aFragment , aLine );
3257
0
                    }
3258
0
                }
3259
524
                else
3260
524
                {
3261
                    // change the fragment to accommodate the bookmark (only if the file extension
3262
                    // is PDF and the requested action is of the correct type)
3263
524
                    if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3264
0
                               bTargetHasPDFExtension && !aFragment.isEmpty() )
3265
0
                    {
3266
0
                        OStringBuffer aLineLoc( PDFWRITER_IMPL_BUFFERSIZE );
3267
0
                        appendDestinationName( aFragment , aLineLoc );
3268
                        //substitute the fragment
3269
0
                        aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
3270
0
                    }
3271
524
                    OUString aURL = bUnparsedURI ? url :
3272
524
                                                   aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
3273
431
                                                                                      INetURLObject::DecodeMechanism::NONE );
3274
524
                    aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3275
0
                                                                                        INetURLObject::EncodeMechanism::WasEncoded,
3276
0
                                                                                            bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3277
0
                                                                                            ) :
3278
524
                                                                               aURL , rLink.m_nObject, osl_getThreadTextEncoding() );
3279
524
                }
3280
524
            }
3281
524
            aLine.append( ">>\n" );
3282
524
        }
3283
3.34k
        if (rLink.m_nStructParent != -1)
3284
0
        {
3285
0
            aLine.append( "/StructParent " );
3286
0
            aLine.append( rLink.m_nStructParent );
3287
0
        }
3288
3.34k
        aLine.append( ">>\nendobj\n\n" );
3289
3.34k
        if (!writeBuffer(aLine))
3290
0
            return false;
3291
3.34k
    }
3292
3293
8.85k
    return true;
3294
8.85k
}
3295
3296
namespace
3297
{
3298
3299
void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3300
388
{
3301
388
    aLine.append("/Rect [");
3302
388
    appendFixedInt(rRectangle.Left(), aLine);
3303
388
    aLine.append(' ');
3304
388
    appendFixedInt(rRectangle.Top(), aLine);
3305
388
    aLine.append(' ');
3306
388
    appendFixedInt(rRectangle.Right(), aLine);
3307
388
    aLine.append(' ');
3308
388
    appendFixedInt(rRectangle.Bottom(), aLine);
3309
388
    aLine.append("] ");
3310
388
}
3311
3312
void appendAnnotationColor(Color const& rColor, OStringBuffer & aLine)
3313
0
{
3314
0
    aLine.append("/C [");
3315
0
    appendColor(rColor, aLine, false);
3316
0
    aLine.append("] ");
3317
0
}
3318
3319
void appendAnnotationInteriorColor(Color const& rColor, OStringBuffer & aLine)
3320
0
{
3321
0
    aLine.append("/IC [");
3322
0
    appendColor(rColor, aLine, false);
3323
0
    aLine.append("] ");
3324
0
}
3325
3326
void appendPolygon(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3327
0
{
3328
0
    for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
3329
0
    {
3330
0
        appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
3331
0
        aLine.append(" ");
3332
0
        appendDouble(fPageHeight - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), aLine, nLog10Divisor);
3333
0
        aLine.append(" ");
3334
0
    }
3335
0
}
3336
3337
void appendVertices(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3338
0
{
3339
0
    aLine.append("/Vertices [");
3340
0
    appendPolygon(rPolygon, fPageHeight, aLine);
3341
0
    aLine.append("] ");
3342
3343
0
}
3344
3345
void appendAnnotationBorder(float fBorderWidth, OStringBuffer & aLine)
3346
0
{
3347
0
    aLine.append("/Border [0 0 ");
3348
0
    appendDouble(convertMm100ToPoint(fBorderWidth), aLine, nLog10Divisor);
3349
0
    aLine.append("] ");
3350
0
}
3351
3352
} // end anonymous namespace
3353
3354
void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3355
388
{
3356
388
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3357
3358
388
    appendObjectID(rNote.m_nObject, aLine);
3359
3360
388
    double fPageHeight = m_aPages[rNote.m_nPage].getHeight();
3361
3362
388
    aLine.append("<</Type /Annot ");
3363
3364
388
    appendAnnotationRect(rNote.m_aRect, aLine);
3365
3366
388
    aLine.append("/Subtype ");
3367
3368
388
    if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon ||
3369
388
        rNote.m_aContents.meType == PDFAnnotationSubType::Polyline)
3370
0
    {
3371
0
        auto const& rPolygon = rNote.m_aContents.maPolygons[0];
3372
0
        if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon)
3373
0
            aLine.append("/Polygon ");
3374
0
        else
3375
0
            aLine.append("/Polyline ");
3376
3377
0
        appendVertices(rPolygon, fPageHeight, aLine);
3378
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3379
3380
0
        if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon)
3381
0
            appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3382
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3383
0
    }
3384
388
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::Square)
3385
0
    {
3386
0
        aLine.append("/Square ");
3387
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3388
0
        appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3389
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3390
0
    }
3391
388
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::Circle)
3392
0
    {
3393
0
        aLine.append("/Circle ");
3394
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3395
0
        appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3396
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3397
0
    }
3398
388
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::Ink)
3399
0
    {
3400
0
        aLine.append("/Ink ");
3401
3402
0
        aLine.append("/InkList [");
3403
0
        for (auto const& rPolygon : rNote.m_aContents.maPolygons)
3404
0
        {
3405
0
            aLine.append("[");
3406
0
            appendPolygon(rPolygon, fPageHeight, aLine);
3407
0
            aLine.append("]");
3408
0
        }
3409
0
        aLine.append("] ");
3410
3411
0
        appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3412
0
        appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3413
3414
0
    }
3415
388
    else if (rNote.m_aContents.meType == PDFAnnotationSubType::FreeText)
3416
0
    {
3417
0
        aLine.append("/FreeText ");
3418
0
    }
3419
388
    else
3420
388
    {
3421
388
        aLine.append("/Text ");
3422
388
    }
3423
3424
    // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3425
    // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3426
388
    if (m_nPDFA_Version > 0)
3427
0
        aLine.append("/F 4 ");
3428
3429
388
    aLine.append("/Popup ");
3430
388
    appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3431
3432
388
    auto & rDateTime = rNote.m_aContents.maModificationDate;
3433
3434
388
    aLine.append("/M (");
3435
388
    appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
3436
388
    aLine.append(") ");
3437
3438
    // contents of the note (type text string)
3439
388
    aLine.append("/Contents ");
3440
388
    aWriter.writeUnicodeEncrypt(rNote.m_aContents.maContents, rNote.m_nObject);
3441
388
    aLine.append("\n");
3442
3443
    // optional title
3444
388
    if (!rNote.m_aContents.maTitle.isEmpty())
3445
388
    {
3446
388
        aLine.append("/T ");
3447
388
        aWriter.writeUnicodeEncrypt(rNote.m_aContents.maTitle, rNote.m_nObject);
3448
388
        aLine.append("\n");
3449
388
    }
3450
3451
388
    if (-1 != rNote.m_nStructParent)
3452
0
    {
3453
0
        aLine.append("/StructParent ");
3454
0
        aLine.append(rNote.m_nStructParent);
3455
0
        aLine.append("\n");
3456
0
    }
3457
3458
388
    aLine.append(">>\n");
3459
388
    aLine.append("endobj\n\n");
3460
388
}
3461
3462
void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
3463
388
{
3464
388
    appendObjectID(rPopUp.m_nObject, aLine);
3465
388
    aLine.append("<</Type /Annot /Subtype /Popup ");
3466
388
    aLine.append("/Rect[");
3467
388
    appendFixedInt(rPopUp.m_aRect.Left(), aLine);
3468
388
    aLine.append(' ');
3469
388
    appendFixedInt(rPopUp.m_aRect.Top(), aLine);
3470
388
    aLine.append(' ');
3471
388
    appendFixedInt(rPopUp.m_aRect.Right(), aLine);
3472
388
    aLine.append(' ');
3473
388
    appendFixedInt(rPopUp.m_aRect.Bottom(), aLine);
3474
388
    aLine.append("]");
3475
388
    aLine.append("/Parent ");
3476
388
    appendObjectReference(rPopUp.m_nParentObject, aLine);
3477
388
    aLine.append(">>\n");
3478
388
    aLine.append("endobj\n\n");
3479
388
}
3480
3481
bool PDFWriterImpl::emitNoteAnnotations()
3482
8.85k
{
3483
    // emit note annotations
3484
8.85k
    int nAnnots = m_aNotes.size();
3485
9.24k
    for( int i = 0; i < nAnnots; i++ )
3486
388
    {
3487
388
        const PDFNoteEntry& rNote = m_aNotes[i];
3488
388
        const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
3489
3490
388
        {
3491
388
            if (!updateObject(rNote.m_nObject))
3492
0
                return false;
3493
3494
388
            OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
3495
3496
388
            emitTextAnnotationLine(aLine, rNote);
3497
3498
388
            if (!writeBuffer(aLine))
3499
0
                return false;
3500
388
        }
3501
3502
388
        {
3503
3504
388
            if (!updateObject(rPopUp.m_nObject))
3505
0
                return false;
3506
3507
388
            OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
3508
3509
388
            emitPopupAnnotationLine(aLine, rPopUp);
3510
3511
388
            if (!writeBuffer(aLine))
3512
0
                return false;
3513
388
        }
3514
388
    }
3515
8.85k
    return true;
3516
8.85k
}
3517
3518
Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font&  rAppSetFont )
3519
0
{
3520
0
    bool bAdjustSize = false;
3521
3522
0
    Font aFont( rControlFont );
3523
0
    if( aFont.GetFamilyName().isEmpty() )
3524
0
    {
3525
0
        aFont = rAppSetFont;
3526
0
        if( rControlFont.GetFontHeight() )
3527
0
            aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
3528
0
        else
3529
0
            bAdjustSize = true;
3530
0
        if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
3531
0
            aFont.SetItalic( rControlFont.GetItalic() );
3532
0
        if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
3533
0
            aFont.SetWeight( rControlFont.GetWeight() );
3534
0
    }
3535
0
    else if( ! aFont.GetFontHeight() )
3536
0
    {
3537
0
        aFont.SetFontSize( rAppSetFont.GetFontSize() );
3538
0
        bAdjustSize = true;
3539
0
    }
3540
0
    if( bAdjustSize )
3541
0
    {
3542
0
        Size aFontSize = aFont.GetFontSize();
3543
0
        OutputDevice* pDefDev = Application::GetDefaultDevice();
3544
0
        aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
3545
0
        aFont.SetFontSize( aFontSize );
3546
0
    }
3547
0
    return aFont;
3548
0
}
3549
3550
sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
3551
0
{
3552
0
    sal_Int32 nBest = 4; // default to Helvetica
3553
3554
0
    if (rFont.GetFamilyType() == FAMILY_ROMAN)
3555
0
    {
3556
        // Serif: default to Times-Roman.
3557
0
        nBest = 8;
3558
0
    }
3559
3560
0
    OUString aFontName( rFont.GetFamilyName() );
3561
0
    aFontName = aFontName.toAsciiLowerCase();
3562
3563
0
    if( aFontName.indexOf( "times" ) != -1 )
3564
0
        nBest = 8;
3565
0
    else if( aFontName.indexOf( "courier" ) != -1 )
3566
0
        nBest = 0;
3567
0
    else if( aFontName.indexOf( "dingbats" ) != -1 )
3568
0
        nBest = 13;
3569
0
    else if( aFontName.indexOf( "symbol" ) != -1 )
3570
0
        nBest = 12;
3571
0
    if( nBest < 12 )
3572
0
    {
3573
0
        if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
3574
0
            nBest += 1;
3575
0
        if( rFont.GetWeight() > WEIGHT_MEDIUM )
3576
0
            nBest += 2;
3577
0
    }
3578
3579
0
    if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
3580
0
        m_aBuildinFontToObjectMap[ nBest ] = createObject();
3581
3582
0
    return nBest;
3583
0
}
3584
3585
static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
3586
0
{
3587
0
    return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
3588
0
}
3589
3590
void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
3591
0
{
3592
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3593
3594
    // save graphics state
3595
0
    push( PushFlags::ALL );
3596
3597
    // transform relative to control's coordinates since an
3598
    // appearance stream is a form XObject
3599
    // this relies on the m_aRect member of rButton NOT already being transformed
3600
    // to default user space
3601
0
    if( rWidget.Background || rWidget.Border )
3602
0
    {
3603
0
        setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
3604
0
        setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
3605
0
        drawRectangle( rWidget.Location );
3606
0
    }
3607
    // prepare font to use
3608
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
3609
0
    setFont( aFont );
3610
0
    setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
3611
3612
0
    drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
3613
3614
    // create DA string while local mapmode is still in place
3615
    // (that is before endRedirect())
3616
0
    OStringBuffer aDA( 256 );
3617
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
3618
0
    Font aDummyFont( u"Helvetica"_ustr, aFont.GetFontSize() );
3619
0
    sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
3620
0
    aDA.append( ' ' );
3621
0
    aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
3622
0
    aDA.append( ' ' );
3623
0
    m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3624
0
    aDA.append( " Tf" );
3625
0
    rButton.m_aDAString = aDA.makeStringAndClear();
3626
3627
0
    pop();
3628
3629
0
    rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
3630
3631
    /* seems like a bad hack but at least works in both AR5 and 6:
3632
       we draw the button ourselves and tell AR
3633
       the button would be totally transparent with no text
3634
3635
       One would expect that simply setting a normal appearance
3636
       should suffice, but no, as soon as the user actually presses
3637
       the button and an action is tied to it (gasp! a button that
3638
       does something) the appearance gets replaced by some crap that AR
3639
       creates on the fly even if no DA or MK is given. On AR6 at least
3640
       the DA and MK work as expected, but on AR5 this creates a region
3641
       filled with the background color but nor text. Urgh.
3642
    */
3643
0
    rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr;
3644
0
    rButton.m_aMKDictCAString = ""_ostr;
3645
0
}
3646
3647
Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
3648
                                     const PDFWriter::AnyWidget& rWidget,
3649
                                     const StyleSettings& rSettings )
3650
0
{
3651
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
3652
3653
0
    if( rWidget.Background || rWidget.Border )
3654
0
    {
3655
0
        if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
3656
0
        {
3657
0
            sal_Int32 nDelta = GetDPIX() / 500;
3658
0
            if( nDelta < 1 )
3659
0
                nDelta = 1;
3660
0
            setLineColor( COL_TRANSPARENT );
3661
0
            tools::Rectangle aRect = rIntern.m_aRect;
3662
0
            setFillColor( rSettings.GetLightBorderColor() );
3663
0
            drawRectangle( aRect );
3664
0
            aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
3665
0
            aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
3666
0
            setFillColor( rSettings.GetFieldColor() );
3667
0
            drawRectangle( aRect );
3668
0
            setFillColor( rSettings.GetLightColor() );
3669
0
            drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
3670
0
            drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
3671
0
            setFillColor( rSettings.GetDarkShadowColor() );
3672
0
            drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
3673
0
            drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
3674
0
        }
3675
0
        else
3676
0
        {
3677
0
            setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
3678
0
            setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3679
0
            drawRectangle( rIntern.m_aRect );
3680
0
        }
3681
3682
0
        if( rWidget.Border )
3683
0
        {
3684
            // adjust edit area accounting for border
3685
0
            sal_Int32 nDelta = aFont.GetFontHeight()/4;
3686
0
            if( nDelta < 1 )
3687
0
                nDelta = 1;
3688
0
            rIntern.m_aRect.AdjustLeft(nDelta );
3689
0
            rIntern.m_aRect.AdjustTop(nDelta );
3690
0
            rIntern.m_aRect.AdjustRight( -nDelta );
3691
0
            rIntern.m_aRect.AdjustBottom( -nDelta );
3692
0
        }
3693
0
    }
3694
0
    return aFont;
3695
0
}
3696
3697
void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
3698
0
{
3699
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3700
0
    SvMemoryStream* pEditStream = new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE );
3701
3702
0
    push( PushFlags::ALL );
3703
3704
    // prepare font to use, draw field border
3705
0
    Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
3706
    // Get the built-in font which is closest to aFont.
3707
0
    sal_Int32 nBest = getBestBuildinFont(aFont);
3708
3709
    // prepare DA string
3710
0
    OStringBuffer aDA( 32 );
3711
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3712
0
    aDA.append( ' ' );
3713
0
    aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
3714
3715
0
    OStringBuffer aDR( 32 );
3716
0
    aDR.append( "/Font " );
3717
0
    aDR.append( getFontDictObject() );
3718
0
    aDR.append( " 0 R" );
3719
0
    rEdit.m_aDRDict = aDR.makeStringAndClear();
3720
0
    aDA.append( ' ' );
3721
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3722
0
    aDA.append( " Tf" );
3723
3724
    /*  create an empty appearance stream, let the viewer create
3725
        the appearance at runtime. This is because AR5 seems to
3726
        paint the widget appearance always, and a dynamically created
3727
        appearance on top of it. AR6 is well behaved in that regard, so
3728
        that behaviour seems to be a bug. Anyway this empty appearance
3729
        relies on /NeedAppearances in the AcroForm dictionary set to "true"
3730
     */
3731
0
    beginRedirect( pEditStream, rEdit.m_aRect );
3732
0
    writeBuffer( "/Tx BMC\nEMC\n" );
3733
3734
0
    endRedirect();
3735
0
    pop();
3736
3737
0
    rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream;
3738
3739
0
    rEdit.m_aDAString = aDA.makeStringAndClear();
3740
0
}
3741
3742
void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
3743
0
{
3744
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3745
0
    SvMemoryStream* pListBoxStream = new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE );
3746
3747
0
    push( PushFlags::ALL );
3748
3749
    // prepare font to use, draw field border
3750
0
    Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
3751
0
    sal_Int32 nBest = getSystemFont( aFont );
3752
3753
0
    setLineColor( COL_TRANSPARENT );
3754
0
    setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
3755
0
    drawRectangle( rBox.m_aRect );
3756
3757
0
    pop();
3758
3759
    // prepare DA string
3760
0
    OStringBuffer aDA( 256 );
3761
    // prepare DA string
3762
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3763
0
    aDA.append( ' ' );
3764
0
    aDA.append( "/F" );
3765
0
    aDA.append( nBest );
3766
3767
0
    OStringBuffer aDR( 32 );
3768
0
    aDR.append( "/Font " );
3769
0
    aDR.append( getFontDictObject() );
3770
0
    aDR.append( " 0 R" );
3771
0
    rBox.m_aDRDict = aDR.makeStringAndClear();
3772
0
    aDA.append( ' ' );
3773
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3774
0
    aDA.append( " Tf" );
3775
3776
0
    beginRedirect(pListBoxStream, rBox.m_aRect);
3777
    // empty appearance, see createDefaultEditAppearance for reference
3778
0
    writeBuffer("/Tx BMC\nEMC\n");
3779
0
    endRedirect();
3780
3781
0
    rBox.m_aAppearances["N"_ostr]["Standard"_ostr] = pListBoxStream;
3782
0
    rBox.m_aDAString = aDA.makeStringAndClear();
3783
0
}
3784
3785
void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
3786
0
{
3787
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3788
3789
    // save graphics state
3790
0
    push( PushFlags::ALL );
3791
3792
0
    if( rWidget.Background || rWidget.Border )
3793
0
    {
3794
0
        setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3795
0
        setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3796
0
        drawRectangle( rBox.m_aRect );
3797
0
    }
3798
3799
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3800
0
    setFont( aFont );
3801
0
    Size aFontSize = aFont.GetFontSize();
3802
0
    if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3803
0
        aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3804
0
    sal_Int32 nDelta = aFontSize.Height()/10;
3805
0
    if( nDelta < 1 )
3806
0
        nDelta = 1;
3807
3808
0
    tools::Rectangle aCheckRect, aTextRect;
3809
0
    {
3810
0
        aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3811
0
        aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3812
0
        aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3813
0
        aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3814
3815
        // #i74206# handle small controls without text area
3816
0
        while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3817
0
        {
3818
0
            aCheckRect.AdjustRight( -nDelta );
3819
0
            aCheckRect.AdjustTop(nDelta/2 );
3820
0
            aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3821
0
        }
3822
3823
0
        aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3824
0
        aTextRect.SetTop( rBox.m_aRect.Top() );
3825
0
        aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3826
0
        aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3827
0
    }
3828
0
    setLineColor( COL_BLACK );
3829
0
    setFillColor( COL_TRANSPARENT );
3830
0
    OStringBuffer aLW( 32 );
3831
0
    aLW.append( "q " );
3832
0
    m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
3833
0
    aLW.append( " w " );
3834
0
    writeBuffer( aLW );
3835
0
    drawRectangle( aCheckRect );
3836
0
    writeBuffer( " Q\n" );
3837
0
    setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3838
0
    drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3839
3840
0
    pop();
3841
3842
0
    OStringBuffer aDA( 256 );
3843
3844
    // tdf#93853 don't rely on Zapf (or any other 'standard' font)
3845
    // being present, but our own OpenSymbol - N.B. PDF/A for good
3846
    // reasons require even the standard PS fonts to be embedded!
3847
0
    Push();
3848
0
    SetFont( Font( u"OpenSymbol"_ustr, aFont.GetFontSize() ) );
3849
0
    const LogicalFontInstance* pFontInstance = GetFontInstance();
3850
0
    const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
3851
0
    Pop();
3852
3853
    // make sure OpenSymbol is embedded, and includes our checkmark
3854
0
    const sal_Unicode cMark=0x2713;
3855
0
    const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
3856
0
    const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
3857
3858
0
    sal_uInt8 nMappedGlyph;
3859
0
    sal_Int32 nMappedFontObject;
3860
0
    registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject);
3861
3862
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3863
0
    aDA.append( ' ' );
3864
0
    aDA.append( "/F" );
3865
0
    aDA.append( nMappedFontObject );
3866
0
    aDA.append( " 0 Tf" );
3867
3868
0
    OStringBuffer aDR( 32 );
3869
0
    aDR.append( "/Font " );
3870
0
    aDR.append( getFontDictObject() );
3871
0
    aDR.append( " 0 R" );
3872
0
    rBox.m_aDRDict = aDR.makeStringAndClear();
3873
0
    rBox.m_aDAString = aDA.makeStringAndClear();
3874
0
    rBox.m_aMKDict = "/CA"_ostr;
3875
0
    rBox.m_aMKDictCAString = "8"_ostr;
3876
0
    rBox.m_aRect = aCheckRect;
3877
3878
    // create appearance streams
3879
0
    sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
3880
0
    nCharXOffset *= aCheckRect.GetHeight();
3881
0
    nCharXOffset /= 2000;
3882
0
    sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
3883
0
    nCharYOffset *= aCheckRect.GetHeight();
3884
0
    nCharYOffset /= 2000;
3885
3886
    // write 'checked' appearance stream
3887
0
    SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3888
0
    beginRedirect( pCheckStream, aCheckRect );
3889
0
    aDA.append( "/Tx BMC\nq BT\n" );
3890
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3891
0
    aDA.append( ' ' );
3892
0
    aDA.append( "/F" );
3893
0
    aDA.append( nMappedFontObject );
3894
0
    aDA.append( ' ' );
3895
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3896
0
    aDA.append( " Tf\n" );
3897
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
3898
0
    aDA.append( " " );
3899
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
3900
0
    aDA.append( " Td <" );
3901
0
    COSWriter::appendHex( nMappedGlyph, aDA );
3902
0
    aDA.append( "> Tj\nET\nQ\nEMC\n" );
3903
0
    writeBuffer( aDA );
3904
0
    endRedirect();
3905
0
    rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
3906
3907
    // write 'unchecked' appearance stream
3908
0
    SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
3909
0
    beginRedirect( pUncheckStream, aCheckRect );
3910
0
    writeBuffer( "/Tx BMC\nEMC\n" );
3911
0
    endRedirect();
3912
0
    rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
3913
0
}
3914
3915
void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
3916
0
{
3917
0
    const StyleSettings& rSettings = m_aWidgetStyleSettings;
3918
3919
    // save graphics state
3920
0
    push( PushFlags::ALL );
3921
3922
0
    if( rWidget.Background || rWidget.Border )
3923
0
    {
3924
0
        setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3925
0
        setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3926
0
        drawRectangle( rBox.m_aRect );
3927
0
    }
3928
3929
0
    Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3930
0
    setFont( aFont );
3931
0
    Size aFontSize = aFont.GetFontSize();
3932
0
    if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3933
0
        aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3934
0
    sal_Int32 nDelta = aFontSize.Height()/10;
3935
0
    if( nDelta < 1 )
3936
0
        nDelta = 1;
3937
3938
0
    tools::Rectangle aCheckRect, aTextRect;
3939
0
    {
3940
0
        aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3941
0
        aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3942
0
        aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3943
0
        aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3944
3945
        // #i74206# handle small controls without text area
3946
0
        while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3947
0
        {
3948
0
            aCheckRect.AdjustRight( -nDelta );
3949
0
            aCheckRect.AdjustTop(nDelta/2 );
3950
0
            aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3951
0
        }
3952
3953
0
        aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3954
0
        aTextRect.SetTop( rBox.m_aRect.Top() );
3955
0
        aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3956
0
        aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3957
0
    }
3958
0
    setLineColor( COL_BLACK );
3959
0
    setFillColor( COL_TRANSPARENT );
3960
0
    OStringBuffer aLW( 32 );
3961
0
    aLW.append( "q " );
3962
0
    m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
3963
0
    aLW.append( " w " );
3964
0
    writeBuffer( aLW );
3965
0
    drawEllipse( aCheckRect );
3966
0
    writeBuffer( " Q\n" );
3967
0
    setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3968
0
    drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3969
3970
0
    pop();
3971
3972
    //to encrypt this (el)
3973
0
    rBox.m_aMKDict = "/CA"_ostr;
3974
    //after this assignment, to m_aMKDic cannot be added anything
3975
0
    rBox.m_aMKDictCAString = "l"_ostr;
3976
3977
0
    rBox.m_aRect = aCheckRect;
3978
3979
    // create appearance streams
3980
0
    push( PushFlags::ALL);
3981
0
    SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3982
3983
0
    beginRedirect( pCheckStream, aCheckRect );
3984
0
    OStringBuffer aDA( 256 );
3985
0
    aDA.append( "/Tx BMC\nq BT\n" );
3986
0
    appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3987
0
    aDA.append( ' ' );
3988
0
    m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3989
0
    aDA.append( " 0 0 Td\nET\nQ\n" );
3990
0
    writeBuffer( aDA );
3991
0
    setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3992
0
    setLineColor( COL_TRANSPARENT );
3993
0
    aCheckRect.AdjustLeft(3*nDelta );
3994
0
    aCheckRect.AdjustTop(3*nDelta );
3995
0
    aCheckRect.AdjustBottom( -(3*nDelta) );
3996
0
    aCheckRect.AdjustRight( -(3*nDelta) );
3997
0
    drawEllipse( aCheckRect );
3998
0
    writeBuffer( "\nEMC\n" );
3999
0
    endRedirect();
4000
4001
0
    pop();
4002
0
    rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
4003
4004
0
    SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4005
0
    beginRedirect( pUncheckStream, aCheckRect );
4006
0
    writeBuffer( "/Tx BMC\nEMC\n" );
4007
0
    endRedirect();
4008
0
    rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
4009
0
}
4010
4011
bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4012
0
{
4013
    // TODO: check and insert default streams
4014
0
    OString aStandardAppearance;
4015
0
    switch( rWidget.m_eType )
4016
0
    {
4017
0
        case PDFWriter::CheckBox:
4018
0
            aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4019
0
            break;
4020
0
        default:
4021
0
            break;
4022
0
    }
4023
4024
0
    if( !rWidget.m_aAppearances.empty() )
4025
0
    {
4026
0
        rAnnotDict.append( "/AP<<\n" );
4027
0
        for (auto & dict_item : rWidget.m_aAppearances)
4028
0
        {
4029
0
            rAnnotDict.append( "/" );
4030
0
            rAnnotDict.append( dict_item.first );
4031
0
            bool bUseSubDict = (dict_item.second.size() > 1);
4032
4033
            // PDF/A requires sub-dicts for /FT/Btn objects (clause
4034
            // 6.3.3)
4035
0
            if (m_nPDFA_Version > 0)
4036
0
            {
4037
0
                if( rWidget.m_eType == PDFWriter::RadioButton ||
4038
0
                    rWidget.m_eType == PDFWriter::CheckBox ||
4039
0
                    rWidget.m_eType == PDFWriter::PushButton )
4040
0
                {
4041
0
                    bUseSubDict = true;
4042
0
                }
4043
0
            }
4044
4045
0
            rAnnotDict.append( bUseSubDict ? "<<" : " " );
4046
4047
0
            for (auto const& stream_item : dict_item.second)
4048
0
            {
4049
0
                SvMemoryStream* pAppearanceStream = stream_item.second;
4050
0
                dict_item.second[ stream_item.first ] = nullptr;
4051
4052
0
                bool bDeflate = compressStream( pAppearanceStream );
4053
4054
0
                sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
4055
0
                pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4056
0
                sal_Int32 nObject = createObject();
4057
0
                if (!updateObject(nObject))
4058
0
                    return false;
4059
0
                if (g_bDebugDisableCompression)
4060
0
                {
4061
0
                    emitComment( "PDFWriterImpl::emitAppearances" );
4062
0
                }
4063
0
                OStringBuffer aLine;
4064
0
                aLine.append( nObject );
4065
4066
0
                aLine.append( " 0 obj\n"
4067
0
                              "<</Type/XObject\n"
4068
0
                              "/Subtype/Form\n"
4069
0
                              "/BBox[0 0 " );
4070
0
                appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4071
0
                aLine.append( " " );
4072
0
                appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4073
0
                aLine.append( "]\n"
4074
0
                              "/Resources " );
4075
0
                aLine.append( getResourceDictObj() );
4076
0
                aLine.append( " 0 R\n"
4077
0
                              "/Length " );
4078
0
                aLine.append(static_cast<sal_Int64>(calculateStreamSize(nStreamLen)));
4079
0
                aLine.append( "\n" );
4080
0
                if( bDeflate )
4081
0
                    aLine.append( "/Filter/FlateDecode\n" );
4082
0
                aLine.append( ">>\nstream\n" );
4083
0
                if (!writeBuffer(aLine)) return false;
4084
0
                checkAndEnableStreamEncryption( nObject );
4085
0
                if (!writeBufferBytes( pAppearanceStream->GetData(), nStreamLen)) return false;
4086
0
                disableStreamEncryption();
4087
0
                if (!writeBuffer("\nendstream\nendobj\n\n")) return false;
4088
4089
0
                if( bUseSubDict )
4090
0
                {
4091
0
                    rAnnotDict.append( " /" );
4092
0
                    rAnnotDict.append( stream_item.first );
4093
0
                    rAnnotDict.append( " " );
4094
0
                }
4095
0
                rAnnotDict.append( nObject );
4096
0
                rAnnotDict.append( " 0 R" );
4097
4098
0
                delete pAppearanceStream;
4099
0
            }
4100
4101
0
            rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4102
0
        }
4103
0
        rAnnotDict.append( ">>\n" );
4104
0
        if( !aStandardAppearance.isEmpty() )
4105
0
        {
4106
0
            rAnnotDict.append( "/AS /" );
4107
0
            rAnnotDict.append( aStandardAppearance );
4108
0
            rAnnotDict.append( "\n" );
4109
0
        }
4110
0
    }
4111
4112
0
    return true;
4113
0
}
4114
4115
bool PDFWriterImpl::emitWidgetAnnotations()
4116
8.85k
{
4117
8.85k
    ensureUniqueRadioOnValues();
4118
4119
8.85k
    int nAnnots = m_aWidgets.size();
4120
8.85k
    for( int a = 0; a < nAnnots; a++ )
4121
0
    {
4122
0
        PDFWidget& rWidget = m_aWidgets[a];
4123
4124
0
        if( rWidget.m_eType == PDFWriter::CheckBox )
4125
0
        {
4126
0
            if ( !rWidget.m_aOnValue.isEmpty() )
4127
0
            {
4128
0
                auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4129
0
                if( app_it != rWidget.m_aAppearances.end() )
4130
0
                {
4131
0
                    auto stream_it = app_it->second.find( "Yes"_ostr );
4132
0
                    if( stream_it != app_it->second.end() )
4133
0
                    {
4134
0
                        SvMemoryStream* pStream = stream_it->second;
4135
0
                        app_it->second.erase( stream_it );
4136
0
                        OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
4137
0
                        COSWriter::appendName( rWidget.m_aOnValue, aBuf );
4138
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4139
0
                    }
4140
0
                    else
4141
0
                        SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
4142
0
                }
4143
0
            }
4144
4145
0
            if ( !rWidget.m_aOffValue.isEmpty() )
4146
0
            {
4147
0
                auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4148
0
                if( app_it != rWidget.m_aAppearances.end() )
4149
0
                {
4150
0
                    auto stream_it = app_it->second.find( "Off"_ostr );
4151
0
                    if( stream_it != app_it->second.end() )
4152
0
                    {
4153
0
                        SvMemoryStream* pStream = stream_it->second;
4154
0
                        app_it->second.erase( stream_it );
4155
0
                        OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
4156
0
                        COSWriter::appendName( rWidget.m_aOffValue, aBuf );
4157
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4158
0
                    }
4159
0
                    else
4160
0
                        SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
4161
0
                }
4162
0
            }
4163
0
        }
4164
4165
0
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
4166
0
        COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4167
0
        OStringBuffer aValue( 256 );
4168
0
        COSWriter aValueWriter(aValue, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4169
0
        aLine.append( rWidget.m_nObject );
4170
0
        aLine.append( " 0 obj\n"
4171
0
                      "<<" );
4172
0
        if( rWidget.m_eType != PDFWriter::Hierarchy )
4173
0
        {
4174
            // emit widget annotation only for terminal fields
4175
0
            if( rWidget.m_aKids.empty() )
4176
0
            {
4177
0
                int iRectMargin;
4178
4179
0
                aLine.append( "/Type/Annot/Subtype/Widget/F " );
4180
4181
0
                if (rWidget.m_eType == PDFWriter::Signature)
4182
0
                {
4183
0
                    aLine.append( "132\n" ); // Print & Locked
4184
0
                    iRectMargin = 0;
4185
0
                }
4186
0
                else
4187
0
                {
4188
0
                    aLine.append( "4\n" );
4189
0
                    iRectMargin = 1;
4190
0
                }
4191
4192
0
                if (-1 != rWidget.m_nStructParent)
4193
0
                {
4194
0
                    aLine.append("/StructParent ");
4195
0
                    aLine.append(rWidget.m_nStructParent);
4196
0
                    aLine.append("\n");
4197
0
                }
4198
4199
0
                aLine.append("/Rect[" );
4200
0
                appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4201
0
                aLine.append( ' ' );
4202
0
                appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4203
0
                aLine.append( ' ' );
4204
0
                appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4205
0
                aLine.append( ' ' );
4206
0
                appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4207
0
                aLine.append( "]\n" );
4208
0
            }
4209
0
            aLine.append( "/FT/" );
4210
0
            switch( rWidget.m_eType )
4211
0
            {
4212
0
                case PDFWriter::RadioButton:
4213
0
                case PDFWriter::CheckBox:
4214
                    // for radio buttons only the RadioButton field, not the
4215
                    // CheckBox children should have a value, else acrobat reader
4216
                    // does not always check the right button
4217
                    // of course real check boxes (not belonging to a radio group)
4218
                    // need their values, too
4219
0
                    if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4220
0
                    {
4221
0
                        aValue.append( "/" );
4222
                        // check for radio group with all buttons unpressed
4223
0
                        if( rWidget.m_aValue.isEmpty() )
4224
0
                            aValue.append( "Off" );
4225
0
                        else
4226
0
                            COSWriter::appendName( rWidget.m_aValue, aValue );
4227
0
                    }
4228
0
                    [[fallthrough]];
4229
0
                case PDFWriter::PushButton:
4230
0
                    aLine.append( "Btn" );
4231
0
                    break;
4232
0
                case PDFWriter::ListBox:
4233
0
                    if( rWidget.m_nFlags & 0x200000 ) // multiselect
4234
0
                    {
4235
0
                        aValue.append( "[" );
4236
0
                        for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4237
0
                        {
4238
0
                            sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4239
0
                            if( nEntry >= 0
4240
0
                                && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
4241
0
                            {
4242
0
                                aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject);
4243
0
                            }
4244
0
                        }
4245
0
                        aValue.append( "]" );
4246
0
                    }
4247
0
                    else if( !rWidget.m_aSelectedEntries.empty() &&
4248
0
                             rWidget.m_aSelectedEntries[0] >= 0 &&
4249
0
                             o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
4250
0
                    {
4251
0
                        aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject);
4252
0
                    }
4253
0
                    else
4254
0
                        aValueWriter.writeUnicodeEncrypt( OUString(), rWidget.m_nObject);
4255
0
                    aLine.append( "Ch" );
4256
0
                    break;
4257
0
                case PDFWriter::ComboBox:
4258
0
                    aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4259
0
                    aLine.append( "Ch" );
4260
0
                    break;
4261
0
                case PDFWriter::Edit:
4262
0
                    aLine.append( "Tx" );
4263
0
                    aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4264
0
                    break;
4265
0
                case PDFWriter::Signature:
4266
0
                    aLine.append( "Sig" );
4267
0
                    aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4268
0
                    break;
4269
0
                case PDFWriter::Hierarchy: // make the compiler happy
4270
0
                    break;
4271
0
            }
4272
0
            aLine.append( "\n" );
4273
0
            aLine.append( "/P " );
4274
0
            aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4275
0
            aLine.append( " 0 R\n" );
4276
0
        }
4277
0
        if( rWidget.m_nParent )
4278
0
        {
4279
0
            aLine.append( "/Parent " );
4280
0
            aLine.append( rWidget.m_nParent );
4281
0
            aLine.append( " 0 R\n" );
4282
0
        }
4283
0
        if( !rWidget.m_aKids.empty() )
4284
0
        {
4285
0
            aLine.append( "/Kids[" );
4286
0
            for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4287
0
            {
4288
0
                aLine.append( rWidget.m_aKids[i] );
4289
0
                aLine.append( " 0 R" );
4290
0
                aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4291
0
            }
4292
0
            aLine.append( "]\n" );
4293
0
        }
4294
0
        if( !rWidget.m_aName.isEmpty() )
4295
0
        {
4296
0
            aLine.append( "/T" );
4297
0
            aWriter.writeLiteralEncrypt(rWidget.m_aName, rWidget.m_nObject);
4298
0
            aLine.append( "\n" );
4299
0
        }
4300
0
        if (!rWidget.m_aDescription.isEmpty())
4301
0
        {
4302
            // the alternate field name should be unicode able since it is
4303
            // supposed to be used in UI
4304
0
            aLine.append( "/TU" );
4305
0
            aWriter.writeUnicodeEncrypt(rWidget.m_aDescription, rWidget.m_nObject);
4306
0
            aLine.append( "\n" );
4307
0
        }
4308
4309
0
        if( rWidget.m_nFlags )
4310
0
        {
4311
0
            aLine.append( "/Ff " );
4312
0
            aLine.append( rWidget.m_nFlags );
4313
0
            aLine.append( "\n" );
4314
0
        }
4315
0
        if( !aValue.isEmpty() )
4316
0
        {
4317
0
            OString aVal = aValue.makeStringAndClear();
4318
0
            aLine.append( "/V " );
4319
0
            aLine.append( aVal );
4320
0
            aLine.append( "\n"
4321
0
                          "/DV " );
4322
0
            aLine.append( aVal );
4323
0
            aLine.append( "\n" );
4324
0
        }
4325
0
        if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4326
0
        {
4327
0
            sal_Int32 nTI = -1;
4328
0
            aLine.append( "/Opt[\n" );
4329
0
            sal_Int32 i = 0;
4330
0
            for (auto const& entry : rWidget.m_aListEntries)
4331
0
            {
4332
0
                aWriter.writeUnicodeEncrypt(entry, rWidget.m_nObject);
4333
0
                aLine.append( "\n" );
4334
0
                if( entry == rWidget.m_aValue )
4335
0
                    nTI = i;
4336
0
                ++i;
4337
0
            }
4338
0
            aLine.append( "]\n" );
4339
0
            if( nTI > 0 )
4340
0
            {
4341
0
                aLine.append( "/TI " );
4342
0
                aLine.append( nTI );
4343
0
                aLine.append( "\n" );
4344
0
                if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4345
0
                {
4346
0
                    aLine.append( "/I [" );
4347
0
                    aLine.append( nTI );
4348
0
                    aLine.append( "]\n" );
4349
0
                }
4350
0
            }
4351
0
        }
4352
0
        if( rWidget.m_eType == PDFWriter::Edit )
4353
0
        {
4354
0
            if ( rWidget.m_nMaxLen > 0 )
4355
0
            {
4356
0
                aLine.append( "/MaxLen " );
4357
0
                aLine.append( rWidget.m_nMaxLen );
4358
0
                aLine.append( "\n" );
4359
0
            }
4360
4361
0
            if ( rWidget.m_nFormat == PDFWriter::Number )
4362
0
            {
4363
0
                OString aHexText;
4364
4365
0
                if ( !rWidget.m_aCurrencySymbol.isEmpty() )
4366
0
                {
4367
                    // Get the hexadecimal code
4368
0
                    sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
4369
0
                    aHexText = "\\\\u" + OString::number(cChar, 16);
4370
0
                }
4371
4372
0
                aLine.append("/AA<<\n");
4373
0
                aLine.append("/F<</JS(AFNumber_Format\\(");
4374
0
                aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4375
0
                aLine.append(", 0, 0, 0, \"");
4376
0
                aLine.append( aHexText );
4377
0
                aLine.append("\",");
4378
0
                aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4379
0
                aLine.append("\\);)");
4380
0
                aLine.append("/S/JavaScript>>\n");
4381
0
                aLine.append("/K<</JS(AFNumber_Keystroke\\(");
4382
0
                aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4383
0
                aLine.append(", 0, 0, 0, \"");
4384
0
                aLine.append( aHexText );
4385
0
                aLine.append("\",");
4386
0
                aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4387
0
                aLine.append("\\);)");
4388
0
                aLine.append("/S/JavaScript>>\n");
4389
0
                aLine.append(">>\n");
4390
0
            }
4391
0
            else if ( rWidget.m_nFormat == PDFWriter::Time )
4392
0
            {
4393
0
                aLine.append("/AA<<\n");
4394
0
                aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
4395
0
                aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4396
0
                aLine.append("\"\\);)");
4397
0
                aLine.append("/S/JavaScript>>\n");
4398
0
                aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
4399
0
                aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4400
0
                aLine.append("\"\\);)");
4401
0
                aLine.append("/S/JavaScript>>\n");
4402
0
                aLine.append(">>\n");
4403
0
            }
4404
0
            else if ( rWidget.m_nFormat == PDFWriter::Date )
4405
0
            {
4406
0
                aLine.append("/AA<<\n");
4407
0
                aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
4408
0
                aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4409
0
                aLine.append("\"\\);)");
4410
0
                aLine.append("/S/JavaScript>>\n");
4411
0
                aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
4412
0
                aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4413
0
                aLine.append("\"\\);)");
4414
0
                aLine.append("/S/JavaScript>>\n");
4415
0
                aLine.append(">>\n");
4416
0
            }
4417
0
        }
4418
0
        if( rWidget.m_eType == PDFWriter::PushButton )
4419
0
        {
4420
0
            if (!m_bIsPDF_A1)
4421
0
            {
4422
0
                OStringBuffer aDest;
4423
0
                if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4424
0
                {
4425
0
                    aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4426
0
                    aLine.append( aDest );
4427
0
                    aLine.append( ">>>>\n" );
4428
0
                }
4429
0
                else if( rWidget.m_aListEntries.empty() )
4430
0
                {
4431
0
                    if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4432
0
                    {
4433
                        // create a reset form action
4434
0
                        aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4435
0
                    }
4436
0
                }
4437
0
                else if( rWidget.m_bSubmit )
4438
0
                {
4439
                    // create a submit form action
4440
0
                    aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4441
0
                    aWriter.writeLiteralEncrypt(rWidget.m_aListEntries.front(), rWidget.m_nObject, osl_getThreadTextEncoding());
4442
0
                    aLine.append( "/Flags " );
4443
4444
0
                    sal_Int32 nFlags = 0;
4445
0
                    switch( m_aContext.SubmitFormat )
4446
0
                    {
4447
0
                    case PDFWriter::HTML:
4448
0
                        nFlags |= 4;
4449
0
                        break;
4450
0
                    case PDFWriter::XML:
4451
0
                        nFlags |= 32;
4452
0
                        break;
4453
0
                    case PDFWriter::PDF:
4454
0
                        nFlags |= 256;
4455
0
                        break;
4456
0
                    case PDFWriter::FDF:
4457
0
                    default:
4458
0
                        break;
4459
0
                    }
4460
0
                    if( rWidget.m_bSubmitGet )
4461
0
                        nFlags |= 8;
4462
0
                    aLine.append( nFlags );
4463
0
                    aLine.append( ">>>>\n" );
4464
0
                }
4465
0
                else
4466
0
                {
4467
                    // create a URI action
4468
0
                    aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4469
0
                    aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4470
0
                    aLine.append( ")>>>>\n" );
4471
0
                }
4472
0
            }
4473
0
            else
4474
0
                m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4475
0
        }
4476
0
        if( !rWidget.m_aDAString.isEmpty() )
4477
0
        {
4478
0
            if( !rWidget.m_aDRDict.isEmpty() )
4479
0
            {
4480
0
                aLine.append( "/DR<<" );
4481
0
                aLine.append( rWidget.m_aDRDict );
4482
0
                aLine.append( ">>\n" );
4483
0
            }
4484
0
            else
4485
0
            {
4486
0
                aLine.append( "/DR<</Font<<" );
4487
0
                appendBuildinFontsToDict( aLine );
4488
0
                aLine.append( ">>>>\n" );
4489
0
            }
4490
0
            aLine.append( "/DA" );
4491
0
            aWriter.writeLiteralEncrypt(rWidget.m_aDAString, rWidget.m_nObject);
4492
0
            aLine.append( "\n" );
4493
0
            if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4494
0
                aLine.append( "/Q 1\n" );
4495
0
            else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4496
0
                aLine.append( "/Q 2\n" );
4497
0
        }
4498
        // appearance characteristics for terminal fields
4499
        // which are supposed to have an appearance constructed
4500
        // by the viewer application
4501
0
        if( !rWidget.m_aMKDict.isEmpty() )
4502
0
        {
4503
0
            aLine.append( "/MK<<" );
4504
0
            aLine.append( rWidget.m_aMKDict );
4505
            //add the CA string, encrypting it
4506
0
            aWriter.writeLiteralEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject);
4507
0
            aLine.append( ">>\n" );
4508
0
        }
4509
4510
0
        if (!emitAppearances(rWidget, aLine)) return false;
4511
4512
0
        aLine.append( ">>\n"
4513
0
                      "endobj\n\n" );
4514
0
        if (!updateObject(rWidget.m_nObject)) return false;
4515
0
        if (!writeBuffer(aLine)) return false;
4516
0
    }
4517
8.85k
    return true;
4518
8.85k
}
4519
4520
bool PDFWriterImpl::emitAnnotations()
4521
8.85k
{
4522
8.85k
    if( m_aPages.empty() )
4523
0
        return false;
4524
4525
8.85k
    if (!emitLinkAnnotations()) return false;
4526
8.85k
    if (!emitScreenAnnotations()) return false;
4527
8.85k
    if (!emitNoteAnnotations()) return false;
4528
8.85k
    if (!emitWidgetAnnotations()) return false;
4529
4530
8.85k
    return true;
4531
8.85k
}
4532
4533
class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream >
4534
{
4535
    VclPtr<PDFWriterImpl>  m_pWriter;
4536
    bool            m_bWrite;
4537
    public:
4538
0
    explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
4539
4540
    virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override
4541
0
    {
4542
0
        if( m_bWrite && aData.hasElements() )
4543
0
        {
4544
0
            sal_Int32 nBytes = aData.getLength();
4545
0
            (void)m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
4546
0
        }
4547
0
    }
4548
0
    virtual void SAL_CALL flush() override {}
4549
    virtual void SAL_CALL closeOutput() override
4550
0
    {
4551
0
        m_bWrite = false;
4552
0
    }
4553
};
4554
4555
bool PDFWriterImpl::emitEmbeddedFiles()
4556
8.85k
{
4557
8.85k
    for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4558
0
    {
4559
0
        if (!updateObject(rEmbeddedFile.m_nObject))
4560
0
            continue;
4561
4562
0
        sal_Int32 nSizeObject = createObject();
4563
0
        sal_Int32 nParamsObject = createObject();
4564
4565
0
        OStringBuffer aLine;
4566
0
        aLine.append(rEmbeddedFile.m_nObject);
4567
0
        aLine.append(" 0 obj\n");
4568
0
        aLine.append("<< /Type /EmbeddedFile");
4569
0
        if (!rEmbeddedFile.m_aSubType.isEmpty())
4570
0
        {
4571
0
            aLine.append("/Subtype /");
4572
0
            COSWriter::appendName(rEmbeddedFile.m_aSubType, aLine);
4573
0
        }
4574
0
        aLine.append(" /Length ");
4575
0
        appendObjectReference(nSizeObject, aLine);
4576
0
        aLine.append(" /Params ");
4577
0
        appendObjectReference(nParamsObject, aLine);
4578
0
        aLine.append(">>\nstream\n");
4579
0
        if (!writeBuffer(aLine)) return false;
4580
0
        aLine.setLength(0);
4581
4582
0
        sal_Int64 nSize{};
4583
0
        if (!rEmbeddedFile.m_aDataContainer.isEmpty())
4584
0
        {
4585
0
            checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
4586
0
            sal_uInt64 const nStartPos{getCurrentFilePosition()};
4587
0
            if (!writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()))
4588
0
                return false;
4589
0
            disableStreamEncryption();
4590
0
            nSize = sal_Int64(getCurrentFilePosition() - nStartPos);
4591
0
        }
4592
0
        else if (rEmbeddedFile.m_pStream)
4593
0
        {
4594
0
            checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
4595
0
            sal_uInt64 nBegin = getCurrentFilePosition();
4596
0
            css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
4597
0
            rEmbeddedFile.m_pStream->write(xStream);
4598
0
            rEmbeddedFile.m_pStream.reset();
4599
0
            xStream.clear();
4600
0
            nSize = sal_Int64(getCurrentFilePosition() - nBegin);
4601
0
            disableStreamEncryption();
4602
0
        }
4603
0
        aLine.append("\nendstream\nendobj\n\n");
4604
0
        if (!writeBuffer(aLine)) return false;
4605
0
        aLine.setLength(0);
4606
4607
0
        if (!updateObject(nSizeObject))
4608
0
            return false;
4609
0
        aLine.append(nSizeObject);
4610
0
        aLine.append(" 0 obj\n");
4611
0
        aLine.append(nSize);
4612
0
        aLine.append("\nendobj\n\n");
4613
0
        if (!writeBuffer(aLine))
4614
0
            return false;
4615
0
        aLine.setLength(0);
4616
4617
0
        if (!updateObject(nParamsObject))
4618
0
            return false;
4619
0
        aLine.append(nParamsObject);
4620
0
        aLine.append(" 0 obj\n");
4621
0
        aLine.append("<<");
4622
0
        aLine.append("/Size ");
4623
0
        aLine.append(nSize);
4624
0
        aLine.append(">>");
4625
0
        aLine.append("\nendobj\n\n");
4626
0
        if (!writeBuffer(aLine))
4627
0
            return false;
4628
0
    }
4629
8.85k
    return true;
4630
8.85k
}
4631
4632
bool PDFWriterImpl::emitCatalog()
4633
8.85k
{
4634
    // build page tree
4635
    // currently there is only one node that contains all leaves
4636
4637
    // first create a page tree node id
4638
8.85k
    sal_Int32 nTreeNode = createObject();
4639
4640
    // emit global resource dictionary (page emit needs it)
4641
8.85k
    if (!emitResources()) return false;
4642
4643
    // emit all pages
4644
8.85k
    for (auto & page : m_aPages)
4645
73.7k
        if( ! page.emit( nTreeNode ) )
4646
0
            return false;
4647
4648
8.85k
    sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4649
4650
8.85k
    sal_Int32 nOutlineDict = emitOutline();
4651
4652
    // emit Output intent
4653
8.85k
    sal_Int32 nOutputIntentObject = emitOutputIntent();
4654
4655
    // emit metadata
4656
8.85k
    sal_Int32 nMetadataObject = emitDocumentMetadata();
4657
4658
8.85k
    emitNamespaces();
4659
4660
8.85k
    sal_Int32 nStructureDict = 0;
4661
8.85k
    if(m_aStructure.size() > 1)
4662
0
    {
4663
0
        removePlaceholderSE(m_aStructure, m_aStructure[0]);
4664
        // check if dummy structure containers are needed
4665
0
        addInternalStructureContainer(0);
4666
0
        nStructureDict = m_aStructure[0].m_nObject = createObject();
4667
0
        emitStructure( m_aStructure[ 0 ] );
4668
0
        if (!emitObjectStream())
4669
0
            return false;
4670
        // whatever put its strings in plain has to have landed in an object stream; that the
4671
        // stream is encrypted is asserted where it is written
4672
0
        assert(std::ranges::all_of(m_aPlainStringObjects, [this](sal_Int32 nPlain) {
4673
0
            return m_aObjects[nPlain - 1].isCompressed();
4674
0
        }));
4675
0
    }
4676
4677
    // adjust tree node file offset
4678
8.85k
    if( ! updateObject( nTreeNode ) )
4679
0
        return false;
4680
4681
    // emit tree node
4682
8.85k
    OStringBuffer aLine( 2048 );
4683
8.85k
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4684
8.85k
    aLine.append( nTreeNode );
4685
8.85k
    aLine.append( " 0 obj\n" );
4686
8.85k
    aLine.append( "<</Type/Pages\n" );
4687
8.85k
    aLine.append( "/Resources " );
4688
8.85k
    aLine.append( getResourceDictObj() );
4689
8.85k
    aLine.append( " 0 R\n" );
4690
4691
8.85k
    if( m_aPages.empty() ) // sanity check, this should not happen
4692
0
        aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt
4693
4694
8.85k
    aLine.append("/Kids[ ");
4695
8.85k
    unsigned int i = 0;
4696
8.85k
    for (const auto & page : m_aPages)
4697
73.7k
    {
4698
73.7k
        aLine.append( page.m_nPageObject );
4699
73.7k
        aLine.append( " 0 R" );
4700
73.7k
        aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4701
73.7k
        ++i;
4702
73.7k
    }
4703
8.85k
    aLine.append( "]\n"
4704
8.85k
                  "/Count " );
4705
8.85k
    aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
4706
8.85k
    aLine.append( ">>\n"
4707
8.85k
                  "endobj\n\n" );
4708
8.85k
    if (!writeBuffer(aLine)) return false;
4709
4710
    // emit annotation objects
4711
8.85k
    if (!emitAnnotations()) return false;
4712
8.85k
    if (!emitEmbeddedFiles()) return false;
4713
4714
    // emit attached files
4715
8.85k
    for (auto & rAttachedFile : m_aDocumentAttachedFiles)
4716
0
    {
4717
0
        if (!updateObject(rAttachedFile.mnObjectId))
4718
0
            return false;
4719
0
        aLine.setLength(0);
4720
4721
0
        aWriter.startObject(rAttachedFile.mnObjectId);
4722
0
        aWriter.startDict();
4723
0
        aWriter.write("/Type", "/Filespec");
4724
4725
        // Add associated files relationship (since PDF 2.0)
4726
0
        if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
4727
0
        {
4728
0
            aWriter.write("/AFRelationship", "/Source");
4729
0
        }
4730
4731
0
        aWriter.writeKeyAndUnicodeEncrypt("/F", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
4732
0
        if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
4733
0
        {
4734
0
            aWriter.writeKeyAndUnicodeEncrypt("/UF", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
4735
0
        }
4736
0
        if (!rAttachedFile.maDescription.isEmpty())
4737
0
        {
4738
0
            aWriter.writeKeyAndUnicodeEncrypt("/Desc", rAttachedFile.maDescription, rAttachedFile.mnObjectId);
4739
0
        }
4740
0
        aLine.append("/EF");
4741
0
        aWriter.startDict();
4742
0
        aWriter.writeKeyAndReference("/F", rAttachedFile.mnEmbeddedFileObjectId);
4743
0
        aWriter.endDict();
4744
0
        aWriter.endDict();
4745
0
        aWriter.endObject();
4746
0
        if (!writeBuffer(aLine)) return false;
4747
0
    }
4748
4749
    // emit Catalog
4750
8.85k
    m_nCatalogObject = createObject();
4751
8.85k
    if( ! updateObject( m_nCatalogObject ) )
4752
0
        return false;
4753
8.85k
    aLine.setLength( 0 );
4754
8.85k
    aLine.append( m_nCatalogObject );
4755
8.85k
    aLine.append( " 0 obj\n"
4756
8.85k
                  "<</Type/Catalog/Pages " );
4757
8.85k
    aLine.append( nTreeNode );
4758
8.85k
    aLine.append( " 0 R\n" );
4759
4760
    // check if there are named destinations to emit (root must be inside the catalog)
4761
8.85k
    if( nNamedDestinationsDictionary )
4762
0
    {
4763
0
        aLine.append("/Dests ");
4764
0
        aLine.append( nNamedDestinationsDictionary );
4765
0
        aLine.append( " 0 R\n" );
4766
0
    }
4767
4768
8.85k
    if (!m_aDocumentAttachedFiles.empty())
4769
0
    {
4770
        // Write the associated files catalog entry (since PDF 2.0)
4771
0
        if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
4772
0
        {
4773
0
            aLine.append("/AF");
4774
0
            aLine.append("[");
4775
0
            for (auto & rAttachedFile : m_aDocumentAttachedFiles)
4776
0
                aWriter.writeReference(rAttachedFile.mnObjectId);
4777
0
            aLine.append("]");
4778
0
        }
4779
4780
0
        aWriter.startDictWithKey("/Names");
4781
0
        aWriter.startDictWithKey("/EmbeddedFiles");
4782
0
        aLine.append("/Names [");
4783
0
        for (auto & rAttachedFile : m_aDocumentAttachedFiles)
4784
0
        {
4785
0
            aWriter.writeUnicodeEncrypt(rAttachedFile.maFilename, m_nCatalogObject);
4786
0
            aWriter.writeReference(rAttachedFile.mnObjectId);
4787
0
        }
4788
0
        aLine.append("]");
4789
0
        aWriter.endDict();
4790
0
        aWriter.endDict();
4791
0
    }
4792
4793
8.85k
    if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
4794
0
        switch(  m_aContext.PageLayout )
4795
0
        {
4796
0
        default :
4797
0
        case  PDFWriter::SinglePage :
4798
0
            aLine.append( "/PageLayout/SinglePage\n" );
4799
0
            break;
4800
0
        case  PDFWriter::Continuous :
4801
0
            aLine.append( "/PageLayout/OneColumn\n" );
4802
0
            break;
4803
0
        case  PDFWriter::ContinuousFacing :
4804
            // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
4805
0
            aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
4806
0
            break;
4807
0
        }
4808
8.85k
    if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
4809
0
        switch(  m_aContext.PDFDocumentMode )
4810
0
        {
4811
0
        default :
4812
0
            aLine.append( "/PageMode/UseNone\n" );
4813
0
            break;
4814
0
        case PDFWriter::UseOutlines :
4815
0
            aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
4816
0
            break;
4817
0
        case PDFWriter::UseThumbs :
4818
0
            aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
4819
0
            break;
4820
0
        }
4821
8.85k
    else if( m_aContext.OpenInFullScreenMode )
4822
0
        aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
4823
4824
8.85k
    OStringBuffer aInitPageRef;
4825
8.85k
    if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
4826
8.85k
    {
4827
8.85k
        aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
4828
8.85k
        aInitPageRef.append( " 0 R" );
4829
8.85k
    }
4830
0
    else
4831
0
        aInitPageRef.append( "0" );
4832
4833
8.85k
    switch( m_aContext.PDFDocumentAction )
4834
8.85k
    {
4835
8.85k
    case PDFWriter::ActionDefault :     //do nothing, this is the Acrobat default
4836
8.85k
    default:
4837
8.85k
        if ( aInitPageRef.getLength() > 1 && m_aContext.InitialPage > 0 )
4838
0
        {
4839
0
            aLine.append( "/OpenAction[" );
4840
0
            aLine.append( aInitPageRef );
4841
0
            aLine.append( " /XYZ null null 0]\n" );
4842
0
        }
4843
8.85k
        break;
4844
0
    case PDFWriter::FitInWindow :
4845
0
        aLine.append( "/OpenAction[" );
4846
0
        aLine.append( aInitPageRef );
4847
0
        aLine.append( " /Fit]\n" ); //Open fit page
4848
0
        break;
4849
0
    case PDFWriter::FitWidth :
4850
0
        aLine.append( "/OpenAction[" );
4851
0
        aLine.append( aInitPageRef );
4852
0
        aLine.append( " /FitH null]\n" ); //Open fit width
4853
0
        break;
4854
0
    case PDFWriter::FitVisible :
4855
0
        aLine.append( "/OpenAction[" );
4856
0
        aLine.append( aInitPageRef );
4857
0
        aLine.append( " /FitBH null]\n" ); //Open fit visible
4858
0
        break;
4859
0
    case PDFWriter::ActionZoom :
4860
0
        aLine.append( "/OpenAction[" );
4861
0
        aLine.append( aInitPageRef );
4862
0
        aLine.append( " /XYZ null null " );
4863
0
        if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
4864
0
            aLine.append( m_aContext.Zoom/100.0 );
4865
0
        else
4866
0
            aLine.append( "0" );
4867
0
        aLine.append( "]\n" );
4868
0
        break;
4869
8.85k
    }
4870
4871
    // viewer preferences, if we had some, then emit
4872
8.85k
    if (m_aContext.HideViewerToolbar ||
4873
8.85k
        (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) ||
4874
8.21k
        m_aContext.HideViewerMenubar ||
4875
8.21k
        m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
4876
8.21k
        m_aContext.CenterWindow || (m_aContext.FirstPageLeft  &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
4877
8.21k
        m_aContext.OpenInFullScreenMode ||
4878
8.21k
        m_bIsPDF_UA)
4879
637
    {
4880
637
        aLine.append( "/ViewerPreferences<<" );
4881
637
        if( m_aContext.HideViewerToolbar )
4882
0
            aLine.append( "/HideToolbar true\n" );
4883
637
        if( m_aContext.HideViewerMenubar )
4884
0
            aLine.append( "/HideMenubar true\n" );
4885
637
        if( m_aContext.HideViewerWindowControls )
4886
0
            aLine.append( "/HideWindowUI true\n" );
4887
637
        if( m_aContext.FitWindow )
4888
0
            aLine.append( "/FitWindow true\n" );
4889
637
        if( m_aContext.CenterWindow )
4890
0
            aLine.append( "/CenterWindow true\n" );
4891
        // PDF/UA-2 requires /DisplayDocTitle set to true
4892
637
        if ((!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_bIsPDF_UA)
4893
637
            aLine.append( "/DisplayDocTitle true\n" );
4894
637
        if( m_aContext.FirstPageLeft &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing )
4895
0
            aLine.append( "/Direction/R2L\n" );
4896
637
        if( m_aContext.OpenInFullScreenMode )
4897
0
            switch( m_aContext.PDFDocumentMode )
4898
0
            {
4899
0
            default :
4900
0
            case PDFWriter::ModeDefault :
4901
0
                aLine.append( "/NonFullScreenPageMode/UseNone\n" );
4902
0
                break;
4903
0
            case PDFWriter::UseOutlines :
4904
0
                aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
4905
0
                break;
4906
0
            case PDFWriter::UseThumbs :
4907
0
                aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
4908
0
                break;
4909
0
            }
4910
637
        aLine.append( ">>\n" );
4911
637
    }
4912
4913
8.85k
    if( nOutlineDict )
4914
523
    {
4915
523
        aLine.append( "/Outlines " );
4916
523
        aLine.append( nOutlineDict );
4917
523
        aLine.append( " 0 R\n" );
4918
523
    }
4919
8.85k
    if( nStructureDict )
4920
0
    {
4921
0
        aLine.append( "/StructTreeRoot " );
4922
0
        aLine.append( nStructureDict );
4923
0
        aLine.append( " 0 R\n" );
4924
0
    }
4925
8.85k
    if( !m_aContext.DocumentLocale.Language.isEmpty() )
4926
8.85k
    {
4927
        /* PDF allows only RFC 3066, see above in emitStructure(). */
4928
8.85k
        LanguageTag aLanguageTag( m_aContext.DocumentLocale);
4929
8.85k
        OUString aLanguage, aScript, aCountry;
4930
8.85k
        aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
4931
8.85k
        if (!aLanguage.isEmpty())
4932
8.85k
        {
4933
8.85k
            OUStringBuffer aLocBuf( 16 );
4934
8.85k
            aLocBuf.append( aLanguage );
4935
8.85k
            if( !aCountry.isEmpty() )
4936
476
            {
4937
476
                aLocBuf.append( '-' );
4938
476
                aLocBuf.append( aCountry );
4939
476
            }
4940
8.85k
            aLine.append( "/Lang" );
4941
8.85k
            aWriter.writeLiteralEncrypt(aLocBuf, m_nCatalogObject);
4942
8.85k
            aLine.append( "\n" );
4943
8.85k
        }
4944
8.85k
    }
4945
8.85k
    if (m_aContext.Tagged)
4946
0
    {
4947
0
        aLine.append( "/MarkInfo<</Marked true>>\n" );
4948
0
    }
4949
4950
8.85k
    if (!m_aWidgets.empty() || !m_aCopiedWidgets.empty())
4951
0
    {
4952
0
        aLine.append("/AcroForm");
4953
0
        aLine.append("<<");
4954
0
        aLine.append("/Fields");
4955
0
        aLine.append("[ ");
4956
4957
0
        for (auto const& rWidget : m_aWidgets)
4958
0
        {
4959
            // output only root fields
4960
0
            if (rWidget.m_nParent < 1)
4961
0
            {
4962
0
                appendObjectReference(rWidget.m_nObject, aLine);
4963
0
            }
4964
0
        }
4965
        // Add widgets that were copied from an external PDF
4966
0
        for (auto const& rCopiedWidget : m_aCopiedWidgets)
4967
0
        {
4968
0
            appendObjectReference(rCopiedWidget.m_nObject, aLine);
4969
0
        }
4970
0
        aLine.append(" ]\n");
4971
4972
0
        bool bSigned = false;
4973
#if HAVE_FEATURE_NSS
4974
        if (m_nSignatureObject != -1)
4975
        {
4976
            aLine.append("/SigFlags 3 ");
4977
            bSigned = true;
4978
        }
4979
#endif
4980
0
        aLine.append("/DR<</Font ");
4981
0
        appendObjectReference(getFontDictObject(), aLine);
4982
0
        aLine.append(">>");
4983
4984
        // NeedAppearances must not be used if PDF is signed, PDF/A is used or
4985
        // we have copied widgets (can't guarantee we have appearance streams in this case)
4986
0
        if (m_nPDFA_Version == 0 && !bSigned && m_aCopiedWidgets.empty())
4987
0
            aLine.append("/NeedAppearances true ");
4988
4989
0
        aLine.append(">>\n");
4990
0
    }
4991
4992
    //check if there is a Metadata object
4993
8.85k
    if( nOutputIntentObject )
4994
0
    {
4995
0
        aLine.append("/OutputIntents[");
4996
0
        aLine.append( nOutputIntentObject );
4997
0
        aLine.append( " 0 R]" );
4998
0
    }
4999
5000
8.85k
    if( nMetadataObject )
5001
8.85k
    {
5002
8.85k
        aWriter.writeKeyAndReference("/Metadata", nMetadataObject);
5003
8.85k
    }
5004
5005
8.85k
    aLine.append( ">>\n"
5006
8.85k
                  "endobj\n\n" );
5007
8.85k
    return writeBuffer( aLine );
5008
8.85k
}
5009
5010
#if HAVE_FEATURE_NSS
5011
5012
bool PDFWriterImpl::emitSignature()
5013
{
5014
    if( !updateObject( m_nSignatureObject ) )
5015
        return false;
5016
5017
    OStringBuffer aLine( 0x5000 );
5018
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5019
    aLine.append( m_nSignatureObject );
5020
    aLine.append( " 0 obj\n" );
5021
    aLine.append("<</Contents <" );
5022
5023
    sal_uInt64 nOffset = ~0U;
5024
    if (osl::File::E_None != m_aFile.getPos(nOffset))
5025
        return false;
5026
5027
    m_nSignatureContentOffset = nOffset + aLine.getLength();
5028
5029
    // reserve some space for the PKCS#7 object
5030
    aLine.append(RepeatedChar('0', MAX_SIGNATURE_CONTENT_LENGTH)
5031
                 + ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5032
5033
    if( !m_aContext.DocumentInfo.Author.isEmpty() )
5034
    {
5035
        aLine.append( "/Name" );
5036
        aWriter.writeUnicodeEncrypt(m_aContext.DocumentInfo.Author, m_nSignatureObject);
5037
    }
5038
5039
    aLine.append( " /M ");
5040
    aWriter.writeLiteralEncrypt(m_aCreationDateString, m_nSignatureObject);
5041
5042
    aLine.append( " /ByteRange [ 0 ");
5043
    aLine.append( m_nSignatureContentOffset - 1 );
5044
    aLine.append( " " );
5045
    aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5046
    aLine.append( " " );
5047
5048
    m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5049
5050
    // mark the last ByteRange no and add some space. Now, we don't know
5051
    // how many bytes we need for this ByteRange value
5052
    // The real value will be overwritten in the finalizeSignature method
5053
    aLine.append(RepeatedChar(' ', 100) + "  /Filter/Adobe.PPKMS");
5054
5055
    //emit reason, location and contactinfo
5056
    if ( !m_aContext.SignReason.isEmpty() )
5057
    {
5058
        aLine.append("/Reason");
5059
        aWriter.writeUnicodeEncrypt(m_aContext.SignReason, m_nSignatureObject);
5060
    }
5061
5062
    if ( !m_aContext.SignLocation.isEmpty() )
5063
    {
5064
        aLine.append("/Location");
5065
        aWriter.writeUnicodeEncrypt(m_aContext.SignLocation, m_nSignatureObject);
5066
    }
5067
5068
    if ( !m_aContext.SignContact.isEmpty() )
5069
    {
5070
        aLine.append("/ContactInfo");
5071
        aWriter.writeUnicodeEncrypt(m_aContext.SignContact, m_nSignatureObject);
5072
    }
5073
5074
    aLine.append(" >>\nendobj\n\n" );
5075
5076
    return writeBuffer( aLine );
5077
}
5078
5079
bool PDFWriterImpl::finalizeSignature()
5080
{
5081
    if (!m_aContext.SignCertificate.is())
5082
        return false;
5083
5084
    // 1- calculate last ByteRange value
5085
    sal_uInt64 nOffset = ~0U;
5086
    if (osl::File::E_None != m_aFile.getPos(nOffset))
5087
        return false;
5088
5089
    sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
5090
5091
    // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
5092
    sal_uInt64 nWritten = 0;
5093
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset))
5094
        return false;
5095
    OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
5096
5097
    if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
5098
    {
5099
        (void)m_aFile.setPos(osl_Pos_Absolut, nOffset);
5100
        return false;
5101
    }
5102
5103
    // 3- create the PKCS#7 object using NSS
5104
5105
    // Prepare buffer and calculate PDF file digest
5106
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, 0))
5107
        return false;
5108
5109
    std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
5110
    sal_uInt64 bytesRead1;
5111
5112
    //FIXME: Check if hash is calculated from the correct byterange
5113
    if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
5114
        bytesRead1 != o3tl::make_unsigned(m_nSignatureContentOffset) - 1)
5115
    {
5116
        SAL_WARN("vcl.pdfwriter", "First buffer read failed");
5117
        return false;
5118
    }
5119
5120
    std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
5121
    sal_uInt64 bytesRead2;
5122
5123
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
5124
        osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
5125
        bytesRead2 != o3tl::make_unsigned(nLastByteRangeNo))
5126
    {
5127
        SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
5128
        return false;
5129
    }
5130
5131
    OStringBuffer aCMSHexBuffer;
5132
    svl::crypto::SigningContext aSigningContext;
5133
    aSigningContext.m_xCertificate = m_aContext.SignCertificate;
5134
    svl::crypto::Signing aSigning(aSigningContext);
5135
    aSigning.AddDataRange(buffer1.get(), bytesRead1);
5136
    aSigning.AddDataRange(buffer2.get(), bytesRead2);
5137
    aSigning.SetSignTSA(m_aContext.SignTSA);
5138
    aSigning.SetSignPassword(m_aContext.SignPassword);
5139
    if (!aSigning.Sign(aCMSHexBuffer))
5140
    {
5141
        SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
5142
        return false;
5143
    }
5144
5145
    assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
5146
5147
    // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
5148
    nWritten = 0;
5149
    if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset))
5150
        return false;
5151
    m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
5152
5153
    return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
5154
}
5155
5156
#endif //HAVE_FEATURE_NSS
5157
5158
sal_Int32 PDFWriterImpl::emitInfoDict( )
5159
8.85k
{
5160
8.85k
    sal_Int32 nObject = createObject();
5161
5162
8.85k
    if (!updateObject(nObject))
5163
0
        return 0;
5164
5165
8.85k
    COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5166
8.85k
    aWriter.startObject(nObject);
5167
8.85k
    aWriter.startDict();
5168
5169
    // These entries are deprecated in PDF 2.0 (in favor of XMP metadata) and shouldn't be written.
5170
    // Exception: CreationDate and ModDate (which we don't write)
5171
8.85k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
5172
8.85k
    {
5173
8.85k
        if (!m_aContext.DocumentInfo.Title.isEmpty())
5174
637
        {
5175
637
            aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject);
5176
637
        }
5177
8.85k
        if (!m_aContext.DocumentInfo.Author.isEmpty())
5178
450
        {
5179
450
            aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject);
5180
450
        }
5181
8.85k
        if (!m_aContext.DocumentInfo.Subject.isEmpty())
5182
58
        {
5183
58
            aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject);
5184
58
        }
5185
8.85k
        if (!m_aContext.DocumentInfo.Keywords.isEmpty())
5186
275
        {
5187
275
            aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject);
5188
275
        }
5189
8.85k
        if (!m_aContext.DocumentInfo.Creator.isEmpty())
5190
8.85k
        {
5191
8.85k
            aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject);
5192
8.85k
        }
5193
8.85k
        if (!m_aContext.DocumentInfo.Producer.isEmpty())
5194
0
        {
5195
0
            aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject);
5196
0
        }
5197
8.85k
    }
5198
    // Allowed in PDF 2.0
5199
8.85k
    aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject);
5200
8.85k
    aWriter.endDict();
5201
8.85k
    aWriter.endObject();
5202
5203
8.85k
    if (!writeBuffer(aWriter.getLine()))
5204
0
        nObject = 0;
5205
5206
8.85k
    return nObject;
5207
8.85k
}
5208
5209
// Part of this function may be shared with method appendDest.
5210
sal_Int32 PDFWriterImpl::emitNamedDestinations()
5211
8.85k
{
5212
8.85k
    sal_Int32  nCount = m_aNamedDests.size();
5213
8.85k
    if( nCount <= 0 )
5214
8.85k
        return 0;//define internal error
5215
5216
    //get the object number for all the destinations
5217
0
    sal_Int32 nObject = createObject();
5218
5219
0
    if( updateObject( nObject ) )
5220
0
    {
5221
        //emit the dictionary
5222
0
        OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
5223
0
        aLine.append( nObject );
5224
0
        aLine.append( " 0 obj\n"
5225
0
                      "<<" );
5226
5227
0
        sal_Int32  nDestID;
5228
0
        for( nDestID = 0; nDestID < nCount; nDestID++ )
5229
0
        {
5230
0
            const PDFNamedDest& rDest   = m_aNamedDests[ nDestID ];
5231
            // In order to correctly function both under an Internet browser and
5232
            // directly with a reader (provided the reader has the feature) we
5233
            // need to set the name of the destination the same way it will be encoded
5234
            // in an Internet link
5235
0
            INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
5236
0
            aLocalURL.SetMark( rDest.m_aDestName );
5237
5238
0
            const OUString aName   = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5239
            // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5240
0
            const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
5241
5242
0
            aLine.append( '/' );
5243
0
            appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5244
0
            aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5245
                                 //maps the preceding character properly
5246
0
            aLine.append( rDestPage.m_nPageObject );
5247
0
            aLine.append( " 0 R" );
5248
5249
0
            switch( rDest.m_eType )
5250
0
            {
5251
0
            case PDFWriter::DestAreaType::XYZ:
5252
0
            default:
5253
0
                aLine.append( "/XYZ " );
5254
0
                appendFixedInt( rDest.m_aRect.Left(), aLine );
5255
0
                aLine.append( ' ' );
5256
0
                appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5257
0
                aLine.append( " 0" );
5258
0
                break;
5259
0
            case PDFWriter::DestAreaType::FitRectangle:
5260
0
                aLine.append( "/FitR " );
5261
0
                appendFixedInt( rDest.m_aRect.Left(), aLine );
5262
0
                aLine.append( ' ' );
5263
0
                appendFixedInt( rDest.m_aRect.Top(), aLine );
5264
0
                aLine.append( ' ' );
5265
0
                appendFixedInt( rDest.m_aRect.Right(), aLine );
5266
0
                aLine.append( ' ' );
5267
0
                appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5268
0
                break;
5269
0
            }
5270
0
            aLine.append( "]\n" );
5271
0
        }
5272
5273
        //close
5274
0
        aLine.append( ">>\nendobj\n\n" );
5275
0
        if( ! writeBuffer( aLine ) )
5276
0
            nObject = 0;
5277
0
    }
5278
0
    else
5279
0
        nObject = 0;
5280
5281
0
    return nObject;
5282
0
}
5283
5284
// emits the output intent dictionary
5285
sal_Int32 PDFWriterImpl::emitOutputIntent()
5286
8.85k
{
5287
8.85k
    if (m_nPDFA_Version == 0) // not PDFA
5288
8.85k
        return 0;
5289
5290
    //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5291
5292
0
    OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE );
5293
0
    COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5294
0
    sal_Int32 nICCObject = createObject();
5295
0
    sal_Int32 nStreamLengthObject = createObject();
5296
5297
0
    aLine.append( nICCObject );
5298
// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5299
0
    aLine.append( " 0 obj\n<</N 3/Length " );
5300
0
    aLine.append( nStreamLengthObject );
5301
0
    aLine.append( " 0 R" );
5302
0
    if (!g_bDebugDisableCompression)
5303
0
        aLine.append( "/Filter/FlateDecode" );
5304
0
    aLine.append( ">>\nstream\n" );
5305
0
    if ( !updateObject( nICCObject ) ) return 0;
5306
0
    if ( !writeBuffer( aLine ) ) return 0;
5307
    //get file position
5308
0
    sal_uInt64 nBeginStreamPos = 0;
5309
0
    if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5310
0
        return 0;
5311
0
    beginCompression();
5312
0
    checkAndEnableStreamEncryption( nICCObject );
5313
0
    cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5314
    //force ICC profile version 2.1
5315
0
    cmsSetProfileVersion(hProfile, 2.1);
5316
0
    cmsUInt32Number nBytesNeeded = 0;
5317
0
    cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5318
0
    if (!nBytesNeeded)
5319
0
      return 0;
5320
0
    std::vector<unsigned char> aBuffer(nBytesNeeded);
5321
0
    cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5322
0
    cmsCloseProfile(hProfile);
5323
0
    bool written = writeBufferBytes( aBuffer.data(), aBuffer.size() );
5324
0
    disableStreamEncryption();
5325
0
    endCompression();
5326
5327
0
    sal_uInt64 nEndStreamPos = 0;
5328
0
    if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5329
0
        return 0;
5330
5331
0
    if( !written )
5332
0
        return 0;
5333
0
    if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
5334
0
        return 0 ;
5335
0
    aLine.setLength( 0 );
5336
5337
    //emit the stream length   object
5338
0
    if ( !updateObject( nStreamLengthObject ) ) return 0;
5339
0
    aLine.setLength( 0 );
5340
0
    aLine.append( nStreamLengthObject );
5341
0
    aLine.append( " 0 obj\n" );
5342
0
    aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5343
0
    aLine.append( "\nendobj\n\n" );
5344
0
    if ( !writeBuffer( aLine ) ) return 0;
5345
0
    aLine.setLength( 0 );
5346
5347
    //emit the OutputIntent dictionary
5348
0
    sal_Int32 nOIObject = createObject();
5349
0
    if ( !updateObject( nOIObject ) ) return 0;
5350
0
    aLine.append( nOIObject );
5351
0
    aLine.append( " 0 obj\n"
5352
0
                  "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5353
5354
0
    aWriter.writeLiteralEncrypt(std::string_view("sRGB IEC61966-2.1"), nOIObject);
5355
0
    aLine.append("/DestOutputProfile ");
5356
0
    aLine.append( nICCObject );
5357
0
    aLine.append( " 0 R>>\nendobj\n\n" );
5358
0
    if ( !writeBuffer( aLine ) ) return 0;
5359
5360
0
    return nOIObject;
5361
0
}
5362
5363
static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
5364
97.3k
{
5365
97.3k
    if (!aValue.empty())
5366
10.2k
    {
5367
10.2k
        aMeta = OUStringToOString(aValue, RTL_TEXTENCODING_UTF8);
5368
10.2k
    }
5369
97.3k
}
5370
5371
static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta)
5372
26.5k
{
5373
26.5k
    if (!rValues.hasElements())
5374
26.5k
        return;
5375
5376
0
    std::vector<OString> aNewMetaVector;
5377
0
    aNewMetaVector.reserve(rValues.getLength());
5378
5379
0
    for (const OUString& rValue : rValues)
5380
0
    {
5381
0
        aNewMetaVector.emplace_back(
5382
0
            OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8));
5383
0
    }
5384
5385
0
    rMeta = std::move(aNewMetaVector);
5386
0
}
5387
5388
// emits the document metadata
5389
// Since in PDF 1.4
5390
sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5391
8.85k
{
5392
8.85k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4)
5393
0
        return 0;
5394
5395
    //get the object number for all the destinations
5396
8.85k
    sal_Int32 nObject = createObject();
5397
5398
8.85k
    if (!updateObject(nObject))
5399
0
        return 0;
5400
5401
8.85k
    pdf::XmpMetadata aMetadata;
5402
5403
8.85k
    if (m_nPDFA_Version > 0)
5404
0
        aMetadata.mnPDF_A = m_nPDFA_Version;
5405
5406
8.85k
    if (m_bIsPDF_UA)
5407
0
    {
5408
        // If PDF 2.0+ we need to use PDF/UA-2 otherwise PDF/UA-1
5409
0
        aMetadata.mnPDF_UA = (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) ? 2 : 1;
5410
0
    }
5411
5412
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
5413
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
5414
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
5415
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
5416
8.85k
    aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version);
5417
8.85k
    if (m_nPDFA_Version == 4)
5418
0
    {
5419
        // if we have embedded files we need to use conformance level "F"
5420
0
        aMetadata.msConformance = m_aEmbeddedFiles.empty() ? ""_ostr : "F"_ostr;
5421
0
    }
5422
8.85k
    else
5423
8.85k
    {
5424
8.85k
        aMetadata.msConformance = "B"_ostr;
5425
8.85k
    }
5426
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
5427
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor);
5428
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage);
5429
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier);
5430
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher);
5431
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation);
5432
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights);
5433
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource);
5434
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType);
5435
8.85k
    lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
5436
8.85k
    aMetadata.m_sCreateDate = m_aCreationMetaDateString;
5437
5438
8.85k
    bool const bEncryptMetadata{m_pPDFEncryptor && m_pPDFEncryptor->isMetadataEncrypted()};
5439
5440
8.85k
    {
5441
8.85k
        sal_uInt64 const nLength{bEncryptMetadata
5442
8.85k
                ? calculateStreamSize(aMetadata.getSize())
5443
8.85k
                : aMetadata.getSize()};
5444
8.85k
        COSWriter aWriter;
5445
8.85k
        aWriter.startObject(nObject);
5446
8.85k
        aWriter.startDict();
5447
8.85k
        aWriter.write("/Type", "/Metadata");
5448
8.85k
        aWriter.write("/Subtype", "/XML");
5449
8.85k
        aWriter.write("/Length", sal_Int32(nLength));
5450
8.85k
        aWriter.endDict();
5451
8.85k
        aWriter.startStream();
5452
8.85k
        if (!writeBuffer(aWriter.getLine()))
5453
0
            return 0;
5454
8.85k
    }
5455
5456
    //emit the stream
5457
8.85k
    if (bEncryptMetadata)
5458
0
        checkAndEnableStreamEncryption(nObject);
5459
5460
8.85k
    if (!writeBufferBytes(aMetadata.getData(), aMetadata.getSize()))
5461
0
        return 0;
5462
5463
8.85k
    if (bEncryptMetadata)
5464
0
        disableStreamEncryption();
5465
5466
8.85k
    {
5467
8.85k
        COSWriter aWriter;
5468
8.85k
        aWriter.endStream();
5469
8.85k
        aWriter.endObject();
5470
8.85k
        if (!writeBuffer(aWriter.getLine()))
5471
0
            return 0;
5472
8.85k
    }
5473
5474
8.85k
    return nObject;
5475
8.85k
}
5476
5477
sal_Int32 PDFWriterImpl::emitEncrypt()
5478
0
{
5479
    //emit the security information
5480
    //must be emitted as indirect dictionary object, since
5481
    //Acrobat Reader 5 works only with this kind of implementation
5482
5483
0
    sal_Int32 nObject = createObject();
5484
5485
0
    if (updateObject(nObject))
5486
0
    {
5487
0
        PDFEncryptionProperties& rProperties = m_aContext.Encryption;
5488
0
        COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5489
0
        aWriter.startObject(nObject);
5490
0
        aWriter.startDict();
5491
0
        aWriter.write("/Filter", "/Standard");
5492
0
        aWriter.write("/V", m_pPDFEncryptor->getVersion());
5493
0
        aWriter.write("/Length", m_pPDFEncryptor->getKeyLength() * 8);
5494
0
        aWriter.write("/R", m_pPDFEncryptor->getRevision());
5495
5496
0
        if (m_pPDFEncryptor->getVersion() == 5 && m_pPDFEncryptor->getRevision() == 6)
5497
0
        {
5498
            // emit the owner password, must not be encrypted
5499
0
            aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
5500
0
            aWriter.writeHexArray("/UE", rProperties.UE.data(), rProperties.UE.size());
5501
0
            aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
5502
0
            aWriter.writeHexArray("/OE", rProperties.OE.data(), rProperties.OE.size());
5503
5504
            // Encrypted perms
5505
0
            std::vector<sal_uInt8> aEncryptedPermissions = m_pPDFEncryptor->getEncryptedAccessPermissions(rProperties.EncryptionKey);
5506
0
            aWriter.writeHexArray("/Perms", aEncryptedPermissions.data(), aEncryptedPermissions.size());
5507
5508
            // Write content filter stuff - to select we want AESv3 256bit
5509
0
            aWriter.write("/CF", "<</StdCF <</CFM /AESV3 /Length 256>>>>");
5510
0
            aWriter.write("/StmF", "/StdCF");
5511
0
            aWriter.write("/StrF", "/StdCF");
5512
            // Encrypt metadata. Default is true. Relevant for Revision 6+
5513
0
            if (!m_pPDFEncryptor->isMetadataEncrypted())
5514
0
                aWriter.write("/EncryptMetadata", " false ");
5515
0
        }
5516
0
        else
5517
0
        {
5518
            // emit the owner password, must not be encrypted
5519
0
            aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
5520
0
            aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
5521
0
        }
5522
0
        aWriter.write("/P", m_pPDFEncryptor->getAccessPermissions());
5523
0
        aWriter.endDict();
5524
0
        aWriter.endObject();
5525
5526
0
        if (!writeBuffer(aWriter.getLine()))
5527
0
            nObject = 0;
5528
0
    }
5529
0
    else
5530
0
        nObject = 0;
5531
5532
0
    return nObject;
5533
0
}
5534
5535
bool PDFWriterImpl::emitTrailer()
5536
8.85k
{
5537
    // emit doc info
5538
8.85k
    sal_Int32 nDocInfoObject = 0;
5539
    // Deprecated in PDF 2.0, but still allowed if /PieceInfo is written (which we don't support)
5540
8.85k
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
5541
8.85k
        nDocInfoObject = emitInfoDict();
5542
5543
8.85k
    sal_Int32 nSecObject = 0;
5544
5545
8.85k
    if (m_aContext.Encryption.canEncrypt())
5546
0
    {
5547
0
        nSecObject = emitEncrypt();
5548
0
    }
5549
    // a cross-reference table cannot address an object inside an object stream
5550
8.85k
    const bool bXRefStream = std::ranges::any_of(m_aObjects, &ObjectLocation::isCompressed);
5551
5552
8.85k
    sal_uInt64 nXRefOffset = 0;
5553
8.85k
    OStringBuffer aLine;
5554
8.85k
    if (!bXRefStream)
5555
8.85k
    {
5556
        // emit the xref table, remembering where it starts
5557
8.85k
        if (osl::File::E_None != m_aFile.getPos(nXRefOffset))
5558
0
            return false;
5559
8.85k
        if (!writeBuffer("xref\n"))
5560
0
            return false;
5561
5562
8.85k
        aLine.append("0 ");
5563
8.85k
        aLine.append(static_cast<sal_Int32>(m_aObjects.size() + 1));
5564
8.85k
        aLine.append("\n");
5565
8.85k
        aLine.append("0000000000 65535 f \n");
5566
8.85k
        if (!writeBuffer(aLine))
5567
0
            return false;
5568
5569
8.85k
        for (const auto& rLocation : m_aObjects)
5570
394k
        {
5571
394k
            if (rLocation.m_nOffset == SAL_MAX_UINT64)
5572
0
            {
5573
0
                SAL_WARN("vcl.pdfwriter", "object was created but never written");
5574
0
                if (!writeBuffer("0000000000 65535 f \n"))
5575
0
                    return false;
5576
0
                continue;
5577
0
            }
5578
394k
            auto aOffset(OString::number(rLocation.m_nOffset));
5579
394k
            aLine = RepeatedChar('0', 10 - aOffset.length) + aOffset + " 00000 n \n";
5580
394k
            SAL_WARN_IF(aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry");
5581
394k
            if (!writeBuffer(aLine))
5582
0
                return false;
5583
394k
        }
5584
8.85k
    }
5585
5586
    // prepare document checksum
5587
8.85k
    OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
5588
8.85k
    ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
5589
8.85k
    for (sal_uInt8 i : nMD5Sum)
5590
141k
        COSWriter::appendHex( i, aDocChecksum );
5591
    // document id set in setDocInfo method
5592
    // emit trailer
5593
8.85k
    aLine.setLength( 0 );
5594
8.85k
    aLine.append( "/Root " );
5595
8.85k
    aLine.append( m_nCatalogObject );
5596
8.85k
    aLine.append( " 0 R\n" );
5597
8.85k
    if( nSecObject )
5598
0
    {
5599
0
        aLine.append( "/Encrypt ");
5600
0
        aLine.append( nSecObject );
5601
0
        aLine.append( " 0 R\n" );
5602
0
    }
5603
8.85k
    if( nDocInfoObject )
5604
8.85k
    {
5605
8.85k
        aLine.append( "/Info " );
5606
8.85k
        aLine.append( nDocInfoObject );
5607
8.85k
        aLine.append( " 0 R\n" );
5608
8.85k
    }
5609
8.85k
    if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
5610
8.85k
    {
5611
8.85k
        aLine.append( "/ID [ <" );
5612
8.85k
        for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5613
141k
        {
5614
141k
            COSWriter::appendHex( sal_Int8(item), aLine );
5615
141k
        }
5616
8.85k
        aLine.append( ">\n"
5617
8.85k
                      "<" );
5618
8.85k
        for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5619
141k
        {
5620
141k
            COSWriter::appendHex( sal_Int8(item), aLine );
5621
141k
        }
5622
8.85k
        aLine.append( "> ]\n" );
5623
8.85k
    }
5624
5625
    // Writes the /DocChecksum - hash of the PDF stream
5626
    // This entry is not defined in the standard, so don't write it if we
5627
    // are using PDF/UA or PDF/A as the compliance checkers will complain.
5628
    // Actually we shouldn't write it at all...
5629
8.85k
    if (!aDocChecksum.isEmpty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
5630
8.85k
    {
5631
8.85k
        aLine.append( "/DocChecksum /" );
5632
8.85k
        aLine.append( aDocChecksum );
5633
8.85k
        aLine.append( "\n" );
5634
8.85k
    }
5635
5636
    // Writes the /AdditionalStreams - writes the embedded / attached files into the PDF
5637
    // This entry is not defined in the standard, so don't write it if we
5638
    // are using PDF/UA or PDF/A as the compliance checkers will complain.
5639
8.85k
    if (!m_aDocumentAttachedFiles.empty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
5640
0
    {
5641
0
        aLine.append( "/AdditionalStreams [" );
5642
0
        for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
5643
0
        {
5644
0
            aLine.append( "/" );
5645
0
            COSWriter::appendName(rAttachedFile.maMimeType, aLine);
5646
0
            aLine.append(" ");
5647
0
            appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
5648
0
            aLine.append("\n");
5649
0
        }
5650
0
        aLine.append( "]\n" );
5651
0
    }
5652
5653
    // After calling m_DocDigest.finalize(), we need to initialize the hash again,
5654
    // otherwise, m_DocDigest.update() inside writeBuffer will fail with
5655
    // Assertion failure: rv == SECSuccess, at sechash.c:140
5656
8.85k
    m_DocDigest.initialize();
5657
5658
8.85k
    if (bXRefStream)
5659
0
        return emitXRefStream(aLine);
5660
5661
8.85k
    return writeBuffer("trailer\n<</Size " + OString::number(m_aObjects.size() + 1))
5662
8.85k
           && writeBuffer(aLine)
5663
8.85k
           && writeBuffer(">>\nstartxref\n" + OString::number(static_cast<sal_Int64>(nXRefOffset))
5664
8.85k
                          + "\n%%EOF\n");
5665
8.85k
}
5666
5667
bool PDFWriterImpl::emitXRefStream(std::string_view aTrailerEntries)
5668
0
{
5669
    // ISO 32000-2 7.5.8.3 requires an entry for the stream itself
5670
0
    const sal_Int32 nXRefObject = createObject();
5671
0
    sal_uInt64 nXRefOffset = 0;
5672
0
    if (osl::File::E_None != m_aFile.getPos(nXRefOffset))
5673
0
        return false;
5674
0
    m_aObjects[nXRefObject - 1].m_nOffset = nXRefOffset;
5675
5676
    // the second field is a byte offset or an object stream number, so measure both
5677
0
    const auto aSecondField = [](const ObjectLocation& rLocation) -> sal_uInt64 {
5678
0
        if (rLocation.isCompressed())
5679
0
            return rLocation.m_nObjectStream;
5680
        // an unwritten object gets a free entry, which has no offset
5681
0
        return rLocation.m_nOffset == SAL_MAX_UINT64 ? 0 : rLocation.m_nOffset;
5682
0
    };
5683
0
    sal_Int32 nOffsetBytes = 1;
5684
0
    for (sal_uInt64 nWidest = std::ranges::max(m_aObjects | std::views::transform(aSecondField));
5685
0
         nWidest > 0xff; nWidest >>= 8)
5686
0
        ++nOffsetBytes;
5687
5688
0
    OStringBuffer aEntries(static_cast<sal_Int32>((3 + nOffsetBytes) * (m_aObjects.size() + 1)));
5689
0
    auto appendEntry
5690
0
        = [&aEntries, nOffsetBytes](sal_uInt8 nType, sal_uInt64 nSecond, sal_uInt16 nThird) {
5691
0
              aEntries.append(char(nType));
5692
0
              for (sal_Int32 nByte = nOffsetBytes; nByte-- > 0;)
5693
0
                  aEntries.append(char(nSecond >> (8 * nByte)));
5694
0
              aEntries.append(char(nThird >> 8));
5695
0
              aEntries.append(char(nThird));
5696
0
          };
5697
5698
    // object 0 heads the list of free objects, as it does in a table
5699
0
    appendEntry(0, 0, 0xffff);
5700
0
    for (const auto& rLocation : m_aObjects)
5701
0
    {
5702
0
        if (rLocation.isCompressed())
5703
0
            appendEntry(2, rLocation.m_nObjectStream, rLocation.m_nIndex);
5704
0
        else if (rLocation.m_nOffset == SAL_MAX_UINT64)
5705
0
            appendEntry(0, 0, 0xffff);
5706
0
        else
5707
0
            appendEntry(1, rLocation.m_nOffset, 0);
5708
0
    }
5709
5710
0
    const std::vector<sal_uInt8> aStream(deflateBuffer(aEntries));
5711
0
    if (aStream.empty())
5712
0
        return false;
5713
5714
0
    OStringBuffer aLine(512);
5715
    // 7.5.8.2: every entry of Table 17 shall be direct, and a reader cannot resolve an
5716
    // indirect /Length before it has read this stream
5717
0
    aLine.append(OString::number(nXRefObject) + " 0 obj\n<</Type/XRef/Size "
5718
0
                 + OString::number(static_cast<sal_Int32>(m_aObjects.size() + 1)) + "/W[1 "
5719
0
                 + OString::number(nOffsetBytes) + " 2]/Length "
5720
0
                 + OString::number(static_cast<sal_Int32>(aStream.size())));
5721
0
    if (!g_bDebugDisableCompression)
5722
0
        aLine.append("/Filter/FlateDecode");
5723
0
    aLine.append(aTrailerEntries);
5724
0
    aLine.append(">>\nstream\n");
5725
5726
    // 7.5.8.2 forbids encrypting this stream, so it goes out as built
5727
0
    return writeBuffer(aLine) && writeBufferBytes(aStream.data(), aStream.size())
5728
0
           && writeBuffer("\nendstream\nendobj\n\nstartxref\n"
5729
0
                          + OString::number(static_cast<sal_Int64>(nXRefOffset)) + "\n%%EOF\n");
5730
0
}
5731
5732
namespace {
5733
5734
struct AnnotationSortEntry
5735
{
5736
    sal_Int32 nTabOrder;
5737
    sal_Int32 nObject;
5738
    sal_Int32 nWidgetIndex;
5739
5740
    AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
5741
0
        nTabOrder( nTab ),
5742
0
        nObject( nObj ),
5743
0
        nWidgetIndex( nI )
5744
0
    {}
5745
};
5746
5747
struct AnnotSortContainer
5748
{
5749
    o3tl::sorted_vector< sal_Int32 >      aObjects;
5750
    std::vector< AnnotationSortEntry >    aSortedAnnots;
5751
};
5752
5753
struct AnnotSorterLess
5754
{
5755
    std::vector<PDFWidget>& m_rWidgets;
5756
5757
0
    explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
5758
5759
    bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
5760
0
    {
5761
0
        if( rLeft.nTabOrder < rRight.nTabOrder )
5762
0
            return true;
5763
0
        if( rRight.nTabOrder < rLeft.nTabOrder )
5764
0
            return false;
5765
0
        if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
5766
0
            return false;
5767
0
        if( rRight.nWidgetIndex < 0 )
5768
0
            return true;
5769
0
        if( rLeft.nWidgetIndex < 0 )
5770
0
            return false;
5771
        // remember: widget rects are in PDF coordinates, so they are ordered down up
5772
0
        if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
5773
0
            m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
5774
0
            return true;
5775
0
        if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
5776
0
            m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
5777
0
            return false;
5778
0
        if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
5779
0
            m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
5780
0
            return true;
5781
0
        return false;
5782
0
    }
5783
};
5784
5785
}
5786
5787
void PDFWriterImpl::sortWidgets()
5788
8.85k
{
5789
    // sort widget annotations on each page as per their
5790
    // TabOrder attribute
5791
8.85k
    std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
5792
8.85k
    int nWidgets = m_aWidgets.size();
5793
8.85k
    for( int nW = 0; nW < nWidgets; nW++ )
5794
0
    {
5795
0
        const PDFWidget& rWidget = m_aWidgets[nW];
5796
0
        if( rWidget.m_nPage >= 0 )
5797
0
        {
5798
0
            AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
5799
            // optimize vector allocation
5800
0
            if( rCont.aSortedAnnots.empty() )
5801
0
                rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
5802
            // insert widget to tab sorter
5803
            // RadioButtons are not page annotations, only their individual check boxes are
5804
0
            if( rWidget.m_eType != PDFWriter::RadioButton )
5805
0
            {
5806
0
                rCont.aObjects.insert( rWidget.m_nObject );
5807
0
                rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
5808
0
            }
5809
0
        }
5810
0
    }
5811
8.85k
    for (auto & item : sorted)
5812
0
    {
5813
        // append entries for non widget annotations
5814
0
        PDFPage& rPage = m_aPages[ item.first ];
5815
0
        unsigned int nAnnots = rPage.m_aAnnotations.size();
5816
0
        for( unsigned int nA = 0; nA < nAnnots; nA++ )
5817
0
            if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
5818
0
                item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
5819
5820
0
        AnnotSorterLess aLess( m_aWidgets );
5821
0
        std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
5822
        // sanity check
5823
0
        if( item.second.aSortedAnnots.size() == nAnnots)
5824
0
        {
5825
0
            for( unsigned int nA = 0; nA < nAnnots; nA++ )
5826
0
                rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
5827
0
        }
5828
0
        else
5829
0
        {
5830
0
            SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
5831
0
            SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
5832
0
                     "on page nr " << item.first << ", " <<
5833
0
                     item.second.aSortedAnnots.size() << " sorted and " <<
5834
0
                     nAnnots << " unsorted");
5835
0
        }
5836
0
    }
5837
5838
    // FIXME: implement tab order in structure tree for PDF 1.5
5839
8.85k
}
5840
5841
bool PDFWriterImpl::emit()
5842
8.85k
{
5843
8.85k
    endPage();
5844
5845
    // resort structure tree and annotations if necessary
5846
    // needed for widget tab order
5847
8.85k
    sortWidgets();
5848
5849
#if HAVE_FEATURE_NSS
5850
    if( m_aContext.SignPDF )
5851
    {
5852
        // sign the document
5853
        PDFWriter::SignatureWidget aSignature;
5854
        aSignature.Name = "Signature1";
5855
        createControl( aSignature, 0 );
5856
    }
5857
#endif
5858
5859
    // emit catalog
5860
8.85k
    if (!emitCatalog())
5861
0
        return false;
5862
5863
#if HAVE_FEATURE_NSS
5864
    if (m_nSignatureObject != -1) // if document is signed, emit sigdict
5865
    {
5866
        if( !emitSignature() )
5867
        {
5868
            m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5869
            return false;
5870
        }
5871
    }
5872
#endif
5873
5874
    // emit trailer
5875
8.85k
    if (!emitTrailer())
5876
0
        return false;
5877
5878
#if HAVE_FEATURE_NSS
5879
    if (m_nSignatureObject != -1) // finalize the signature
5880
    {
5881
        if( !finalizeSignature() )
5882
        {
5883
            m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5884
            return false;
5885
        }
5886
    }
5887
#endif
5888
5889
8.85k
    m_aFile.close();
5890
8.85k
    m_bOpen = false;
5891
5892
8.85k
    return true;
5893
8.85k
}
5894
5895
5896
sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
5897
0
{
5898
0
    auto popIt = ScopedPush();
5899
5900
0
    SetFont( i_rFont );
5901
5902
0
    const LogicalFontInstance* pFontInstance = GetFontInstance();
5903
0
    const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
5904
0
    sal_Int32 nFontID = 0;
5905
0
    auto it = m_aSystemFonts.find( pFace );
5906
0
    if( it != m_aSystemFonts.end() )
5907
0
        nFontID = it->second.m_nNormalFontID;
5908
0
    else
5909
0
    {
5910
0
        nFontID = m_nNextFID++;
5911
0
        m_aSystemFonts[ pFace ] = EmbedFont();
5912
0
        m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
5913
0
        m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
5914
0
    }
5915
5916
0
    return nFontID;
5917
0
}
5918
5919
void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
5920
                                  const vcl::font::PhysicalFontFace* pFace,
5921
                                  const LogicalFontInstance* pFont,
5922
                                  const std::vector<sal_Ucs>& rCodeUnits,
5923
                                  sal_Int32 nGlyphWidth,
5924
                                  sal_uInt8& nMappedGlyph,
5925
                                  sal_Int32& nMappedFontObject)
5926
13.2M
{
5927
13.2M
    FontSubset& rSubset = m_aSubsets[ { pFace, pFont } ];
5928
5929
    // search for font specific glyphID
5930
13.2M
    auto it = rSubset.m_aMapping.find( nFontGlyphId );
5931
13.2M
    if( it != rSubset.m_aMapping.end() )
5932
12.7M
    {
5933
12.7M
        nMappedFontObject = it->second.m_nFontID;
5934
12.7M
        nMappedGlyph = it->second.m_nSubsetGlyphID;
5935
12.7M
    }
5936
489k
    else
5937
489k
    {
5938
        // create new subset if necessary
5939
489k
        if( rSubset.m_aSubsets.empty()
5940
472k
        || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
5941
16.3k
        {
5942
16.3k
            rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
5943
16.3k
        }
5944
5945
        // copy font id
5946
489k
        nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
5947
        // create new glyph in subset
5948
489k
        sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
5949
489k
        nMappedGlyph = nNewId;
5950
5951
        // add new glyph to emitted font subset
5952
489k
        GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
5953
489k
        rNewGlyphEmit.setGlyphId( nNewId );
5954
489k
        rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth));
5955
489k
        for (const auto nCode : rCodeUnits)
5956
487k
            rNewGlyphEmit.addCode(nCode);
5957
5958
        // add new glyph to font mapping
5959
489k
        Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
5960
489k
        rNewGlyph.m_nFontID = nMappedFontObject;
5961
489k
        rNewGlyph.m_nSubsetGlyphID = nNewId;
5962
489k
    }
5963
13.2M
}
5964
5965
void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId,
5966
                                  const vcl::font::PhysicalFontFace* pFace,
5967
                                  const LogicalFontInstance* pFont,
5968
                                  const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
5969
                                  sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
5970
13.2M
{
5971
13.2M
    if (pFace->IsColorFont())
5972
0
    {
5973
        // Font has colors, check if this glyph has color layers or bitmap.
5974
0
        tools::Rectangle aRect;
5975
0
        auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
5976
0
        auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
5977
0
        if (!aLayers.empty() || !aBitmap.empty())
5978
0
        {
5979
0
            auto& rSubset = m_aType3Fonts[pFace];
5980
0
            auto it = rSubset.m_aMapping.find(nFontGlyphId);
5981
0
            if (it != rSubset.m_aMapping.end())
5982
0
            {
5983
0
                nMappedFontObject = it->second.m_nFontID;
5984
0
                nMappedGlyph = it->second.m_nSubsetGlyphID;
5985
0
            }
5986
0
            else
5987
0
            {
5988
                // create new subset if necessary
5989
0
                if (rSubset.m_aSubsets.empty()
5990
0
                    || (rSubset.m_aSubsets.back().m_aMapping.size() > 254))
5991
0
                {
5992
0
                    rSubset.m_aSubsets.emplace_back(m_nNextFID++);
5993
0
                }
5994
5995
                // copy font id
5996
0
                nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
5997
                // create new glyph in subset
5998
0
                sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(
5999
0
                    rSubset.m_aSubsets.back().m_aMapping.size() + 1);
6000
0
                nMappedGlyph = nNewId;
6001
6002
                // add new glyph to emitted font subset
6003
0
                auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
6004
0
                rNewGlyphEmit.setGlyphId(nNewId);
6005
0
                rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
6006
0
                for (const auto nCode : rCodeUnits)
6007
0
                    rNewGlyphEmit.addCode(nCode);
6008
6009
                // add color layers to the glyphs
6010
0
                if (!aLayers.empty())
6011
0
                {
6012
0
                    for (const auto& aLayer : aLayers)
6013
0
                    {
6014
0
                        sal_uInt8 nLayerGlyph;
6015
0
                        sal_Int32 nLayerFontID;
6016
0
                        registerSimpleGlyph(aLayer.nGlyphIndex, pFace, pFont, rCodeUnits,
6017
0
                                            nGlyphWidth, nLayerGlyph, nLayerFontID);
6018
6019
0
                        rNewGlyphEmit.addColorLayer(
6020
0
                            { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
6021
0
                    }
6022
0
                }
6023
0
                else if (!aBitmap.empty())
6024
0
                    rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
6025
6026
                // add new glyph to font mapping
6027
0
                Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
6028
0
                rNewGlyph.m_nFontID = nMappedFontObject;
6029
0
                rNewGlyph.m_nSubsetGlyphID = nNewId;
6030
0
            }
6031
0
            return;
6032
0
        }
6033
0
    }
6034
6035
    // If we reach here then the glyph has no color layers.
6036
13.2M
    registerSimpleGlyph(nFontGlyphId, pFace, pFont, rCodeUnits, nGlyphWidth, nMappedGlyph,
6037
13.2M
                        nMappedFontObject);
6038
13.2M
}
6039
6040
void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6041
0
{
6042
0
    push( PushFlags::ALL );
6043
6044
0
    FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
6045
6046
0
    Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
6047
0
    Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6048
0
    Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6049
0
    Color aReliefColor( COL_LIGHTGRAY );
6050
0
    if( aTextColor == COL_BLACK )
6051
0
        aTextColor = COL_WHITE;
6052
0
    if( aTextLineColor == COL_BLACK )
6053
0
        aTextLineColor = COL_WHITE;
6054
0
    if( aOverlineColor == COL_BLACK )
6055
0
        aOverlineColor = COL_WHITE;
6056
    // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
6057
0
    if( aTextColor == COL_WHITE )
6058
0
        aReliefColor = COL_BLACK;
6059
6060
0
    Font aSetFont = m_aCurrentPDFState.m_aFont;
6061
0
    aSetFont.SetRelief( FontRelief::NONE );
6062
0
    aSetFont.SetShadow( false );
6063
6064
0
    aSetFont.SetColor( aReliefColor );
6065
0
    setTextLineColor( aReliefColor );
6066
0
    setOverlineColor( aReliefColor );
6067
0
    setFont( aSetFont );
6068
0
    tools::Long nOff = 1 + GetDPIX()/300;
6069
0
    if( eRelief == FontRelief::Engraved )
6070
0
        nOff = -nOff;
6071
6072
0
    auto aPrevOffset = rLayout.DrawOffset();
6073
0
    rLayout.DrawOffset()
6074
0
        += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) };
6075
0
    updateGraphicsState();
6076
0
    drawLayout( rLayout, rText, bTextLines );
6077
6078
0
    rLayout.DrawOffset() = aPrevOffset;
6079
0
    setTextLineColor( aTextLineColor );
6080
0
    setOverlineColor( aOverlineColor );
6081
0
    aSetFont.SetColor( aTextColor );
6082
0
    setFont( aSetFont );
6083
0
    updateGraphicsState();
6084
0
    drawLayout( rLayout, rText, bTextLines );
6085
6086
    // clean up the mess
6087
0
    pop();
6088
0
}
6089
6090
void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6091
2.69k
{
6092
2.69k
    Font aSaveFont = m_aCurrentPDFState.m_aFont;
6093
2.69k
    Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6094
2.69k
    Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6095
6096
2.69k
    Font& rFont = m_aCurrentPDFState.m_aFont;
6097
2.69k
    if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
6098
420
        rFont.SetColor( COL_LIGHTGRAY );
6099
2.27k
    else
6100
2.27k
        rFont.SetColor( COL_BLACK );
6101
2.69k
    rFont.SetShadow( false );
6102
2.69k
    rFont.SetOutline( false );
6103
2.69k
    setFont( rFont );
6104
2.69k
    setTextLineColor( rFont.GetColor() );
6105
2.69k
    setOverlineColor( rFont.GetColor() );
6106
2.69k
    updateGraphicsState();
6107
6108
2.69k
    tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
6109
2.69k
    if( rFont.IsOutline() )
6110
0
        nOff++;
6111
2.69k
    rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff);
6112
2.69k
    drawLayout( rLayout, rText, bTextLines );
6113
2.69k
    rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff);
6114
6115
2.69k
    setFont( aSaveFont );
6116
2.69k
    setTextLineColor( aSaveTextLineColor );
6117
2.69k
    setOverlineColor( aSaveOverlineColor );
6118
2.69k
    updateGraphicsState();
6119
2.69k
}
6120
6121
void PDFWriterImpl::drawVerticalGlyphs(
6122
        const std::vector<PDFGlyph>& rGlyphs,
6123
        OStringBuffer& rLine,
6124
        const Point& rAlignOffset,
6125
        const Matrix3& rRotScale,
6126
        double fAngle,
6127
        double fXScale,
6128
        sal_Int32 nFontHeight)
6129
0
{
6130
0
    double nXOffset = 0;
6131
0
    Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
6132
0
    aCurPos += rAlignOffset;
6133
0
    for( size_t i = 0; i < rGlyphs.size(); i++ )
6134
0
    {
6135
        // have to emit each glyph on its own
6136
0
        double fDeltaAngle = 0.0;
6137
0
        double fYScale = 1.0;
6138
0
        double fTempXScale = fXScale;
6139
6140
0
        Point aDeltaPos;
6141
0
        if (rGlyphs[i].m_pGlyph->IsVertical())
6142
0
        {
6143
0
            fDeltaAngle = M_PI/2.0;
6144
0
            fYScale = fXScale;
6145
0
            fTempXScale = 1.0;
6146
0
        }
6147
0
        aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
6148
0
        if( i < rGlyphs.size()-1 )
6149
        // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
6150
0
        {
6151
0
            double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
6152
0
            double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
6153
0
            nXOffset += std::hypot(nOffsetX, nOffsetY);
6154
0
        }
6155
0
        if (!rGlyphs[i].m_pGlyph->glyphId())
6156
0
            continue;
6157
6158
0
        aDeltaPos = rRotScale.transform( aDeltaPos );
6159
6160
0
        Matrix3 aMat;
6161
        // perform artificial italics if necessary
6162
0
        if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
6163
0
            aMat.skew(0.0, ARTIFICIAL_ITALIC_SKEW);
6164
0
        aMat.scale( fTempXScale, fYScale );
6165
0
        aMat.rotate( fAngle+fDeltaAngle );
6166
0
        aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
6167
0
        m_aPages.back().appendMatrix3(aMat, rLine);
6168
0
        rLine.append( " Tm" );
6169
0
        if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
6170
0
        {
6171
0
            rLine.append( " /F" );
6172
0
            rLine.append( rGlyphs[i].m_nMappedFontId );
6173
0
            rLine.append( ' ' );
6174
0
            m_aPages.back().appendMappedLength( nFontHeight, rLine );
6175
0
            rLine.append( " Tf" );
6176
0
        }
6177
0
        rLine.append( "<" );
6178
0
        COSWriter::appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
6179
0
        rLine.append( ">Tj\n" );
6180
0
    }
6181
0
}
6182
6183
void PDFWriterImpl::drawHorizontalGlyphs(
6184
        const std::vector<PDFGlyph>& rGlyphs,
6185
        OStringBuffer& rLine,
6186
        const Point& rAlignOffset,
6187
        bool bFirst,
6188
        double fAngle,
6189
        double fXScale,
6190
        sal_Int32 nFontHeight,
6191
        sal_Int32 nPixelFontHeight)
6192
733k
{
6193
    // horizontal (= normal) case
6194
6195
    // fill in  run end indices
6196
    // end is marked by index of the first glyph of the next run
6197
    // a run is marked by same mapped font id and same Y position
6198
733k
    std::vector< sal_uInt32 > aRunEnds;
6199
733k
    aRunEnds.reserve( rGlyphs.size() );
6200
13.2M
    for( size_t i = 1; i < rGlyphs.size(); i++ )
6201
12.5M
    {
6202
12.5M
        if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6203
12.5M
            rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont ||
6204
12.5M
            rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
6205
2.73k
        {
6206
2.73k
            aRunEnds.push_back(i);
6207
2.73k
        }
6208
12.5M
    }
6209
    // last run ends at last glyph
6210
733k
    aRunEnds.push_back( rGlyphs.size() );
6211
6212
    // loop over runs of the same font
6213
733k
    sal_uInt32 nBeginRun = 0;
6214
1.47M
    for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6215
736k
    {
6216
        // setup text matrix back transformed to current coordinate system
6217
736k
        Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
6218
736k
        aCurPos += rAlignOffset;
6219
6220
        // perform artificial italics if necessary
6221
736k
        double fSkew = 0.0;
6222
736k
        if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
6223
0
            fSkew = ARTIFICIAL_ITALIC_SKEW;
6224
6225
        // the first run can be set with "Td" operator
6226
        // subsequent use of that operator would move
6227
        // the textline matrix relative to what was set before
6228
        // making use of that would drive us into rounding issues
6229
736k
        Matrix3 aMat;
6230
736k
        if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6231
665k
        {
6232
665k
            m_aPages.back().appendPoint( aCurPos, rLine );
6233
665k
            rLine.append( " Td " );
6234
665k
        }
6235
71.2k
        else
6236
71.2k
        {
6237
71.2k
            if( fSkew != 0.0 )
6238
0
                aMat.skew( 0.0, fSkew );
6239
71.2k
            aMat.scale( fXScale, 1.0 );
6240
71.2k
            aMat.rotate( fAngle );
6241
71.2k
            aMat.translate( aCurPos.X(), aCurPos.Y() );
6242
71.2k
            m_aPages.back().appendMatrix3(aMat, rLine);
6243
71.2k
            rLine.append( " Tm\n" );
6244
71.2k
        }
6245
        // set up correct font
6246
736k
        rLine.append( "/F" );
6247
736k
        rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6248
736k
        rLine.append( ' ' );
6249
736k
        m_aPages.back().appendMappedLength( nFontHeight, rLine );
6250
736k
        rLine.append( " Tf" );
6251
6252
        // output glyphs using Tj or TJ
6253
736k
        OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6254
736k
        aKernedLine.append( "[<" );
6255
736k
        aUnkernedLine.append( '<' );
6256
736k
        COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6257
736k
        COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6258
6259
736k
        aMat.invert();
6260
736k
        bool bNeedKern = false;
6261
13.2M
        for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6262
12.5M
        {
6263
12.5M
            COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6264
            // check if default glyph positioning is sufficient
6265
12.5M
            const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6266
12.5M
            const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6267
12.5M
            double fAdvance = aThisPos.getX() - aPrevPos.getX();
6268
12.5M
            fAdvance *= 1000.0 / nPixelFontHeight;
6269
12.5M
            const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6270
12.5M
            SAL_WARN_IF(
6271
12.5M
                fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6272
12.5M
                "adjustment " << fAdjustment << " outside 32-bit int");
6273
12.5M
            const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6274
12.5M
                std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6275
12.5M
            if( nAdjustment != 0 )
6276
8.60M
            {
6277
                // apply individual glyph positioning
6278
8.60M
                bNeedKern = true;
6279
8.60M
                aKernedLine.append( ">" );
6280
8.60M
                aKernedLine.append( nAdjustment );
6281
8.60M
                aKernedLine.append( "<" );
6282
8.60M
            }
6283
12.5M
            COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6284
12.5M
        }
6285
736k
        aKernedLine.append( ">]TJ\n" );
6286
736k
        aUnkernedLine.append( ">Tj\n" );
6287
736k
        rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
6288
6289
        // set beginning of next run
6290
736k
        nBeginRun = aRunEnds[nRun];
6291
736k
    }
6292
733k
}
6293
6294
void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6295
665k
{
6296
    // relief takes precedence over shadow (see outdev3.cxx)
6297
665k
    if(  m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6298
0
    {
6299
0
        drawRelief( rLayout, rText, bTextLines );
6300
0
        return;
6301
0
    }
6302
665k
    else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6303
2.69k
        drawShadow( rLayout, rText, bTextLines );
6304
6305
665k
    OStringBuffer aLine( 512 );
6306
6307
665k
    const int nMaxGlyphs = 256;
6308
6309
665k
    std::vector<sal_Ucs> aCodeUnits;
6310
665k
    bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6311
665k
    int nIndex = 0;
6312
665k
    double fXScale = 1.0;
6313
665k
    sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6314
665k
    TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6315
6316
    // transform font height back to current units
6317
    // note: the layout calculates in outdevs device pixel !!
6318
665k
    sal_Int32 nFontHeight = DevicePixelToLogicHeight(nPixelFontHeight);
6319
665k
    if( m_aCurrentPDFState.m_aFont.GetFontWidth() )
6320
19
    {
6321
19
        Font aFont( m_aCurrentPDFState.m_aFont );
6322
19
        aFont.SetFontWidth( 0 );
6323
19
        FontMetric aMetric = GetFontMetric( aFont );
6324
19
        if( aMetric.GetFontWidth() != m_aCurrentPDFState.m_aFont.GetFontWidth() )
6325
19
        {
6326
19
            fXScale =
6327
19
                static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontWidth()) /
6328
19
                static_cast<double>(aMetric.GetFontWidth());
6329
19
        }
6330
19
    }
6331
6332
    // if the mapmode is distorted we need to adjust for that also
6333
665k
    if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6334
35
    {
6335
35
        fXScale *= m_aCurrentPDFState.m_aMapMode.GetScaleX() / m_aCurrentPDFState.m_aMapMode.GetScaleY();
6336
35
    }
6337
6338
665k
    Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6339
    // normalize angles
6340
665k
    while( nAngle < 0_deg10 )
6341
2
        nAngle += 3600_deg10;
6342
665k
    nAngle = nAngle % 3600_deg10;
6343
665k
    double fAngle = toRadians(nAngle);
6344
6345
665k
    Matrix3 aRotScale;
6346
665k
    aRotScale.scale( fXScale, 1.0 );
6347
665k
    if( fAngle != 0.0 )
6348
2
        aRotScale.rotate( -fAngle );
6349
6350
665k
    bool bPop = false;
6351
665k
    bool bABold = false;
6352
    // artificial bold necessary ?
6353
665k
    if (GetFontInstance()->NeedsArtificialBold())
6354
0
    {
6355
0
        aLine.append("q ");
6356
0
        bPop = true;
6357
0
        bABold = true;
6358
0
    }
6359
    // setup text colors (if necessary)
6360
665k
    Color aStrokeColor( COL_TRANSPARENT );
6361
665k
    Color aNonStrokeColor( COL_TRANSPARENT );
6362
6363
665k
    if( m_aCurrentPDFState.m_aFont.IsOutline() )
6364
343
    {
6365
343
        aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6366
343
        aNonStrokeColor = COL_WHITE;
6367
343
    }
6368
665k
    else
6369
665k
        aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6370
665k
    if( bABold )
6371
0
        aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6372
6373
665k
    if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6374
343
    {
6375
343
        if( ! bPop )
6376
343
            aLine.append( "q " );
6377
343
        bPop = true;
6378
343
        appendStrokingColor( aStrokeColor, aLine );
6379
343
        aLine.append( "\n" );
6380
343
    }
6381
665k
    if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6382
665k
    {
6383
665k
        if( ! bPop )
6384
665k
            aLine.append( "q " );
6385
665k
        bPop = true;
6386
665k
        appendNonStrokingColor( aNonStrokeColor, aLine );
6387
665k
        aLine.append( "\n" );
6388
665k
    }
6389
6390
    // begin text object
6391
665k
    aLine.append( "BT\n" );
6392
    // outline attribute ?
6393
665k
    if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6394
343
    {
6395
        // set correct text mode, set stroke width
6396
343
        aLine.append( "2 Tr " ); // fill, then stroke
6397
6398
343
        if( m_aCurrentPDFState.m_aFont.IsOutline() )
6399
343
        {
6400
            // unclear what to do in case of outline and artificial bold
6401
            // for the time being outline wins
6402
343
            aLine.append( "0.25 w \n" );
6403
343
        }
6404
0
        else
6405
0
        {
6406
0
            double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6407
0
            m_aPages.back().appendMappedLength( fW, aLine );
6408
0
            aLine.append ( " w\n" );
6409
0
        }
6410
343
    }
6411
6412
665k
    FontMetric aRefDevFontMetric = GetFontMetric();
6413
665k
    const GlyphItem* pGlyph = nullptr;
6414
665k
    const LogicalFontInstance* pGlyphFont = nullptr;
6415
6416
    // collect the glyphs into a single array
6417
665k
    std::vector< PDFGlyph > aGlyphs;
6418
665k
    aGlyphs.reserve( nMaxGlyphs );
6419
    // first get all the glyphs and register them; coordinates still in Pixel
6420
665k
    basegfx::B2DPoint aPos;
6421
13.9M
    while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6422
13.2M
    {
6423
13.2M
        const auto* pFace = pGlyphFont->GetFontFace();
6424
6425
13.2M
        aCodeUnits.clear();
6426
6427
        // tdf#66597, tdf#115117
6428
        //
6429
        // Here is how we embed textual content in PDF files, to allow for
6430
        // better text extraction for complex and typography-rich text.
6431
        //
6432
        // * If there is many to one or many to many mapping, use an
6433
        //   ActualText span embedding the original string, since ToUnicode
6434
        //   can't handle these.
6435
        // * If the one glyph is used for several Unicode code points, also
6436
        //   use ActualText since ToUnicode can map each glyph in the font
6437
        //   only once.
6438
        // * Limit ActualText to single cluster at a time, since using it
6439
        //   for whole words or sentences breaks text selection and
6440
        //   highlighting in PDF viewers (there will be no way to tell
6441
        //   which glyphs belong to which characters).
6442
6443
13.2M
        assert(pGlyph->charCount() >= 0);
6444
26.5M
        for (int n = 0; n < pGlyph->charCount(); n++)
6445
13.2M
            aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6446
6447
13.2M
        bool bUseActualText = false;
6448
6449
        // If this is a start of complex cluster, use ActualText.
6450
13.2M
        if (pGlyph->IsClusterStart())
6451
7.11k
            bUseActualText = true;
6452
6453
13.2M
        const auto nGlyphId = pGlyph->glyphId();
6454
6455
        // A glyph can't have more than one ToUnicode entry, use ActualText
6456
        // instead.
6457
13.2M
        if (!aCodeUnits.empty() && !bUseActualText)
6458
13.2M
        {
6459
13.2M
            for (const auto& rSubset : m_aSubsets[{ pFace, pGlyphFont }].m_aSubsets)
6460
13.2M
            {
6461
13.2M
                const auto it = rSubset.m_aMapping.find(nGlyphId);
6462
13.2M
                if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6463
50.4k
                    bUseActualText = true;
6464
13.2M
            }
6465
13.2M
        }
6466
6467
        // If we are using ActualText, then ToUnicode mapping is broken, drop it.
6468
13.2M
        if (bUseActualText)
6469
57.6k
            aCodeUnits.clear();
6470
6471
        // tdf#157390: The width stored by registerGlyph() must be the actual glyph width.
6472
        // This must be obtained by calling GetGlyphWidth(vertical=false), otherwise an incorrect
6473
        // width value will be returned for CJK characters.
6474
13.2M
        auto nMappedGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ false, false);
6475
13.2M
        auto nGlyphWidth = nMappedGlyphWidth;
6476
13.2M
        if (pGlyph->IsVertical())
6477
0
        {
6478
0
            nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ true, false);
6479
0
        }
6480
6481
13.2M
        sal_uInt8 nMappedGlyph;
6482
13.2M
        sal_Int32 nMappedFontObject;
6483
13.2M
        registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nMappedGlyphWidth, nMappedGlyph,
6484
13.2M
                      nMappedFontObject);
6485
6486
13.2M
        int nCharPos = -1;
6487
13.2M
        if (bUseActualText || pGlyph->IsInCluster())
6488
64.8k
            nCharPos = pGlyph->charPos();
6489
6490
13.2M
        aGlyphs.emplace_back(aPos,
6491
13.2M
                             pGlyph,
6492
13.2M
                             pGlyphFont,
6493
13.2M
                             XUnits(pFace->UnitsPerEm(), nGlyphWidth),
6494
13.2M
                             nMappedFontObject,
6495
13.2M
                             nMappedGlyph,
6496
13.2M
                             nCharPos);
6497
13.2M
    }
6498
6499
    // Avoid fill color when map mode is in pixels, the below code assumes
6500
    // logic map mode.
6501
665k
    bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6502
665k
    if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6503
0
    {
6504
        // PDF doesn't have a text fill color, so draw a rectangle before
6505
        // drawing the actual text.
6506
0
        push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6507
0
        setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6508
        // Avoid border around the rectangle for Writer shape text.
6509
0
        setLineColor(COL_TRANSPARENT);
6510
6511
        // The rectangle is the bounding box of the text, but also includes
6512
        // ascent / descent to match the on-screen rendering.
6513
        // This is the top left of the text without ascent / descent.
6514
0
        basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
6515
0
        tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
6516
0
                                    Size(DevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
6517
0
        aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
6518
        // This includes ascent / descent.
6519
0
        aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6520
6521
0
        const LogicalFontInstance* pFontInstance = GetFontInstance();
6522
0
        if (pFontInstance->mnOrientation)
6523
0
        {
6524
            // Adapt rectangle for rotated text.
6525
0
            tools::Polygon aPolygon(aRectangle);
6526
0
            aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation);
6527
0
            drawPolygon(aPolygon);
6528
0
        }
6529
0
        else
6530
0
            drawRectangle(aRectangle);
6531
6532
0
        pop();
6533
0
    }
6534
6535
665k
    Point aAlignOffset;
6536
665k
    if ( eAlign == ALIGN_BOTTOM )
6537
0
        aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6538
665k
    else if ( eAlign == ALIGN_TOP )
6539
0
        aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6540
665k
    if( aAlignOffset.X() || aAlignOffset.Y() )
6541
0
        aAlignOffset = aRotScale.transform( aAlignOffset );
6542
6543
    /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6544
       string contained only one of the UTF16 BOMs
6545
    */
6546
665k
    if( ! aGlyphs.empty() )
6547
665k
    {
6548
665k
        size_t nStart = 0;
6549
665k
        size_t nEnd = 0;
6550
1.39M
        while (nStart < aGlyphs.size())
6551
733k
        {
6552
14.0M
            while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6553
13.2M
                nEnd++;
6554
6555
733k
            std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6556
6557
733k
            int nCharPos, nCharCount;
6558
733k
            if (!aRun.front().m_pGlyph->IsRTLGlyph())
6559
679k
            {
6560
679k
                nCharPos = aRun.front().m_nCharPos;
6561
679k
                nCharCount = aRun.front().m_pGlyph->charCount();
6562
679k
            }
6563
54.4k
            else
6564
54.4k
            {
6565
54.4k
                nCharPos = aRun.back().m_nCharPos;
6566
54.4k
                nCharCount = aRun.back().m_pGlyph->charCount();
6567
54.4k
            }
6568
6569
733k
            if (nCharPos >= 0 && nCharCount)
6570
57.6k
            {
6571
57.6k
                aLine.append("/Span<</ActualText<FEFF");
6572
127k
                for (int i = 0; i < nCharCount; i++)
6573
69.7k
                {
6574
69.7k
                    sal_Unicode aChar = rText[nCharPos + i];
6575
69.7k
                    COSWriter::appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6576
69.7k
                    COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6577
69.7k
                }
6578
57.6k
                aLine.append( ">>>\nBDC\n" );
6579
57.6k
            }
6580
6581
733k
            if (bVertical)
6582
0
                drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight);
6583
733k
            else
6584
733k
                drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight);
6585
6586
733k
            if (nCharPos >= 0 && nCharCount)
6587
57.6k
                aLine.append( "EMC\n" );
6588
6589
733k
            nStart = nEnd;
6590
733k
        }
6591
665k
    }
6592
6593
    // end textobject
6594
665k
    aLine.append( "ET\n" );
6595
665k
    if( bPop )
6596
665k
        aLine.append( "Q\n" );
6597
6598
665k
    writeBuffer( aLine );
6599
6600
    // draw eventual textlines
6601
665k
    FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6602
665k
    FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6603
665k
    FontLineStyle eOverline  = m_aCurrentPDFState.m_aFont.GetOverline();
6604
665k
    if( bTextLines &&
6605
665k
        (
6606
665k
         ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6607
656k
         ( eOverline  != LINESTYLE_NONE && eOverline  != LINESTYLE_DONTKNOW ) ||
6608
656k
         ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6609
665k
         )
6610
665k
        )
6611
10.1k
    {
6612
10.1k
        bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
6613
10.1k
        if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6614
290
        {
6615
290
            basegfx::B2DPoint aStartPt;
6616
290
            double nWidth = 0;
6617
290
            nIndex = 0;
6618
7.97k
            while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6619
7.68k
            {
6620
7.68k
                if (!pGlyph->IsSpacing())
6621
6.74k
                {
6622
6.74k
                    if( !nWidth )
6623
1.07k
                        aStartPt = aPos;
6624
6625
6.74k
                    nWidth += pGlyph->newWidth();
6626
6.74k
                }
6627
937
                else if( nWidth > 0 )
6628
880
                {
6629
880
                    drawTextLine( SubPixelToLogic(aStartPt),
6630
880
                                  DevicePixelToLogicWidth(nWidth),
6631
880
                                  eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6632
880
                    nWidth = 0;
6633
880
                }
6634
7.68k
            }
6635
6636
290
            if( nWidth > 0 )
6637
194
            {
6638
194
                drawTextLine( SubPixelToLogic(aStartPt),
6639
194
                              DevicePixelToLogicWidth(nWidth),
6640
194
                              eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6641
194
            }
6642
290
        }
6643
9.81k
        else
6644
9.81k
        {
6645
9.81k
            basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
6646
9.81k
            int nWidth = rLayout.GetTextWidth();
6647
9.81k
            drawTextLine( SubPixelToLogic(aStartPt),
6648
9.81k
                          DevicePixelToLogicWidth(nWidth),
6649
9.81k
                          eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6650
9.81k
        }
6651
10.1k
    }
6652
6653
    // write eventual emphasis marks
6654
665k
    if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
6655
665k
        return;
6656
6657
0
    push( PushFlags::ALL );
6658
6659
0
    aLine.setLength( 0 );
6660
0
    aLine.append( "q\n" );
6661
6662
0
    FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
6663
6664
0
    tools::Long nEmphHeight;
6665
0
    if ( nEmphMark & FontEmphasisMark::PosBelow )
6666
0
        nEmphHeight = GetEmphasisDescent();
6667
0
    else
6668
0
        nEmphHeight = GetEmphasisAscent();
6669
6670
0
    vcl::font::EmphasisMark aEmphasisMark(nEmphMark, DevicePixelToLogicWidth(nEmphHeight), GetDPIY());
6671
0
    if ( aEmphasisMark.IsShapePolyLine() )
6672
0
    {
6673
0
        setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
6674
0
        setFillColor( COL_TRANSPARENT );
6675
0
    }
6676
0
    else
6677
0
    {
6678
0
        setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
6679
0
        setLineColor( COL_TRANSPARENT );
6680
0
    }
6681
6682
0
    writeBuffer( aLine );
6683
6684
0
    Point aOffset(0,0);
6685
0
    Point aOffsetVert(0,0);
6686
6687
0
    if ( nEmphMark & FontEmphasisMark::PosBelow )
6688
0
    {
6689
0
        aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
6690
0
        aOffsetVert = aOffset;
6691
0
    }
6692
0
    else
6693
0
    {
6694
0
        aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
6695
        // Todo: use ideographic em-box or ideographic character face information.
6696
0
        aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
6697
0
                    GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
6698
0
    }
6699
6700
0
    tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2;
6701
0
    tools::Long nEmphHeight2 = nEmphHeight / 2;
6702
0
    aOffset += Point( nEmphWidth2, nEmphHeight2 );
6703
6704
0
    if ( eAlign == ALIGN_BOTTOM )
6705
0
        aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
6706
0
    else if ( eAlign == ALIGN_TOP )
6707
0
        aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
6708
6709
0
    basegfx::B2DRectangle aRectangle;
6710
0
    nIndex = 0;
6711
0
    while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6712
0
    {
6713
0
        if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
6714
0
            continue;
6715
6716
0
        if (!pGlyph->IsSpacing())
6717
0
        {
6718
0
            basegfx::B2DPoint aAdjOffset;
6719
0
            if (pGlyph->IsVertical())
6720
0
            {
6721
0
                aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
6722
0
                aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
6723
0
            }
6724
0
            else
6725
0
            {
6726
0
                aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
6727
0
                aAdjOffset.adjustX(aRectangle.getMinX() + (aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 );
6728
0
            }
6729
6730
0
            aAdjOffset = aRotScale.transform( aAdjOffset );
6731
6732
0
            aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
6733
6734
0
            basegfx::B2DPoint aMarkDevPos(aPos);
6735
0
            aMarkDevPos += aAdjOffset;
6736
0
            Point aMarkPos = SubPixelToLogic(aMarkDevPos);
6737
0
            drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
6738
0
                              aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
6739
0
                              aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
6740
0
        }
6741
0
    }
6742
6743
0
    writeBuffer( "Q\n" );
6744
0
    pop();
6745
6746
0
}
6747
6748
void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
6749
                                      const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
6750
                                      const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
6751
0
{
6752
    // TODO: pass nWidth as width of this mark
6753
    // long nWidth = 0;
6754
6755
0
    if ( rPolyPoly.Count() )
6756
0
    {
6757
0
        if ( bPolyLine )
6758
0
        {
6759
0
            tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
6760
0
            aPoly.Move( nX, nY );
6761
0
            drawPolyLine( aPoly );
6762
0
        }
6763
0
        else
6764
0
        {
6765
0
            tools::PolyPolygon aPolyPoly = rPolyPoly;
6766
0
            aPolyPoly.Move( nX, nY );
6767
0
            drawPolyPolygon( aPolyPoly );
6768
0
        }
6769
0
    }
6770
6771
0
    if ( !rRect1.IsEmpty() )
6772
0
    {
6773
0
        tools::Rectangle aRect( Point( nX+rRect1.Left(),
6774
0
                                nY+rRect1.Top() ), rRect1.GetSize() );
6775
0
        drawRectangle( aRect );
6776
0
    }
6777
6778
0
    if ( !rRect2.IsEmpty() )
6779
0
    {
6780
0
        tools::Rectangle aRect( Point( nX+rRect2.Left(),
6781
0
                                nY+rRect2.Top() ), rRect2.GetSize() );
6782
6783
0
        drawRectangle( aRect );
6784
0
    }
6785
0
}
6786
6787
void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
6788
0
{
6789
0
    MARK( "drawText" );
6790
6791
0
    updateGraphicsState();
6792
6793
    // get a layout from the OutputDevice's SalGraphics
6794
    // this also enforces font substitution and sets the font on SalGraphics
6795
0
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
6796
0
        GetLayoutGlyphs( this, rText, nIndex, nLen );
6797
0
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
6798
0
        0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
6799
0
    if( pLayout )
6800
0
    {
6801
0
        drawLayout( *pLayout, rText, bTextLines );
6802
0
    }
6803
0
}
6804
6805
void PDFWriterImpl::drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray,
6806
                                  std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
6807
                                  sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
6808
                                  sal_Int32 nLayoutContextLen)
6809
657k
{
6810
657k
    MARK( "drawText with array" );
6811
6812
657k
    updateGraphicsState();
6813
6814
    // get a layout from the OutputDevice's SalGraphics
6815
    // this also enforces font substitution and sets the font on SalGraphics
6816
657k
    std::unique_ptr<SalLayout> pLayout;
6817
657k
    if (nLayoutContextIndex >= 0)
6818
493k
    {
6819
493k
        const auto* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
6820
493k
            this, rText, nLayoutContextIndex, nLayoutContextLen, nIndex, nIndex + nLen);
6821
493k
        pLayout = ImplLayout(rText, nLayoutContextIndex, nLayoutContextLen, rPos, 0, pDXArray,
6822
493k
                             pKashidaArray, SalLayoutFlags::UnclusteredGlyphs, nullptr,
6823
493k
                             layoutGlyphs, /*nDrawOriginCluster=*/nIndex,
6824
493k
                             /*nDrawMinCharPos=*/nIndex, /*nDrawEndCharPos=*/nIndex + nLen);
6825
493k
    }
6826
164k
    else
6827
164k
    {
6828
164k
        const auto* layoutGlyphs
6829
164k
            = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(this, rText, nIndex, nLen);
6830
164k
        pLayout = ImplLayout(rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
6831
164k
                             SalLayoutFlags::NONE, nullptr, layoutGlyphs);
6832
164k
    }
6833
6834
657k
    if( pLayout )
6835
657k
    {
6836
657k
        drawLayout( *pLayout, rText, true );
6837
657k
    }
6838
657k
}
6839
6840
void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
6841
5.43k
{
6842
5.43k
    MARK( "drawStretchText" );
6843
6844
5.43k
    updateGraphicsState();
6845
6846
    // get a layout from the OutputDevice's SalGraphics
6847
    // this also enforces font substitution and sets the font on SalGraphics
6848
5.43k
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
6849
5.43k
        GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
6850
5.43k
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
6851
5.43k
        {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
6852
5.43k
    if( pLayout )
6853
5.43k
    {
6854
5.43k
        drawLayout( *pLayout, rText, true );
6855
5.43k
    }
6856
5.43k
}
6857
6858
void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
6859
0
{
6860
0
    tools::Long        nWidth          = rRect.GetWidth();
6861
0
    tools::Long        nHeight         = rRect.GetHeight();
6862
6863
0
    if ( nWidth <= 0 || nHeight <= 0 )
6864
0
        return;
6865
6866
0
    MARK( "drawText with rectangle" );
6867
6868
0
    updateGraphicsState();
6869
6870
    // clip with rectangle
6871
0
    OStringBuffer aLine;
6872
0
    aLine.append( "q " );
6873
0
    m_aPages.back().appendRect( rRect, aLine );
6874
0
    aLine.append( " W* n\n" );
6875
0
    writeBuffer( aLine );
6876
6877
    // if disabled text is needed, put in here
6878
6879
0
    Point       aPos            = rRect.TopLeft();
6880
6881
0
    tools::Long        nTextHeight     = GetTextHeight();
6882
6883
0
    OUString aStr = rOrigStr;
6884
0
    if ( nStyle & DrawTextFlags::Mnemonic )
6885
0
        aStr = removeMnemonicFromString(aStr);
6886
6887
    // multiline text
6888
0
    if ( nStyle & DrawTextFlags::MultiLine )
6889
0
    {
6890
0
        ImplMultiTextLineInfo   aMultiLineInfo;
6891
0
        sal_Int32               i;
6892
0
        sal_Int32               nFormatLines;
6893
6894
0
        if ( nTextHeight )
6895
0
        {
6896
0
            vcl::DefaultTextLayout aLayout( *this );
6897
0
            OUString               aLastLine;
6898
0
            aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle );
6899
0
            sal_Int32              nLines = nHeight/nTextHeight;
6900
0
            nFormatLines = aMultiLineInfo.Count();
6901
0
            if ( !nLines )
6902
0
                nLines = 1;
6903
0
            if ( nFormatLines > nLines )
6904
0
            {
6905
0
                if ( nStyle & DrawTextFlags::EndEllipsis )
6906
0
                {
6907
                    // handle last line
6908
0
                    nFormatLines = nLines-1;
6909
6910
0
                    ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
6911
0
                    aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
6912
                    // replace line feed by space
6913
0
                    aLastLine = aLastLine.replace('\n', ' ');
6914
0
                    aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
6915
0
                    nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
6916
0
                    nStyle |= DrawTextFlags::Top;
6917
0
                }
6918
0
            }
6919
6920
            // vertical alignment
6921
0
            if ( nStyle & DrawTextFlags::Bottom )
6922
0
                aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
6923
0
            else if ( nStyle & DrawTextFlags::VCenter )
6924
0
                aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
6925
6926
            // draw all lines excluding the last
6927
0
            for ( i = 0; i < nFormatLines; i++ )
6928
0
            {
6929
0
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
6930
0
                if ( nStyle & DrawTextFlags::Right )
6931
0
                    aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
6932
0
                else if ( nStyle & DrawTextFlags::Center )
6933
0
                    aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
6934
0
                sal_Int32 nIndex = rLineInfo.GetIndex();
6935
0
                sal_Int32 nLineLen = rLineInfo.GetLen();
6936
0
                drawText( aPos, aStr, nIndex, nLineLen );
6937
                // mnemonics should not appear in documents,
6938
                // if the need arises, put them in here
6939
0
                aPos.AdjustY(nTextHeight );
6940
0
                aPos.setX( rRect.Left() );
6941
0
            }
6942
6943
            // output last line left adjusted since it was shortened
6944
0
            if (!aLastLine.isEmpty())
6945
0
                drawText( aPos, aLastLine, 0, aLastLine.getLength() );
6946
0
        }
6947
0
    }
6948
0
    else
6949
0
    {
6950
0
        tools::Long nTextWidth = GetTextWidth( aStr );
6951
6952
        // Evt. Text kuerzen
6953
0
        if ( nTextWidth > nWidth )
6954
0
        {
6955
0
            if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
6956
0
            {
6957
0
                aStr = GetEllipsisString( aStr, nWidth, nStyle );
6958
0
                nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
6959
0
                nStyle |= DrawTextFlags::Left;
6960
0
                nTextWidth = GetTextWidth( aStr );
6961
0
            }
6962
0
        }
6963
6964
        // vertical alignment
6965
0
        if ( nStyle & DrawTextFlags::Right )
6966
0
            aPos.AdjustX(nWidth-nTextWidth );
6967
0
        else if ( nStyle & DrawTextFlags::Center )
6968
0
            aPos.AdjustX((nWidth-nTextWidth)/2 );
6969
6970
0
        if ( nStyle & DrawTextFlags::Bottom )
6971
0
            aPos.AdjustY(nHeight-nTextHeight );
6972
0
        else if ( nStyle & DrawTextFlags::VCenter )
6973
0
            aPos.AdjustY((nHeight-nTextHeight)/2 );
6974
6975
        // mnemonics should be inserted here if the need arises
6976
6977
        // draw the actual text
6978
0
        drawText( aPos, aStr, 0, aStr.getLength() );
6979
0
    }
6980
6981
    // reset clip region to original value
6982
0
    aLine.setLength( 0 );
6983
0
    aLine.append( "Q\n" );
6984
0
    writeBuffer( aLine );
6985
0
}
6986
6987
void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
6988
0
{
6989
0
    MARK( "drawLine" );
6990
6991
0
    updateGraphicsState();
6992
6993
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6994
0
        return;
6995
6996
0
    OStringBuffer aLine;
6997
0
    m_aPages.back().appendPoint( rStart, aLine );
6998
0
    aLine.append( " m " );
6999
0
    m_aPages.back().appendPoint( rStop, aLine );
7000
0
    aLine.append( " l S\n" );
7001
7002
0
    writeBuffer( aLine );
7003
0
}
7004
7005
void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
7006
0
{
7007
0
    MARK( "drawLine with LineInfo" );
7008
0
    updateGraphicsState();
7009
7010
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7011
0
        return;
7012
7013
0
    if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
7014
0
    {
7015
0
        drawLine( rStart, rStop );
7016
0
        return;
7017
0
    }
7018
7019
0
    OStringBuffer aLine;
7020
7021
0
    aLine.append( "q " );
7022
0
    if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7023
0
    {
7024
0
        m_aPages.back().appendPoint( rStart, aLine );
7025
0
        aLine.append( " m " );
7026
0
        m_aPages.back().appendPoint( rStop, aLine );
7027
0
        aLine.append( " l S Q\n" );
7028
7029
0
        writeBuffer( aLine );
7030
0
    }
7031
0
    else
7032
0
    {
7033
0
        PDFWriter::ExtLineInfo aInfo;
7034
0
        convertLineInfoToExtLineInfo( rInfo, aInfo );
7035
0
        Point aPolyPoints[2] = { rStart, rStop };
7036
0
        tools::Polygon aPoly( 2, aPolyPoints );
7037
0
        drawPolyLine( aPoly, aInfo );
7038
0
    }
7039
0
}
7040
7041
32.6k
#define HCONV(x) DevicePixelToLogicHeight(x)
7042
7043
void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7044
0
{
7045
    // note: units in pFontInstance are ref device pixel
7046
0
    const LogicalFontInstance*  pFontInstance = GetFontInstance();
7047
0
    tools::Long            nLineHeight = 0;
7048
0
    tools::Long            nLinePos = 0;
7049
7050
0
    appendStrokingColor( aColor, aLine );
7051
0
    aLine.append( "\n" );
7052
7053
0
    if ( bIsAbove )
7054
0
    {
7055
0
        if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
7056
0
            ImplInitAboveTextLineSize();
7057
0
        nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
7058
0
        nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
7059
0
    }
7060
0
    else
7061
0
    {
7062
0
        if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
7063
0
            ImplInitTextLineSize();
7064
0
        nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
7065
0
        nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
7066
0
    }
7067
0
    if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
7068
0
        nLineHeight = 3;
7069
7070
0
    tools::Long nLineWidth = GetDPIX()/450;
7071
0
    if ( ! nLineWidth )
7072
0
        nLineWidth = 1;
7073
7074
0
    if ( eTextLine == LINESTYLE_BOLDWAVE )
7075
0
        nLineWidth = 3*nLineWidth;
7076
7077
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
7078
0
    aLine.append( " w " );
7079
7080
0
    if ( eTextLine == LINESTYLE_DOUBLEWAVE )
7081
0
    {
7082
0
        tools::Long nOrgLineHeight = nLineHeight;
7083
0
        nLineHeight /= 3;
7084
0
        if ( nLineHeight < 2 )
7085
0
        {
7086
0
            if ( nOrgLineHeight > 1 )
7087
0
                nLineHeight = 2;
7088
0
            else
7089
0
                nLineHeight = 1;
7090
0
        }
7091
0
        tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
7092
0
        if ( nLineDY < nLineWidth )
7093
0
            nLineDY = nLineWidth;
7094
0
        tools::Long nLineDY2 = nLineDY/2;
7095
0
        if ( !nLineDY2 )
7096
0
            nLineDY2 = 1;
7097
7098
0
        nLinePos -= nLineWidth-nLineDY2;
7099
7100
0
        m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7101
7102
0
        nLinePos += nLineWidth+nLineDY;
7103
0
        m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7104
0
    }
7105
0
    else
7106
0
    {
7107
0
        if ( eTextLine != LINESTYLE_BOLDWAVE )
7108
0
            nLinePos -= nLineWidth/2;
7109
0
        m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
7110
0
    }
7111
0
}
7112
7113
void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7114
21.7k
{
7115
    // note: units in pFontInstance are ref device pixel
7116
21.7k
    const LogicalFontInstance*  pFontInstance = GetFontInstance();
7117
21.7k
    tools::Long            nLineHeight = 0;
7118
21.7k
    tools::Long            nLinePos  = 0;
7119
21.7k
    tools::Long            nLinePos2 = 0;
7120
7121
21.7k
    if ( eTextLine > LINESTYLE_BOLDWAVE )
7122
0
        eTextLine = LINESTYLE_SINGLE;
7123
7124
21.7k
    switch ( eTextLine )
7125
21.7k
    {
7126
5.04k
        case LINESTYLE_SINGLE:
7127
5.06k
        case LINESTYLE_DOTTED:
7128
5.06k
        case LINESTYLE_DASH:
7129
5.06k
        case LINESTYLE_LONGDASH:
7130
5.06k
        case LINESTYLE_DASHDOT:
7131
5.06k
        case LINESTYLE_DASHDOTDOT:
7132
5.06k
            if ( bIsAbove )
7133
0
            {
7134
0
                if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
7135
0
                    ImplInitAboveTextLineSize();
7136
0
                nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
7137
0
                nLinePos    = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
7138
0
            }
7139
5.06k
            else
7140
5.06k
            {
7141
5.06k
                if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
7142
0
                    ImplInitTextLineSize();
7143
5.06k
                nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
7144
5.06k
                nLinePos    = pFontInstance->mxFontMetric->GetUnderlineOffset();
7145
5.06k
            }
7146
5.06k
            break;
7147
0
        case LINESTYLE_BOLD:
7148
0
        case LINESTYLE_BOLDDOTTED:
7149
0
        case LINESTYLE_BOLDDASH:
7150
0
        case LINESTYLE_BOLDLONGDASH:
7151
0
        case LINESTYLE_BOLDDASHDOT:
7152
0
        case LINESTYLE_BOLDDASHDOTDOT:
7153
0
            if ( bIsAbove )
7154
0
            {
7155
0
                if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
7156
0
                    ImplInitAboveTextLineSize();
7157
0
                nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
7158
0
                nLinePos    = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
7159
0
            }
7160
0
            else
7161
0
            {
7162
0
                if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
7163
0
                    ImplInitTextLineSize();
7164
0
                nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
7165
0
                nLinePos    = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
7166
0
            }
7167
0
            break;
7168
5.16k
        case LINESTYLE_DOUBLE:
7169
5.16k
            if ( bIsAbove )
7170
0
            {
7171
0
                if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
7172
0
                    ImplInitAboveTextLineSize();
7173
0
                nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
7174
0
                nLinePos    = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
7175
0
                nLinePos2   = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
7176
0
            }
7177
5.16k
            else
7178
5.16k
            {
7179
5.16k
                if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
7180
0
                    ImplInitTextLineSize();
7181
5.16k
                nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
7182
5.16k
                nLinePos    = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
7183
5.16k
                nLinePos2   = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
7184
5.16k
            }
7185
5.16k
            break;
7186
11.5k
        default:
7187
11.5k
            break;
7188
21.7k
    }
7189
7190
21.7k
    if ( !nLineHeight )
7191
11.5k
        return;
7192
7193
    // tdf#154235
7194
    // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
7195
    // while in PDF we stroke the line so the position is to the middle of the
7196
    // line, we add half of nLineHeight to account for that.
7197
10.2k
    auto nOffset = nLineHeight / 2;
7198
10.2k
    if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7199
2
    {
7200
        // Except when outlining, as now we are drawing a rectangle, so
7201
        // nLinePos is the bottom of the rectangle, so need to add nLineHeight
7202
        // to it.
7203
2
        nOffset = nLineHeight;
7204
2
    }
7205
7206
10.2k
    nLineHeight = HCONV(nLineHeight);
7207
10.2k
    nLinePos = HCONV(nLinePos + nOffset);
7208
10.2k
    nLinePos2 = HCONV(nLinePos2 + nOffset);
7209
7210
    // outline attribute ?
7211
10.2k
    if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7212
2
    {
7213
2
        appendStrokingColor(aColor, aLine); // stroke with text color
7214
2
        aLine.append( " " );
7215
2
        appendNonStrokingColor(COL_WHITE, aLine); // fill with white
7216
2
        aLine.append( "\n" );
7217
2
        aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
7218
7219
        // draw rectangle instead
7220
2
        aLine.append( "0 " );
7221
2
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7222
2
        aLine.append( " " );
7223
2
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7224
2
        aLine.append( ' ' );
7225
2
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7226
2
        aLine.append( " re h B\n" );
7227
2
        return;
7228
2
    }
7229
7230
7231
10.2k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7232
10.2k
    aLine.append( " w " );
7233
10.2k
    appendStrokingColor( aColor, aLine );
7234
10.2k
    aLine.append( "\n" );
7235
7236
10.2k
    switch ( eTextLine )
7237
10.2k
    {
7238
18
        case LINESTYLE_DOTTED:
7239
18
        case LINESTYLE_BOLDDOTTED:
7240
18
            aLine.append( "[ " );
7241
18
            m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7242
18
            aLine.append( " ] 0 d\n" );
7243
18
            break;
7244
0
        case LINESTYLE_DASH:
7245
0
        case LINESTYLE_LONGDASH:
7246
0
        case LINESTYLE_BOLDDASH:
7247
0
        case LINESTYLE_BOLDLONGDASH:
7248
0
            {
7249
0
                sal_Int32 nDashLength = 4*nLineHeight;
7250
0
                sal_Int32 nVoidLength = 2*nLineHeight;
7251
0
                if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7252
0
                    nDashLength = 8*nLineHeight;
7253
7254
0
                aLine.append( "[ " );
7255
0
                m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7256
0
                aLine.append( ' ' );
7257
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7258
0
                aLine.append( " ] 0 d\n" );
7259
0
            }
7260
0
            break;
7261
0
        case LINESTYLE_DASHDOT:
7262
0
        case LINESTYLE_BOLDDASHDOT:
7263
0
            {
7264
0
                sal_Int32 nDashLength = 4*nLineHeight;
7265
0
                sal_Int32 nVoidLength = 2*nLineHeight;
7266
0
                aLine.append( "[ " );
7267
0
                m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7268
0
                aLine.append( ' ' );
7269
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7270
0
                aLine.append( ' ' );
7271
0
                m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7272
0
                aLine.append( ' ' );
7273
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7274
0
                aLine.append( " ] 0 d\n" );
7275
0
            }
7276
0
            break;
7277
0
        case LINESTYLE_DASHDOTDOT:
7278
0
        case LINESTYLE_BOLDDASHDOTDOT:
7279
0
            {
7280
0
                sal_Int32 nDashLength = 4*nLineHeight;
7281
0
                sal_Int32 nVoidLength = 2*nLineHeight;
7282
0
                aLine.append( "[ " );
7283
0
                m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7284
0
                aLine.append( ' ' );
7285
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7286
0
                aLine.append( ' ' );
7287
0
                m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7288
0
                aLine.append( ' ' );
7289
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7290
0
                aLine.append( ' ' );
7291
0
                m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7292
0
                aLine.append( ' ' );
7293
0
                m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7294
0
                aLine.append( " ] 0 d\n" );
7295
0
            }
7296
0
            break;
7297
10.2k
        default:
7298
10.2k
            break;
7299
10.2k
    }
7300
7301
10.2k
    aLine.append( "0 " );
7302
10.2k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7303
10.2k
    aLine.append( " m " );
7304
10.2k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7305
10.2k
    aLine.append( ' ' );
7306
10.2k
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7307
10.2k
    aLine.append( " l S\n" );
7308
10.2k
    if ( eTextLine == LINESTYLE_DOUBLE )
7309
5.16k
    {
7310
5.16k
        aLine.append( "0 " );
7311
5.16k
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7312
5.16k
        aLine.append( " m " );
7313
5.16k
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7314
5.16k
        aLine.append( ' ' );
7315
5.16k
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7316
5.16k
        aLine.append( " l S\n" );
7317
5.16k
    }
7318
7319
10.2k
}
7320
7321
void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
7322
10.8k
{
7323
    // note: units in pFontInstance are ref device pixel
7324
10.8k
    const LogicalFontInstance*  pFontInstance = GetFontInstance();
7325
10.8k
    tools::Long            nLineHeight = 0;
7326
10.8k
    tools::Long            nLinePos  = 0;
7327
10.8k
    tools::Long            nLinePos2 = 0;
7328
7329
10.8k
    if ( eStrikeout > STRIKEOUT_X )
7330
0
        eStrikeout = STRIKEOUT_SINGLE;
7331
7332
10.8k
    switch ( eStrikeout )
7333
10.8k
    {
7334
663
        case STRIKEOUT_SINGLE:
7335
663
            if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7336
0
                ImplInitTextLineSize();
7337
663
            nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
7338
663
            nLinePos    = pFontInstance->mxFontMetric->GetStrikeoutOffset();
7339
663
            break;
7340
0
        case STRIKEOUT_BOLD:
7341
0
            if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7342
0
                ImplInitTextLineSize();
7343
0
            nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
7344
0
            nLinePos    = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
7345
0
            break;
7346
0
        case STRIKEOUT_DOUBLE:
7347
0
            if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7348
0
                ImplInitTextLineSize();
7349
0
            nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
7350
0
            nLinePos    = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
7351
0
            nLinePos2   = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
7352
0
            break;
7353
10.2k
        default:
7354
10.2k
            break;
7355
10.8k
    }
7356
7357
10.8k
    if ( !nLineHeight )
7358
10.2k
        return;
7359
7360
    // tdf#154235
7361
    // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
7362
    // while in PDF we stroke the line so the position is to the middle of the
7363
    // line, we add half of nLineHeight to account for that.
7364
663
    nLinePos = HCONV(nLinePos + nLineHeight / 2);
7365
663
    nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
7366
663
    nLineHeight = HCONV(nLineHeight);
7367
7368
663
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7369
663
    aLine.append( " w " );
7370
663
    appendStrokingColor( aColor, aLine );
7371
663
    aLine.append( "\n" );
7372
7373
663
    aLine.append( "0 " );
7374
663
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7375
663
    aLine.append( " m " );
7376
663
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7377
663
    aLine.append( ' ' );
7378
663
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7379
663
    aLine.append( " l S\n" );
7380
7381
663
    if ( eStrikeout == STRIKEOUT_DOUBLE )
7382
0
    {
7383
0
        aLine.append( "0 " );
7384
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7385
0
        aLine.append( " m " );
7386
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7387
0
        aLine.append( ' ' );
7388
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7389
0
        aLine.append( " l S\n" );
7390
0
    }
7391
7392
663
}
7393
7394
void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
7395
0
{
7396
    //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7397
    //to tweak this
7398
7399
0
    OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? u"/"_ustr : u"X"_ustr;
7400
0
    OUString aStrikeout = aStrikeoutChar;
7401
0
    while( GetTextWidth( aStrikeout ) < nWidth )
7402
0
        aStrikeout += aStrikeout;
7403
7404
    // do not get broader than nWidth modulo 1 character
7405
0
    while( GetTextWidth( aStrikeout ) >= nWidth )
7406
0
        aStrikeout = aStrikeout.copy(1);
7407
0
    aStrikeout += aStrikeoutChar;
7408
0
    bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7409
0
    if ( bShadow )
7410
0
    {
7411
0
        Font aFont = m_aCurrentPDFState.m_aFont;
7412
0
        aFont.SetShadow( false );
7413
0
        setFont( aFont );
7414
0
        updateGraphicsState();
7415
0
    }
7416
7417
0
    push(PushFlags::TEXTLAYOUTMODE | PushFlags::CLIPREGION);
7418
7419
    // strikeout string is left aligned non-CTL text
7420
0
    setLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
7421
7422
0
    FontMetric aRefDevFontMetric = GetFontMetric();
7423
0
    tools::Rectangle aRect;
7424
0
    aRect.SetLeft( rPos.X() );
7425
0
    aRect.SetRight( aRect.Left()+nWidth );
7426
0
    aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7427
0
    aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7428
7429
0
    const LogicalFontInstance* pFontInstance = GetFontInstance();
7430
0
    if (pFontInstance->mnOrientation)
7431
0
    {
7432
0
        tools::Polygon aPoly( aRect );
7433
0
        aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7434
0
        aRect = aPoly.GetBoundRect();
7435
0
    }
7436
7437
0
    intersectClipRegion( aRect );
7438
0
    drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7439
0
    pop();
7440
7441
0
    if ( bShadow )
7442
0
    {
7443
0
        Font aFont = m_aCurrentPDFState.m_aFont;
7444
0
        aFont.SetShadow( true );
7445
0
        setFont( aFont );
7446
0
        updateGraphicsState();
7447
0
    }
7448
0
}
7449
7450
void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7451
10.8k
{
7452
10.8k
    if ( !nWidth ||
7453
10.8k
         ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7454
10.2k
           ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7455
0
           ((eOverline  == LINESTYLE_NONE)||(eOverline  == LINESTYLE_DONTKNOW)) ) )
7456
3
        return;
7457
7458
10.8k
    MARK( "drawTextLine" );
7459
10.8k
    updateGraphicsState();
7460
7461
    // note: units in pFontInstance are ref device pixel
7462
10.8k
    const LogicalFontInstance* pFontInstance = GetFontInstance();
7463
10.8k
    Color           aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7464
10.8k
    Color           aOverlineColor  = m_aCurrentPDFState.m_aOverlineColor;
7465
10.8k
    Color           aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7466
10.8k
    bool            bStrikeoutDone = false;
7467
10.8k
    bool            bUnderlineDone = false;
7468
10.8k
    bool            bOverlineDone  = false;
7469
7470
10.8k
    if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7471
0
    {
7472
0
        drawStrikeoutChar( rPos, nWidth, eStrikeout );
7473
0
        bStrikeoutDone = true;
7474
0
    }
7475
7476
10.8k
    Point aPos( rPos );
7477
10.8k
    TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7478
10.8k
    if( eAlign == ALIGN_TOP )
7479
0
        aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7480
10.8k
    else if( eAlign == ALIGN_BOTTOM )
7481
0
        aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7482
7483
10.8k
    OStringBuffer aLine( 512 );
7484
    // save GS
7485
10.8k
    aLine.append( "q " );
7486
7487
    // rotate and translate matrix
7488
10.8k
    double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
7489
10.8k
    Matrix3 aMat;
7490
10.8k
    aMat.rotate( fAngle );
7491
10.8k
    aMat.translate( aPos.X(), aPos.Y() );
7492
10.8k
    m_aPages.back().appendMatrix3(aMat, aLine);
7493
10.8k
    aLine.append( " cm\n" );
7494
7495
10.8k
    if ( aUnderlineColor.IsTransparent() )
7496
10.7k
        aUnderlineColor = aStrikeoutColor;
7497
7498
10.8k
    if ( aOverlineColor.IsTransparent() )
7499
10.7k
        aOverlineColor = aStrikeoutColor;
7500
7501
10.8k
    if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7502
10.8k
         (eUnderline == LINESTYLE_WAVE) ||
7503
10.8k
         (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7504
10.8k
         (eUnderline == LINESTYLE_BOLDWAVE) )
7505
0
    {
7506
0
        drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7507
0
        bUnderlineDone = true;
7508
0
    }
7509
7510
10.8k
    if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7511
10.8k
         (eOverline == LINESTYLE_WAVE) ||
7512
10.8k
         (eOverline == LINESTYLE_DOUBLEWAVE) ||
7513
10.8k
         (eOverline == LINESTYLE_BOLDWAVE) )
7514
0
    {
7515
0
        drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7516
0
        bOverlineDone = true;
7517
0
    }
7518
7519
10.8k
    if ( !bUnderlineDone )
7520
10.8k
    {
7521
10.8k
        drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7522
10.8k
    }
7523
7524
10.8k
    if ( !bOverlineDone )
7525
10.8k
    {
7526
10.8k
        drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7527
10.8k
    }
7528
7529
10.8k
    if ( !bStrikeoutDone )
7530
10.8k
    {
7531
10.8k
        drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7532
10.8k
    }
7533
7534
10.8k
    aLine.append( "Q\n" );
7535
10.8k
    writeBuffer( aLine );
7536
10.8k
}
7537
7538
void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7539
0
{
7540
0
    MARK( "drawPolygon" );
7541
7542
0
    updateGraphicsState();
7543
7544
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7545
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7546
0
        return;
7547
7548
0
    int nPoints = rPoly.GetSize();
7549
0
    OStringBuffer aLine( 20 * nPoints );
7550
0
    m_aPages.back().appendPolygon( rPoly, aLine );
7551
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7552
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7553
0
        aLine.append( "B*\n" );
7554
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7555
0
        aLine.append( "S\n" );
7556
0
    else
7557
0
        aLine.append( "f*\n" );
7558
7559
0
    writeBuffer( aLine );
7560
0
}
7561
7562
void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7563
3.54k
{
7564
3.54k
    MARK( "drawPolyPolygon" );
7565
7566
3.54k
    updateGraphicsState();
7567
7568
3.54k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7569
1.13k
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7570
0
        return;
7571
7572
3.54k
    int nPolygons = rPolyPoly.Count();
7573
7574
3.54k
    OStringBuffer aLine( 40 * nPolygons );
7575
3.54k
    m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7576
3.54k
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7577
2.41k
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7578
2.41k
        aLine.append( "B*\n" );
7579
1.13k
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7580
0
        aLine.append( "S\n" );
7581
1.13k
    else
7582
1.13k
        aLine.append( "f*\n" );
7583
7584
3.54k
    writeBuffer( aLine );
7585
3.54k
}
7586
7587
void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7588
0
{
7589
0
    SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7590
0
    nTransparentPercent = nTransparentPercent % 100;
7591
7592
0
    MARK( "drawTransparent" );
7593
7594
0
    updateGraphicsState();
7595
7596
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7597
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7598
0
        return;
7599
7600
0
    if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7601
0
    {
7602
0
        m_aErrors.insert( m_bIsPDF_A1 ?
7603
0
                          PDFWriter::Warning_Transparency_Omitted_PDFA :
7604
0
                          PDFWriter::Warning_Transparency_Omitted_PDF13 );
7605
7606
0
        drawPolyPolygon( rPolyPoly );
7607
0
        return;
7608
0
    }
7609
7610
    // create XObject
7611
0
    m_aTransparentObjects.emplace_back( );
7612
    // FIXME: polygons with beziers may yield incorrect bound rect
7613
0
    m_aTransparentObjects.back().m_aBoundRect     = rPolyPoly.GetBoundRect();
7614
    // convert rectangle to default user space
7615
0
    m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7616
0
    m_aTransparentObjects.back().m_nObject          = createObject();
7617
0
    m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7618
0
    m_aTransparentObjects.back().m_fAlpha           = (100-nTransparentPercent) / 100.0;
7619
0
    m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7620
    // create XObject's content stream
7621
0
    OStringBuffer aContent( 256 );
7622
0
    m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7623
0
    if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7624
0
        m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7625
0
        aContent.append( " B*\n" );
7626
0
    else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7627
0
        aContent.append( " S\n" );
7628
0
    else
7629
0
        aContent.append( " f*\n" );
7630
0
    m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7631
0
        aContent.getStr(), aContent.getLength() );
7632
7633
0
    OStringBuffer aObjName( 16 );
7634
0
    aObjName.append( "Tr" );
7635
0
    aObjName.append( m_aTransparentObjects.back().m_nObject );
7636
0
    OString aTrName( aObjName.makeStringAndClear() );
7637
0
    aObjName.append( "EGS" );
7638
0
    aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7639
0
    OString aExtName( aObjName.makeStringAndClear() );
7640
7641
0
    OString aLine =
7642
    // insert XObject
7643
0
        "q /" +
7644
0
        aExtName +
7645
0
        " gs /" +
7646
0
        aTrName +
7647
0
        " Do Q\n";
7648
0
    writeBuffer( aLine );
7649
7650
0
    pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
7651
0
    pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7652
0
}
7653
7654
void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
7655
12
{
7656
12
    if( nObject < 0 )
7657
0
        return;
7658
7659
12
    switch( eKind )
7660
12
    {
7661
6
        case ResourceKind::XObject:
7662
6
            rResourceDict.m_aXObjects[rResource] = nObject;
7663
6
            if (!rOutputStreams.empty())
7664
0
                rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
7665
6
            break;
7666
6
        case ResourceKind::ExtGState:
7667
6
            rResourceDict.m_aExtGStates[rResource] = nObject;
7668
6
            if (!rOutputStreams.empty())
7669
0
                rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
7670
6
            break;
7671
0
        case ResourceKind::Shading:
7672
0
            rResourceDict.m_aShadings[rResource] = nObject;
7673
0
            if (!rOutputStreams.empty())
7674
0
                rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
7675
0
            break;
7676
0
        case ResourceKind::Pattern:
7677
0
            rResourceDict.m_aPatterns[rResource] = nObject;
7678
0
            if (!rOutputStreams.empty())
7679
0
                rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
7680
0
            break;
7681
12
    }
7682
12
}
7683
7684
void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
7685
12
{
7686
12
    pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
7687
12
}
7688
7689
void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
7690
6
{
7691
6
    push( PushFlags::ALL );
7692
7693
    // force reemitting clip region inside the new stream, and
7694
    // prevent emitting an unbalanced "Q" at the start
7695
6
    clearClipRegion();
7696
    // this is needed to point m_aCurrentPDFState at the pushed state
7697
    // ... but it's pointless to actually write into the "outer" stream here!
7698
6
    updateGraphicsState(Mode::NOWRITE);
7699
7700
6
    m_aOutputStreams.push_front( StreamRedirect() );
7701
6
    m_aOutputStreams.front().m_pStream = pStream;
7702
6
    m_aOutputStreams.front().m_aMapMode = m_aMapMode;
7703
7704
6
    if( !rTargetRect.IsEmpty() )
7705
0
    {
7706
0
        m_aOutputStreams.front().m_aTargetRect =
7707
0
            convert( m_aGraphicsStack.front().m_aMapMode,
7708
0
                         m_aMapMode,
7709
0
                         this,
7710
0
                         rTargetRect );
7711
0
        Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
7712
0
        tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
7713
0
        aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
7714
0
        m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
7715
0
    }
7716
7717
    // setup graphics state for independent object stream
7718
7719
    // force reemitting colors
7720
6
    m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7721
6
    m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7722
6
}
7723
7724
SvStream* PDFWriterImpl::endRedirect()
7725
6
{
7726
6
    SvStream* pStream = nullptr;
7727
6
    if( ! m_aOutputStreams.empty() )
7728
6
    {
7729
6
        pStream     = m_aOutputStreams.front().m_pStream;
7730
6
        m_aMapMode  = m_aOutputStreams.front().m_aMapMode;
7731
6
        m_aOutputStreams.pop_front();
7732
6
    }
7733
7734
6
    pop();
7735
7736
6
    m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7737
6
    m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7738
7739
    // needed after pop() to set m_aCurrentPDFState
7740
6
    updateGraphicsState(Mode::NOWRITE);
7741
7742
6
    return pStream;
7743
6
}
7744
7745
void PDFWriterImpl::beginTransparencyGroup()
7746
6
{
7747
6
    updateGraphicsState();
7748
6
    if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
7749
6
        beginRedirect( new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE ), tools::Rectangle() );
7750
6
}
7751
7752
void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
7753
6
{
7754
6
    SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7755
6
    nTransparentPercent = nTransparentPercent % 100;
7756
7757
6
    if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7758
0
        return;
7759
7760
    // create XObject
7761
6
    m_aTransparentObjects.emplace_back( );
7762
6
    m_aTransparentObjects.back().m_aBoundRect   = rBoundingBox;
7763
    // convert rectangle to default user space
7764
6
    m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7765
6
    m_aTransparentObjects.back().m_nObject      = createObject();
7766
6
    m_aTransparentObjects.back().m_fAlpha       = (100-nTransparentPercent) / 100.0;
7767
    // get XObject's content stream
7768
6
    m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
7769
6
    m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7770
7771
6
    OStringBuffer aObjName( 16 );
7772
6
    aObjName.append( "Tr" );
7773
6
    aObjName.append( m_aTransparentObjects.back().m_nObject );
7774
6
    OString aTrName( aObjName.makeStringAndClear() );
7775
6
    aObjName.append( "EGS" );
7776
6
    aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7777
6
    OString aExtName( aObjName.makeStringAndClear() );
7778
7779
6
    OString aLine =
7780
    // insert XObject
7781
6
        "q /" +
7782
6
        aExtName +
7783
6
        " gs /" +
7784
6
        aTrName +
7785
6
        " Do Q\n";
7786
6
    writeBuffer( aLine );
7787
7788
6
    pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
7789
6
    pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7790
7791
6
}
7792
7793
void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
7794
74.1k
{
7795
74.1k
    MARK( "drawRectangle" );
7796
7797
74.1k
    updateGraphicsState();
7798
7799
74.1k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7800
74.1k
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7801
73.3k
        return;
7802
7803
761
    OStringBuffer aLine( 40 );
7804
761
    m_aPages.back().appendRect( rRect, aLine );
7805
7806
761
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7807
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7808
0
        aLine.append( " B*\n" );
7809
761
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7810
0
        aLine.append( " S\n" );
7811
761
    else
7812
761
        aLine.append( " f*\n" );
7813
7814
761
    writeBuffer( aLine );
7815
761
}
7816
7817
void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
7818
0
{
7819
0
    MARK( "drawRectangle with rounded edges" );
7820
7821
0
    if( !nHorzRound && !nVertRound )
7822
0
        drawRectangle( rRect );
7823
7824
0
    updateGraphicsState();
7825
7826
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7827
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7828
0
        return;
7829
7830
0
    if( nHorzRound > o3tl::make_unsigned(rRect.GetWidth())/2 )
7831
0
        nHorzRound = rRect.GetWidth()/2;
7832
0
    if( nVertRound > o3tl::make_unsigned(rRect.GetHeight())/2 )
7833
0
        nVertRound = rRect.GetHeight()/2;
7834
7835
0
    Point aPoints[16];
7836
0
    const double kappa = 0.5522847498;
7837
0
    const sal_uInt32 kx = (kappa*nHorzRound)+0.5;
7838
0
    const sal_uInt32 ky = (kappa*nVertRound)+0.5;
7839
7840
0
    aPoints[1]  = Point( rRect.Left() + nHorzRound, rRect.Top() );
7841
0
    aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7842
0
    aPoints[2]  = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
7843
0
    aPoints[3]  = Point( aPoints[2].X()+kx, aPoints[2].Y() );
7844
7845
0
    aPoints[5]  = Point( rRect.Right()+1, rRect.Top()+nVertRound );
7846
0
    aPoints[4]  = Point( aPoints[5].X(), aPoints[5].Y()-ky );
7847
0
    aPoints[6]  = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
7848
0
    aPoints[7]  = Point( aPoints[6].X(), aPoints[6].Y()+ky );
7849
7850
0
    aPoints[9]  = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
7851
0
    aPoints[8]  = Point( aPoints[9].X()+kx, aPoints[9].Y() );
7852
0
    aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
7853
0
    aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
7854
7855
0
    aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
7856
0
    aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
7857
0
    aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
7858
0
    aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
7859
7860
0
    OStringBuffer aLine( 80 );
7861
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7862
0
    aLine.append( " m " );
7863
0
    m_aPages.back().appendPoint( aPoints[2], aLine );
7864
0
    aLine.append( " l " );
7865
0
    m_aPages.back().appendPoint( aPoints[3], aLine );
7866
0
    aLine.append( ' ' );
7867
0
    m_aPages.back().appendPoint( aPoints[4], aLine );
7868
0
    aLine.append( ' ' );
7869
0
    m_aPages.back().appendPoint( aPoints[5], aLine );
7870
0
    aLine.append( " c\n" );
7871
0
    m_aPages.back().appendPoint( aPoints[6], aLine );
7872
0
    aLine.append( " l " );
7873
0
    m_aPages.back().appendPoint( aPoints[7], aLine );
7874
0
    aLine.append( ' ' );
7875
0
    m_aPages.back().appendPoint( aPoints[8], aLine );
7876
0
    aLine.append( ' ' );
7877
0
    m_aPages.back().appendPoint( aPoints[9], aLine );
7878
0
    aLine.append( " c\n" );
7879
0
    m_aPages.back().appendPoint( aPoints[10], aLine );
7880
0
    aLine.append( " l " );
7881
0
    m_aPages.back().appendPoint( aPoints[11], aLine );
7882
0
    aLine.append( ' ' );
7883
0
    m_aPages.back().appendPoint( aPoints[12], aLine );
7884
0
    aLine.append( ' ' );
7885
0
    m_aPages.back().appendPoint( aPoints[13], aLine );
7886
0
    aLine.append( " c\n" );
7887
0
    m_aPages.back().appendPoint( aPoints[14], aLine );
7888
0
    aLine.append( " l " );
7889
0
    m_aPages.back().appendPoint( aPoints[15], aLine );
7890
0
    aLine.append( ' ' );
7891
0
    m_aPages.back().appendPoint( aPoints[0], aLine );
7892
0
    aLine.append( ' ' );
7893
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7894
0
    aLine.append( " c " );
7895
7896
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7897
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7898
0
        aLine.append( "b*\n" );
7899
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7900
0
        aLine.append( "s\n" );
7901
0
    else
7902
0
        aLine.append( "f*\n" );
7903
7904
0
    writeBuffer( aLine );
7905
0
}
7906
7907
void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
7908
0
{
7909
0
    MARK( "drawEllipse" );
7910
7911
0
    updateGraphicsState();
7912
7913
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7914
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7915
0
        return;
7916
7917
0
    Point aPoints[12];
7918
0
    const double kappa = 0.5522847498;
7919
0
    const sal_uInt32 kx = (kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5;
7920
0
    const sal_uInt32 ky = (kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5;
7921
7922
0
    aPoints[1]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
7923
0
    aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7924
0
    aPoints[2]  = Point( aPoints[1].X() + kx, aPoints[1].Y() );
7925
7926
0
    aPoints[4]  = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
7927
0
    aPoints[3]  = Point( aPoints[4].X(), aPoints[4].Y() - ky );
7928
0
    aPoints[5]  = Point( aPoints[4].X(), aPoints[4].Y() + ky );
7929
7930
0
    aPoints[7]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
7931
0
    aPoints[6]  = Point( aPoints[7].X() + kx, aPoints[7].Y() );
7932
0
    aPoints[8]  = Point( aPoints[7].X() - kx, aPoints[7].Y() );
7933
7934
0
    aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
7935
0
    aPoints[9]  = Point( aPoints[10].X(), aPoints[10].Y() + ky );
7936
0
    aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
7937
7938
0
    OStringBuffer aLine( 80 );
7939
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7940
0
    aLine.append( " m " );
7941
0
    m_aPages.back().appendPoint( aPoints[2], aLine );
7942
0
    aLine.append( ' ' );
7943
0
    m_aPages.back().appendPoint( aPoints[3], aLine );
7944
0
    aLine.append( ' ' );
7945
0
    m_aPages.back().appendPoint( aPoints[4], aLine );
7946
0
    aLine.append( " c\n" );
7947
0
    m_aPages.back().appendPoint( aPoints[5], aLine );
7948
0
    aLine.append( ' ' );
7949
0
    m_aPages.back().appendPoint( aPoints[6], aLine );
7950
0
    aLine.append( ' ' );
7951
0
    m_aPages.back().appendPoint( aPoints[7], aLine );
7952
0
    aLine.append( " c\n" );
7953
0
    m_aPages.back().appendPoint( aPoints[8], aLine );
7954
0
    aLine.append( ' ' );
7955
0
    m_aPages.back().appendPoint( aPoints[9], aLine );
7956
0
    aLine.append( ' ' );
7957
0
    m_aPages.back().appendPoint( aPoints[10], aLine );
7958
0
    aLine.append( " c\n" );
7959
0
    m_aPages.back().appendPoint( aPoints[11], aLine );
7960
0
    aLine.append( ' ' );
7961
0
    m_aPages.back().appendPoint( aPoints[0], aLine );
7962
0
    aLine.append( ' ' );
7963
0
    m_aPages.back().appendPoint( aPoints[1], aLine );
7964
0
    aLine.append( " c " );
7965
7966
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7967
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7968
0
        aLine.append( "b*\n" );
7969
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7970
0
        aLine.append( "s\n" );
7971
0
    else
7972
0
        aLine.append( "f*\n" );
7973
7974
0
    writeBuffer( aLine );
7975
0
}
7976
7977
static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
7978
0
{
7979
0
    Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
7980
0
                  (rRect.Top()+rRect.Bottom()+1)/2);
7981
0
    Point aPoint = rPoint - aOrigin;
7982
7983
0
    double fX = static_cast<double>(aPoint.X());
7984
0
    double fY = static_cast<double>(-aPoint.Y());
7985
7986
0
    if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
7987
0
        throw o3tl::divide_by_zero();
7988
7989
0
    if( rRect.GetWidth() > rRect.GetHeight() )
7990
0
        fY *= static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight());
7991
0
    else if( rRect.GetHeight() > rRect.GetWidth() )
7992
0
        fX *= static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth());
7993
0
    return atan2( fY, fX );
7994
0
}
7995
7996
void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
7997
0
{
7998
0
    MARK( "drawArc" );
7999
8000
0
    updateGraphicsState();
8001
8002
0
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8003
0
        m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8004
0
        return;
8005
8006
    // calculate start and stop angles
8007
0
    const double fStartAngle = calcAngle( rRect, rStart );
8008
0
    double fStopAngle  = calcAngle( rRect, rStop );
8009
0
    while( fStopAngle < fStartAngle )
8010
0
        fStopAngle += 2.0*M_PI;
8011
0
    const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
8012
0
    const double fFragmentDelta = (fStopAngle-fStartAngle)/nFragments;
8013
0
    const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
8014
0
    const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
8015
0
    const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
8016
8017
0
    const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
8018
0
                         (rRect.Top()+rRect.Bottom()+1)/2 );
8019
8020
0
    OStringBuffer aLine( 30*nFragments );
8021
0
    Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
8022
0
                  -static_cast<int>(halfHeight * sin(fStartAngle) ) );
8023
0
    aPoint += aCenter;
8024
0
    m_aPages.back().appendPoint( aPoint, aLine );
8025
0
    aLine.append( " m " );
8026
0
    if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
8027
0
    {
8028
0
        for( int i = 0; i < nFragments; i++ )
8029
0
        {
8030
0
            const double fStartFragment = fStartAngle + i*fFragmentDelta;
8031
0
            const double fStopFragment = fStartFragment + fFragmentDelta;
8032
0
            aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
8033
0
                            -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
8034
0
            aPoint += aCenter;
8035
0
            m_aPages.back().appendPoint( aPoint, aLine );
8036
0
            aLine.append( ' ' );
8037
8038
0
            aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
8039
0
                            -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
8040
0
            aPoint += aCenter;
8041
0
            m_aPages.back().appendPoint( aPoint, aLine );
8042
0
            aLine.append( ' ' );
8043
8044
0
            aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
8045
0
                            -static_cast<int>(halfHeight * sin(fStopFragment) ) );
8046
0
            aPoint += aCenter;
8047
0
            m_aPages.back().appendPoint( aPoint, aLine );
8048
0
            aLine.append( " c\n" );
8049
0
        }
8050
0
    }
8051
0
    if( bWithChord || bWithPie )
8052
0
    {
8053
0
        if( bWithPie )
8054
0
        {
8055
0
            m_aPages.back().appendPoint( aCenter, aLine );
8056
0
            aLine.append( " l " );
8057
0
        }
8058
0
        aLine.append( "h " );
8059
0
    }
8060
0
    if( ! bWithChord && ! bWithPie )
8061
0
        aLine.append( "S\n" );
8062
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8063
0
        m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8064
0
        aLine.append( "B*\n" );
8065
0
    else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8066
0
        aLine.append( "S\n" );
8067
0
    else
8068
0
        aLine.append( "f*\n" );
8069
8070
0
    writeBuffer( aLine );
8071
0
}
8072
8073
void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
8074
13.1k
{
8075
13.1k
    MARK( "drawPolyLine" );
8076
8077
13.1k
    sal_uInt16 nPoints = rPoly.GetSize();
8078
13.1k
    if( nPoints < 2 )
8079
0
        return;
8080
8081
13.1k
    updateGraphicsState();
8082
8083
13.1k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8084
0
        return;
8085
8086
13.1k
    OStringBuffer aLine( 20 * nPoints );
8087
13.1k
    m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
8088
13.1k
    aLine.append( "S\n" );
8089
8090
13.1k
    writeBuffer( aLine );
8091
13.1k
}
8092
8093
void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
8094
2
{
8095
2
    MARK( "drawPolyLine with LineInfo" );
8096
8097
2
    updateGraphicsState();
8098
8099
2
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8100
0
        return;
8101
8102
2
    OStringBuffer aLine;
8103
2
    aLine.append( "q " );
8104
2
    if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
8105
0
    {
8106
0
        writeBuffer( aLine );
8107
0
        drawPolyLine( rPoly );
8108
0
        writeBuffer( "Q\n" );
8109
0
    }
8110
2
    else
8111
2
    {
8112
2
        PDFWriter::ExtLineInfo aInfo;
8113
2
        convertLineInfoToExtLineInfo( rInfo, aInfo );
8114
2
        drawPolyLine( rPoly, aInfo );
8115
2
    }
8116
2
}
8117
8118
void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
8119
2
{
8120
2
    SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
8121
2
    rOut.m_fLineWidth           = rIn.GetWidth();
8122
2
    rOut.m_fTransparency        = 0.0;
8123
2
    rOut.m_eCap                 = PDFWriter::capButt;
8124
2
    rOut.m_eJoin                = PDFWriter::joinMiter;
8125
2
    rOut.m_fMiterLimit          = 10;
8126
2
    rOut.m_aDashArray           = rIn.GetDotDashArray();
8127
8128
    // add LineJoin
8129
2
    switch(rIn.GetLineJoin())
8130
2
    {
8131
0
        case basegfx::B2DLineJoin::Bevel :
8132
0
        {
8133
0
            rOut.m_eJoin = PDFWriter::joinBevel;
8134
0
            break;
8135
0
        }
8136
        // Pdf has no 'none' lineJoin, default is miter
8137
0
        case basegfx::B2DLineJoin::NONE :
8138
0
        case basegfx::B2DLineJoin::Miter :
8139
0
        {
8140
0
            rOut.m_eJoin = PDFWriter::joinMiter;
8141
0
            break;
8142
0
        }
8143
2
        case basegfx::B2DLineJoin::Round :
8144
2
        {
8145
2
            rOut.m_eJoin = PDFWriter::joinRound;
8146
2
            break;
8147
0
        }
8148
2
    }
8149
8150
    // add LineCap
8151
2
    switch(rIn.GetLineCap())
8152
2
    {
8153
2
        default: /* css::drawing::LineCap_BUTT */
8154
2
        {
8155
2
            rOut.m_eCap = PDFWriter::capButt;
8156
2
            break;
8157
0
        }
8158
0
        case css::drawing::LineCap_ROUND:
8159
0
        {
8160
0
            rOut.m_eCap = PDFWriter::capRound;
8161
0
            break;
8162
0
        }
8163
0
        case css::drawing::LineCap_SQUARE:
8164
0
        {
8165
0
            rOut.m_eCap = PDFWriter::capSquare;
8166
0
            break;
8167
0
        }
8168
2
    }
8169
2
}
8170
8171
void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
8172
13.1k
{
8173
13.1k
    MARK( "drawPolyLine with ExtLineInfo" );
8174
8175
13.1k
    updateGraphicsState();
8176
8177
13.1k
    if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8178
0
        return;
8179
8180
13.1k
    if( rInfo.m_fTransparency >= 1.0 )
8181
0
        return;
8182
8183
13.1k
    if( rInfo.m_fTransparency != 0.0 )
8184
0
        beginTransparencyGroup();
8185
8186
13.1k
    OStringBuffer aLine;
8187
13.1k
    aLine.append( "q " );
8188
13.1k
    m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
8189
13.1k
    aLine.append( " w" );
8190
13.1k
    if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
8191
13.1k
    {
8192
13.1k
        switch( rInfo.m_eCap )
8193
13.1k
        {
8194
0
            default:
8195
13.1k
            case PDFWriter::capButt:   aLine.append( " 0 J" );break;
8196
6
            case PDFWriter::capRound:  aLine.append( " 1 J" );break;
8197
0
            case PDFWriter::capSquare: aLine.append( " 2 J" );break;
8198
13.1k
        }
8199
13.1k
        switch( rInfo.m_eJoin )
8200
13.1k
        {
8201
0
            default:
8202
13
            case PDFWriter::joinMiter:
8203
13
            {
8204
13
                double fLimit = rInfo.m_fMiterLimit;
8205
13
                if (rInfo.m_fLineWidth < rInfo.m_fMiterLimit)
8206
13
                {
8207
13
                    if (rInfo.m_fLineWidth != 0.0)
8208
13
                        fLimit = fLimit / rInfo.m_fLineWidth;
8209
0
                    else
8210
0
                    {
8211
0
                        SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::drawPolyLine divide by zero attempt");
8212
0
                        fLimit = 1.0;
8213
0
                    }
8214
13
                }
8215
13
                if( fLimit < 1.0 )
8216
0
                    fLimit = 1.0;
8217
13
                aLine.append( " 0 j " );
8218
13
                appendDouble( fLimit, aLine );
8219
13
                aLine.append( " M" );
8220
13
            }
8221
0
            break;
8222
13.1k
            case PDFWriter::joinRound:  aLine.append( " 1 j" );break;
8223
0
            case PDFWriter::joinBevel:  aLine.append( " 2 j" );break;
8224
13.1k
        }
8225
13.1k
        if( !rInfo.m_aDashArray.empty() )
8226
2
        {
8227
2
            aLine.append( " [ " );
8228
2
            for (auto const& dash : rInfo.m_aDashArray)
8229
4
            {
8230
4
                m_aPages.back().appendMappedLength( dash, aLine );
8231
4
                aLine.append( ' ' );
8232
4
            }
8233
2
            aLine.append( "] 0 d" );
8234
2
        }
8235
13.1k
        aLine.append( "\n" );
8236
13.1k
        writeBuffer( aLine );
8237
13.1k
        drawPolyLine( rPoly );
8238
13.1k
    }
8239
0
    else
8240
0
    {
8241
0
        basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8242
0
        basegfx::B2DPolyPolygon aPolyPoly;
8243
8244
0
        basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8245
8246
        // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8247
        // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8248
        // this line needs to be removed and the loop below adapted accordingly
8249
0
        aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8250
8251
0
        const sal_uInt32 nPolygonCount(aPolyPoly.count());
8252
8253
0
        for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8254
0
        {
8255
0
            aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8256
0
            aPoly = aPolyPoly.getB2DPolygon( nPoly );
8257
0
            const sal_uInt32 nPointCount(aPoly.count());
8258
8259
0
            if(nPointCount)
8260
0
            {
8261
0
                const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8262
0
                basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8263
8264
0
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
8265
0
                {
8266
0
                    if( a > 0 )
8267
0
                        aLine.append( " " );
8268
0
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8269
0
                    const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8270
8271
0
                    m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aCurrent.getX()),
8272
0
                                                        basegfx::fround<tools::Long>(aCurrent.getY()) ),
8273
0
                                                 aLine );
8274
0
                    aLine.append( " m " );
8275
0
                    m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aNext.getX()),
8276
0
                                                        basegfx::fround<tools::Long>(aNext.getY()) ),
8277
0
                                                 aLine );
8278
0
                    aLine.append( " l" );
8279
8280
                    // prepare next edge
8281
0
                    aCurrent = aNext;
8282
0
                }
8283
0
            }
8284
0
        }
8285
0
        aLine.append( " S " );
8286
0
        writeBuffer( aLine );
8287
0
    }
8288
13.1k
    writeBuffer( "Q\n" );
8289
8290
13.1k
    if( rInfo.m_fTransparency == 0.0 )
8291
13.1k
        return;
8292
8293
    // FIXME: actually this may be incorrect with bezier polygons
8294
0
    tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8295
    // avoid clipping with thick lines
8296
0
    if( rInfo.m_fLineWidth > 0.0 )
8297
0
    {
8298
0
        sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8299
0
        aBoundRect.AdjustTop( -nLW );
8300
0
        aBoundRect.AdjustLeft( -nLW );
8301
0
        aBoundRect.AdjustRight(nLW );
8302
0
        aBoundRect.AdjustBottom(nLW );
8303
0
    }
8304
0
    endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8305
0
}
8306
8307
void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8308
0
{
8309
0
    MARK( "drawPixel" );
8310
8311
0
    Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8312
8313
0
    if( aColor == COL_TRANSPARENT )
8314
0
        return;
8315
8316
    // pixels are drawn in line color, so have to set
8317
    // the nonstroking color to line color
8318
0
    Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8319
0
    setFillColor( aColor );
8320
8321
0
    updateGraphicsState();
8322
8323
0
    OStringBuffer aLine( 20 );
8324
0
    m_aPages.back().appendPoint( rPoint, aLine );
8325
0
    aLine.append( ' ' );
8326
0
    appendDouble( 1.0/GetDPIX(), aLine );
8327
0
    aLine.append( ' ' );
8328
0
    appendDouble( 1.0/GetDPIY(), aLine );
8329
0
    aLine.append( " re f\n" );
8330
0
    writeBuffer( aLine );
8331
8332
0
    setFillColor( aOldFillColor );
8333
0
}
8334
8335
void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8336
6
{
8337
6
    if (!updateObject(rObject.m_nObject))
8338
0
        return;
8339
8340
6
    bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8341
6
    sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
8342
6
    rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8343
6
    if (g_bDebugDisableCompression)
8344
0
    {
8345
0
        emitComment( "PDFWriterImpl::writeTransparentObject" );
8346
0
    }
8347
6
    OStringBuffer aLine( 512 );
8348
6
    if (!updateObject(rObject.m_nObject))
8349
0
        return;
8350
6
    aLine.append( rObject.m_nObject );
8351
6
    aLine.append( " 0 obj\n"
8352
6
                  "<</Type/XObject\n"
8353
6
                  "/Subtype/Form\n"
8354
6
                  "/BBox[ " );
8355
6
    appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8356
6
    aLine.append( ' ' );
8357
6
    appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8358
6
    aLine.append( ' ' );
8359
6
    appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8360
6
    aLine.append( ' ' );
8361
6
    appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8362
6
    aLine.append( " ]\n" );
8363
6
    if( ! m_bIsPDF_A1 )
8364
6
    {
8365
        // 7.8.3 Resource dicts are required for content streams
8366
6
        aLine.append( "/Resources " );
8367
6
        aLine.append( getResourceDictObj() );
8368
6
        aLine.append( " 0 R\n" );
8369
8370
6
        aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8371
6
    }
8372
8373
6
    aLine.append( "/Length " );
8374
6
    aLine.append(static_cast<sal_Int64>(calculateStreamSize(nSize)));
8375
6
    aLine.append( "\n" );
8376
6
    if( bFlateFilter )
8377
6
        aLine.append( "/Filter/FlateDecode\n" );
8378
6
    aLine.append( ">>\n"
8379
6
                  "stream\n" );
8380
6
    if (!writeBuffer(aLine))
8381
0
        return;
8382
6
    checkAndEnableStreamEncryption( rObject.m_nObject );
8383
6
    if (!writeBufferBytes(rObject.m_pContentStream->GetData(), nSize))
8384
0
        return;
8385
6
    disableStreamEncryption();
8386
6
    aLine.setLength( 0 );
8387
6
    aLine.append( "\n"
8388
6
                  "endstream\n"
8389
6
                  "endobj\n\n" );
8390
6
    if (!writeBuffer(aLine))
8391
0
        return;
8392
8393
    // write ExtGState dict for this XObject
8394
6
    aLine.setLength( 0 );
8395
6
    aLine.append( rObject.m_nExtGStateObject );
8396
6
    aLine.append( " 0 obj\n"
8397
6
                  "<<" );
8398
8399
6
    if( m_bIsPDF_A1 )
8400
0
    {
8401
0
        aLine.append( "/CA 1.0/ca 1.0" );
8402
0
        m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8403
0
    }
8404
6
    else
8405
6
    {
8406
6
        aLine.append(  "/CA " );
8407
6
        appendDouble( rObject.m_fAlpha, aLine );
8408
6
        aLine.append( "\n"
8409
6
                          "   /ca " );
8410
6
        appendDouble( rObject.m_fAlpha, aLine );
8411
6
    }
8412
6
    aLine.append( "\n" );
8413
8414
6
    aLine.append( ">>\n"
8415
6
                  "endobj\n\n" );
8416
6
    if (!updateObject(rObject.m_nExtGStateObject)) return;
8417
6
    if (!writeBuffer(aLine)) return;
8418
6
}
8419
8420
bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8421
0
{
8422
    // LO internal gradient -> PDF shading type:
8423
    //  * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
8424
    //                          [t=0:colorStart, t=1:colorEnd]
8425
    //  * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples
8426
    //                          [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8427
    //  * other styles: function shading with aSize.Width() * aSize.Height() samples
8428
0
    sal_Int32 nFunctionObject = createObject();
8429
0
    if (!updateObject(nFunctionObject))
8430
0
        return false;
8431
8432
0
    ScopedVclPtrInstance< VirtualDevice > aDev;
8433
0
    aDev->SetOutputSizePixel( rObject.m_aSize );
8434
0
    aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8435
0
    if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8436
0
        aDev->SetDrawMode( aDev->GetDrawMode() |
8437
0
                          ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8438
0
                            DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8439
0
    aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8440
8441
0
    Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8442
0
    BitmapScopedReadAccess pAccess(aSample);
8443
8444
0
    Size aSize = aSample.GetSizePixel();
8445
8446
0
    sal_Int32 nStreamLengthObject = createObject();
8447
0
    if (g_bDebugDisableCompression)
8448
0
    {
8449
0
        emitComment( "PDFWriterImpl::writeGradientFunction" );
8450
0
    }
8451
0
    OStringBuffer aLine( 120 );
8452
0
    aLine.append( nFunctionObject );
8453
0
    aLine.append( " 0 obj\n"
8454
0
                  "<</FunctionType 0\n");
8455
0
    switch (rObject.m_aGradient.GetStyle())
8456
0
    {
8457
0
        case css::awt::GradientStyle_LINEAR:
8458
0
        case css::awt::GradientStyle_AXIAL:
8459
0
            aLine.append("/Domain[ 0 1]\n");
8460
0
            break;
8461
0
        default:
8462
0
            aLine.append("/Domain[ 0 1 0 1]\n");
8463
0
    }
8464
0
    aLine.append("/Size[ " );
8465
0
    switch (rObject.m_aGradient.GetStyle())
8466
0
    {
8467
0
        case css::awt::GradientStyle_LINEAR:
8468
0
            aLine.append('2');
8469
0
            break;
8470
0
        case css::awt::GradientStyle_AXIAL:
8471
0
            aLine.append('3');
8472
0
            break;
8473
0
        default:
8474
0
            aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8475
0
            aLine.append( ' ' );
8476
0
            aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8477
0
    }
8478
0
    aLine.append( " ]\n"
8479
0
                  "/BitsPerSample 8\n"
8480
0
                  "/Range[ 0 1 0 1 0 1 ]\n"
8481
0
                  "/Order 3\n"
8482
0
                  "/Length " );
8483
0
    aLine.append( nStreamLengthObject );
8484
0
    if (!g_bDebugDisableCompression)
8485
0
        aLine.append( " 0 R\n"
8486
0
                      "/Filter/FlateDecode"
8487
0
                      ">>\n"
8488
0
                      "stream\n" );
8489
0
    else
8490
0
        aLine.append( " 0 R\n"
8491
0
                      ">>\n"
8492
0
                      "stream\n" );
8493
0
    if (!writeBuffer(aLine))
8494
0
        return false;
8495
8496
0
    sal_uInt64 nStartStreamPos = 0;
8497
0
    if (osl::File::E_None != m_aFile.getPos(nStartStreamPos))
8498
0
        return false;
8499
8500
0
    checkAndEnableStreamEncryption( nFunctionObject );
8501
0
    beginCompression();
8502
0
    sal_uInt8 aCol[3];
8503
0
    switch (rObject.m_aGradient.GetStyle())
8504
0
    {
8505
0
        case css::awt::GradientStyle_AXIAL:
8506
0
            aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8507
0
            aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8508
0
            aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8509
0
            if (!writeBufferBytes(aCol, 3))
8510
0
                return false;
8511
0
            [[fallthrough]];
8512
0
        case css::awt::GradientStyle_LINEAR:
8513
0
        {
8514
0
            aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8515
0
            aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8516
0
            aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8517
0
            if (!writeBufferBytes(aCol, 3))
8518
0
                return false;
8519
8520
0
            aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8521
0
            aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8522
0
            aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8523
0
            if (!writeBufferBytes(aCol, 3))
8524
0
                return false;
8525
0
            break;
8526
0
        }
8527
0
        default:
8528
0
            for( int y = aSize.Height()-1; y >= 0; y-- )
8529
0
            {
8530
0
                for( tools::Long x = 0; x < aSize.Width(); x++ )
8531
0
                {
8532
0
                    BitmapColor aColor = pAccess->GetColor( y, x );
8533
0
                    aCol[0] = aColor.GetRed();
8534
0
                    aCol[1] = aColor.GetGreen();
8535
0
                    aCol[2] = aColor.GetBlue();
8536
0
                    if (!writeBufferBytes(aCol, 3))
8537
0
                        return false;
8538
0
                }
8539
0
            }
8540
0
    }
8541
0
    endCompression();
8542
0
    disableStreamEncryption();
8543
8544
0
    sal_uInt64 nEndStreamPos = 0;
8545
0
    if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
8546
0
        return false;
8547
8548
0
    aLine.setLength( 0 );
8549
0
    aLine.append( "\nendstream\nendobj\n\n" );
8550
0
    if (!writeBuffer(aLine)) return false;
8551
8552
    // write stream length
8553
0
    if (!updateObject(nStreamLengthObject)) return false;
8554
0
    aLine.setLength( 0 );
8555
0
    aLine.append( nStreamLengthObject );
8556
0
    aLine.append( " 0 obj\n" );
8557
0
    aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8558
0
    aLine.append( "\nendobj\n\n" );
8559
0
    if (!writeBuffer(aLine)) return false;
8560
8561
0
    if (!updateObject(rObject.m_nObject)) return false;
8562
0
    aLine.setLength( 0 );
8563
0
    aLine.append( rObject.m_nObject );
8564
0
    aLine.append( " 0 obj\n");
8565
0
    switch (rObject.m_aGradient.GetStyle())
8566
0
    {
8567
0
        case css::awt::GradientStyle_LINEAR:
8568
0
        case css::awt::GradientStyle_AXIAL:
8569
0
            aLine.append("<</ShadingType 2\n");
8570
0
            break;
8571
0
        default:
8572
0
            aLine.append("<</ShadingType 1\n");
8573
0
    }
8574
0
    aLine.append("/ColorSpace/DeviceRGB\n"
8575
0
                  "/AntiAlias true\n");
8576
8577
    // Determination of shading axis
8578
    // See: OutputDevice::ImplDrawLinearGradient for reference
8579
0
    tools::Rectangle aRect;
8580
0
    aRect.SetLeft(0);
8581
0
    aRect.SetTop(0);
8582
0
    aRect.SetRight( aSize.Width() );
8583
0
    aRect.SetBottom( aSize.Height() );
8584
8585
0
    tools::Rectangle aBoundRect;
8586
0
    Point     aCenter;
8587
0
    Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
8588
0
    rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8589
8590
0
    const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
8591
0
    double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8592
0
    if ( !bLinear )
8593
0
    {
8594
0
        fBorder /= 2.0;
8595
0
    }
8596
8597
0
    aBoundRect.AdjustBottom( -fBorder );
8598
0
    if (!bLinear)
8599
0
    {
8600
0
        aBoundRect.AdjustTop(fBorder );
8601
0
    }
8602
8603
0
    switch (rObject.m_aGradient.GetStyle())
8604
0
    {
8605
0
        case css::awt::GradientStyle_LINEAR:
8606
0
        case css::awt::GradientStyle_AXIAL:
8607
0
        {
8608
0
            aLine.append("/Domain[ 0 1 ]\n"
8609
0
                    "/Coords[ " );
8610
0
            tools::Polygon aPoly( 2 );
8611
0
            aPoly[0] = aBoundRect.BottomCenter();
8612
0
            aPoly[1] = aBoundRect.TopCenter();
8613
0
            aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
8614
8615
0
            aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8616
0
            aLine.append( " " );
8617
0
            aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8618
0
            aLine.append( " " );
8619
0
            aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8620
0
            aLine.append( " ");
8621
0
            aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8622
0
            aLine.append( " ]\n");
8623
0
            aLine.append("/Extend [true true]\n");
8624
0
            break;
8625
0
        }
8626
0
        default:
8627
0
            aLine.append("/Domain[ 0 1 0 1 ]\n"
8628
0
                    "/Matrix[ " );
8629
0
            aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8630
0
            aLine.append( " 0 0 " );
8631
0
            aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8632
0
            aLine.append( " 0 0 ]\n");
8633
0
    }
8634
0
    aLine.append("/Function " );
8635
0
    aLine.append( nFunctionObject );
8636
0
    aLine.append( " 0 R\n"
8637
0
                  ">>\n"
8638
0
                  "endobj\n\n" );
8639
0
    return writeBuffer( aLine );
8640
0
}
8641
8642
void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
8643
0
{
8644
0
    if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
8645
0
    {
8646
0
        writeReferenceXObject(rObject.m_aReferenceXObject);
8647
0
        return;
8648
0
    }
8649
8650
0
    if (!rObject.m_pStream) return;
8651
0
    if (!updateObject(rObject.m_nObject)) return;
8652
8653
0
    sal_uInt64 nLength = rObject.m_pStream->TellEnd();
8654
0
    rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8655
8656
0
    sal_Int32 nMaskObject = 0;
8657
0
    if( !rObject.m_aAlphaMask.IsEmpty() )
8658
0
    {
8659
0
        if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
8660
0
                && !m_bIsPDF_A1)
8661
0
        {
8662
0
            nMaskObject = createObject();
8663
0
        }
8664
0
        else if( m_bIsPDF_A1 )
8665
0
            m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8666
0
        else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8667
0
            m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8668
8669
0
    }
8670
0
    if (g_bDebugDisableCompression)
8671
0
    {
8672
0
        emitComment( "PDFWriterImpl::writeJPG" );
8673
0
    }
8674
8675
0
    OStringBuffer aLine(200);
8676
0
    aLine.append( rObject.m_nObject );
8677
0
    aLine.append( " 0 obj\n"
8678
0
                  "<</Type/XObject/Subtype/Image/Width " );
8679
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
8680
0
    aLine.append( " /Height " );
8681
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
8682
0
    aLine.append( " /BitsPerComponent 8 " );
8683
0
    if( rObject.m_bTrueColor )
8684
0
        aLine.append( "/ColorSpace/DeviceRGB" );
8685
0
    else
8686
0
        aLine.append( "/ColorSpace/DeviceGray" );
8687
0
    aLine.append( "/Filter/DCTDecode/Length " );
8688
0
    aLine.append(static_cast<sal_Int64>(calculateStreamSize(nLength)));
8689
0
    if( nMaskObject )
8690
0
    {
8691
0
        aLine.append(" /SMask ");
8692
0
        aLine.append( nMaskObject );
8693
0
        aLine.append( " 0 R " );
8694
0
    }
8695
0
    aLine.append( ">>\nstream\n" );
8696
0
    if (!writeBuffer(aLine))
8697
0
        return;
8698
8699
0
    checkAndEnableStreamEncryption( rObject.m_nObject );
8700
0
    if (!writeBufferBytes(rObject.m_pStream->GetData(), nLength))
8701
0
        return;
8702
0
    disableStreamEncryption();
8703
8704
0
    aLine.setLength( 0 );
8705
0
    if (!writeBuffer("\nendstream\nendobj\n\n"))
8706
0
        return;
8707
8708
0
    if( nMaskObject )
8709
0
        writeBitmapMaskObject( nMaskObject, rObject.m_aAlphaMask );
8710
8711
0
    writeReferenceXObject(rObject.m_aReferenceXObject);
8712
0
}
8713
8714
void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
8715
0
{
8716
0
    if (rEmit.m_nFormObject <= 0)
8717
0
        return;
8718
8719
    // Count /Matrix and /BBox.
8720
    // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
8721
    // rendering DPI so we have to take into account that here too.
8722
0
    static const double fResolutionDPI = getDefaultPdfResolutionDpi();
8723
0
    static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR;
8724
8725
0
    sal_Int32 nOldDPIX = GetDPIX();
8726
0
    sal_Int32 nOldDPIY = GetDPIY();
8727
0
    SetDPIX(fResolutionDPI);
8728
0
    SetDPIY(fResolutionDPI);
8729
0
    Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
8730
0
    SetDPIX(nOldDPIX);
8731
0
    SetDPIY(nOldDPIY);
8732
0
    double fScaleX = 1.0 / aSize.Width();
8733
0
    double fScaleY = 1.0 / aSize.Height();
8734
8735
0
    sal_Int32 nWrappedFormObject = 0;
8736
0
    if (!m_aContext.UseReferenceXObject)
8737
0
    {
8738
        // tdf#156842 increase scale for external PDF data
8739
        // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS
8740
        // that scale all images by this number.
8741
        // This fix also allows the CppunitTest_vcl_pdfexport to run
8742
        // successfully on macOS.
8743
0
        fScaleX = fMagicScaleFactor / aSize.Width();
8744
0
        fScaleY = fMagicScaleFactor / aSize.Height();
8745
8746
        // Parse the PDF data, we need that to write the PDF dictionary of our
8747
        // object.
8748
0
        if (rEmit.m_nExternalPDFDataIndex < 0)
8749
0
            return;
8750
0
        auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
8751
0
        auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
8752
0
        if (!pPDFDocument)
8753
0
        {
8754
            // Couldn't parse the document and can't continue
8755
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
8756
0
            return;
8757
0
        }
8758
8759
0
        std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
8760
0
        if (aPages.empty())
8761
0
        {
8762
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
8763
0
            return;
8764
0
        }
8765
8766
0
        size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
8767
8768
0
        filter::PDFObjectElement* pPage = aPages[nPageIndex];
8769
0
        if (!pPage)
8770
0
        {
8771
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
8772
0
            return;
8773
0
        }
8774
8775
        // Get the copied resource map, so we can use that to skip objects we already copied
8776
0
        auto& rCopiedResourcesMap = rExternalPDFStream.getCopiedResources();
8777
8778
        // Add page mapping to the copied resources map.
8779
        // Needed if we reference the current page and we want to prevent copying the page
8780
        // if it is referenced.
8781
0
        rCopiedResourcesMap.emplace(pPage->GetObjectValue(), m_aPages.back().m_nPageObject);
8782
8783
0
        double aOrigin[2] = { 0.0, 0.0 };
8784
8785
        // tdf#160714 use crop box for bounds of embedded PDF object
8786
        // If there is no crop box, fallback to the media box just to be safe.
8787
0
        auto* pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("CropBox"_ostr));
8788
0
        if (!pBoundsArray)
8789
0
            pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr));
8790
0
        if (pBoundsArray)
8791
0
        {
8792
0
            const auto& rElements = pBoundsArray->GetElements();
8793
0
            if (rElements.size() >= 4)
8794
0
            {
8795
                // get x1, y1 of the rectangle.
8796
0
                for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
8797
0
                {
8798
0
                    if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
8799
0
                        aOrigin[nIdx] = pNumElement->GetValue();
8800
0
                }
8801
0
            }
8802
0
        }
8803
8804
0
        std::vector<filter::PDFObjectElement*> aContentStreams;
8805
0
        if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr))
8806
0
            aContentStreams.push_back(pContentStream);
8807
0
        else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr)))
8808
0
        {
8809
0
            for (const auto pElement : pArray->GetElements())
8810
0
            {
8811
0
                auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8812
0
                if (!pReference)
8813
0
                    continue;
8814
8815
0
                filter::PDFObjectElement* pObject = pReference->LookupObject();
8816
0
                if (!pObject)
8817
0
                    continue;
8818
8819
0
                aContentStreams.push_back(pObject);
8820
0
            }
8821
0
        }
8822
8823
0
        if (aContentStreams.empty())
8824
0
        {
8825
0
            SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
8826
0
            return;
8827
0
        }
8828
8829
        // Merge link and widget annotations from pPage to our page.
8830
0
        mergeAnnotationsFromExternalPage(pPage, rCopiedResourcesMap);
8831
8832
0
        nWrappedFormObject = createObject();
8833
        // Write the form XObject wrapped below. This is a separate object from
8834
        // the wrapper, this way there is no need to alter the stream contents.
8835
8836
0
        OStringBuffer aLine;
8837
0
        aLine.append(nWrappedFormObject);
8838
0
        aLine.append(" 0 obj\n");
8839
0
        aLine.append("<< /Type /XObject");
8840
0
        aLine.append(" /Subtype /Form");
8841
8842
0
        tools::Long nWidth = aSize.Width();
8843
0
        tools::Long nHeight = aSize.Height();
8844
0
        basegfx::B2DRange aBBox(0, 0, aSize.Width(),  aSize.Height());
8845
0
        if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr)))
8846
0
        {
8847
            // The original page was rotated, then construct a transformation matrix which does the
8848
            // same with our form object.
8849
0
            sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
8850
            // /Rotate is clockwise, matrix rotate is counter-clockwise.
8851
0
            sal_Int32 nAngle = -1 * nRotAngle;
8852
8853
            // The bounding box just rotates.
8854
0
            basegfx::B2DHomMatrix aBBoxMat;
8855
0
            aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
8856
0
            aBBox.transform(aBBoxMat);
8857
8858
            // Now transform the object: rotate around the center and make sure that the rotation
8859
            // doesn't affect the aspect ratio.
8860
0
            basegfx::B2DHomMatrix aMat;
8861
0
            aMat.translate((-0.5 * aBBox.getWidth() / fMagicScaleFactor) - aOrigin[0], (-0.5 * aBBox.getHeight() / fMagicScaleFactor) - aOrigin[1]);
8862
0
            aMat.rotate(basegfx::deg2rad(nAngle));
8863
0
            aMat.translate(0.5 * nWidth / fMagicScaleFactor, 0.5 * nHeight / fMagicScaleFactor);
8864
8865
0
            aLine.append(" /Matrix [ ");
8866
0
            aLine.append(aMat.a());
8867
0
            aLine.append(" ");
8868
0
            aLine.append(aMat.b());
8869
0
            aLine.append(" ");
8870
0
            aLine.append(aMat.c());
8871
0
            aLine.append(" ");
8872
0
            aLine.append(aMat.d());
8873
0
            aLine.append(" ");
8874
0
            aLine.append(aMat.e());
8875
0
            aLine.append(" ");
8876
0
            aLine.append(aMat.f());
8877
0
            aLine.append(" ] ");
8878
0
        }
8879
8880
0
        PDFObjectCopier aCopier(*this);
8881
0
        aCopier.copyPageResources(pPage, aLine, rCopiedResourcesMap);
8882
8883
0
        aLine.append(" /BBox [ ");
8884
0
        aLine.append(aOrigin[0]);
8885
0
        aLine.append(' ');
8886
0
        aLine.append(aOrigin[1]);
8887
0
        aLine.append(' ');
8888
0
        aLine.append(aBBox.getWidth() + aOrigin[0]);
8889
0
        aLine.append(' ');
8890
0
        aLine.append(aBBox.getHeight() + aOrigin[1]);
8891
0
        aLine.append(" ]");
8892
8893
0
        if (!g_bDebugDisableCompression)
8894
0
            aLine.append(" /Filter/FlateDecode");
8895
0
        aLine.append(" /Length ");
8896
8897
0
        SvMemoryStream aStream;
8898
0
        bool bCompressed = false;
8899
0
        bool bIsTaggedNonReferenceXObject = m_aContext.Tagged && !m_aContext.UseReferenceXObject;
8900
0
        sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed,
8901
0
                                                             bIsTaggedNonReferenceXObject);
8902
0
        aLine.append(static_cast<sal_Int64>(calculateStreamSize(nLength)));
8903
8904
0
        aLine.append(">>\nstream\n");
8905
0
        if (g_bDebugDisableCompression)
8906
0
        {
8907
0
            emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
8908
0
        }
8909
0
        if (!updateObject(nWrappedFormObject))
8910
0
            return;
8911
0
        if (!writeBuffer(aLine))
8912
0
            return;
8913
0
        aLine.setLength(0);
8914
8915
0
        checkAndEnableStreamEncryption(nWrappedFormObject);
8916
        // Copy the original page streams to the form XObject stream.
8917
0
        aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
8918
0
        if (!writeBuffer(aLine))
8919
0
            return;
8920
0
        aLine.setLength(0);
8921
0
        disableStreamEncryption();
8922
8923
0
        aLine.append("\nendstream\nendobj\n\n");
8924
0
        if (!writeBuffer(aLine))
8925
0
            return;
8926
0
    }
8927
8928
0
    OStringBuffer aLine;
8929
0
    if (g_bDebugDisableCompression)
8930
0
    {
8931
0
        emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
8932
0
    }
8933
0
    if (!updateObject(rEmit.m_nFormObject))
8934
0
        return;
8935
8936
    // Now have all the info to write the form XObject.
8937
0
    aLine.append(rEmit.m_nFormObject);
8938
0
    aLine.append(" 0 obj\n");
8939
0
    aLine.append("<< /Type /XObject");
8940
0
    aLine.append(" /Subtype /Form");
8941
0
    aLine.append(" /Resources << /XObject<<");
8942
8943
0
    sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
8944
0
    aLine.append(" /Im");
8945
0
    aLine.append(nObject);
8946
0
    aLine.append(" ");
8947
0
    aLine.append(nObject);
8948
0
    aLine.append(" 0 R");
8949
8950
0
    aLine.append(">> >>");
8951
0
    aLine.append(" /Matrix [ ");
8952
0
    appendDouble(fScaleX, aLine);
8953
0
    aLine.append(" 0 0 ");
8954
0
    appendDouble(fScaleY, aLine);
8955
0
    aLine.append(" 0 0 ]");
8956
0
    aLine.append(" /BBox [ 0 0 ");
8957
    // tdf#157680 reduce size by magic scale factor in /BBox
8958
0
    aLine.append(aSize.Width() / fMagicScaleFactor);
8959
0
    aLine.append(" ");
8960
    // tdf#157680 reduce size by magic scale factor in /BBox
8961
0
    aLine.append(aSize.Height() / fMagicScaleFactor);
8962
0
    aLine.append(" ]\n");
8963
8964
0
    if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
8965
0
    {
8966
        // Write the reference dictionary.
8967
0
        aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) ");
8968
0
        if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
8969
0
        {   // ISO 14289-1:2014, Clause: 7.11
8970
0
            aLine.append("/UF (<embedded file>) ");
8971
0
        }
8972
0
        aLine.append("/EF << /F ");
8973
0
        aLine.append(rEmit.m_nEmbeddedObject);
8974
0
        aLine.append(" 0 R >> >> /Page 0 >>\n");
8975
0
    }
8976
8977
0
    aLine.append("/Length ");
8978
8979
0
    OStringBuffer aStream;
8980
0
    aStream.append("q ");
8981
0
    if (m_aContext.UseReferenceXObject)
8982
0
    {
8983
        // Reference XObject markup is used, just refer to the fallback bitmap
8984
        // here.
8985
0
        aStream.append(aSize.Width());
8986
0
        aStream.append(" 0 0 ");
8987
0
        aStream.append(aSize.Height());
8988
0
        aStream.append(" 0 0 cm\n");
8989
0
        aStream.append("/Im");
8990
0
        aStream.append(rEmit.m_nBitmapObject);
8991
0
        aStream.append(" Do\n");
8992
0
    }
8993
0
    else
8994
0
    {
8995
        // Reset line width to the default.
8996
0
        aStream.append(" 1 w\n");
8997
8998
        // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
8999
        // consistent with that.
9000
0
        aStream.append("1 1 1 rg\n");
9001
0
        aStream.append("0 0 ");
9002
0
        aStream.append(aSize.Width());
9003
0
        aStream.append(" ");
9004
0
        aStream.append(aSize.Height());
9005
0
        aStream.append(" re\n");
9006
0
        aStream.append("f*\n");
9007
9008
        // Reset non-stroking color in case the XObject uses the default
9009
0
        aStream.append("0 0 0 rg\n");
9010
        // No reference XObject, draw the form XObject containing the original
9011
        // page streams.
9012
0
        aStream.append("/Im");
9013
0
        aStream.append(nWrappedFormObject);
9014
0
        aStream.append(" Do\n");
9015
0
    }
9016
0
    aStream.append("Q");
9017
0
    aLine.append(static_cast<sal_Int64>(calculateStreamSize(aStream.getLength())));
9018
9019
0
    aLine.append(">>\nstream\n");
9020
0
    if (!writeBuffer(aLine))
9021
0
        return;
9022
0
    aLine.setLength(0);
9023
9024
0
    checkAndEnableStreamEncryption(rEmit.m_nFormObject);
9025
0
    aLine.append(aStream.getStr());
9026
0
    if (!writeBuffer(aLine))
9027
0
        return;
9028
0
    aLine.setLength(0);
9029
0
    disableStreamEncryption();
9030
9031
0
    aLine.append("\nendstream\nendobj\n\n");
9032
0
    if (!writeBuffer(aLine))
9033
0
        return;
9034
0
}
9035
9036
namespace
9037
{
9038
9039
sal_Int32 getRootParent(filter::PDFObjectElement* pObject)
9040
0
{
9041
0
    auto* pReference = dynamic_cast<filter::PDFReferenceElement*>(pObject->Lookup("Parent"_ostr));
9042
0
    if (!pReference)
9043
0
        return pObject->GetObjectValue();
9044
9045
0
    auto* pParent = pReference->LookupObject();
9046
0
    return getRootParent(pParent);
9047
0
}
9048
9049
} // end anonymous
9050
9051
void PDFWriterImpl::mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage,  std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap)
9052
0
{
9053
0
    auto* pResult = pPage->Lookup("Annots"_ostr);
9054
0
    filter::PDFArrayElement* pArray = nullptr;
9055
    // If the Annots array is a reference - get the array from the referenced object
9056
0
    auto pAnnotsReference = dynamic_cast<filter::PDFReferenceElement*>(pResult);
9057
0
    if (pAnnotsReference)
9058
0
    {
9059
0
        filter::PDFObjectElement* pObject = pAnnotsReference->LookupObject();
9060
0
        pArray = pObject->GetArray();
9061
0
    }
9062
0
    else
9063
0
    {
9064
        // Not a reference so is it an array
9065
0
        pArray = dynamic_cast<filter::PDFArrayElement*>(pResult);
9066
0
    }
9067
9068
    // Have we found our /Annots array?
9069
0
    if (!pArray)
9070
0
        return;
9071
9072
0
    std::unordered_set<sal_Int32> aAlreadyCopied;
9073
0
    PDFObjectCopier aCopier(*this);
9074
0
    SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
9075
9076
0
    for (const auto pElement : pArray->GetElements())
9077
0
    {
9078
0
        auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9079
0
        if (!pReference)
9080
0
            continue;
9081
9082
0
        filter::PDFObjectElement* pObject = pReference->LookupObject();
9083
0
        if (!pObject)
9084
0
            continue;
9085
9086
        // Get the /Type and the /Subtype
9087
0
        auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
9088
0
        auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
9089
9090
        // Is it a /Annot we want to copy?
9091
0
        if (pType && pType->GetValue() == "Annot" && pSubtype)
9092
0
        {
9093
0
            bool bIsLink = pSubtype->GetValue() == "Link";
9094
0
            bool bIsWidget = pSubtype->GetValue() == "Widget";
9095
9096
            // is link or widget
9097
0
            if (!bIsLink && !bIsWidget)
9098
0
                continue;
9099
9100
            // Copy over the annotation and refer to its new id.
9101
0
            sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pObject, rCopiedResourcesMap);
9102
0
            m_aPages.back().m_aAnnotations.push_back(nNewId);
9103
9104
0
            if (!bIsWidget)
9105
0
                continue;
9106
9107
            // Find the root
9108
0
            sal_Int32 nRootID = getRootParent(pObject);
9109
9110
0
            auto aIterator = rCopiedResourcesMap.find(nRootID);
9111
0
            if (aIterator == rCopiedResourcesMap.end()) // Can't find the mapped ID ?
9112
0
                continue;
9113
9114
0
            nNewId = aIterator->second;
9115
9116
            // Ignore if we added the ID already
9117
0
            if (aAlreadyCopied.find(nNewId) == aAlreadyCopied.end())
9118
0
            {
9119
                // Add new entry into copied widgets vector
9120
0
                auto& rCopiedWidget = m_aCopiedWidgets.emplace_back();
9121
0
                rCopiedWidget.m_nObject = nNewId;
9122
0
                aAlreadyCopied.emplace(nNewId);
9123
0
            }
9124
0
        }
9125
0
    }
9126
9127
0
}
9128
9129
bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject )
9130
0
{
9131
0
    if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9132
0
    {
9133
0
        writeReferenceXObject(rObject.m_aReferenceXObject);
9134
0
        return true;
9135
0
    }
9136
9137
0
    if (!updateObject(rObject.m_nObject))
9138
0
        return false;
9139
9140
0
    bool    bWriteMask = false;
9141
0
    if( rObject.m_aBitmap.HasAlpha() )
9142
0
    {
9143
0
        if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9144
0
            bWriteMask = true;
9145
        // else draw without alpha channel
9146
0
    }
9147
9148
0
    BitmapScopedReadAccess pAccess(rObject.m_aBitmap);
9149
9150
0
    bool bTrueColor = true;
9151
0
    sal_Int32 nBitsPerComponent = 0;
9152
0
    auto const ePixelFormat = rObject.m_aBitmap.getPixelFormat();
9153
0
    switch (ePixelFormat)
9154
0
    {
9155
0
        case vcl::PixelFormat::N8_BPP:
9156
0
            bTrueColor = false;
9157
0
            nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
9158
0
            break;
9159
0
        case vcl::PixelFormat::N24_BPP:
9160
0
        case vcl::PixelFormat::N32_BPP:
9161
0
            bTrueColor = true;
9162
0
            nBitsPerComponent = 8;
9163
0
            break;
9164
0
        case vcl::PixelFormat::INVALID:
9165
0
            return false;
9166
0
    }
9167
9168
0
    sal_Int32 nStreamLengthObject   = createObject();
9169
0
    sal_Int32 nMaskObject           = 0;
9170
9171
0
    if (g_bDebugDisableCompression)
9172
0
    {
9173
0
        emitComment( "PDFWriterImpl::writeBitmapObject" );
9174
0
    }
9175
0
    OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
9176
0
    aLine.append( rObject.m_nObject );
9177
0
    aLine.append( " 0 obj\n"
9178
0
                  "<</Type/XObject/Subtype/Image/Width " );
9179
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9180
0
    aLine.append( "/Height " );
9181
0
    aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Height()) );
9182
0
    aLine.append( "/BitsPerComponent " );
9183
0
    aLine.append( nBitsPerComponent );
9184
0
    aLine.append( "/Length " );
9185
0
    aLine.append( nStreamLengthObject );
9186
0
    aLine.append( " 0 R\n" );
9187
0
    if (!g_bDebugDisableCompression)
9188
0
    {
9189
0
        if( nBitsPerComponent != 1 )
9190
0
        {
9191
0
            aLine.append( "/Filter/FlateDecode" );
9192
0
        }
9193
0
        else
9194
0
        {
9195
0
            aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9196
0
            aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9197
0
            aLine.append( ">>\n" );
9198
0
        }
9199
0
    }
9200
0
    aLine.append( "/ColorSpace" );
9201
0
    if( bTrueColor )
9202
0
        aLine.append( "/DeviceRGB\n" );
9203
0
    else
9204
0
    {
9205
0
        aLine.append( "[ /Indexed/DeviceRGB " );
9206
0
        aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9207
0
        aLine.append( "\n<" );
9208
0
        if (m_aContext.Encryption.canEncrypt())
9209
0
        {
9210
0
            enableStringEncryption(rObject.m_nObject);
9211
            //check encryption buffer size
9212
0
            m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9213
0
            int nChar = 0;
9214
            //fill the encryption buffer
9215
0
            for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9216
0
            {
9217
0
                const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9218
0
                m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9219
0
                m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9220
0
                m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9221
0
            }
9222
            //encrypt the colorspace lookup table
9223
0
            std::vector<sal_uInt8> aOutputBuffer(nChar);
9224
0
            m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, aOutputBuffer, nChar);
9225
            //now queue the data for output
9226
0
            COSWriter::appendHexArray(aOutputBuffer.data(), aOutputBuffer.size(), aLine);
9227
0
        }
9228
0
        else //no encryption requested (PDF/A-1a program flow drops here)
9229
0
        {
9230
0
            for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9231
0
            {
9232
0
                const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9233
0
                COSWriter::appendHex( rColor.GetRed(), aLine );
9234
0
                COSWriter::appendHex( rColor.GetGreen(), aLine );
9235
0
                COSWriter::appendHex( rColor.GetBlue(), aLine );
9236
0
            }
9237
0
        }
9238
0
        aLine.append( ">\n]\n" );
9239
0
    }
9240
9241
0
    if (!m_bIsPDF_A1)
9242
0
    {
9243
0
        if( bWriteMask )
9244
0
        {
9245
0
            nMaskObject = createObject();
9246
0
            aLine.append( "/SMask " );
9247
0
            aLine.append( nMaskObject );
9248
0
            aLine.append( " 0 R\n" );
9249
0
        }
9250
0
    }
9251
0
    else if( m_bIsPDF_A1 && bWriteMask )
9252
0
        m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9253
9254
0
    aLine.append( ">>\n"
9255
0
                  "stream\n" );
9256
0
    if (!writeBuffer(aLine)) return false;
9257
0
    sal_uInt64 nStartPos = 0;
9258
0
    if (osl::File::E_None != m_aFile.getPos(nStartPos))
9259
0
        return false;
9260
9261
0
    checkAndEnableStreamEncryption( rObject.m_nObject );
9262
0
    {
9263
0
        beginCompression();
9264
0
        if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9265
0
        {
9266
            //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9267
0
            const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9268
9269
0
            for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9270
0
            {
9271
0
                if (!writeBufferBytes(pAccess->GetScanline(i), nScanLineBytes))
9272
0
                    return false;
9273
0
            }
9274
0
        }
9275
0
        else
9276
0
        {
9277
0
            const int nScanLineBytes = pAccess->Width()*3;
9278
0
            std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9279
0
            for( tools::Long y = 0, nHeight = pAccess->Height(); y < nHeight; y++ )
9280
0
            {
9281
0
                for( tools::Long x = 0, nWidth = pAccess->Width(); x < nWidth; x++ )
9282
0
                {
9283
0
                    BitmapColor aColor = pAccess->GetColor( y, x );
9284
0
                    xCol[3*x+0] = aColor.GetRed();
9285
0
                    xCol[3*x+1] = aColor.GetGreen();
9286
0
                    xCol[3*x+2] = aColor.GetBlue();
9287
0
                }
9288
0
                if (!writeBufferBytes(xCol.get(), nScanLineBytes))
9289
0
                    return false;
9290
0
            }
9291
0
        }
9292
0
        endCompression();
9293
0
    }
9294
0
    disableStreamEncryption();
9295
9296
0
    sal_uInt64 nEndPos = 0;
9297
0
    if (osl::File::E_None != m_aFile.getPos(nEndPos))
9298
0
        return false;
9299
0
    aLine.setLength( 0 );
9300
0
    aLine.append( "\nendstream\nendobj\n\n" );
9301
0
    if (!writeBuffer(aLine)) return false;
9302
0
    if (!updateObject(nStreamLengthObject)) return false;
9303
0
    aLine.setLength( 0 );
9304
0
    aLine.append( nStreamLengthObject );
9305
0
    aLine.append( " 0 obj\n" );
9306
0
    aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9307
0
    aLine.append( "\nendobj\n\n" );
9308
0
    if (!writeBuffer(aLine)) return false;
9309
9310
0
    if( nMaskObject )
9311
0
        return writeBitmapMaskObject( nMaskObject, rObject.m_aBitmap.CreateAlphaMask() );
9312
9313
0
    writeReferenceXObject(rObject.m_aReferenceXObject);
9314
9315
0
    return true;
9316
0
}
9317
9318
bool PDFWriterImpl::writeBitmapMaskObject( sal_Int32 nMaskObject, const AlphaMask& rAlphaMask )
9319
0
{
9320
0
    assert( rAlphaMask.GetBitmap().getPixelFormat() == vcl::PixelFormat::N8_BPP );
9321
9322
0
    if (!updateObject(nMaskObject))
9323
0
        return false;
9324
9325
0
    Bitmap  aBitmap;
9326
0
    if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9327
0
    {
9328
0
        aBitmap = rAlphaMask.GetBitmap();
9329
0
        aBitmap.Convert( BmpConversion::N1BitThreshold );
9330
0
    }
9331
0
    else
9332
0
    {
9333
0
        aBitmap = rAlphaMask.GetBitmap();
9334
0
    }
9335
9336
0
    const sal_Int32 nBitsPerComponent = 8;
9337
9338
0
    sal_Int32 nStreamLengthObject   = createObject();
9339
9340
0
    if (g_bDebugDisableCompression)
9341
0
    {
9342
0
        emitComment( "PDFWriterImpl::writeBitmapObject" );
9343
0
    }
9344
0
    OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE);
9345
0
    aLine.append( nMaskObject );
9346
0
    aLine.append( " 0 obj\n"
9347
0
                  "<</Type/XObject/Subtype/Image/Width " );
9348
0
    aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9349
0
    aLine.append( "/Height " );
9350
0
    aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9351
0
    aLine.append( "/BitsPerComponent " );
9352
0
    aLine.append( nBitsPerComponent );
9353
0
    aLine.append( "/Length " );
9354
0
    aLine.append( nStreamLengthObject );
9355
0
    aLine.append( " 0 R\n" );
9356
0
    if (!g_bDebugDisableCompression)
9357
0
    {
9358
0
        aLine.append( "/Filter/FlateDecode" );
9359
0
    }
9360
0
    aLine.append( "/ColorSpace/DeviceGray\n"
9361
0
                  "/Decode [ 1 0 ]\n" );
9362
9363
0
    aLine.append( ">>\n"
9364
0
                  "stream\n" );
9365
0
    if (!writeBuffer(aLine)) return false;
9366
0
    sal_uInt64 nStartPos = 0;
9367
0
    if (osl::File::E_None != m_aFile.getPos(nStartPos))
9368
0
        return false;
9369
9370
0
    checkAndEnableStreamEncryption( nMaskObject );
9371
0
    beginCompression();
9372
0
    BitmapScopedReadAccess pAccess(aBitmap);
9373
    //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9374
0
    const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9375
    // we have alpha, but we want to output transparency, so we need to invert the data
9376
0
    std::unique_ptr<sal_uInt8[]> pInvertedBytes = std::make_unique<sal_uInt8[]>(nScanLineBytes);
9377
0
    for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9378
0
    {
9379
0
        const Scanline pScanline = pAccess->GetScanline(i);
9380
0
        std::copy(pScanline, pScanline + nScanLineBytes, pInvertedBytes.get());
9381
0
        for (auto p = pInvertedBytes.get(); p < pInvertedBytes.get() + nScanLineBytes; ++p)
9382
0
            *p = ~(*p);
9383
0
        if (!writeBufferBytes(pInvertedBytes.get(), nScanLineBytes))
9384
0
            return false;
9385
0
    }
9386
0
    endCompression();
9387
0
    disableStreamEncryption();
9388
9389
0
    sal_uInt64 nEndPos = 0;
9390
0
    if (osl::File::E_None != m_aFile.getPos(nEndPos))
9391
0
        return false;
9392
0
    aLine.setLength( 0 );
9393
0
    aLine.append( "\nendstream\nendobj\n\n" );
9394
0
    if (!writeBuffer(aLine)) return false;
9395
0
    if (!updateObject(nStreamLengthObject)) return false;
9396
0
    aLine.setLength( 0 );
9397
0
    aLine.append( nStreamLengthObject );
9398
0
    aLine.append( " 0 obj\n" );
9399
0
    aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9400
0
    aLine.append( "\nendobj\n\n" );
9401
0
    if (!writeBuffer(aLine)) return false;
9402
9403
0
    return true;
9404
0
}
9405
9406
void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9407
0
{
9408
    // The bitmap object is always a valid identifier, even if the graphic has
9409
    // no pdf data.
9410
0
    rEmit.m_nBitmapObject = nBitmapObject;
9411
9412
0
    if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
9413
0
        return;
9414
9415
0
    BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
9416
9417
0
    if (m_aContext.UseReferenceXObject)
9418
0
    {
9419
        // Store the original PDF data as an embedded file.
9420
0
        auto nObjectID = addEmbeddedFile(rDataContainer);
9421
0
        rEmit.m_nEmbeddedObject = nObjectID;
9422
0
    }
9423
0
    else
9424
0
    {
9425
0
        sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
9426
0
        rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
9427
0
        rEmit.m_nExternalPDFDataIndex = aIndex;
9428
0
    }
9429
9430
0
    rEmit.m_nFormObject = createObject();
9431
0
    rEmit.m_aPixelSize = rGraphic.GetSizePixel();
9432
0
}
9433
9434
void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
9435
0
{
9436
0
    MARK( "drawJPGBitmap" );
9437
9438
0
    OStringBuffer aLine( 80 );
9439
0
    updateGraphicsState();
9440
9441
    // #i40055# sanity check
9442
0
    if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9443
0
        return;
9444
0
    if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9445
0
        return;
9446
9447
0
    rDCTData.Seek( 0 );
9448
0
    if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9449
0
    {
9450
        // need to convert to grayscale;
9451
        // load stream to bitmap and draw the bitmap instead
9452
0
        Graphic aGraphic;
9453
0
        GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9454
0
        if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
9455
0
        {
9456
0
            Bitmap aBmp( aGraphic.GetBitmap().CreateColorBitmap() );
9457
0
            Bitmap aBmp2( aBmp, rAlphaMask );
9458
0
            drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmp2 );
9459
0
        }
9460
0
        else
9461
0
            drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmap() );
9462
0
        return;
9463
0
    }
9464
9465
0
    std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9466
0
    pStream->WriteStream( rDCTData );
9467
0
    pStream->Seek( STREAM_SEEK_TO_END );
9468
9469
0
    BitmapID aID;
9470
0
    aID.m_aPixelSize    = rSizePixel;
9471
0
    aID.m_nSize         = pStream->Tell();
9472
0
    pStream->Seek( STREAM_SEEK_TO_BEGIN );
9473
0
    aID.m_nChecksum     = rtl_crc32( 0, pStream->GetData(), aID.m_nSize );
9474
0
    if( ! rAlphaMask.IsEmpty() )
9475
0
        aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
9476
9477
0
    std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9478
0
                                             [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9479
0
    if( it == m_aJPGs.end() )
9480
0
    {
9481
0
        m_aJPGs.emplace( m_aJPGs.begin() );
9482
0
        JPGEmit& rEmit = m_aJPGs.front();
9483
0
        if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9484
0
            rEmit.m_nObject = createObject();
9485
0
        rEmit.m_aID         = aID;
9486
0
        rEmit.m_pStream = std::move( pStream );
9487
0
        rEmit.m_bTrueColor  = bIsTrueColor;
9488
0
        if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
9489
0
            rEmit.m_aAlphaMask = rAlphaMask;
9490
0
        createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9491
9492
0
        it = m_aJPGs.begin();
9493
0
    }
9494
9495
0
    aLine.append( "q " );
9496
0
    sal_Int32 nCheckWidth = 0;
9497
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9498
0
    aLine.append( " 0 0 " );
9499
0
    sal_Int32 nCheckHeight = 0;
9500
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9501
0
    aLine.append( ' ' );
9502
0
    m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9503
0
    aLine.append( " cm\n/Im" );
9504
0
    sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9505
0
    aLine.append(nObject);
9506
0
    aLine.append( " Do Q\n" );
9507
0
    if( nCheckWidth == 0 || nCheckHeight == 0 )
9508
0
    {
9509
        // #i97512# avoid invalid current matrix
9510
0
        aLine.setLength( 0 );
9511
0
        aLine.append( "\n%jpeg image /Im" );
9512
0
        aLine.append( it->m_nObject );
9513
0
        aLine.append( " scaled to zero size, omitted\n" );
9514
0
    }
9515
0
    writeBuffer( aLine );
9516
9517
0
    OString aObjName = "Im" + OString::number(nObject);
9518
0
    pushResource( ResourceKind::XObject, aObjName, nObject );
9519
9520
0
}
9521
9522
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9523
0
{
9524
0
    OStringBuffer& rLine = drawBitmapLine;
9525
0
    rLine.setLength(0);
9526
0
    updateGraphicsState();
9527
9528
0
    rLine.append( "q " );
9529
0
    if( rFillColor != COL_TRANSPARENT )
9530
0
    {
9531
0
        appendNonStrokingColor( rFillColor, rLine );
9532
0
        rLine.append( ' ' );
9533
0
    }
9534
0
    sal_Int32 nCheckWidth = 0;
9535
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), rLine, false, &nCheckWidth );
9536
0
    rLine.append( " 0 0 " );
9537
0
    sal_Int32 nCheckHeight = 0;
9538
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), rLine, true, &nCheckHeight );
9539
0
    rLine.append( ' ' );
9540
0
    m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), rLine );
9541
0
    rLine.append( " cm\n/Im" );
9542
0
    sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9543
0
    rLine.append(nObject);
9544
0
    rLine.append( " Do Q\n" );
9545
0
    if( nCheckWidth == 0 || nCheckHeight == 0 )
9546
0
    {
9547
        // #i97512# avoid invalid current matrix
9548
0
        rLine.setLength( 0 );
9549
0
        rLine.append( "\n%bitmap image /Im" );
9550
0
        rLine.append( rBitmap.m_nObject );
9551
0
        rLine.append( " scaled to zero size, omitted\n" );
9552
0
    }
9553
0
    writeBuffer( rLine );
9554
0
}
9555
9556
const BitmapEmit& PDFWriterImpl::createBitmapEmit(const Bitmap& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
9557
0
{
9558
0
    Bitmap aBitmap( i_rBitmap );
9559
0
    auto ePixelFormat = aBitmap.getPixelFormat();
9560
0
    if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9561
0
        aBitmap.Convert(BmpConversion::N8BitGreys);
9562
0
    BitmapID aID;
9563
0
    aID.m_aPixelSize        = aBitmap.GetSizePixel();
9564
0
    aID.m_nSize             = vcl::pixelFormatBitCount(ePixelFormat);
9565
0
    if( aBitmap.HasAlpha() )
9566
0
    {
9567
0
        std::pair<Bitmap, AlphaMask> aPair = aBitmap.SplitIntoColorAndAlpha();
9568
0
        aID.m_nChecksum     = aPair.first.GetChecksum();
9569
0
        aID.m_nMaskChecksum = aPair.second.GetChecksum();
9570
0
    }
9571
0
    else
9572
0
    {
9573
0
        aID.m_nChecksum         = aBitmap.GetChecksum();
9574
0
        aID.m_nMaskChecksum     = 0;
9575
0
    }
9576
0
    std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
9577
0
                                             [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9578
0
    if (it == rBitmaps.end())
9579
0
    {
9580
0
        rBitmaps.push_front(BitmapEmit());
9581
0
        rBitmaps.front().m_aID = aID;
9582
0
        rBitmaps.front().m_aBitmap = std::move(aBitmap);
9583
0
        if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9584
0
            rBitmaps.front().m_nObject = createObject();
9585
0
        createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
9586
0
        it = rBitmaps.begin();
9587
0
    }
9588
9589
0
    sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9590
0
    OString aObjName = "Im" + OString::number(nObject);
9591
0
    pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
9592
9593
0
    return *it;
9594
0
}
9595
9596
const BitmapEmit& PDFWriterImpl::createBitmapEmit( const Bitmap& i_rBitmap, const Graphic& rGraphic )
9597
0
{
9598
0
    return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
9599
0
}
9600
9601
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9602
0
{
9603
0
    MARK( "drawBitmap (Bitmap)" );
9604
9605
    // #i40055# sanity check
9606
0
    if( ! (rDestSize.Width() && rDestSize.Height()) )
9607
0
        return;
9608
9609
0
    const BitmapEmit& rEmit = createBitmapEmit( rBitmap, rGraphic );
9610
0
    drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9611
0
}
9612
9613
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap )
9614
0
{
9615
0
    MARK( "drawBitmap (Bitmap)" );
9616
9617
    // #i40055# sanity check
9618
0
    if( ! (rDestSize.Width() && rDestSize.Height()) )
9619
0
        return;
9620
9621
0
    const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9622
0
    drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9623
0
}
9624
9625
sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9626
0
{
9627
0
    Size aPtSize( convert( m_aGraphicsStack.front().m_aMapMode,
9628
0
                               MapMode( MapUnit::MapPoint ),
9629
0
                               this,
9630
0
                               rSize ) );
9631
    // check if we already have this gradient
9632
    // rounding to point will generally lose some pixels
9633
    // round up to point boundary
9634
0
    aPtSize.AdjustWidth( 1 );
9635
0
    aPtSize.AdjustHeight( 1 );
9636
0
    std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9637
0
                                             [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9638
9639
0
    if( it == m_aGradients.end() )
9640
0
    {
9641
0
        m_aGradients.push_front( GradientEmit() );
9642
0
        m_aGradients.front().m_aGradient    = rGradient;
9643
0
        m_aGradients.front().m_nObject      = createObject();
9644
0
        m_aGradients.front().m_aSize        = aPtSize;
9645
0
        it = m_aGradients.begin();
9646
0
    }
9647
9648
0
    OStringBuffer aObjName( 16 );
9649
0
    aObjName.append( 'P' );
9650
0
    aObjName.append( it->m_nObject );
9651
0
    pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
9652
9653
0
    return it->m_nObject;
9654
0
}
9655
9656
void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9657
0
{
9658
0
    MARK( "drawGradient (Rectangle)" );
9659
9660
0
    sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9661
9662
0
    Point aTranslate( rRect.BottomLeft() );
9663
0
    aTranslate += Point( 0, 1 );
9664
9665
0
    updateGraphicsState();
9666
9667
0
    OStringBuffer aLine( 80 );
9668
0
    aLine.append( "q 1 0 0 1 " );
9669
0
    m_aPages.back().appendPoint( aTranslate, aLine );
9670
0
    aLine.append( " cm " );
9671
    // if a stroke is appended reset the clip region before stroke
9672
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9673
0
        aLine.append( "q " );
9674
0
    aLine.append( "0 0 " );
9675
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9676
0
    aLine.append( ' ' );
9677
0
    m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9678
0
    aLine.append( " re W n\n" );
9679
9680
0
    aLine.append( "/P" );
9681
0
    aLine.append( nGradient );
9682
0
    aLine.append( " sh " );
9683
0
    if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9684
0
    {
9685
0
        aLine.append( "Q 0 0 " );
9686
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9687
0
        aLine.append( ' ' );
9688
0
        m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9689
0
        aLine.append( " re S " );
9690
0
    }
9691
0
    aLine.append( "Q\n" );
9692
0
    writeBuffer( aLine );
9693
0
}
9694
9695
void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
9696
0
{
9697
0
    MARK( "drawHatch" );
9698
9699
0
    updateGraphicsState();
9700
9701
0
    if( rPolyPoly.Count() )
9702
0
    {
9703
0
        tools::PolyPolygon     aPolyPoly( rPolyPoly );
9704
9705
0
        aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
9706
0
        push( PushFlags::LINECOLOR );
9707
0
        setLineColor( rHatch.GetColor() );
9708
0
        DrawHatch( aPolyPoly, rHatch, false );
9709
0
        pop();
9710
0
    }
9711
0
}
9712
9713
void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
9714
0
{
9715
0
    MARK( "drawWallpaper" );
9716
9717
0
    bool bDrawColor         = false;
9718
0
    bool bDrawGradient      = false;
9719
0
    bool bDrawBitmap        = false;
9720
9721
0
    Bitmap aBitmap;
9722
0
    Point aBmpPos = rRect.TopLeft();
9723
0
    Size aBmpSize;
9724
0
    if( rWall.IsBitmap() )
9725
0
    {
9726
0
        aBitmap = rWall.GetBitmap();
9727
0
        aBmpSize = convert( aBitmap.GetPrefMapMode(),
9728
0
                                getMapMode(),
9729
0
                                this,
9730
0
                                aBitmap.GetPrefSize() );
9731
0
        tools::Rectangle aRect( rRect );
9732
0
        if( rWall.IsRect() )
9733
0
        {
9734
0
            aRect = rWall.GetRect();
9735
0
            aBmpPos = aRect.TopLeft();
9736
0
            aBmpSize = aRect.GetSize();
9737
0
        }
9738
0
        if( rWall.GetStyle() != WallpaperStyle::Scale )
9739
0
        {
9740
0
            if( rWall.GetStyle() != WallpaperStyle::Tile )
9741
0
            {
9742
0
                bDrawBitmap     = true;
9743
0
                if( rWall.IsGradient() )
9744
0
                    bDrawGradient = true;
9745
0
                else
9746
0
                    bDrawColor = true;
9747
0
                switch( rWall.GetStyle() )
9748
0
                {
9749
0
                    case WallpaperStyle::TopLeft:
9750
0
                        break;
9751
0
                    case WallpaperStyle::Top:
9752
0
                        aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9753
0
                        break;
9754
0
                    case WallpaperStyle::Left:
9755
0
                        aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9756
0
                        break;
9757
0
                    case WallpaperStyle::TopRight:
9758
0
                        aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9759
0
                        break;
9760
0
                    case WallpaperStyle::Center:
9761
0
                        aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9762
0
                        aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9763
0
                        break;
9764
0
                    case WallpaperStyle::Right:
9765
0
                        aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9766
0
                        aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9767
0
                        break;
9768
0
                    case WallpaperStyle::BottomLeft:
9769
0
                        aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9770
0
                        break;
9771
0
                    case WallpaperStyle::Bottom:
9772
0
                        aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9773
0
                        aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9774
0
                        break;
9775
0
                    case WallpaperStyle::BottomRight:
9776
0
                        aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9777
0
                        aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9778
0
                        break;
9779
0
                    default: ;
9780
0
                }
9781
0
            }
9782
0
            else
9783
0
            {
9784
                // push the bitmap
9785
0
                const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
9786
9787
                // convert to page coordinates; this needs to be done here
9788
                // since the emit does not know the page anymore
9789
0
                tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
9790
0
                m_aPages.back().convertRect( aConvertRect );
9791
9792
0
                OString aImageName = "Im" + OString::number( rEmit.m_nObject );
9793
9794
                // push the pattern
9795
0
                OStringBuffer aTilingStream( 32 );
9796
0
                appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
9797
0
                aTilingStream.append( " 0 0 " );
9798
0
                appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
9799
0
                aTilingStream.append( " 0 0 cm\n/" );
9800
0
                aTilingStream.append( aImageName );
9801
0
                aTilingStream.append( " Do\n" );
9802
9803
0
                m_aTilings.emplace_back( );
9804
0
                m_aTilings.back().m_nObject         = createObject();
9805
0
                m_aTilings.back().m_aRectangle      = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
9806
0
                m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
9807
0
                m_aTilings.back().m_pTilingStream->WriteBytes(
9808
0
                    aTilingStream.getStr(), aTilingStream.getLength() );
9809
                // phase the tiling so wallpaper begins on upper left
9810
0
                if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
9811
0
                    throw o3tl::divide_by_zero();
9812
0
                m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
9813
0
                m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
9814
0
                m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
9815
9816
0
                updateGraphicsState();
9817
9818
0
                OStringBuffer aObjName( 16 );
9819
0
                aObjName.append( 'P' );
9820
0
                aObjName.append( m_aTilings.back().m_nObject );
9821
0
                OString aPatternName( aObjName.makeStringAndClear() );
9822
0
                pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
9823
9824
                // fill a rRect with the pattern
9825
0
                OStringBuffer aLine( 100 );
9826
0
                aLine.append( "q /Pattern cs /" );
9827
0
                aLine.append( aPatternName );
9828
0
                aLine.append( " scn " );
9829
0
                m_aPages.back().appendRect( rRect, aLine );
9830
0
                aLine.append( " f Q\n" );
9831
0
                writeBuffer( aLine );
9832
0
            }
9833
0
        }
9834
0
        else
9835
0
        {
9836
0
            aBmpPos     = aRect.TopLeft();
9837
0
            aBmpSize    = aRect.GetSize();
9838
0
            bDrawBitmap = true;
9839
0
        }
9840
9841
0
        if( aBitmap.HasAlpha() )
9842
0
        {
9843
0
            if( rWall.IsGradient() )
9844
0
                bDrawGradient = true;
9845
0
            else
9846
0
                bDrawColor = true;
9847
0
        }
9848
0
    }
9849
0
    else if( rWall.IsGradient() )
9850
0
        bDrawGradient = true;
9851
0
    else
9852
0
        bDrawColor = true;
9853
9854
0
    if( bDrawGradient )
9855
0
    {
9856
0
        drawGradient( rRect, rWall.GetGradient() );
9857
0
    }
9858
0
    if( bDrawColor )
9859
0
    {
9860
0
        Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
9861
0
        Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
9862
0
        setLineColor( COL_TRANSPARENT );
9863
0
        setFillColor( rWall.GetColor() );
9864
0
        drawRectangle( rRect );
9865
0
        setLineColor( aOldLineColor );
9866
0
        setFillColor( aOldFillColor );
9867
0
    }
9868
0
    if( bDrawBitmap )
9869
0
    {
9870
        // set temporary clip region since aBmpPos and aBmpSize
9871
        // may be outside rRect
9872
0
        OStringBuffer aLine( 20 );
9873
0
        aLine.append( "q " );
9874
0
        m_aPages.back().appendRect( rRect, aLine );
9875
0
        aLine.append( " W n\n" );
9876
0
        writeBuffer( aLine );
9877
0
        drawBitmap( aBmpPos, aBmpSize, aBitmap );
9878
0
        writeBuffer( "Q\n" );
9879
0
    }
9880
0
}
9881
9882
 void PDFWriterImpl::updateGraphicsState(Mode const mode)
9883
857k
{
9884
857k
    OStringBuffer& rLine = updateGraphicsStateLine;
9885
857k
    rLine.setLength(0);
9886
857k
    GraphicsState& rNewState = m_aGraphicsStack.front();
9887
    // first set clip region since it might invalidate everything else
9888
9889
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
9890
254k
    {
9891
254k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
9892
9893
254k
        if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
9894
105k
            ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
9895
220k
        {
9896
220k
            if( m_aCurrentPDFState.m_bClipRegion )
9897
145k
            {
9898
145k
                rLine.append( "Q " );
9899
                // invalidate everything but the clip region
9900
145k
                m_aCurrentPDFState = GraphicsState();
9901
145k
                rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
9902
145k
            }
9903
220k
            if( rNewState.m_bClipRegion )
9904
145k
            {
9905
                // clip region is always stored in private PDF mapmode
9906
145k
                MapMode aNewMapMode = std::move(rNewState.m_aMapMode);
9907
145k
                rNewState.m_aMapMode = m_aMapMode;
9908
145k
                SetMapMode( rNewState.m_aMapMode );
9909
145k
                m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9910
9911
145k
                rLine.append("q ");
9912
145k
                if ( rNewState.m_aClipRegion.count() )
9913
145k
                {
9914
145k
                    m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, rLine );
9915
145k
                }
9916
0
                else
9917
0
                {
9918
                    // tdf#130150 Need to revert tdf#99680, that breaks the
9919
                    // rule that an set but empty clip-region clips everything
9920
                    // aka draws nothing -> nothing is in an empty clip-region
9921
0
                    rLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
9922
0
                }
9923
145k
                rLine.append( "W* n\n" );
9924
9925
145k
                rNewState.m_aMapMode = std::move(aNewMapMode);
9926
145k
                SetMapMode( rNewState.m_aMapMode );
9927
145k
                m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9928
145k
            }
9929
220k
        }
9930
254k
    }
9931
9932
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
9933
189k
    {
9934
189k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
9935
189k
        SetMapMode( rNewState.m_aMapMode );
9936
189k
    }
9937
9938
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
9939
722k
    {
9940
722k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
9941
722k
        SetFont( rNewState.m_aFont );
9942
722k
    }
9943
9944
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
9945
336k
    {
9946
336k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
9947
336k
        SetLayoutMode( rNewState.m_nLayoutMode );
9948
336k
    }
9949
9950
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
9951
761k
    {
9952
761k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
9953
761k
        SetDigitLanguage( rNewState.m_aDigitLanguage );
9954
761k
    }
9955
9956
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
9957
255k
    {
9958
255k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
9959
255k
        if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
9960
4.59k
            rNewState.m_aLineColor != COL_TRANSPARENT )
9961
4.18k
        {
9962
4.18k
            appendStrokingColor( rNewState.m_aLineColor, rLine );
9963
4.18k
            rLine.append( "\n" );
9964
4.18k
        }
9965
255k
    }
9966
9967
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
9968
254k
    {
9969
254k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
9970
254k
        if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
9971
4.24k
            rNewState.m_aFillColor != COL_TRANSPARENT )
9972
3.38k
        {
9973
3.38k
            appendNonStrokingColor( rNewState.m_aFillColor, rLine );
9974
3.38k
            rLine.append( "\n" );
9975
3.38k
        }
9976
254k
    }
9977
9978
857k
    if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
9979
189k
    {
9980
189k
        rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
9981
189k
        if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9982
189k
        {
9983
            // TODO: switch extended graphicsstate
9984
189k
        }
9985
189k
    }
9986
9987
    // everything is up to date now
9988
857k
    m_aCurrentPDFState = m_aGraphicsStack.front();
9989
857k
    if ((mode != Mode::NOWRITE) &&  !rLine.isEmpty())
9990
222k
        writeBuffer( rLine );
9991
857k
}
9992
9993
/* #i47544# imitate OutputDevice behaviour:
9994
*  if a font with a nontransparent color is set, it overwrites the current
9995
*  text color. OTOH setting the text color will overwrite the color of the font.
9996
*/
9997
void PDFWriterImpl::setFont( const vcl::Font& rFont )
9998
2.01M
{
9999
2.01M
    Color aColor = rFont.GetColor();
10000
2.01M
    if( aColor == COL_TRANSPARENT )
10001
1.30M
        aColor = m_aGraphicsStack.front().m_aFont.GetColor();
10002
2.01M
    m_aGraphicsStack.front().m_aFont = rFont;
10003
2.01M
    m_aGraphicsStack.front().m_aFont.SetColor( aColor );
10004
2.01M
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
10005
2.01M
}
10006
10007
void PDFWriterImpl::push( PushFlags nFlags )
10008
730k
{
10009
730k
    OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
10010
730k
    m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
10011
730k
    m_aGraphicsStack.front().m_nFlags = nFlags;
10012
730k
}
10013
10014
void PDFWriterImpl::pop()
10015
730k
{
10016
730k
    OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
10017
730k
    if( m_aGraphicsStack.size() < 2 )
10018
0
        return;
10019
10020
730k
    GraphicsState aState = m_aGraphicsStack.front();
10021
730k
    m_aGraphicsStack.pop_front();
10022
730k
    GraphicsState& rOld = m_aGraphicsStack.front();
10023
10024
    // move those parameters back that were not pushed
10025
    // in the first place
10026
730k
    if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
10027
259k
        setLineColor( aState.m_aLineColor );
10028
730k
    if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
10029
417k
        setFillColor( aState.m_aFillColor );
10030
730k
    if( ! (aState.m_nFlags & PushFlags::FONT) )
10031
586k
        setFont( aState.m_aFont );
10032
730k
    if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
10033
586k
        setTextColor( aState.m_aFont.GetColor() );
10034
730k
    if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
10035
561k
        setMapMode( aState.m_aMapMode );
10036
730k
    if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
10037
351k
    {
10038
        // do not use setClipRegion here
10039
        // it would convert again assuming the current mapmode
10040
351k
        rOld.m_aClipRegion = aState.m_aClipRegion;
10041
351k
        rOld.m_bClipRegion = aState.m_bClipRegion;
10042
351k
    }
10043
730k
    if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
10044
586k
        setTextLineColor( aState.m_aTextLineColor );
10045
730k
    if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
10046
586k
        setOverlineColor( aState.m_aOverlineColor );
10047
730k
    if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
10048
586k
        setTextAlign( aState.m_aFont.GetAlignment() );
10049
730k
    if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
10050
586k
        setTextFillColor( aState.m_aFont.GetFillColor() );
10051
730k
    if (!(aState.m_nFlags & PushFlags::TEXTLAYOUTMODE))
10052
586k
    {
10053
586k
        setLayoutMode(aState.m_nLayoutMode);
10054
586k
    }
10055
730k
    if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10056
586k
    {
10057
        // what ?
10058
586k
    }
10059
    // invalidate graphics state
10060
730k
    m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10061
730k
}
10062
10063
void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10064
960k
{
10065
960k
    m_aGraphicsStack.front().m_aMapMode = rMapMode;
10066
960k
    SetMapMode( rMapMode );
10067
960k
    m_aCurrentPDFState.m_aMapMode = rMapMode;
10068
960k
}
10069
10070
void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10071
149k
{
10072
    // tdf#130150 improve coordinate manipulations to double precision transformations
10073
149k
    const basegfx::B2DHomMatrix aCurrentTransform(
10074
149k
        GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10075
149k
    basegfx::B2DPolyPolygon aRegion(rRegion);
10076
10077
149k
    aRegion.transform(aCurrentTransform);
10078
149k
    m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10079
149k
    m_aGraphicsStack.front().m_bClipRegion = true;
10080
149k
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10081
149k
}
10082
10083
void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10084
0
{
10085
0
    if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
10086
0
        return;
10087
10088
    // tdf#130150 improve coordinate manipulations to double precision transformations
10089
0
    basegfx::B2DHomMatrix aConvertA;
10090
10091
0
    if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
10092
0
    {
10093
0
        aConvertA = GetInverseViewTransformation(m_aMapMode);
10094
0
    }
10095
0
    else
10096
0
    {
10097
0
        aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
10098
0
    }
10099
10100
0
    basegfx::B2DPoint aB2DPointA(nX, nY);
10101
0
    basegfx::B2DPoint aB2DPointB(0.0, 0.0);
10102
0
    aB2DPointA *= aConvertA;
10103
0
    aB2DPointB *= aConvertA;
10104
0
    aB2DPointA -= aB2DPointB;
10105
0
    basegfx::B2DHomMatrix aMat;
10106
10107
0
    aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
10108
0
    m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10109
0
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10110
0
}
10111
10112
void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10113
215
{
10114
215
    basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10115
215
                                    vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10116
215
    intersectClipRegion( aRect );
10117
215
}
10118
10119
void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10120
231k
{
10121
    // tdf#130150 improve coordinate manipulations to double precision transformations
10122
231k
    const basegfx::B2DHomMatrix aCurrentTransform(
10123
231k
        GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10124
231k
    basegfx::B2DPolyPolygon aRegion(rRegion);
10125
10126
231k
    aRegion.transform(aCurrentTransform);
10127
231k
    m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10128
10129
231k
    if( m_aGraphicsStack.front().m_bClipRegion )
10130
231k
    {
10131
231k
        basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10132
231k
        aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10133
231k
        m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10134
231k
    }
10135
215
    else
10136
215
    {
10137
215
        m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10138
215
        m_aGraphicsStack.front().m_bClipRegion = true;
10139
215
    }
10140
231k
}
10141
10142
sal_Int32 PDFWriterImpl::createNote(const tools::Rectangle& rRect,
10143
                                    const tools::Rectangle& rPopupRect, const pdf::PDFNote& rNote,
10144
                                    sal_Int32 nPageNr)
10145
388
{
10146
388
    if (nPageNr < 0)
10147
0
        nPageNr = m_nCurrentPage;
10148
10149
388
    if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10150
0
        return -1;
10151
10152
388
    sal_Int32 nRet = m_aNotes.size();
10153
10154
388
    m_aNotes.emplace_back();
10155
388
    auto & rNoteEntry = m_aNotes.back();
10156
388
    rNoteEntry.m_nObject = createObject();
10157
388
    rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
10158
388
    rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
10159
388
    rNoteEntry.m_aPopUpAnnotation.m_aRect = rPopupRect;
10160
388
    rNoteEntry.m_aContents = rNote;
10161
388
    rNoteEntry.m_aRect = rRect;
10162
388
    rNoteEntry.m_nPage = nPageNr;
10163
    // convert to default user space now, since the mapmode may change
10164
388
    m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
10165
388
    m_aPages[nPageNr].convertRect(rNoteEntry.m_aPopUpAnnotation.m_aRect);
10166
10167
    // insert note to page's annotation list
10168
388
    m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
10169
388
    m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
10170
10171
388
    return nRet;
10172
388
}
10173
10174
sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
10175
3.34k
{
10176
3.34k
    if( nPageNr < 0 )
10177
0
        nPageNr = m_nCurrentPage;
10178
10179
3.34k
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10180
0
        return -1;
10181
10182
3.34k
    sal_Int32 nRet = m_aLinks.size();
10183
10184
3.34k
    m_aLinks.emplace_back(rAltText);
10185
3.34k
    m_aLinks.back().m_nObject   = createObject();
10186
3.34k
    m_aLinks.back().m_nPage     = nPageNr;
10187
3.34k
    m_aLinks.back().m_aRect     = rRect;
10188
    // convert to default user space now, since the mapmode may change
10189
3.34k
    m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10190
10191
    // insert link to page's annotation list
10192
3.34k
    m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10193
10194
3.34k
    return nRet;
10195
3.34k
}
10196
10197
sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
10198
0
{
10199
0
    if (nPageNr < 0)
10200
0
        nPageNr = m_nCurrentPage;
10201
10202
0
    if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10203
0
        return -1;
10204
10205
0
    sal_Int32 nRet = m_aScreens.size();
10206
10207
0
    m_aScreens.emplace_back(rAltText, rMimeType);
10208
0
    m_aScreens.back().m_nObject = createObject();
10209
0
    m_aScreens.back().m_nPage = nPageNr;
10210
0
    m_aScreens.back().m_aRect = rRect;
10211
    // Convert to default user space now, since the mapmode may change.
10212
0
    m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10213
10214
    // Insert link to page's annotation list.
10215
0
    m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10216
10217
0
    return nRet;
10218
0
}
10219
10220
sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10221
0
{
10222
0
    if( nPageNr < 0 )
10223
0
        nPageNr = m_nCurrentPage;
10224
10225
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10226
0
        return -1;
10227
10228
0
    sal_Int32 nRet = m_aNamedDests.size();
10229
10230
0
    m_aNamedDests.emplace_back( );
10231
0
    m_aNamedDests.back().m_aDestName = sDestName;
10232
0
    m_aNamedDests.back().m_nPage = nPageNr;
10233
0
    m_aNamedDests.back().m_eType = eType;
10234
0
    m_aNamedDests.back().m_aRect = rRect;
10235
    // convert to default user space now, since the mapmode may change
10236
0
    m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10237
10238
0
    return nRet;
10239
0
}
10240
10241
sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10242
3.54k
{
10243
3.54k
    if( nPageNr < 0 )
10244
0
        nPageNr = m_nCurrentPage;
10245
10246
3.54k
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10247
0
        return -1;
10248
10249
3.54k
    sal_Int32 nRet = m_aDests.size();
10250
10251
3.54k
    m_aDests.emplace_back( );
10252
3.54k
    m_aDests.back().m_nPage = nPageNr;
10253
3.54k
    m_aDests.back().m_eType = eType;
10254
3.54k
    m_aDests.back().m_aRect = rRect;
10255
    // convert to default user space now, since the mapmode may change
10256
3.54k
    m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10257
10258
3.54k
    return nRet;
10259
3.54k
}
10260
10261
sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10262
0
{
10263
0
    m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10264
0
    return m_aDestinationIdTranslation[ nDestId ];
10265
0
}
10266
10267
void PDFWriterImpl::setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId)
10268
0
{
10269
0
    if (nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size())
10270
0
        return;
10271
10272
0
    m_aDests[nDestId].m_nStructElement = nStructElementId;
10273
0
}
10274
10275
void PDFWriterImpl::addStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId)
10276
0
{
10277
0
    if (nElementId < 0 || o3tl::make_unsigned(nElementId) >= m_aStructure.size())
10278
0
        return;
10279
0
    if (nRefElementId < 0 || o3tl::make_unsigned(nRefElementId) >= m_aStructure.size())
10280
0
        return;
10281
    // an element left out of the tree has no object to point at
10282
0
    if (m_aStructure[nRefElementId].m_nObject <= 0)
10283
0
        return;
10284
    // ISO 32000-2 added Ref to a structure element, and PDF 1.7 has no entry of that name
10285
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10286
0
        return;
10287
10288
0
    m_aStructure[nElementId].m_RefElements.push_back(nRefElementId);
10289
0
}
10290
10291
void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10292
2.82k
{
10293
2.82k
    if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10294
0
        return;
10295
2.82k
    if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
10296
0
        return;
10297
10298
2.82k
    m_aLinks[ nLinkId ].m_nDest = nDestId;
10299
2.82k
}
10300
10301
void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10302
524
{
10303
524
    if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10304
0
        return;
10305
10306
524
    m_aLinks[ nLinkId ].m_nDest = -1;
10307
10308
524
    using namespace ::com::sun::star;
10309
10310
524
    if (!m_xTrans.is())
10311
200
    {
10312
200
        const uno::Reference< uno::XComponentContext >& xContext( comphelper::getProcessComponentContext() );
10313
200
        m_xTrans = util::URLTransformer::create(xContext);
10314
200
    }
10315
10316
524
    util::URL aURL;
10317
524
    aURL.Complete = rURL;
10318
10319
524
    m_xTrans->parseStrict( aURL );
10320
10321
524
    m_aLinks[ nLinkId ].m_aURL  = aURL.Complete;
10322
524
}
10323
10324
void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10325
0
{
10326
0
    if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10327
0
        return;
10328
10329
0
    m_aScreens[nScreenId].m_aURL = rURL;
10330
0
}
10331
10332
void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10333
0
{
10334
0
    if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10335
0
        return;
10336
10337
0
    m_aScreens[nScreenId].m_aTempFileURL = rURL;
10338
0
    m_aScreens[nScreenId].m_nTempFileObject = createObject();
10339
0
}
10340
10341
void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10342
3.73k
{
10343
3.73k
    m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10344
3.73k
}
10345
10346
sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
10347
723
{
10348
    // create new item
10349
723
    sal_Int32 nNewItem = m_aOutline.size();
10350
723
    m_aOutline.emplace_back( );
10351
10352
    // set item attributes
10353
723
    setOutlineItemParent( nNewItem, nParent );
10354
723
    setOutlineItemText( nNewItem, rText );
10355
723
    setOutlineItemDest( nNewItem, nDestID );
10356
10357
723
    return nNewItem;
10358
723
}
10359
10360
void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10361
723
{
10362
723
    if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10363
0
        return;
10364
10365
723
    if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
10366
719
    {
10367
719
        nNewParent = 0;
10368
719
    }
10369
    // insert item to new parent's list of children
10370
723
    m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10371
723
}
10372
10373
void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
10374
723
{
10375
723
    if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10376
0
        return;
10377
10378
723
    m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10379
723
}
10380
10381
void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10382
723
{
10383
723
    if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
10384
0
        return;
10385
723
    if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
10386
0
        return;
10387
723
    m_aOutline[nItem].m_nDestID = nDestID;
10388
723
}
10389
10390
const char* PDFWriterImpl::getStructureTag(StructElement eType)
10391
0
{
10392
    // First handle fallbacks for elements that were added in a certain PDF version
10393
10394
    // PDF 1.5 fallbacks
10395
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5 && eType == StructElement::Annot)
10396
0
        eType = StructElement::Figure;
10397
10398
    // PDF 2.0 fallbacks
10399
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10400
0
    {
10401
0
        switch (eType)
10402
0
        {
10403
0
            case StructElement::Title:
10404
0
                eType = StructElement::Paragraph; break;
10405
0
            case StructElement::Emphasis:
10406
0
                eType = StructElement::Span; break;
10407
0
            case StructElement::Strong:
10408
0
                eType = StructElement::Span; break;
10409
0
            case StructElement::FENote:
10410
0
                eType = StructElement::Note; break;
10411
0
            default:
10412
0
                break;
10413
0
        }
10414
0
    }
10415
10416
0
    auto iterator = constTagStrings.find(eType);
10417
10418
0
    if (iterator == constTagStrings.end())
10419
0
        return "Div";
10420
10421
0
    return iterator->second;
10422
0
}
10423
10424
bool PDFWriterImpl::isStandardStructureName(std::string_view aName)
10425
0
{
10426
    // below 2.0 getStructureTag returns P for Title, so a style may take that name
10427
0
    const auto it(std::ranges::find_if(constTagStrings,
10428
0
                                       [aName](const auto& rTag) { return aName == rTag.second; }));
10429
0
    if ((it != constTagStrings.end() && aName == getStructureTag(it->first))
10430
0
        || std::ranges::find(constUnusedTagStrings, aName) != std::end(constUnusedTagStrings))
10431
0
        return true;
10432
10433
0
    if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10434
0
        return false;
10435
10436
0
    return isDeepHeading(aName)
10437
0
           || std::ranges::find(constUnusedTagStrings20, aName)
10438
0
                  != std::end(constUnusedTagStrings20);
10439
0
}
10440
10441
OString PDFWriterImpl::claimRoleName(const OString& rAlias, StructElement eType)
10442
0
{
10443
0
    const OString aTag(getStructureTag(eType));
10444
    // For PDF/UA it's not allowed to map an alias with the same name.
10445
    // Not aware of a reason for doing it in any case, so just don't do it.
10446
0
    if (rAlias == aTag)
10447
0
        return rAlias;
10448
10449
    // a heading deeper than the six this writer emits: PDF 2.0 knows the name, so the element
10450
    // says the level it is and needs no entry
10451
0
    if (isDeepHeading(rAlias) && StructElement::H1 <= eType && eType <= StructElement::H6
10452
0
        && PDFWriter::PDFVersion::PDF_2_0 <= m_aContext.Version)
10453
0
        return rAlias;
10454
10455
0
    OString aName(rAlias);
10456
0
    for (sal_Int32 nIndex(1);; ++nIndex)
10457
0
    {
10458
        // a standard type's name cannot be made to mean another type
10459
0
        if (!isStandardStructureName(aName))
10460
0
        {
10461
            // any other name belongs to the alias that asked for it first
10462
0
            const auto[it, bInserted](
10463
0
                m_aRoleMap.emplace(aName, RoleMapEntry{ .m_aTag = aTag, .m_aAsked = rAlias }));
10464
0
            if (bInserted || (it->second.m_aTag == aTag && it->second.m_aAsked == rAlias))
10465
0
                return aName;
10466
0
        }
10467
10468
        // name taken, try the next index
10469
0
        aName = rAlias + "-" + OString::number(nIndex);
10470
0
    }
10471
0
}
10472
10473
void PDFWriterImpl::beginStructureElementMCSeq()
10474
860k
{
10475
860k
    assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
10476
860k
    if( m_bEmitStructure &&
10477
860k
        m_nCurrentStructElement > 0 && // StructTreeRoot
10478
        // Document = SwPageFrame => this is not *inside* the page content
10479
        // stream so do not emit MCID!
10480
0
        m_aStructure[m_nCurrentStructElement].m_oType &&
10481
0
        *m_aStructure[m_nCurrentStructElement].m_oType != StructElement::Document &&
10482
0
        ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10483
860k
        )
10484
0
    {
10485
0
        PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10486
0
        OStringBuffer aLine( 128 );
10487
0
        sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10488
0
        aLine.append( "/" );
10489
0
        if( !rEle.m_aAlias.isEmpty() )
10490
0
            aLine.append( rEle.m_aAlias );
10491
0
        else
10492
0
            aLine.append( getStructureTag(*rEle.m_oType) );
10493
0
        aLine.append( "<</MCID " );
10494
0
        aLine.append( nMCID );
10495
0
        aLine.append( ">>BDC\n" );
10496
0
        writeBuffer( aLine );
10497
10498
        // update the element's content list
10499
0
        SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10500
0
                 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10501
0
                 << rEle.m_nFirstPageObject);
10502
0
        rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID});
10503
        // update the page's mcid parent list
10504
0
        m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10505
        // mark element MC sequence as open
10506
0
        rEle.m_bOpenMCSeq = true;
10507
0
    }
10508
    // handle artifacts
10509
860k
    else if( ! m_bEmitStructure && m_aContext.Tagged &&
10510
0
               m_nCurrentStructElement > 0 &&
10511
0
               m_aStructure[m_nCurrentStructElement].m_oType &&
10512
0
               *m_aStructure[m_nCurrentStructElement].m_oType == StructElement::NonStructElement &&
10513
0
             ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10514
860k
             )
10515
0
    {
10516
0
        OString aLine = "/Artifact "_ostr;
10517
0
        writeBuffer( aLine );
10518
        // emit property list if requested
10519
0
        OStringBuffer buf;
10520
0
        for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
10521
0
        {
10522
0
            appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
10523
0
        }
10524
0
        if (buf.isEmpty())
10525
0
        {
10526
0
            writeBuffer("BMC\n");
10527
0
        }
10528
0
        else
10529
0
        {
10530
0
            writeBuffer("<<");
10531
0
            writeBuffer(buf);
10532
0
            writeBuffer(">> BDC\n");
10533
0
        }
10534
        // mark element MC sequence as open
10535
0
        m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10536
0
    }
10537
860k
}
10538
10539
void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
10540
73.7k
{
10541
73.7k
    if (m_nCurrentStructElement > 0 // not StructTreeRoot
10542
0
        && m_aStructure[m_nCurrentStructElement].m_oType
10543
0
        && (m_bEmitStructure
10544
0
            || (endMode != EndMode::OnlyStruct
10545
0
                && m_aStructure[m_nCurrentStructElement].m_oType == StructElement::NonStructElement))
10546
0
        && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
10547
0
    {
10548
0
        writeBuffer( "EMC\n" );
10549
0
        m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10550
0
    }
10551
73.7k
}
10552
10553
bool PDFWriterImpl::checkEmitStructure()
10554
0
{
10555
0
    bool bEmit = false;
10556
0
    if( m_aContext.Tagged )
10557
0
    {
10558
0
        bEmit = true;
10559
0
        sal_Int32 nEle = m_nCurrentStructElement;
10560
0
        while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
10561
0
        {
10562
0
            if (m_aStructure[nEle].m_oType
10563
0
                && *m_aStructure[nEle].m_oType == StructElement::NonStructElement)
10564
0
            {
10565
0
                bEmit = false;
10566
0
                break;
10567
0
            }
10568
0
            nEle = m_aStructure[ nEle ].m_nParentElement;
10569
0
        }
10570
0
    }
10571
0
    return bEmit;
10572
0
}
10573
10574
sal_Int32 PDFWriterImpl::ensureStructureElement()
10575
25
{
10576
25
    if( ! m_aContext.Tagged )
10577
25
        return -1;
10578
10579
0
    sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10580
10581
    // use m_nCurrentStructElement as temporary parent
10582
0
    m_aStructure.emplace_back(nNewId, m_nCurrentStructElement, m_aPages[m_nCurrentPage].m_nPageObject);
10583
10584
0
    m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10585
0
    return nNewId;
10586
25
}
10587
10588
void PDFWriterImpl::initStructureElement(sal_Int32 const id,
10589
        StructElement const eType, std::u16string_view const rAlias)
10590
25
{
10591
25
    if( ! m_aContext.Tagged )
10592
25
        return;
10593
10594
0
    if( m_nCurrentStructElement == 0 &&
10595
0
        eType != StructElement::Document && eType != StructElement::NonStructElement )
10596
0
    {
10597
        // struct tree root hit, but not beginning document
10598
        // this might happen with setCurrentStructureElement
10599
        // silently insert structure into document again if one properly exists
10600
0
        if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10601
0
        {
10602
0
            const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10603
0
            auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
10604
0
                [&](sal_Int32 nElement) {
10605
0
                    return m_aStructure[nElement].m_oType
10606
0
                        && *m_aStructure[nElement].m_oType == StructElement::Document; });
10607
0
            if( it != rRootChildren.end() )
10608
0
            {
10609
0
                m_nCurrentStructElement = *it;
10610
0
                SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10611
0
            }
10612
0
            else {
10613
0
                OSL_FAIL( "document structure in disorder !" );
10614
0
            }
10615
0
        }
10616
0
        else {
10617
0
            OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10618
0
        }
10619
0
    }
10620
10621
0
    PDFStructureElement& rEle = m_aStructure[id];
10622
0
    assert(!rEle.m_oType);
10623
0
    rEle.m_oType.emplace(eType);
10624
    // remove it from its possibly placeholder parent; append to real parent
10625
0
    auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
10626
0
        m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
10627
0
    assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
10628
0
    m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
10629
0
    rEle.m_nParentElement   = m_nCurrentStructElement;
10630
0
    rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10631
0
    m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
10632
10633
    // handle alias names
10634
0
    if( !rAlias.empty() && eType != StructElement::NonStructElement )
10635
0
    {
10636
0
        OStringBuffer aNameBuf( rAlias.size() );
10637
0
        COSWriter::appendName( rAlias, aNameBuf );
10638
0
        OString aAliasName( aNameBuf.makeStringAndClear() );
10639
0
        rEle.m_aAlias = claimRoleName(aAliasName, eType);
10640
0
    }
10641
10642
0
    if (m_bEmitStructure && eType != StructElement::NonStructElement) // don't create nonexistent objects
10643
0
    {
10644
0
        rEle.m_nObject      = createObject();
10645
        // update parent's kids list
10646
0
        m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject});
10647
        // ISO 14289-1:2014, Clause: 7.9, for the element it names Note
10648
0
        if (*rEle.m_oType == StructElement::FENote)
10649
0
        {
10650
0
            m_StructElemObjsWithID.insert(rEle.m_nObject);
10651
0
        }
10652
0
    }
10653
0
}
10654
10655
void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
10656
25
{
10657
25
    if( m_nCurrentPage < 0 )
10658
0
        return;
10659
10660
25
    if( ! m_aContext.Tagged )
10661
25
        return;
10662
10663
25
    assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
10664
10665
    // close eventual current MC sequence
10666
0
    endStructureElementMCSeq(EndMode::OnlyStruct);
10667
10668
0
    PDFStructureElement& rEle = m_aStructure[id];
10669
0
    m_StructElementStack.push(m_nCurrentStructElement);
10670
0
    m_nCurrentStructElement = id;
10671
10672
0
    if (g_bDebugDisableCompression)
10673
0
    {
10674
0
        OStringBuffer aLine( "beginStructureElement " );
10675
0
        aLine.append( m_nCurrentStructElement );
10676
0
        aLine.append( ": " );
10677
0
        aLine.append( rEle.m_oType
10678
0
            ? getStructureTag(*rEle.m_oType)
10679
0
            : "<placeholder>" );
10680
0
        if( !rEle.m_aAlias.isEmpty() )
10681
0
        {
10682
0
            aLine.append( " aliased as \"" );
10683
0
            aLine.append( rEle.m_aAlias );
10684
0
            aLine.append( '\"' );
10685
0
        }
10686
0
        emitComment( aLine.getStr() );
10687
0
    }
10688
10689
    // check whether to emit structure henceforth
10690
0
    m_bEmitStructure = checkEmitStructure();
10691
0
}
10692
10693
void PDFWriterImpl::endStructureElement()
10694
25
{
10695
25
    if( m_nCurrentPage < 0 )
10696
0
        return;
10697
10698
25
    if( ! m_aContext.Tagged )
10699
25
        return;
10700
10701
0
    if( m_nCurrentStructElement == 0 )
10702
0
    {
10703
        // hit the struct tree root, that means there is an endStructureElement
10704
        // without corresponding beginStructureElement
10705
0
        return;
10706
0
    }
10707
10708
    // end the marked content sequence
10709
0
    endStructureElementMCSeq();
10710
10711
0
    OStringBuffer aLine;
10712
0
    if (g_bDebugDisableCompression)
10713
0
    {
10714
0
        aLine.append( "endStructureElement " );
10715
0
        aLine.append( m_nCurrentStructElement );
10716
0
        aLine.append( ": " );
10717
0
        aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
10718
0
            ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
10719
0
            : "<placeholder>" );
10720
0
        if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10721
0
        {
10722
0
            aLine.append( " aliased as \"" );
10723
0
            aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10724
0
            aLine.append( '\"' );
10725
0
        }
10726
0
    }
10727
10728
    // "end" the structure element, the parent becomes current element
10729
0
    m_nCurrentStructElement = m_StructElementStack.top();
10730
0
    m_StructElementStack.pop();
10731
10732
    // check whether to emit structure henceforth
10733
0
    m_bEmitStructure = checkEmitStructure();
10734
10735
0
    if (g_bDebugDisableCompression && m_bEmitStructure)
10736
0
    {
10737
0
        emitComment( aLine.getStr() );
10738
0
    }
10739
0
}
10740
10741
namespace {
10742
10743
void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
10744
        std::vector<sal_Int32>::iterator & rParentIt)
10745
0
{
10746
0
    PDFStructureElement& rEle(rStructure[*rParentIt]);
10747
0
    removePlaceholderSE(rStructure, rEle);
10748
10749
0
    if (!rEle.m_oType)
10750
0
    {
10751
        // Placeholder was not initialised - should not happen when printing
10752
        // a full page, but might if a selection is printed, which can be only
10753
        // a shape without its anchor.
10754
        // Handle this by moving the children to the parent SE.
10755
0
        PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
10756
0
        rParentIt = rParent.m_aChildren.erase(rParentIt);
10757
0
        std::vector<sal_Int32> children;
10758
0
        for (auto const child : rEle.m_aChildren)
10759
0
        {
10760
0
            PDFStructureElement& rChild = rStructure[child];
10761
0
            rChild.m_nParentElement = rEle.m_nParentElement;
10762
0
            children.push_back(rChild.m_nOwnElement);
10763
0
        }
10764
0
        rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
10765
0
            + children.size();
10766
0
    }
10767
0
    else
10768
0
    {
10769
0
        ++rParentIt;
10770
0
    }
10771
10772
0
}
10773
10774
void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
10775
0
{
10776
0
    for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
10777
0
    {
10778
0
        removePlaceholderSEImpl(rStructure, it);
10779
0
    }
10780
0
}
10781
10782
} // end anonymous namespace
10783
10784
/*
10785
 * This function adds an internal structure list container to overcome the 8191 elements array limitation
10786
 * in kids element emission.
10787
 * Recursive function
10788
 *
10789
 */
10790
void PDFWriterImpl::addInternalStructureContainer(const sal_Int32 nEle)
10791
0
{
10792
    // m_aStructure grows below, so this addresses elements by index throughout: any
10793
    // reference into the vector dies at the next push_back
10794
0
    if (m_aStructure[nEle].m_nOwnElement != m_aStructure[nEle].m_nParentElement
10795
0
        && *m_aStructure[nEle].m_oType == StructElement::NonStructElement)
10796
0
    {
10797
0
        return;
10798
0
    }
10799
10800
0
    for (size_t i = 0; i < m_aStructure[nEle].m_aChildren.size(); ++i)
10801
0
    {
10802
0
        const sal_Int32 nChild = m_aStructure[nEle].m_aChildren[i];
10803
0
        assert(nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size());
10804
0
        if (nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size())
10805
0
        {
10806
0
            if (*m_aStructure[nChild].m_oType != StructElement::NonStructElement)
10807
0
            {
10808
                //triggered when a child of the nEle element is found
10809
0
                assert(m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement);
10810
0
                if (m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement)
10811
0
                    addInternalStructureContainer(nChild); //examine the child
10812
0
                else
10813
0
                {
10814
0
                    OSL_FAIL("PDFWriterImpl::addInternalStructureContainer: invalid child "
10815
0
                             "structure element");
10816
0
                    SAL_INFO("vcl.pdfwriter",
10817
0
                             "PDFWriterImpl::addInternalStructureContainer: invalid child "
10818
0
                             "structure element with id "
10819
0
                                 << nChild);
10820
0
                }
10821
0
            }
10822
0
        }
10823
0
        else
10824
0
        {
10825
0
            OSL_FAIL("PDFWriterImpl::emitStructure: invalid child structure id");
10826
0
            SAL_INFO("vcl.pdfwriter",
10827
0
                     "PDFWriterImpl::addInternalStructureContainer: invalid child structure id "
10828
0
                         << nChild);
10829
0
        }
10830
0
    }
10831
10832
0
    if (m_aStructure[nEle].m_nOwnElement == m_aStructure[nEle].m_nParentElement)
10833
0
        return;
10834
10835
0
    if (m_aStructure[nEle].m_aKids.size() <= ncMaxPDFArraySize)
10836
0
        return;
10837
10838
    //then we need to add the containers for the kids elements
10839
    // a list to be used for the new kid element
10840
0
    std::list<PDFStructureElementKid> aNewKids;
10841
0
    std::vector<sal_Int32> aNewChildren;
10842
10843
0
    while (m_aStructure[nEle].m_aKids.size() > ncMaxPDFArraySize)
10844
0
    {
10845
0
        const sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10846
0
        {
10847
            //inherit the same page as the first child to be reparented
10848
0
            const sal_Int32 nPage(
10849
0
                m_aStructure[m_aStructure[nEle].m_aChildren.front()].m_nFirstPageObject);
10850
0
            PDFStructureElement aNew(nNewId, nEle, nPage);
10851
0
            aNew.m_oType.emplace(StructElement::Division); // a new Div type container
10852
0
            aNew.m_nObject = createObject(); //assign a PDF object number
10853
0
            m_aStructure.push_back(std::move(aNew));
10854
0
        }
10855
        //add the object to the kid list of the parent
10856
0
        aNewKids.emplace_back(ObjReference{ m_aStructure[nNewId].m_nObject });
10857
0
        aNewChildren.push_back(nNewId);
10858
10859
        // m_aKids and m_aChildren are not parallel: every element gets a child entry, only an
10860
        // emitted one gets a kid, and marked content adds kids of its own. Cut the two at
10861
        // corresponding points, not at the same count.
10862
0
        const auto aKidEndIt(std::next(m_aStructure[nEle].m_aKids.begin(), ncMaxPDFArraySize));
10863
0
        size_t nMovedChildren(0);
10864
0
        for (auto it = m_aStructure[nEle].m_aKids.begin(); it != aKidEndIt; ++it)
10865
0
        {
10866
0
            if (std::holds_alternative<ObjReference>(*it))
10867
0
                ++nMovedChildren;
10868
0
        }
10869
0
        auto aChildEndIt(m_aStructure[nEle].m_aChildren.begin());
10870
0
        for (size_t nSeen = 0;
10871
0
             nSeen < nMovedChildren && aChildEndIt != m_aStructure[nEle].m_aChildren.end();
10872
0
             ++aChildEndIt)
10873
0
        {
10874
0
            if (m_aStructure[*aChildEndIt].m_nObject != 0) // an emitted child has a kid entry
10875
0
                ++nSeen;
10876
0
        }
10877
10878
0
        m_aStructure[nNewId].m_aKids.splice(m_aStructure[nNewId].m_aKids.begin(),
10879
0
                                            m_aStructure[nEle].m_aKids,
10880
0
                                            m_aStructure[nEle].m_aKids.begin(), aKidEndIt);
10881
0
        m_aStructure[nNewId].m_aChildren.insert(m_aStructure[nNewId].m_aChildren.begin(),
10882
0
                                                m_aStructure[nEle].m_aChildren.begin(),
10883
0
                                                aChildEndIt);
10884
0
        m_aStructure[nEle].m_aChildren.erase(m_aStructure[nEle].m_aChildren.begin(), aChildEndIt);
10885
10886
        // set the kid's new parent
10887
0
        for (const auto nMoved : m_aStructure[nNewId].m_aChildren)
10888
0
        {
10889
0
            m_aStructure[nMoved].m_nParentElement = nNewId;
10890
0
        }
10891
0
    }
10892
    //finally add the new kids resulting from the container added
10893
0
    m_aStructure[nEle].m_aKids.insert(m_aStructure[nEle].m_aKids.begin(), aNewKids.begin(),
10894
0
                                      aNewKids.end());
10895
0
    m_aStructure[nEle].m_aChildren.insert(m_aStructure[nEle].m_aChildren.begin(),
10896
0
                                          aNewChildren.begin(), aNewChildren.end());
10897
0
}
10898
10899
bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
10900
0
{
10901
0
    bool bSuccess = false;
10902
10903
0
    if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
10904
0
    {
10905
        // end eventual previous marked content sequence
10906
0
        endStructureElementMCSeq();
10907
10908
0
        m_nCurrentStructElement = nEle;
10909
0
        m_bEmitStructure = checkEmitStructure();
10910
0
        if (g_bDebugDisableCompression)
10911
0
        {
10912
0
            OStringBuffer aLine( "setCurrentStructureElement " );
10913
0
            aLine.append( m_nCurrentStructElement );
10914
0
            aLine.append( ": " );
10915
0
            aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
10916
0
                ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
10917
0
                : "<placeholder>" );
10918
0
            if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10919
0
            {
10920
0
                aLine.append( " aliased as \"" );
10921
0
                aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10922
0
                aLine.append( '\"' );
10923
0
            }
10924
0
            if( ! m_bEmitStructure )
10925
0
                aLine.append( " (inside NonStruct)" );
10926
0
            emitComment( aLine.getStr() );
10927
0
        }
10928
0
        bSuccess = true;
10929
0
    }
10930
10931
0
    return bSuccess;
10932
0
}
10933
10934
bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
10935
0
{
10936
0
    if( !m_aContext.Tagged )
10937
0
        return false;
10938
10939
0
    assert(m_aStructure[m_nCurrentStructElement].m_oType);
10940
0
    bool bInsert = false;
10941
0
    if (m_nCurrentStructElement > 0
10942
0
        && (m_bEmitStructure
10943
            // allow it for topmost non-structured element
10944
0
            || (m_aContext.Tagged
10945
0
                && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
10946
0
                    || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
10947
0
                    || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != StructElement::NonStructElement))))
10948
0
    {
10949
0
        StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
10950
0
        switch( eAttr )
10951
0
        {
10952
0
            case PDFWriter::Placement:
10953
0
                if( eVal == PDFWriter::Block        ||
10954
0
                    eVal == PDFWriter::Inline       ||
10955
0
                    eVal == PDFWriter::Before       ||
10956
0
                    eVal == PDFWriter::Start        ||
10957
0
                    eVal == PDFWriter::End )
10958
0
                    bInsert = true;
10959
0
                break;
10960
0
            case PDFWriter::WritingMode:
10961
0
                if( eVal == PDFWriter::LrTb         ||
10962
0
                    eVal == PDFWriter::RlTb         ||
10963
0
                    eVal == PDFWriter::TbRl )
10964
0
                {
10965
0
                    bInsert = true;
10966
0
                }
10967
0
                break;
10968
0
            case PDFWriter::TextAlign:
10969
0
                if( eVal == PDFWriter::Start        ||
10970
0
                    eVal == PDFWriter::Center       ||
10971
0
                    eVal == PDFWriter::End          ||
10972
0
                    eVal == PDFWriter::Justify )
10973
0
                {
10974
0
                    if (eType == StructElement::Paragraph   ||
10975
0
                        eType == StructElement::Title ||
10976
0
                        eType == StructElement::Heading     ||
10977
0
                        eType == StructElement::H1          ||
10978
0
                        eType == StructElement::H2          ||
10979
0
                        eType == StructElement::H3          ||
10980
0
                        eType == StructElement::H4          ||
10981
0
                        eType == StructElement::H5          ||
10982
0
                        eType == StructElement::H6          ||
10983
0
                        eType == StructElement::List        ||
10984
0
                        eType == StructElement::ListItem    ||
10985
0
                        eType == StructElement::LILabel     ||
10986
0
                        eType == StructElement::LIBody      ||
10987
0
                        eType == StructElement::Table       ||
10988
0
                        eType == StructElement::TableRow    ||
10989
0
                        eType == StructElement::TableHeader ||
10990
0
                        eType == StructElement::TableData)
10991
0
                    {
10992
0
                        bInsert = true;
10993
0
                    }
10994
0
                }
10995
0
                break;
10996
0
            case PDFWriter::Width:
10997
0
            case PDFWriter::Height:
10998
0
                if( eVal == PDFWriter::Auto )
10999
0
                {
11000
0
                    if (eType == StructElement::Figure      ||
11001
0
                        eType == StructElement::Formula     ||
11002
0
                        eType == StructElement::Form        ||
11003
0
                        eType == StructElement::Table       ||
11004
0
                        eType == StructElement::TableHeader ||
11005
0
                        eType == StructElement::TableData)
11006
0
                    {
11007
0
                        bInsert = true;
11008
0
                    }
11009
0
                }
11010
0
                break;
11011
0
            case PDFWriter::BlockAlign:
11012
0
                if( eVal == PDFWriter::Before       ||
11013
0
                    eVal == PDFWriter::Middle       ||
11014
0
                    eVal == PDFWriter::After        ||
11015
0
                    eVal == PDFWriter::Justify )
11016
0
                {
11017
0
                    if (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::InlineAlign:
11025
0
                if( eVal == PDFWriter::Start        ||
11026
0
                    eVal == PDFWriter::Center       ||
11027
0
                    eVal == PDFWriter::End )
11028
0
                {
11029
0
                    if (eType == StructElement::TableHeader ||
11030
0
                        eType == StructElement::TableData)
11031
0
                    {
11032
0
                        bInsert = true;
11033
0
                    }
11034
0
                }
11035
0
                break;
11036
0
            case PDFWriter::LineHeight:
11037
0
                if( eVal == PDFWriter::Normal       ||
11038
0
                    eVal == PDFWriter::Auto )
11039
0
                {
11040
                    // only for ILSE and BLSE
11041
0
                    if (eType == StructElement::Paragraph   ||
11042
0
                        eType == StructElement::Title ||
11043
0
                        eType == StructElement::Heading     ||
11044
0
                        eType == StructElement::H1          ||
11045
0
                        eType == StructElement::H2          ||
11046
0
                        eType == StructElement::H3          ||
11047
0
                        eType == StructElement::H4          ||
11048
0
                        eType == StructElement::H5          ||
11049
0
                        eType == StructElement::H6          ||
11050
0
                        eType == StructElement::List        ||
11051
0
                        eType == StructElement::ListItem    ||
11052
0
                        eType == StructElement::LILabel     ||
11053
0
                        eType == StructElement::LIBody      ||
11054
0
                        eType == StructElement::Table       ||
11055
0
                        eType == StructElement::TableRow    ||
11056
0
                        eType == StructElement::TableHeader ||
11057
0
                        eType == StructElement::TableData   ||
11058
0
                        eType == StructElement::Span        ||
11059
0
                        eType == StructElement::Quote       ||
11060
0
                        eType == StructElement::Emphasis ||
11061
0
                        eType == StructElement::Strong ||
11062
0
                        eType == StructElement::Note        ||
11063
0
                        eType == StructElement::FENote      ||
11064
0
                        eType == StructElement::Reference   ||
11065
0
                        eType == StructElement::BibEntry    ||
11066
0
                        eType == StructElement::Code        ||
11067
0
                        eType == StructElement::Link)
11068
0
                    {
11069
0
                        bInsert = true;
11070
0
                    }
11071
0
                }
11072
0
                break;
11073
0
            case PDFWriter::TextDecorationType:
11074
0
                if( eVal == PDFWriter::NONE         ||
11075
0
                    eVal == PDFWriter::Underline    ||
11076
0
                    eVal == PDFWriter::Overline     ||
11077
0
                    eVal == PDFWriter::LineThrough )
11078
0
                {
11079
                    // only for ILSE and BLSE
11080
0
                    if (eType == StructElement::Paragraph   ||
11081
0
                        eType == StructElement::Title ||
11082
0
                        eType == StructElement::Heading     ||
11083
0
                        eType == StructElement::H1          ||
11084
0
                        eType == StructElement::H2          ||
11085
0
                        eType == StructElement::H3          ||
11086
0
                        eType == StructElement::H4          ||
11087
0
                        eType == StructElement::H5          ||
11088
0
                        eType == StructElement::H6          ||
11089
0
                        eType == StructElement::List        ||
11090
0
                        eType == StructElement::ListItem    ||
11091
0
                        eType == StructElement::LILabel     ||
11092
0
                        eType == StructElement::LIBody      ||
11093
0
                        eType == StructElement::Table       ||
11094
0
                        eType == StructElement::TableRow    ||
11095
0
                        eType == StructElement::TableHeader ||
11096
0
                        eType == StructElement::TableData   ||
11097
0
                        eType == StructElement::Span        ||
11098
0
                        eType == StructElement::Quote       ||
11099
0
                        eType == StructElement::Emphasis ||
11100
0
                        eType == StructElement::Strong ||
11101
0
                        eType == StructElement::Note        ||
11102
0
                        eType == StructElement::FENote      ||
11103
0
                        eType == StructElement::Reference   ||
11104
0
                        eType == StructElement::BibEntry    ||
11105
0
                        eType == StructElement::Code        ||
11106
0
                        eType == StructElement::Link)
11107
0
                    {
11108
0
                        bInsert = true;
11109
0
                    }
11110
0
                }
11111
0
                break;
11112
0
            case PDFWriter::Scope:
11113
0
                if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
11114
0
                {
11115
                    // The Scope attribute on TH was introduced in PDF 1.5, but it
11116
                    // is also mandatory for PDF/UA-1 (clause 7.5). When PDF/UA is
11117
                    // requested with a PDF/A-1 (PDF 1.4) target, accept Scope so
11118
                    // the resulting file can satisfy the PDF/UA structure rules.
11119
0
                    if (eType == StructElement::TableHeader
11120
0
                        && (PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version
11121
0
                            || m_aContext.UniversalAccessibilityCompliance))
11122
0
                    {
11123
0
                        bInsert = true;
11124
0
                    }
11125
0
                }
11126
0
                break;
11127
0
            case PDFWriter::Type:
11128
0
                if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
11129
                    // + Background for PDF >= 1.7
11130
0
                {
11131
0
                    if (eType == StructElement::NonStructElement)
11132
0
                    {
11133
0
                        bInsert = true;
11134
0
                    }
11135
0
                }
11136
0
                break;
11137
0
            case PDFWriter::Subtype:
11138
0
                if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
11139
0
                {
11140
0
                    if (eType == StructElement::NonStructElement
11141
0
                        && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11142
0
                    {
11143
0
                        bInsert = true;
11144
0
                    }
11145
0
                }
11146
0
                break;
11147
0
            case PDFWriter::Role:
11148
0
                if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
11149
0
                {
11150
0
                    if (eType == StructElement::Form
11151
0
                        && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11152
0
                    {
11153
0
                        bInsert = true;
11154
0
                    }
11155
0
                }
11156
0
                break;
11157
0
            case PDFWriter::RubyAlign:
11158
0
                if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute)
11159
0
                {
11160
0
                    if (eType == StructElement::RT
11161
0
                        && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11162
0
                    {
11163
0
                        bInsert = true;
11164
0
                    }
11165
0
                }
11166
0
                break;
11167
0
            case PDFWriter::RubyPosition:
11168
0
                if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline)
11169
0
                {
11170
0
                    if (eType == StructElement::RT
11171
0
                        && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11172
0
                    {
11173
0
                        bInsert = true;
11174
0
                    }
11175
0
                }
11176
0
                break;
11177
0
            case PDFWriter::ListNumbering:
11178
0
                if( eVal == PDFWriter::NONE         ||
11179
0
                    eVal == PDFWriter::Disc         ||
11180
0
                    eVal == PDFWriter::Circle       ||
11181
0
                    eVal == PDFWriter::Square       ||
11182
0
                    eVal == PDFWriter::Decimal      ||
11183
0
                    eVal == PDFWriter::UpperRoman   ||
11184
0
                    eVal == PDFWriter::LowerRoman   ||
11185
0
                    eVal == PDFWriter::UpperAlpha   ||
11186
0
                    eVal == PDFWriter::LowerAlpha )
11187
0
                {
11188
0
                    if( eType == StructElement::List )
11189
0
                        bInsert = true;
11190
0
                }
11191
0
                break;
11192
0
            default: break;
11193
0
        }
11194
0
    }
11195
11196
0
    if( bInsert )
11197
0
        m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
11198
0
    else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11199
0
        SAL_INFO("vcl.pdfwriter",
11200
0
                 "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
11201
0
                 << ", " << getAttributeValueTag( eVal )
11202
0
                 << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11203
0
                 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11204
0
                 << ") element");
11205
11206
0
    return bInsert;
11207
0
}
11208
11209
sal_Int32 PDFWriterImpl::convertStructureAttribute(enum PDFWriter::StructAttribute eAttr,
11210
                                                   sal_Int32 nValue)
11211
0
{
11212
0
    if (!isLengthAttribute(eAttr))
11213
0
        return nValue;
11214
11215
0
    const Size aSize(
11216
0
        convert(m_aGraphicsStack.front().m_aMapMode, m_aMapMode, this, Size(nValue, nValue)));
11217
    // the map mode can scale X and Y differently
11218
0
    const bool bHorizontal(eAttr == PDFWriter::StartIndent || eAttr == PDFWriter::EndIndent
11219
0
                           || eAttr == PDFWriter::TextIndent || eAttr == PDFWriter::Width);
11220
11221
0
    return bHorizontal ? aSize.Width() : aSize.Height();
11222
0
}
11223
11224
bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
11225
0
{
11226
0
    if( ! m_aContext.Tagged )
11227
0
        return false;
11228
11229
0
    assert(m_aStructure[m_nCurrentStructElement].m_oType);
11230
0
    bool bInsert = false;
11231
0
    if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11232
0
    {
11233
0
        if( eAttr == PDFWriter::Language )
11234
0
        {
11235
0
            m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
11236
0
            return true;
11237
0
        }
11238
11239
0
        StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11240
0
        switch( eAttr )
11241
0
        {
11242
0
            case PDFWriter::SpaceBefore:
11243
0
            case PDFWriter::SpaceAfter:
11244
0
            case PDFWriter::StartIndent:
11245
0
            case PDFWriter::EndIndent:
11246
                // just for BLSE
11247
0
                if (eType == StructElement::Paragraph   ||
11248
0
                    eType == StructElement::Title ||
11249
0
                    eType == StructElement::Heading     ||
11250
0
                    eType == StructElement::H1          ||
11251
0
                    eType == StructElement::H2          ||
11252
0
                    eType == StructElement::H3          ||
11253
0
                    eType == StructElement::H4          ||
11254
0
                    eType == StructElement::H5          ||
11255
0
                    eType == StructElement::H6          ||
11256
0
                    eType == StructElement::List        ||
11257
0
                    eType == StructElement::ListItem    ||
11258
0
                    eType == StructElement::LILabel     ||
11259
0
                    eType == StructElement::LIBody      ||
11260
0
                    eType == StructElement::Table       ||
11261
0
                    eType == StructElement::TableRow    ||
11262
0
                    eType == StructElement::TableHeader ||
11263
0
                    eType == StructElement::TableData)
11264
0
                {
11265
0
                    bInsert = true;
11266
0
                }
11267
0
                break;
11268
0
            case PDFWriter::TextIndent:
11269
                // paragraph like BLSE and additional elements
11270
0
                if (eType == StructElement::Paragraph   ||
11271
0
                    eType == StructElement::Title ||
11272
0
                    eType == StructElement::Heading     ||
11273
0
                    eType == StructElement::H1          ||
11274
0
                    eType == StructElement::H2          ||
11275
0
                    eType == StructElement::H3          ||
11276
0
                    eType == StructElement::H4          ||
11277
0
                    eType == StructElement::H5          ||
11278
0
                    eType == StructElement::H6          ||
11279
0
                    eType == StructElement::LILabel     ||
11280
0
                    eType == StructElement::LIBody      ||
11281
0
                    eType == StructElement::TableHeader ||
11282
0
                    eType == StructElement::TableData)
11283
0
                {
11284
0
                    bInsert = true;
11285
0
                }
11286
0
                break;
11287
0
            case PDFWriter::Width:
11288
0
            case PDFWriter::Height:
11289
0
                if (eType == StructElement::Figure      ||
11290
0
                    eType == StructElement::Formula     ||
11291
0
                    eType == StructElement::Form        ||
11292
0
                    eType == StructElement::Table       ||
11293
0
                    eType == StructElement::TableHeader ||
11294
0
                    eType == StructElement::TableData)
11295
0
                {
11296
0
                    bInsert = true;
11297
0
                }
11298
0
                break;
11299
0
            case PDFWriter::LineHeight:
11300
0
            case PDFWriter::BaselineShift:
11301
                // only for ILSE and BLSE
11302
0
                if (eType == StructElement::Paragraph   ||
11303
0
                    eType == StructElement::Title ||
11304
0
                    eType == StructElement::Heading     ||
11305
0
                    eType == StructElement::H1          ||
11306
0
                    eType == StructElement::H2          ||
11307
0
                    eType == StructElement::H3          ||
11308
0
                    eType == StructElement::H4          ||
11309
0
                    eType == StructElement::H5          ||
11310
0
                    eType == StructElement::H6          ||
11311
0
                    eType == StructElement::List        ||
11312
0
                    eType == StructElement::ListItem    ||
11313
0
                    eType == StructElement::LILabel     ||
11314
0
                    eType == StructElement::LIBody      ||
11315
0
                    eType == StructElement::Table       ||
11316
0
                    eType == StructElement::TableRow    ||
11317
0
                    eType == StructElement::TableHeader ||
11318
0
                    eType == StructElement::TableData   ||
11319
0
                    eType == StructElement::Span        ||
11320
0
                    eType == StructElement::Quote       ||
11321
0
                    eType == StructElement::Emphasis ||
11322
0
                    eType == StructElement::Strong ||
11323
0
                    eType == StructElement::Note        ||
11324
0
                    eType == StructElement::FENote      ||
11325
0
                    eType == StructElement::Reference   ||
11326
0
                    eType == StructElement::BibEntry    ||
11327
0
                    eType == StructElement::Code        ||
11328
0
                    eType == StructElement::Link)
11329
0
                {
11330
0
                        bInsert = true;
11331
0
                }
11332
0
                break;
11333
0
            case PDFWriter::RowSpan:
11334
0
            case PDFWriter::ColSpan:
11335
                // only for table cells
11336
0
                if (eType == StructElement::TableHeader ||
11337
0
                    eType == StructElement::TableData)
11338
0
                {
11339
0
                    bInsert = true;
11340
0
                }
11341
0
                break;
11342
0
            case PDFWriter::LinkAnnotation:
11343
0
                if (eType == StructElement::Link)
11344
0
                    bInsert = true;
11345
0
                break;
11346
0
            case PDFWriter::NoteAnnotation:
11347
0
                if (eType == StructElement::Annot)
11348
0
                    bInsert = true;
11349
0
                break;
11350
0
            default: break;
11351
0
        }
11352
0
    }
11353
11354
0
    if (bInsert)
11355
0
    {
11356
0
        if (eAttr == PDFWriter::LinkAnnotation)
11357
0
        {
11358
0
            m_aStructure[m_nCurrentStructElement].m_LinkAnnotIds.push_back(nValue);
11359
0
        }
11360
0
        else
11361
0
        {
11362
0
            m_aStructure[m_nCurrentStructElement].m_aAttributes[eAttr]
11363
0
                = PDFStructureAttribute(convertStructureAttribute(eAttr, nValue));
11364
0
        }
11365
0
    }
11366
0
    else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11367
0
        SAL_INFO("vcl.pdfwriter",
11368
0
                 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
11369
0
                 << ", " << nValue
11370
0
                 << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11371
0
                 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11372
0
                 << ") element");
11373
11374
0
    return bInsert;
11375
0
}
11376
11377
void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
11378
0
{
11379
0
    sal_Int32 nPageNr = m_nCurrentPage;
11380
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
11381
0
        return;
11382
11383
0
    if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
11384
0
        return;
11385
11386
0
    assert(m_aStructure[m_nCurrentStructElement].m_oType);
11387
0
    StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11388
0
    if (eType == StructElement::Figure ||
11389
0
        eType == StructElement::Formula ||
11390
0
        eType == StructElement::Form ||
11391
0
        eType == StructElement::Division ||
11392
0
        eType == StructElement::Table)
11393
0
    {
11394
0
        m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
11395
        // convert to default user space now, since the mapmode may change
11396
0
        m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11397
0
    }
11398
0
}
11399
11400
void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
11401
0
{
11402
0
    assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
11403
11404
0
    if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
11405
0
    {
11406
0
        return;
11407
0
    }
11408
11409
0
    m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
11410
0
}
11411
11412
void PDFWriterImpl::setActualText( const OUString& rText )
11413
0
{
11414
0
    if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11415
0
    {
11416
0
        m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11417
0
    }
11418
0
}
11419
11420
void PDFWriterImpl::setAlternateText( const OUString& rText )
11421
0
{
11422
0
    if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11423
0
    {
11424
0
        m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11425
0
    }
11426
0
}
11427
11428
void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11429
0
{
11430
0
    if( nPageNr < 0 )
11431
0
        nPageNr = m_nCurrentPage;
11432
11433
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11434
0
        return;
11435
11436
0
    m_aPages[ nPageNr ].m_eTransition   = eType;
11437
0
    m_aPages[ nPageNr ].m_nTransTime    = nMilliSec;
11438
0
}
11439
11440
void PDFWriterImpl::ensureUniqueRadioOnValues()
11441
8.85k
{
11442
    // loop over radio groups
11443
8.85k
    for (auto const& group : m_aRadioGroupWidgets)
11444
0
    {
11445
0
        PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11446
        // check whether all kids have a unique OnValue
11447
0
        std::unordered_map< OUString, sal_Int32 > aOnValues;
11448
0
        bool bIsUnique = true;
11449
0
        for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11450
0
        {
11451
0
            const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11452
0
            SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11453
0
            if( aOnValues.find( rVal ) == aOnValues.end() )
11454
0
            {
11455
0
                aOnValues[ rVal ] = 1;
11456
0
            }
11457
0
            else
11458
0
            {
11459
0
                bIsUnique = false;
11460
0
                break;
11461
0
            }
11462
0
        }
11463
0
        if( ! bIsUnique )
11464
0
        {
11465
0
            SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11466
            // make unique by using ascending OnValues
11467
0
            int nKid = 0;
11468
0
            for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11469
0
            {
11470
0
                PDFWidget& rKid = m_aWidgets[nKidIndex];
11471
0
                rKid.m_aOnValue = OUString::number( nKid+1 );
11472
0
                if( rKid.m_aValue != "Off" )
11473
0
                    rKid.m_aValue = rKid.m_aOnValue;
11474
0
                ++nKid;
11475
0
            }
11476
0
        }
11477
        // finally move the "Yes" appearance to the OnValue appearance
11478
0
        for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11479
0
        {
11480
0
            PDFWidget& rKid = m_aWidgets[nKidIndex];
11481
0
            if ( !rKid.m_aOnValue.isEmpty() )
11482
0
            {
11483
0
                auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11484
0
                if( app_it != rKid.m_aAppearances.end() )
11485
0
                {
11486
0
                    auto stream_it = app_it->second.find( "Yes"_ostr );
11487
0
                    if( stream_it != app_it->second.end() )
11488
0
                    {
11489
0
                        SvMemoryStream* pStream = stream_it->second;
11490
0
                        app_it->second.erase( stream_it );
11491
0
                        OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11492
0
                        COSWriter::appendName( rKid.m_aOnValue, aBuf );
11493
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11494
0
                    }
11495
0
                    else
11496
0
                        SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11497
0
                }
11498
0
            }
11499
11500
0
            if ( !rKid.m_aOffValue.isEmpty() )
11501
0
            {
11502
0
                auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11503
0
                if( app_it != rKid.m_aAppearances.end() )
11504
0
                {
11505
0
                    auto stream_it = app_it->second.find( "Off"_ostr );
11506
0
                    if( stream_it != app_it->second.end() )
11507
0
                    {
11508
0
                        SvMemoryStream* pStream = stream_it->second;
11509
0
                        app_it->second.erase( stream_it );
11510
0
                        OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
11511
0
                        COSWriter::appendName( rKid.m_aOffValue, aBuf );
11512
0
                        (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11513
0
                    }
11514
0
                    else
11515
0
                        SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
11516
0
                }
11517
0
            }
11518
11519
            // update selected radio button
11520
0
            if( rKid.m_aValue != "Off" )
11521
0
            {
11522
0
                rGroupWidget.m_aValue = rKid.m_aValue;
11523
0
            }
11524
0
        }
11525
0
    }
11526
8.85k
}
11527
11528
sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11529
0
{
11530
0
    sal_Int32 nRadioGroupWidget = -1;
11531
11532
0
    std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11533
11534
0
    if( it == m_aRadioGroupWidgets.end() )
11535
0
    {
11536
0
        m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11537
0
            sal_Int32(m_aWidgets.size());
11538
11539
        // new group, insert the radiobutton
11540
0
        m_aWidgets.emplace_back( );
11541
0
        m_aWidgets.back().m_nObject     = createObject();
11542
0
        m_aWidgets.back().m_nPage       = m_nCurrentPage;
11543
0
        m_aWidgets.back().m_eType       = PDFWriter::RadioButton;
11544
0
        m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11545
0
        m_aWidgets.back().m_nFlags |= 0x0000C000;   // NoToggleToOff and Radio bits
11546
11547
0
        createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11548
0
    }
11549
0
    else
11550
0
        nRadioGroupWidget = it->second;
11551
11552
0
    return nRadioGroupWidget;
11553
0
}
11554
11555
sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11556
0
{
11557
0
    if( nPageNr < 0 )
11558
0
        nPageNr = m_nCurrentPage;
11559
11560
0
    if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11561
0
        return -1;
11562
11563
0
    sal_Int32 nNewWidget = m_aWidgets.size();
11564
0
    m_aWidgets.emplace_back( );
11565
11566
0
    m_aWidgets.back().m_nObject         = createObject();
11567
0
    m_aWidgets.back().m_aRect           = rControl.Location;
11568
0
    m_aWidgets.back().m_nPage           = nPageNr;
11569
0
    m_aWidgets.back().m_eType           = rControl.getType();
11570
11571
0
    sal_Int32 nRadioGroupWidget = -1;
11572
    // for unknown reasons the radio buttons of a radio group must not have a
11573
    // field name, else the buttons are in fact check boxes -
11574
    // that is multiple buttons of the radio group can be selected
11575
0
    if( rControl.getType() == PDFWriter::RadioButton )
11576
0
        nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11577
0
    else
11578
0
    {
11579
0
        createWidgetFieldName( nNewWidget, rControl );
11580
0
    }
11581
11582
    // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11583
0
    PDFWidget& rNewWidget           = m_aWidgets[nNewWidget];
11584
0
    rNewWidget.m_aDescription       = rControl.Description;
11585
0
    rNewWidget.m_aText              = rControl.Text;
11586
0
    rNewWidget.m_nTextStyle         = rControl.TextStyle &
11587
0
        (  DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11588
0
           DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11589
0
           DrawTextFlags::MultiLine | DrawTextFlags::WordBreak  );
11590
0
    rNewWidget.m_nTabOrder          = rControl.TabOrder;
11591
11592
    // various properties are set via the flags (/Ff) property of the field dict
11593
0
    if( rControl.ReadOnly )
11594
0
        rNewWidget.m_nFlags |= 1;
11595
0
    if( rControl.getType() == PDFWriter::PushButton )
11596
0
    {
11597
0
        const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11598
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11599
0
            rNewWidget.m_nTextStyle =
11600
0
                DrawTextFlags::Center | DrawTextFlags::VCenter |
11601
0
                DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11602
11603
0
        rNewWidget.m_nFlags |= 0x00010000;
11604
0
        if( !rBtn.URL.isEmpty() )
11605
0
            rNewWidget.m_aListEntries.push_back( rBtn.URL );
11606
0
        rNewWidget.m_bSubmit    = rBtn.Submit;
11607
0
        rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
11608
0
        rNewWidget.m_nDest      = rBtn.Dest;
11609
0
        createDefaultPushButtonAppearance( rNewWidget, rBtn );
11610
0
    }
11611
0
    else if( rControl.getType() == PDFWriter::RadioButton )
11612
0
    {
11613
0
        const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
11614
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11615
0
            rNewWidget.m_nTextStyle =
11616
0
                DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11617
        /*  PDF sees a RadioButton group as one radio button with
11618
         *  children which are in turn check boxes
11619
         *
11620
         *  so we need to create a radio button on demand for a new group
11621
         *  and insert a checkbox for each RadioButtonWidget as its child
11622
         */
11623
0
        rNewWidget.m_eType          = PDFWriter::CheckBox;
11624
0
        rNewWidget.m_nRadioGroup    = rBtn.RadioGroup;
11625
11626
0
        SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
11627
11628
0
        PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
11629
0
        rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
11630
0
        rRadioButton.m_aKidsIndex.push_back( nNewWidget );
11631
0
        rNewWidget.m_nParent = rRadioButton.m_nObject;
11632
11633
0
        rNewWidget.m_aValue     = "Off";
11634
0
        rNewWidget.m_aOnValue   = rBtn.OnValue;
11635
0
        rNewWidget.m_aOffValue   = rBtn.OffValue;
11636
0
        if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
11637
0
        {
11638
0
            rNewWidget.m_aValue     = rNewWidget.m_aOnValue;
11639
0
            rRadioButton.m_aValue   = rNewWidget.m_aOnValue;
11640
0
        }
11641
0
        createDefaultRadioButtonAppearance( rNewWidget, rBtn );
11642
11643
        // union rect of radio group
11644
0
        tools::Rectangle aRect = rNewWidget.m_aRect;
11645
0
        m_aPages[ nPageNr ].convertRect( aRect );
11646
0
        rRadioButton.m_aRect.Union( aRect );
11647
0
    }
11648
0
    else if( rControl.getType() == PDFWriter::CheckBox )
11649
0
    {
11650
0
        const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
11651
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11652
0
            rNewWidget.m_nTextStyle =
11653
0
                DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11654
11655
0
        rNewWidget.m_aValue
11656
0
            = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
11657
0
        rNewWidget.m_aOnValue   = rBox.OnValue;
11658
0
        rNewWidget.m_aOffValue   = rBox.OffValue;
11659
        // create default appearance before m_aRect gets transformed
11660
0
        createDefaultCheckBoxAppearance( rNewWidget, rBox );
11661
0
    }
11662
0
    else if( rControl.getType() == PDFWriter::ListBox )
11663
0
    {
11664
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11665
0
            rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11666
11667
0
        const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
11668
0
        rNewWidget.m_aListEntries     = rLstBox.Entries;
11669
0
        rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
11670
0
        rNewWidget.m_aValue           = rLstBox.Text;
11671
0
        if( rLstBox.DropDown )
11672
0
            rNewWidget.m_nFlags |= 0x00020000;
11673
0
        if (rLstBox.MultiSelect && !rLstBox.DropDown)
11674
0
            rNewWidget.m_nFlags |= 0x00200000;
11675
11676
0
        createDefaultListBoxAppearance( rNewWidget, rLstBox );
11677
0
    }
11678
0
    else if( rControl.getType() == PDFWriter::ComboBox )
11679
0
    {
11680
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11681
0
            rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11682
11683
0
        const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
11684
0
        rNewWidget.m_aValue         = rBox.Text;
11685
0
        rNewWidget.m_aListEntries   = rBox.Entries;
11686
0
        rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
11687
11688
0
        PDFWriter::ListBoxWidget aLBox;
11689
0
        aLBox.Name              = rBox.Name;
11690
0
        aLBox.Description       = rBox.Description;
11691
0
        aLBox.Text              = rBox.Text;
11692
0
        aLBox.TextStyle         = rBox.TextStyle;
11693
0
        aLBox.ReadOnly          = rBox.ReadOnly;
11694
0
        aLBox.Border            = rBox.Border;
11695
0
        aLBox.BorderColor       = rBox.BorderColor;
11696
0
        aLBox.Background        = rBox.Background;
11697
0
        aLBox.BackgroundColor   = rBox.BackgroundColor;
11698
0
        aLBox.TextFont          = rBox.TextFont;
11699
0
        aLBox.TextColor         = rBox.TextColor;
11700
0
        aLBox.DropDown          = true;
11701
0
        aLBox.MultiSelect       = false;
11702
0
        aLBox.Entries           = rBox.Entries;
11703
11704
0
        createDefaultListBoxAppearance( rNewWidget, aLBox );
11705
0
    }
11706
0
    else if( rControl.getType() == PDFWriter::Edit )
11707
0
    {
11708
0
        if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11709
0
            rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
11710
11711
0
        const PDFWriter::EditWidget& rEdit = static_cast<const  PDFWriter::EditWidget&>(rControl);
11712
0
        if( rEdit.MultiLine )
11713
0
        {
11714
0
            rNewWidget.m_nFlags |= 0x00001000;
11715
0
            rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11716
0
        }
11717
0
        if( rEdit.Password )
11718
0
            rNewWidget.m_nFlags |= 0x00002000;
11719
0
        if (rEdit.FileSelect)
11720
0
            rNewWidget.m_nFlags |= 0x00100000;
11721
0
        rNewWidget.m_nMaxLen = rEdit.MaxLen;
11722
0
        rNewWidget.m_nFormat = rEdit.Format;
11723
0
        rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
11724
0
        rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
11725
0
        rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
11726
0
        rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
11727
0
        rNewWidget.m_aDateFormat = rEdit.DateFormat;
11728
0
        rNewWidget.m_aValue = rEdit.Text;
11729
11730
0
        createDefaultEditAppearance( rNewWidget, rEdit );
11731
0
    }
11732
#if HAVE_FEATURE_NSS
11733
    else if( rControl.getType() == PDFWriter::Signature)
11734
    {
11735
        rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11736
11737
        m_nSignatureObject = createObject();
11738
        rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11739
        rNewWidget.m_aValue += " 0 R";
11740
        // let's add a fake appearance
11741
        rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
11742
    }
11743
#endif
11744
11745
    // if control is a signature, do not convert coordinates since we
11746
    // need /Rect [ 0 0 0 0 ]
11747
0
    if ( rControl.getType() != PDFWriter::Signature )
11748
0
    {
11749
        // convert to default user space now, since the mapmode may change
11750
        // note: create default appearances before m_aRect gets transformed
11751
0
        m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11752
0
    }
11753
11754
    // insert widget to page's annotation list
11755
0
    m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11756
11757
0
    return nNewWidget;
11758
0
}
11759
11760
void PDFWriterImpl::MARK( const char* pString )
11761
860k
{
11762
860k
    beginStructureElementMCSeq();
11763
860k
    if (g_bDebugDisableCompression)
11764
0
        emitComment( pString );
11765
860k
}
11766
11767
sal_Int32 ReferenceXObjectEmit::getObject() const
11768
0
{
11769
0
    if (m_nFormObject > 0)
11770
0
        return m_nFormObject;
11771
0
    else
11772
0
        return m_nBitmapObject;
11773
0
}
11774
}
11775
11776
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */