Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/svdraw/svdpdf.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 <svdpdf.hxx>
21
22
#include <limits>
23
#include <tools/stream.hxx>
24
#include <tools/UnitConversion.hxx>
25
#include <vcl/canvastools.hxx>
26
#include <vcl/embeddedfontsmanager.hxx>
27
#include <vcl/graph.hxx>
28
#include <vcl/pdf/PDFBitmapType.hxx>
29
#include <vcl/pdf/PDFFillMode.hxx>
30
#include <vcl/pdf/PDFPageObjectType.hxx>
31
#include <vcl/pdf/PDFSegmentType.hxx>
32
#include <vcl/pdf/PDFTextRenderMode.hxx>
33
#include <vcl/rendercontext/DrawModeFlags.hxx>
34
#include <vcl/vectorgraphicdata.hxx>
35
36
#include <editeng/eeitem.hxx>
37
#include <editeng/fhgtitem.hxx>
38
#include <editeng/wghtitem.hxx>
39
#include <editeng/postitem.hxx>
40
#include <editeng/udlnitem.hxx>
41
#include <editeng/crossedoutitem.hxx>
42
#include <editeng/shdditem.hxx>
43
#include <svx/xflbmsxy.hxx>
44
#include <svx/xlnclit.hxx>
45
#include <svx/xlncapit.hxx>
46
#include <svx/xlnwtit.hxx>
47
#include <svx/xflclit.hxx>
48
#include <editeng/fontitem.hxx>
49
#include <editeng/wrlmitem.hxx>
50
#include <editeng/contouritem.hxx>
51
#include <editeng/colritem.hxx>
52
#include <vcl/metric.hxx>
53
#include <editeng/charscaleitem.hxx>
54
#include <editeng/kernitem.hxx>
55
#include <svx/sdtditm.hxx>
56
#include <svx/sdtagitm.hxx>
57
#include <svx/sdtfsitm.hxx>
58
#include <svx/svdmodel.hxx>
59
#include <svx/svdpage.hxx>
60
#include <svx/svdobj.hxx>
61
#include <svx/svdotext.hxx>
62
#include <svx/svdorect.hxx>
63
#include <svx/svdograf.hxx>
64
#include <svx/svdopath.hxx>
65
#include <svx/svdetc.hxx>
66
#include <svl/itemset.hxx>
67
#include <basegfx/polygon/b2dpolygon.hxx>
68
#include <basegfx/polygon/b2dpolygontools.hxx>
69
#include <tools/helpers.hxx>
70
#include <basegfx/matrix/b2dhommatrix.hxx>
71
#include <basegfx/matrix/b2dhommatrixtools.hxx>
72
#include <svx/xlinjoit.hxx>
73
#include <svx/xlndsit.hxx>
74
#include <basegfx/polygon/b2dpolygonclipper.hxx>
75
#include <svx/xbtmpit.hxx>
76
#include <svx/xfillit0.hxx>
77
#include <svx/xflbmtit.hxx>
78
#include <svx/xflbstit.hxx>
79
#include <svx/xlineit0.hxx>
80
#include <basegfx/polygon/b2dpolypolygontools.hxx>
81
#include <svx/svditer.hxx>
82
#include <svx/svdogrp.hxx>
83
#include <vcl/dibtools.hxx>
84
#include <sal/log.hxx>
85
#include <o3tl/string_view.hxx>
86
#include <osl/diagnose.h>
87
#include <osl/file.hxx>
88
#include <unicode/normalizer2.h>
89
#include <set>
90
91
using namespace com::sun::star;
92
93
static OUString getPrefix(void* p)
94
104
{
95
104
    static sal_Int64 snCounter = 0;
96
104
    return OUString::number(reinterpret_cast<sal_IntPtr>(p)) + "_" + OUString::number(snCounter++);
97
104
}
98
99
ImpSdrPdfImport::ImpSdrPdfImport(SdrModel& rModel, SdrLayerID nLay, const tools::Rectangle& rRect,
100
                                 Graphic const& rGraphic)
101
110
    : maScaleRect(rRect)
102
110
    , mnMapScalingOfs(0)
103
110
    , mpModel(&rModel)
104
110
    , mnLayer(nLay)
105
110
    , mnLineWidth(0)
106
110
    , maDash(css::drawing::DashStyle_RECT, 0, 0, 0, 0, 0)
107
110
    , mbMov(false)
108
110
    , mbSize(false)
109
110
    , maOfs(0, 0)
110
110
    , mfScaleX(1.0)
111
110
    , mfScaleY(1.0)
112
110
    , maScaleX(1.0)
113
110
    , maScaleY(1.0)
114
110
    , mbFntDirty(true)
115
110
    , mbLastObjWasPolyWithoutLine(false)
116
110
    , mbNoLine(false)
117
110
    , mbNoFill(false)
118
110
    , mnPageCount(0)
119
110
    , mdPageHeightPts(0)
120
110
    , mpPDFium(vcl::pdf::PDFiumLibrary::get())
121
110
{
122
110
    mpLineAttr = std::make_unique<SfxItemSet>(
123
110
        SfxItemSet::makeFixedSfxItemSet<XATTR_LINE_FIRST, XATTR_LINE_LAST>(rModel.GetItemPool()));
124
110
    mpFillAttr = std::make_unique<SfxItemSet>(
125
110
        SfxItemSet::makeFixedSfxItemSet<XATTR_FILL_FIRST, XATTR_FILL_LAST>(rModel.GetItemPool()));
126
110
    mpTextAttr = std::make_unique<SfxItemSet>(
127
110
        SfxItemSet::makeFixedSfxItemSet<EE_ITEMS_START, EE_ITEMS_END>(rModel.GetItemPool()));
128
129
    // Load the buffer using pdfium.
130
110
    auto const& rVectorGraphicData = rGraphic.getVectorGraphicData();
131
110
    const auto* pData = rVectorGraphicData->getBinaryDataContainer().getData();
132
110
    sal_Int32 nSize = rVectorGraphicData->getBinaryDataContainer().getSize();
133
110
    mpPdfDocument = mpPDFium ? mpPDFium->openDocument(pData, nSize, OString()) : nullptr;
134
110
    if (!mpPdfDocument)
135
0
        return;
136
137
110
    mnPageCount = mpPdfDocument->getPageCount();
138
139
110
    const std::shared_ptr<GfxLink> xGrfLink = rGraphic.GetSharedGfxLink();
140
110
    if (xGrfLink)
141
110
        mxImportedFonts = xGrfLink->getImportedFonts();
142
110
    if (!mxImportedFonts)
143
104
    {
144
104
        mxImportedFonts
145
104
            = std::make_shared<ImportedFontMap>(CollectFonts(getPrefix(this), *mpPdfDocument));
146
104
        if (xGrfLink)
147
104
            xGrfLink->setImportedFonts(mxImportedFonts);
148
104
    }
149
150
    // Same as SdModule
151
110
    mpVD = VclPtr<VirtualDevice>::Create();
152
110
    mpVD->SetReferenceDevice(VirtualDevice::RefDevMode::Dpi600);
153
110
    mpVD->SetMapMode(MapMode(MapUnit::Map100thMM));
154
110
    mpVD->EnableOutput(false);
155
110
    mpVD->SetLineColor();
156
157
    // Get TextBounds relative to baseline
158
110
    vcl::Font aFnt = mpVD->GetFont();
159
110
    aFnt.SetAlignment(ALIGN_BASELINE);
160
110
    aFnt.SetFamily(FAMILY_DONTKNOW);
161
110
    mpVD->SetFont(aFnt);
162
110
}
163
164
110
ImpSdrPdfImport::~ImpSdrPdfImport() = default;
165
166
namespace
167
{
168
OUString GetPostScriptName(const OUString& rBaseFontName)
169
60
{
170
60
    OUString sPostScriptName = rBaseFontName;
171
    /* For a font subset, the PostScript name of the font—the value of the
172
         * font’s BaseFont entry and the font descriptor’s FontName entry shall
173
         * begin with a tag followed by a plus sign (+). The tag shall consist of
174
         * exactly six uppercase letters; the choice of letters is arbitrary, but
175
         * different subsets in the same PDF file shall have different tags */
176
60
    if (sPostScriptName.getLength() > 6 && sPostScriptName[6] == '+')
177
9
        sPostScriptName = sPostScriptName.copy(7);
178
60
    return sPostScriptName;
179
60
}
180
181
bool writeFontFile(const OUString& fileUrl, const std::vector<uint8_t>& rFontData)
182
9
{
183
9
    SAL_INFO("sd.filter", "dumping to: " << fileUrl);
184
185
9
    osl::File file(fileUrl);
186
9
    switch (file.open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write))
187
9
    {
188
2
        case osl::File::E_None:
189
2
            break; // ok
190
0
        case osl::File::E_EXIST:
191
0
            return true; // Assume it's already been added correctly.
192
7
        default:
193
7
            SAL_WARN("sd.filter", "Cannot open file for temporary font");
194
7
            return false;
195
9
    }
196
2
    sal_uInt64 writtenTotal = 0;
197
4
    while (writtenTotal < rFontData.size())
198
2
    {
199
2
        sal_uInt64 written;
200
2
        if (file.write(rFontData.data() + writtenTotal, rFontData.size() - writtenTotal, written)
201
2
            != osl::File::E_None)
202
0
        {
203
0
            SAL_WARN("sd.filter", "Error writing temporary font file");
204
0
            osl::File::remove(fileUrl);
205
0
            return false;
206
0
        }
207
2
        writtenTotal += written;
208
2
    }
209
210
2
    return true;
211
2
}
212
213
FontWeight toOfficeWeight(std::string_view style)
214
10
{
215
10
    if (o3tl::equalsIgnoreAsciiCase(style, "Regular"))
216
0
        return WEIGHT_NORMAL;
217
10
    else if (o3tl::equalsIgnoreAsciiCase(style, "Bold"))
218
0
        return WEIGHT_BOLD;
219
10
    else if (o3tl::equalsIgnoreAsciiCase(style, "BoldItalic"))
220
0
        return WEIGHT_BOLD;
221
10
    return WEIGHT_DONTKNOW;
222
10
}
223
224
OUString stripPostScriptStyle(const OUString& postScriptName, FontWeight& eWeight)
225
60
{
226
60
    OUString sFontName;
227
60
    sal_Int32 lastDash = postScriptName.lastIndexOf('-');
228
60
    if (lastDash == -1)
229
50
    {
230
50
        sFontName = postScriptName;
231
50
        eWeight = WEIGHT_NORMAL;
232
50
    }
233
10
    else
234
10
    {
235
10
        sFontName = postScriptName.copy(0, lastDash);
236
10
        eWeight = toOfficeWeight(postScriptName.copy(lastDash + 1).toUtf8());
237
10
    }
238
60
    return sFontName;
239
60
}
240
241
OUString getFileUrlForTemporaryFont(std::u16string_view prefix, std::u16string_view name,
242
                                    std::u16string_view suffix)
243
9
{
244
9
    return EmbeddedFontsManager::getFileUrlForTemporaryFont(
245
9
        Concat2View(OUString::Concat(prefix) + name), suffix);
246
9
}
247
}
248
249
// Possibly there is some alternative route to query pdfium for all fonts without
250
// iterating through every object to see what font each uses
251
ImportedFontMap ImpSdrPdfImport::CollectFonts(std::u16string_view sPrefix,
252
                                              vcl::pdf::PDFiumDocument& rPdfDocument)
253
104
{
254
104
    ImportedFontMap aImportedFonts;
255
256
104
    std::map<OUString, SubSetInfo> aDifferentSubsetsForFont;
257
    // map of PostScriptName->Merged Font File for that font
258
104
    std::map<OUString, EmbeddedFontInfo> aEmbeddedFonts;
259
260
104
    const int nPageCount = rPdfDocument.getPageCount();
261
262
277
    for (int nPageIndex = 0; nPageIndex < nPageCount; ++nPageIndex)
263
173
    {
264
173
        auto pPdfPage = rPdfDocument.openPage(nPageIndex);
265
173
        if (!pPdfPage)
266
26
        {
267
26
            SAL_WARN("sd.filter", "ImpSdrPdfImport missing page: " << nPageIndex);
268
26
            continue;
269
26
        }
270
147
        auto pTextPage = pPdfPage->getTextPage();
271
272
147
        const int nPageObjectCount = pPdfPage->getObjectCount();
273
2.24k
        for (int nPageObjectIndex = 0; nPageObjectIndex < nPageObjectCount; ++nPageObjectIndex)
274
2.09k
        {
275
2.09k
            auto pPageObject = pPdfPage->getObject(nPageObjectIndex);
276
2.09k
            if (!pPageObject)
277
0
            {
278
0
                SAL_WARN("sd.filter", "ImpSdrPdfImport missing object: "
279
0
                                          << nPageObjectIndex << " on page: " << nPageIndex);
280
0
                continue;
281
0
            }
282
283
2.09k
            const vcl::pdf::PDFPageObjectType ePageObjectType = pPageObject->getType();
284
2.09k
            if (ePageObjectType != vcl::pdf::PDFPageObjectType::Text)
285
1.62k
                continue;
286
467
            std::unique_ptr<vcl::pdf::PDFiumFont> font = pPageObject->getFont();
287
467
            if (!font)
288
0
                continue;
289
290
467
            auto itImportedFont = aImportedFonts.find(font->getFontDictObjNum());
291
467
            if (itImportedFont == aImportedFonts.end())
292
60
            {
293
60
                OUString sPostScriptName = GetPostScriptName(pPageObject->getBaseFontName());
294
295
60
                OUString sFontName = pPageObject->getFontName();
296
297
60
                FontWeight eFontWeight(WEIGHT_DONTKNOW);
298
60
                OUString sPostScriptFontFamily = stripPostScriptStyle(sPostScriptName, eFontWeight);
299
300
60
                if (sFontName.isEmpty())
301
3
                {
302
3
                    sFontName = sPostScriptFontFamily;
303
3
                    SAL_WARN("sd.filter",
304
3
                             "missing font name, attempt to reconstruct from postscriptname as: "
305
3
                                 << sFontName);
306
3
                }
307
308
60
                if (!font->getIsEmbedded())
309
50
                {
310
50
                    SAL_WARN("sd.filter", "skipping not embedded font, map: "
311
50
                                              << sFontName << " to " << sPostScriptFontFamily);
312
50
                    aImportedFonts.insert(OfficeFontInfo{ font->getFontDictObjNum(),
313
50
                                                          sPostScriptFontFamily, eFontWeight });
314
50
                    continue;
315
50
                }
316
317
10
                std::vector<uint8_t> aFontData;
318
10
                if (!font->getFontData(aFontData) || aFontData.empty())
319
1
                {
320
1
                    SAL_WARN("sd.filter", "that's worrying, skipping " << sFontName);
321
1
                    continue;
322
1
                }
323
324
9
                SubSetInfo* pSubSetInfo;
325
326
9
                SAL_INFO("sd.filter", "importing font: " << font);
327
9
                auto itFontName = aDifferentSubsetsForFont.find(sPostScriptName);
328
9
                OUString sFontFileName = sPostScriptName;
329
9
                if (itFontName != aDifferentSubsetsForFont.end())
330
0
                {
331
0
                    sFontFileName += OUString::number(itFontName->second.aComponents.size());
332
0
                    itFontName->second.aComponents.emplace_back();
333
0
                    pSubSetInfo = &itFontName->second;
334
0
                }
335
9
                else
336
9
                {
337
9
                    sFontFileName += "0";
338
9
                    SubSetInfo aSubSetInfo;
339
9
                    aSubSetInfo.aComponents.emplace_back();
340
9
                    auto result
341
9
                        = aDifferentSubsetsForFont.emplace(sPostScriptName, aSubSetInfo).first;
342
9
                    pSubSetInfo = &result->second;
343
9
                }
344
9
                bool bTTF = EmbeddedFontsManager::analyzeTTF(aFontData.data(), aFontData.size(),
345
9
                                                             eFontWeight);
346
9
                SAL_INFO_IF(!bTTF, "sd.filter", "not ttf/otf, converting");
347
9
                OUString fileUrl
348
9
                    = getFileUrlForTemporaryFont(sPrefix, sFontFileName, bTTF ? u".ttf" : u".t1");
349
9
                if (!writeFontFile(fileUrl, aFontData))
350
9
                    SAL_WARN("sd.filter", "ttf not written");
351
2
                else
352
9
                    SAL_INFO("sd.filter", "ttf written to: " << fileUrl);
353
9
                std::vector<uint8_t> aToUnicodeData;
354
9
                if (!font->getFontToUnicode(aToUnicodeData))
355
9
                    SAL_WARN("sd.filter", "that's maybe worrying");
356
9
                if (!bTTF || !aToUnicodeData.empty())
357
0
                {
358
0
                    EmbeddedFontInfo fontInfo
359
0
                        = convertToOTF(sPrefix, *pSubSetInfo, fileUrl, sFontName, sPostScriptName,
360
0
                                       sFontFileName, aToUnicodeData, *font);
361
0
                    fileUrl = fontInfo.sFontFile;
362
0
                    sFontName = fontInfo.sFontName;
363
0
                    eFontWeight = fontInfo.eFontWeight;
364
0
                }
365
366
9
                if (fileUrl.getLength())
367
9
                {
368
9
                    aImportedFonts.insert(
369
9
                        OfficeFontInfo{ font->getFontDictObjNum(), sFontName, eFontWeight });
370
9
                    aEmbeddedFonts[sPostScriptName]
371
9
                        = EmbeddedFontInfo{ sFontName, fileUrl, eFontWeight };
372
9
                }
373
9
            }
374
407
            else
375
407
            {
376
407
                SAL_INFO("sd.filter", "already saw font " << font << " and used "
377
407
                                                          << itImportedFont->sFontName
378
407
                                                          << " as name");
379
407
            }
380
467
        }
381
147
    }
382
383
104
    if (!aEmbeddedFonts.empty())
384
9
    {
385
9
        EmbeddedFontsManager aEmbeddedFontsManager(nullptr);
386
9
        for (const auto& fontinfo : aEmbeddedFonts)
387
9
        {
388
9
            aEmbeddedFontsManager.addEmbeddedFont(fontinfo.second.sFontFile,
389
9
                                                  fontinfo.second.sFontName, true);
390
9
        }
391
9
    }
392
393
104
    return aImportedFonts;
394
104
}
395
396
void ImpSdrPdfImport::DoObjects(SvdProgressInfo* pProgrInfo, sal_uInt32* pActionsToReport,
397
                                int nPageIndex)
398
110
{
399
110
    const int nPageCount = mpPdfDocument->getPageCount();
400
110
    if (!(nPageCount > 0 && nPageIndex >= 0 && nPageIndex < nPageCount))
401
0
        return;
402
403
    // Render next page.
404
110
    auto pPdfPage = mpPdfDocument->openPage(nPageIndex);
405
110
    if (!pPdfPage)
406
0
        return;
407
408
110
    basegfx::B2DSize dPageSize = mpPdfDocument->getPageSize(nPageIndex);
409
410
110
    SetupPageScale(dPageSize.getWidth(), dPageSize.getHeight());
411
412
    // Load the page text to extract it when we get text elements.
413
110
    auto pTextPage = pPdfPage->getTextPage();
414
415
110
    const int nPageObjectCount = pPdfPage->getObjectCount();
416
110
    if (pProgrInfo)
417
0
        pProgrInfo->SetActionCount(nPageObjectCount);
418
419
2.20k
    for (int nPageObjectIndex = 0; nPageObjectIndex < nPageObjectCount; ++nPageObjectIndex)
420
2.09k
    {
421
2.09k
        auto pPageObject = pPdfPage->getObject(nPageObjectIndex);
422
2.09k
        ImportPdfObject(pPageObject, pPdfPage, pTextPage, nPageObjectIndex);
423
2.09k
        if (pProgrInfo && pActionsToReport)
424
0
        {
425
0
            (*pActionsToReport)++;
426
427
0
            if (*pActionsToReport >= 16)
428
0
            {
429
0
                if (!pProgrInfo->ReportActions(*pActionsToReport))
430
0
                    break;
431
432
0
                *pActionsToReport = 0;
433
0
            }
434
0
        }
435
2.09k
    }
436
110
}
437
438
void ImpSdrPdfImport::SetupPageScale(const double dPageWidth, const double dPageHeight)
439
110
{
440
110
    mfScaleX = mfScaleY = 1.0;
441
442
    // Store the page dimensions in Points.
443
110
    mdPageHeightPts = dPageHeight;
444
445
110
    Size aPageSize(convertPointToMm100(dPageWidth), convertPointToMm100(dPageHeight));
446
110
    Size aScaleRectSize = maScaleRect.GetSize();
447
448
110
    if (aPageSize.Width() && aPageSize.Height() && (!maScaleRect.IsEmpty()))
449
110
    {
450
110
        maOfs = maScaleRect.TopLeft();
451
452
110
        if (aPageSize.Width() != aScaleRectSize.Width())
453
59
        {
454
59
            mfScaleX = static_cast<double>(aScaleRectSize.Width())
455
59
                       / static_cast<double>(aPageSize.Width());
456
59
        }
457
458
110
        if (aPageSize.Height() != aScaleRectSize.Height())
459
45
        {
460
45
            mfScaleY = static_cast<double>(aScaleRectSize.Height())
461
45
                       / static_cast<double>(aPageSize.Height());
462
45
        }
463
110
    }
464
465
110
    mbMov = maOfs.X() != 0 || maOfs.Y() != 0;
466
110
    mbSize = false;
467
110
    maScaleX = 1.0;
468
110
    maScaleY = 1.0;
469
470
110
    if (aPageSize.Width() && aPageSize.Width() != aScaleRectSize.Width())
471
59
    {
472
59
        maScaleX = double(aScaleRectSize.Width()) / aPageSize.Width();
473
59
        mbSize = true;
474
59
    }
475
476
110
    if (aPageSize.Height() && aPageSize.Height() != aScaleRectSize.Height())
477
45
    {
478
45
        maScaleY = double(aScaleRectSize.Height()) / aPageSize.Height();
479
45
        mbSize = true;
480
45
    }
481
110
}
482
483
size_t ImpSdrPdfImport::DoImport(SdrObjList& rOL, size_t nInsPos, int nPageNumber,
484
                                 SvdProgressInfo* pProgrInfo)
485
110
{
486
110
    sal_uInt32 nActionsToReport(0);
487
488
    // execute
489
110
    DoObjects(pProgrInfo, &nActionsToReport, nPageNumber);
490
491
110
    if (pProgrInfo)
492
0
    {
493
0
        pProgrInfo->ReportActions(nActionsToReport);
494
0
        nActionsToReport = 0;
495
0
    }
496
497
    // MapMode scaling
498
110
    MapScaling();
499
500
    // To calculate the progress meter, we use GetActionSize()*3.
501
    // However, maTmpList has a lower entry count limit than GetActionSize(),
502
    // so the actions that were assumed were too much have to be re-added.
503
    // nActionsToReport = (rMtf.GetActionSize() - maTmpList.size()) * 2;
504
505
    // announce all currently unannounced rescales
506
110
    if (pProgrInfo)
507
0
    {
508
0
        pProgrInfo->ReportRescales(nActionsToReport);
509
0
        pProgrInfo->SetInsertCount(maTmpList.size());
510
0
    }
511
512
110
    nActionsToReport = 0;
513
514
    // insert all objects cached in aTmpList now into rOL from nInsPos
515
110
    nInsPos = std::min(nInsPos, rOL.GetObjCount());
516
517
110
    for (rtl::Reference<SdrObject>& pObj : maTmpList)
518
5.75k
    {
519
5.75k
        rOL.NbcInsertObject(pObj.get(), nInsPos);
520
5.75k
        nInsPos++;
521
522
5.75k
        if (pProgrInfo)
523
0
        {
524
0
            nActionsToReport++;
525
526
0
            if (nActionsToReport >= 32) // update all 32 actions
527
0
            {
528
0
                pProgrInfo->ReportInserts(nActionsToReport);
529
0
                nActionsToReport = 0;
530
0
            }
531
0
        }
532
5.75k
    }
533
534
    // report all remaining inserts for the last time
535
110
    if (pProgrInfo)
536
0
    {
537
0
        pProgrInfo->ReportInserts(nActionsToReport);
538
0
    }
539
540
110
    return maTmpList.size();
541
110
}
542
543
void ImpSdrPdfImport::SetAttributes(SdrObject* pObj, bool bForceTextAttr)
544
6.53k
{
545
6.53k
    mbNoLine = false;
546
6.53k
    mbNoFill = false;
547
6.53k
    bool bLine(!bForceTextAttr);
548
6.53k
    bool bFill(!pObj || (pObj->IsClosedObj() && !bForceTextAttr));
549
6.53k
    bool bText(bForceTextAttr || (pObj && pObj->GetOutlinerParaObject()));
550
551
6.53k
    if (bLine)
552
3.34k
    {
553
3.34k
        if (mnLineWidth)
554
3.21k
        {
555
3.21k
            mpLineAttr->Put(XLineWidthItem(mnLineWidth));
556
3.21k
        }
557
135
        else
558
135
        {
559
135
            mpLineAttr->Put(XLineWidthItem(0));
560
135
        }
561
562
3.34k
        if (mpVD->IsLineColor())
563
968
        {
564
968
            mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_SOLID)); //TODO support dashed lines.
565
968
            mpLineAttr->Put(XLineColorItem(OUString(), mpVD->GetLineColor()));
566
968
        }
567
2.38k
        else
568
2.38k
        {
569
2.38k
            mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_NONE));
570
2.38k
        }
571
572
3.34k
        mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_NONE));
573
574
        // Add LineCap support
575
3.34k
        mpLineAttr->Put(XLineCapItem(gaLineCap));
576
577
3.34k
        if (((maDash.GetDots() && maDash.GetDotLen())
578
3.34k
             || (maDash.GetDashes() && maDash.GetDashLen()))
579
0
            && maDash.GetDistance())
580
0
        {
581
0
            mpLineAttr->Put(XLineDashItem(OUString(), maDash));
582
0
        }
583
3.34k
        else
584
3.34k
        {
585
3.34k
            mpLineAttr->Put(XLineDashItem(OUString(), XDash(css::drawing::DashStyle_RECT)));
586
3.34k
        }
587
3.34k
    }
588
3.19k
    else
589
3.19k
    {
590
3.19k
        mbNoLine = true;
591
3.19k
    }
592
593
6.53k
    if (bFill)
594
3.34k
    {
595
3.34k
        bool doFill
596
3.34k
            = mpVD->GetDrawMode() != DrawModeFlags::NoFill && (moFillColor || moFillPattern);
597
3.34k
        if (doFill && moFillColor)
598
2.76k
            doFill = !moFillColor->IsTransparent();
599
3.34k
        if (doFill)
600
2.77k
        {
601
2.77k
            if (moFillColor)
602
2.76k
            {
603
2.76k
                mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_SOLID));
604
2.76k
                mpFillAttr->Put(XFillColorItem(OUString(), *moFillColor));
605
2.76k
            }
606
14
            else
607
14
            {
608
14
                assert(moFillPattern && "pattern should exist");
609
14
                mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_BITMAP));
610
14
                mpFillAttr->Put(XFillBitmapItem(OUString(), Graphic(*moFillPattern)));
611
14
                mpFillAttr->Put(XFillBmpStretchItem(false));
612
14
                mpFillAttr->Put(XFillBmpTileItem(true));
613
14
                mpFillAttr->Put(
614
14
                    XFillBmpSizeXItem(convertPointToMm100(moFillPattern->GetSizePixel().Width())));
615
14
                mpFillAttr->Put(
616
14
                    XFillBmpSizeYItem(convertPointToMm100(moFillPattern->GetSizePixel().Height())));
617
14
            }
618
2.77k
        }
619
573
        else
620
573
        {
621
573
            mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_NONE));
622
573
        }
623
3.34k
    }
624
3.19k
    else
625
3.19k
    {
626
3.19k
        mbNoFill = true;
627
3.19k
    }
628
629
6.53k
    if (bText && mbFntDirty)
630
1.15k
    {
631
1.15k
        vcl::Font aFnt(mpVD->GetFont());
632
1.15k
        const sal_uInt32 nHeight(
633
1.15k
            basegfx::fround<sal_uInt32>(aFnt.GetFontSize().Height() * mfScaleY));
634
635
1.15k
        mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(),
636
1.15k
                                    aFnt.GetPitchMaybeAskConfig(), aFnt.GetCharSet(),
637
1.15k
                                    EE_CHAR_FONTINFO));
638
1.15k
        mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(),
639
1.15k
                                    aFnt.GetPitchMaybeAskConfig(), aFnt.GetCharSet(),
640
1.15k
                                    EE_CHAR_FONTINFO_CJK));
641
1.15k
        mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(),
642
1.15k
                                    aFnt.GetPitchMaybeAskConfig(), aFnt.GetCharSet(),
643
1.15k
                                    EE_CHAR_FONTINFO_CTL));
644
1.15k
        mpTextAttr->Put(SvxPostureItem(aFnt.GetItalicMaybeAskConfig(), EE_CHAR_ITALIC));
645
1.15k
        mpTextAttr->Put(SvxWeightItem(aFnt.GetWeightMaybeAskConfig(), EE_CHAR_WEIGHT));
646
1.15k
        mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT));
647
1.15k
        mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CJK));
648
1.15k
        mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CTL));
649
1.15k
        mpTextAttr->Put(SvxCharScaleWidthItem(100, EE_CHAR_FONTWIDTH));
650
1.15k
        mpTextAttr->Put(SvxUnderlineItem(aFnt.GetUnderline(), EE_CHAR_UNDERLINE));
651
1.15k
        mpTextAttr->Put(SvxOverlineItem(aFnt.GetOverline(), EE_CHAR_OVERLINE));
652
1.15k
        mpTextAttr->Put(SvxCrossedOutItem(aFnt.GetStrikeout(), EE_CHAR_STRIKEOUT));
653
1.15k
        mpTextAttr->Put(SvxShadowedItem(aFnt.IsShadow(), EE_CHAR_SHADOW));
654
655
        // #i118485# Setting this item leads to problems (written #i118498# for this)
656
        // mpTextAttr->Put(SvxAutoKernItem(aFnt.IsKerning(), EE_CHAR_KERNING));
657
658
1.15k
        mpTextAttr->Put(SvxWordLineModeItem(aFnt.IsWordLineMode(), EE_CHAR_WLM));
659
1.15k
        mpTextAttr->Put(SvxContourItem(aFnt.IsOutline(), EE_CHAR_OUTLINE));
660
1.15k
        mpTextAttr->Put(SvxColorItem(mpVD->GetTextColor(), EE_CHAR_COLOR));
661
        //... svxfont textitem svditext
662
1.15k
        mbFntDirty = false;
663
1.15k
    }
664
665
6.53k
    if (!pObj)
666
0
        return;
667
668
6.53k
    pObj->SetLayer(mnLayer);
669
670
6.53k
    if (bLine)
671
3.34k
    {
672
3.34k
        pObj->SetMergedItemSet(*mpLineAttr);
673
3.34k
    }
674
675
6.53k
    if (bFill)
676
3.34k
    {
677
3.34k
        pObj->SetMergedItemSet(*mpFillAttr);
678
3.34k
    }
679
680
6.53k
    if (bText)
681
3.19k
    {
682
3.19k
        pObj->SetMergedItemSet(*mpTextAttr);
683
3.19k
        pObj->SetMergedItem(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT));
684
3.19k
    }
685
6.53k
}
686
687
void ImpSdrPdfImport::InsertObj(SdrObject* pObj1, bool bScale)
688
6.96k
{
689
6.96k
    rtl::Reference<SdrObject> pObj = pObj1;
690
6.96k
    if (bScale && !maScaleRect.IsEmpty())
691
431
    {
692
431
        if (mbSize)
693
404
        {
694
404
            pObj->NbcResize(Point(), maScaleX, maScaleY);
695
404
        }
696
697
431
        if (mbMov)
698
0
        {
699
0
            pObj->NbcMove(Size(maOfs.X(), maOfs.Y()));
700
0
        }
701
431
    }
702
703
6.96k
    if (!pObj)
704
0
        return;
705
706
    // #i111954# check object for visibility
707
    // used are SdrPathObj, SdrRectObj, SdrCircObj, SdrGrafObj
708
6.96k
    bool bVisible(false);
709
710
6.96k
    if (pObj->HasLineStyle())
711
968
    {
712
968
        bVisible = true;
713
968
    }
714
715
6.96k
    if (!bVisible && pObj->HasFillStyle())
716
2.37k
    {
717
2.37k
        bVisible = true;
718
2.37k
    }
719
720
6.96k
    if (!bVisible)
721
3.62k
    {
722
3.62k
        SdrTextObj* pTextObj = DynCastSdrTextObj(pObj.get());
723
724
3.62k
        if (pTextObj && pTextObj->HasText())
725
1.98k
        {
726
1.98k
            bVisible = true;
727
1.98k
        }
728
3.62k
    }
729
730
6.96k
    if (!bVisible)
731
1.64k
    {
732
1.64k
        SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>(pObj.get());
733
734
1.64k
        if (pGrafObj)
735
431
        {
736
            // this may be refined to check if the graphic really is visible. It
737
            // is here to ensure that graphic objects without fill, line and text
738
            // get created
739
431
            bVisible = true;
740
431
        }
741
1.64k
    }
742
743
6.96k
    if (bVisible)
744
5.75k
    {
745
5.75k
        maTmpList.push_back(pObj);
746
747
5.75k
        if (dynamic_cast<SdrPathObj*>(pObj.get()))
748
3.34k
        {
749
3.34k
            const bool bClosed(pObj->IsClosedObj());
750
751
3.34k
            mbLastObjWasPolyWithoutLine = mbNoLine && bClosed;
752
3.34k
        }
753
2.41k
        else
754
2.41k
        {
755
2.41k
            mbLastObjWasPolyWithoutLine = false;
756
2.41k
        }
757
5.75k
    }
758
6.96k
}
759
760
bool ImpSdrPdfImport::CheckLastPolyLineAndFillMerge(const basegfx::B2DPolyPolygon& rPolyPolygon)
761
0
{
762
    // #i73407# reformulation to use new B2DPolygon classes
763
0
    if (mbLastObjWasPolyWithoutLine)
764
0
    {
765
0
        SdrObject* pTmpObj = !maTmpList.empty() ? maTmpList[maTmpList.size() - 1].get() : nullptr;
766
0
        SdrPathObj* pLastPoly = dynamic_cast<SdrPathObj*>(pTmpObj);
767
768
0
        if (pLastPoly)
769
0
        {
770
0
            if (pLastPoly->GetPathPoly() == rPolyPolygon)
771
0
            {
772
0
                SetAttributes(nullptr);
773
774
0
                if (!mbNoLine && mbNoFill)
775
0
                {
776
0
                    pLastPoly->SetMergedItemSet(*mpLineAttr);
777
778
0
                    return true;
779
0
                }
780
0
            }
781
0
        }
782
0
    }
783
784
0
    return false;
785
0
}
786
787
void ImpSdrPdfImport::ImportPdfObject(
788
    std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
789
    std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage,
790
    std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int nPageObjectIndex)
791
8.30k
{
792
8.30k
    if (!pPageObject)
793
0
        return;
794
795
8.30k
    const vcl::pdf::PDFPageObjectType ePageObjectType = pPageObject->getType();
796
8.30k
    switch (ePageObjectType)
797
8.30k
    {
798
3.52k
        case vcl::pdf::PDFPageObjectType::Text:
799
3.52k
            ImportText(pPageObject, pPage, pTextPage, nPageObjectIndex);
800
3.52k
            break;
801
3.34k
        case vcl::pdf::PDFPageObjectType::Path:
802
3.34k
            ImportPath(pPageObject, pPage, nPageObjectIndex);
803
3.34k
            break;
804
476
        case vcl::pdf::PDFPageObjectType::Image:
805
476
            ImportImage(pPageObject, nPageObjectIndex);
806
476
            break;
807
1
        case vcl::pdf::PDFPageObjectType::Shading:
808
1
            SAL_WARN("sd.filter", "Got page object SHADING: " << nPageObjectIndex);
809
1
            break;
810
957
        case vcl::pdf::PDFPageObjectType::Form:
811
957
            ImportForm(pPageObject, pPage, pTextPage, nPageObjectIndex);
812
957
            break;
813
0
        default:
814
0
            SAL_WARN("sd.filter", "Unknown PDF page object #" << nPageObjectIndex << " of type: "
815
0
                                                              << static_cast<int>(ePageObjectType));
816
0
            break;
817
8.30k
    }
818
8.30k
}
819
820
void ImpSdrPdfImport::ImportForm(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
821
                                 std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage,
822
                                 std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage,
823
                                 int /*nPageObjectIndex*/)
824
957
{
825
    // Get the form matrix to perform correct translation/scaling of the form sub-objects.
826
957
    const basegfx::B2DHomMatrix aOldMatrix = maCurrentMatrix;
827
828
957
    maCurrentMatrix = pPageObject->getMatrix();
829
830
957
    const int nCount = pPageObject->getFormObjectCount();
831
7.16k
    for (int nIndex = 0; nIndex < nCount; ++nIndex)
832
6.21k
    {
833
6.21k
        auto pFormObject = pPageObject->getFormObject(nIndex);
834
835
6.21k
        ImportPdfObject(pFormObject, pPage, pTextPage, -1);
836
6.21k
    }
837
838
    // Restore the old one.
839
957
    maCurrentMatrix = aOldMatrix;
840
957
}
841
842
static bool extractEntry(std::string_view line, std::string_view key, OString& ret)
843
0
{
844
0
    std::string_view result;
845
0
    if (o3tl::starts_with(line, key, &result))
846
0
    {
847
0
        result = o3tl::trim(result);
848
0
        if (result[0] == '"' && result[result.size() - 1] == '"')
849
0
            result = result.substr(1, result.size() - 2);
850
0
        ret = OString(result);
851
0
        return true;
852
0
    }
853
0
    return false;
854
0
}
855
856
static bool isSimpleFamilyName(std::string_view Weight)
857
0
{
858
0
    return Weight.empty() || Weight == "Regular" || Weight == "Italic" || Weight == "Bold"
859
0
           || Weight == "BoldItalic";
860
0
}
861
862
// a) change brokenName/brokenCIDName if present to fixedName
863
// b) remove preamble FontMatrix and overwrite following ones in the %ADOBeginFontDict
864
// section with that content instead to avoid generating fonts with unusual UnitsPerEm
865
// of 1 that freetype will reject
866
static void rewriteFont(std::string_view brokenName, std::string_view brokenCIDName,
867
                        std::string_view fixedName, const OUString& pfaCIDUrl)
868
0
{
869
0
    OUString oldCIDUrl = pfaCIDUrl + ".broken";
870
0
    if (osl::File::move(pfaCIDUrl, oldCIDUrl) != osl::File::E_None)
871
0
    {
872
0
        SAL_WARN("sd.filter", "unable to move file");
873
0
        return;
874
0
    }
875
876
0
    const bool rewriteNames = !brokenName.empty();
877
878
0
    bool fontDict = false;
879
880
0
    OString sGlobalMatrix;
881
882
0
    const OString sBrokenFontLine = "/FontName /"_ostr + brokenName + " def"_ostr;
883
0
    const OString sFixedFontLine = "/FontName /"_ostr + fixedName + " def"_ostr;
884
885
0
    const OString sBrokenCIDFontLine = "/CIDFontName /"_ostr + brokenCIDName + " def"_ostr;
886
0
    const OString sFixedCIDFontLine = "/CIDFontName /"_ostr + fixedName + " def"_ostr;
887
888
0
    SvFileStream input(oldCIDUrl, StreamMode::READ);
889
0
    SvFileStream output(pfaCIDUrl, StreamMode::WRITE | StreamMode::TRUNC);
890
0
    OString sLine;
891
0
    while (input.ReadLine(sLine))
892
0
    {
893
0
        if (rewriteNames)
894
0
        {
895
0
            if (sLine == sBrokenFontLine)
896
0
            {
897
0
                output.WriteLine(sFixedFontLine);
898
0
                continue;
899
0
            }
900
0
            else if (sLine == sBrokenCIDFontLine)
901
0
            {
902
0
                output.WriteLine(sFixedCIDFontLine);
903
0
                continue;
904
0
            }
905
0
        }
906
907
0
        if (sLine.startsWith("%ADOBeginFontDict"))
908
0
            fontDict = true;
909
0
        else if (sLine.startsWith("/FontMatrix "))
910
0
        {
911
0
            if (!fontDict)
912
0
            {
913
                // Global case, stash and don't emit the global matrix
914
0
                sGlobalMatrix = sLine;
915
0
                continue;
916
0
            }
917
918
0
            if (!sGlobalMatrix.isEmpty())
919
0
            {
920
                // Local case, emit the global matrix instead if
921
                // there was one, otherwise emit the local matrix
922
0
                output.WriteLine(sGlobalMatrix);
923
0
                continue;
924
0
            }
925
0
        }
926
927
0
        output.WriteLine(sLine);
928
0
        if (sLine.startsWith("%%BeginData"))
929
0
        {
930
            // Write the rest direct as-is and quit
931
0
            output.WriteStream(input);
932
0
            break;
933
0
        }
934
0
    }
935
0
}
936
937
// https://ccjktype.fonts.adobe.com/2011/12/leveraging-afdko-part-1.html
938
// https://ccjktype.fonts.adobe.com/2012/01/leveraging-afdko-part-2.html
939
// https://ccjktype.fonts.adobe.com/2012/01/leveraging-afdko-part-3.html
940
static bool toPfaCID(SubSetInfo& rSubSetInfo, const OUString& fileUrl,
941
                     const OUString& postScriptName, bool& bNameKeyed,
942
                     std::map<int, int>& nameIndexToGlyph, OString& FontName, OString& Weight,
943
                     OString& FSType)
944
0
{
945
0
    bNameKeyed = false;
946
0
    FSType = "0"_ostr;
947
948
0
    OUString infoUrl = fileUrl + u".1";
949
0
    OUString cidFontInfoUrl = fileUrl + u".cidfontinfo";
950
0
    OUString nameToCIDMapUrl = fileUrl + u".nametocidmap";
951
0
    OUString toMergedMapUrl = fileUrl + u".tomergedmap";
952
953
0
    OString version, Notice, FullName, FamilyName, CIDFontName, CIDFontVersion, srcFontType,
954
0
        glyphTag, FontMatrix;
955
0
    OString brokenFontName;
956
0
    FontName = postScriptName.toUtf8();
957
0
    std::map<sal_Int32, OString> glyphIndexToName;
958
959
0
    OUString pfaUrl, pfaCIDUrl;
960
961
0
    rSubSetInfo.aComponents.back().cidFontInfoUrl = cidFontInfoUrl;
962
0
    rSubSetInfo.aComponents.back().toMergedMapUrl = toMergedMapUrl;
963
964
0
    pfaUrl = fileUrl + u".pfa";
965
0
    pfaCIDUrl = pfaUrl + u".cid";
966
967
0
    rSubSetInfo.aComponents.back().pfaCIDUrl = pfaCIDUrl;
968
969
0
    if (!EmbeddedFontsManager::tx_dump(fileUrl, infoUrl))
970
0
    {
971
0
        SAL_WARN("sd.filter", "font file info extraction failed");
972
0
        return false;
973
0
    }
974
0
    SAL_INFO("sd.filter", "dump success");
975
0
    SvFileStream info(infoUrl, StreamMode::READ);
976
0
    OStringBuffer glyphBuffer;
977
0
    OString sLine;
978
0
    while (info.ReadLine(sLine))
979
0
    {
980
0
        if (extractEntry(sLine, "version", version))
981
0
            continue;
982
0
        if (extractEntry(sLine, "cid.CIDFontVersion", CIDFontVersion))
983
0
            continue;
984
0
        if (extractEntry(sLine, "Notice", Notice))
985
0
            continue;
986
0
        if (extractEntry(sLine, "FullName", FullName))
987
0
            continue;
988
0
        if (extractEntry(sLine, "FamilyName", FamilyName))
989
0
            continue;
990
0
        if (extractEntry(sLine, "FSType", FSType))
991
0
            continue;
992
0
        if (extractEntry(sLine, "Weight", Weight))
993
0
            continue;
994
0
        if (extractEntry(sLine, "FontMatrix", FontMatrix))
995
0
            continue;
996
0
        if (extractEntry(sLine, "sup.srcFontType", srcFontType))
997
0
            continue;
998
0
        if (extractEntry(sLine, "## glyph[tag]", glyphTag))
999
0
            continue;
1000
0
        if (extractEntry(sLine, "FontName", FontName))
1001
0
        {
1002
0
            const bool bBrokenFontName = FontName != postScriptName.toUtf8();
1003
0
            if (bBrokenFontName)
1004
0
            {
1005
0
                SAL_WARN("sd.filter", "expected that FontName of <"
1006
0
                                          << FontName << "> matches PostScriptName of <"
1007
0
                                          << postScriptName << ">");
1008
0
                brokenFontName = FontName;
1009
0
            }
1010
0
            continue;
1011
0
        }
1012
0
        if (extractEntry(sLine, "cid.CIDFontName", CIDFontName))
1013
0
        {
1014
0
            SAL_WARN_IF(CIDFontName != postScriptName.toUtf8(), "sd.filter",
1015
0
                        "expected that cid.CIDFontName of <"
1016
0
                            << CIDFontName << "> matches PostScriptName of <" << postScriptName
1017
0
                            << ">");
1018
0
            continue;
1019
0
        }
1020
0
        if (sLine.startsWith("glyph["))
1021
0
        {
1022
0
            sal_Int32 i = 6;
1023
0
            while (i < sLine.getLength())
1024
0
            {
1025
0
                ++i;
1026
0
                if (sLine[i - 1] == ']')
1027
0
                    break;
1028
0
                glyphBuffer.append(sLine[i - 1]);
1029
0
            }
1030
0
            sal_Int32 nGlyphIndex = glyphBuffer.makeStringAndClear().toInt32();
1031
0
            while (i < sLine.getLength())
1032
0
            {
1033
0
                ++i;
1034
0
                if (sLine[i - 1] == '{')
1035
0
                    break;
1036
0
            }
1037
0
            while (i < sLine.getLength())
1038
0
            {
1039
0
                ++i;
1040
0
                if (sLine[i - 1] == ',')
1041
0
                    break;
1042
0
                glyphBuffer.append(sLine[i - 1]);
1043
0
            }
1044
0
            SAL_INFO("sd.filter",
1045
0
                     "setting index: " << nGlyphIndex << " as: " << glyphBuffer.toString());
1046
0
            glyphIndexToName[nGlyphIndex] = glyphBuffer.makeStringAndClear();
1047
0
            while (i < sLine.getLength())
1048
0
            {
1049
0
                ++i;
1050
0
                if (sLine[i - 1] == '}')
1051
0
                    break;
1052
0
                glyphBuffer.append(sLine[i - 1]);
1053
0
            }
1054
0
            SAL_INFO("sd.filter", "code text: " << glyphBuffer.toString());
1055
0
            OString sCode = glyphBuffer.makeStringAndClear();
1056
0
            sal_Int32 nCodePoint
1057
0
                = sCode.startsWith("0x") ? o3tl::toInt32(sCode.subView(2), 16) : sCode.toInt32();
1058
0
            SAL_INFO("sd.filter", "codepoint is: " << nCodePoint);
1059
0
            nameIndexToGlyph[nCodePoint] = nGlyphIndex;
1060
0
        }
1061
0
    }
1062
0
    SAL_INFO("sd.filter", "details are: " << version << Notice << FullName << FamilyName << Weight
1063
0
                                          << srcFontType << FontName << CIDFontName
1064
0
                                          << CIDFontVersion);
1065
1066
0
    if (version.isEmpty())
1067
0
        version = CIDFontVersion;
1068
1069
0
    if (version.isEmpty() || version.toDouble() == 0.0)
1070
0
    {
1071
0
        SAL_WARN("sd.filter", "Font version cannot be empty or 0.0");
1072
0
        version = "0.001"_ostr;
1073
0
    }
1074
1075
0
    if (!brokenFontName.isEmpty())
1076
0
        FontName = postScriptName.toUtf8();
1077
1078
    // Always create cidFontInfo, we will need it if we need to merge fonts
1079
0
    OString AdobeCopyright, Trademark;
1080
0
    sal_Int32 nSplit = !Notice.isEmpty() ? Notice.lastIndexOf('.', Notice.getLength() - 1) : -1;
1081
0
    if (nSplit != -1)
1082
0
    {
1083
0
        AdobeCopyright = Notice.copy(0, nSplit + 1);
1084
0
        Trademark = Notice.copy(nSplit + 1);
1085
0
    }
1086
0
    else
1087
0
        AdobeCopyright = Notice;
1088
0
    SvFileStream cidFontInfo(cidFontInfoUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1089
0
    cidFontInfo.WriteLine(Concat2View("FontName\t(" + FontName + ")"));
1090
0
    cidFontInfo.WriteLine(Concat2View("FullName\t(" + FullName + ")"));
1091
0
    OString OutputFamilyName = FamilyName;
1092
0
    if (!isSimpleFamilyName(Weight))
1093
0
        OutputFamilyName = OutputFamilyName + " " + Weight;
1094
0
    cidFontInfo.WriteLine(Concat2View("FamilyName\t(" + OutputFamilyName + ")"));
1095
0
    cidFontInfo.WriteLine(Concat2View("version\t\t(" + version + ")"));
1096
0
    cidFontInfo.WriteLine("Registry\t(Adobe)");
1097
0
    cidFontInfo.WriteLine("Ordering\t(Identity)");
1098
0
    cidFontInfo.WriteLine("Supplement\t0");
1099
0
    cidFontInfo.WriteLine("XUID\t\t[1 11 9273828]");
1100
0
    cidFontInfo.WriteLine(Concat2View("FSType\t\t" + FSType));
1101
0
    cidFontInfo.WriteLine(Concat2View("AdobeCopyright\t(" + AdobeCopyright + ")"));
1102
0
    cidFontInfo.WriteLine(Concat2View("Trademark\t(" + Trademark + ")"));
1103
0
    cidFontInfo.Close();
1104
1105
    /*
1106
      ## glyph[tag] {cid,iFD}
1107
      or
1108
      ## glyph[tag] {name,encoding}
1109
    */
1110
0
    bNameKeyed = glyphTag == "{name,encoding}";
1111
0
    if (bNameKeyed)
1112
0
    {
1113
0
        SAL_INFO("sd.filter", "convert to cid keyed");
1114
0
        SvFileStream nameToCIDMap(nameToCIDMapUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1115
0
        nameToCIDMap.WriteLine(Concat2View("mergefonts " + FontName + " 0"));
1116
0
        for (const auto& glyph : glyphIndexToName)
1117
0
        {
1118
0
            SAL_INFO("sd.filter", "writing index: " << glyph.first << " as: " << glyph.second);
1119
0
            nameToCIDMap.WriteLine(Concat2View(OString::number(glyph.first) + "\t" + glyph.second));
1120
0
        }
1121
0
        nameToCIDMap.Close();
1122
1123
0
        if (!EmbeddedFontsManager::tx_t1(fileUrl, pfaUrl))
1124
0
        {
1125
0
            SAL_WARN("sd.filter", "pfa conversion failed");
1126
0
            return false;
1127
0
        }
1128
1129
0
        std::vector<std::pair<OUString, OUString>> fonts;
1130
0
        fonts.push_back(std::make_pair(nameToCIDMapUrl, pfaUrl));
1131
0
        if (!EmbeddedFontsManager::mergefonts(cidFontInfoUrl, pfaCIDUrl, fonts))
1132
0
        {
1133
0
            SAL_WARN("sd.filter", "conversion 2 failed");
1134
0
            return false;
1135
0
        }
1136
0
    }
1137
0
    else
1138
0
    {
1139
0
        if (!EmbeddedFontsManager::tx_t1(fileUrl, pfaCIDUrl))
1140
0
        {
1141
0
            SAL_WARN("sd.filter", "pfa conversion failed");
1142
0
            return false;
1143
0
        }
1144
0
    }
1145
1146
0
    if (!brokenFontName.isEmpty() || !FontMatrix.isEmpty())
1147
0
    {
1148
        // If the fontname isn't as expected, or if there is a
1149
        // font matrix present then we rewrite the font.
1150
0
        rewriteFont(brokenFontName, CIDFontName, FontName, pfaCIDUrl);
1151
0
    }
1152
1153
0
    return true;
1154
0
}
1155
1156
static void appendFourByteHex(OStringBuffer& rBuffer, sal_uInt32 nNumber)
1157
0
{
1158
0
    OString sCodePoint = OString::number(nNumber, 16);
1159
0
    for (sal_Int32 j = sCodePoint.getLength(); j < 4; ++j)
1160
0
        rBuffer.append('0');
1161
0
    rBuffer.append(sCodePoint);
1162
0
}
1163
1164
static OString decomposeLegacyUnicodeLigature(UChar32 cUnicode)
1165
0
{
1166
0
    UErrorCode eCode(U_ZERO_ERROR);
1167
1168
    // Put in any legacy unicode ligature decompositions, which we detect as U_DT_COMPAT and
1169
    // a multi-character decomposition.
1170
0
    UDecompositionType decompType = static_cast<UDecompositionType>(
1171
0
        u_getIntPropertyValue(cUnicode, UCHAR_DECOMPOSITION_TYPE));
1172
0
    if (decompType == UDecompositionType::U_DT_COMPAT)
1173
0
    {
1174
0
        const icu::Normalizer2* pInstance = icu::Normalizer2::getNFKDInstance(eCode);
1175
1176
0
        if (pInstance && eCode == U_ZERO_ERROR)
1177
0
        {
1178
0
            icu::UnicodeString sDecomposed;
1179
0
            pInstance->getRawDecomposition(cUnicode, sDecomposed);
1180
0
            if (sDecomposed.length() > 1)
1181
0
            {
1182
0
                OStringBuffer aBuffer;
1183
0
                for (int32_t i = 0, nLen = sDecomposed.length(); i < nLen; ++i)
1184
0
                    appendFourByteHex(aBuffer, sDecomposed[i]);
1185
0
                return aBuffer.makeStringAndClear();
1186
0
            }
1187
0
        }
1188
0
    }
1189
1190
0
    return OString();
1191
0
}
1192
1193
const char cmapprefix[] = "%!PS-Adobe-3.0 Resource-CMap\n"
1194
                          "/WMode 0 def\n"
1195
                          "\n"
1196
                          "/CIDInit /ProcSet findresource begin\n"
1197
                          "12 dict begin\n"
1198
                          "begincmap\n"
1199
                          "/CIDSystemInfo\n"
1200
                          "<< /Registry (Adobe)\n"
1201
                          "   /Ordering (UCS)\n"
1202
                          "   /Supplement 0\n"
1203
                          ">> def\n"
1204
                          "/CMapName /Adobe-Identity-UCS def\n"
1205
                          "/CMapType 2 def\n"
1206
                          "1 begincodespacerange\n"
1207
                          "<0000> <ffff>\n"
1208
                          "endcodespacerange\n";
1209
1210
const char cmapsuffix[] = "endcmap\n"
1211
                          "CMapName currentdict /CMap defineresource pop\n"
1212
                          "end\n"
1213
                          "end\n";
1214
1215
namespace
1216
{
1217
struct ToUnicodeData
1218
{
1219
    std::vector<OString> bfcharlines;
1220
    std::vector<OString> bfcharranges;
1221
1222
    // one or the other of these
1223
    const vcl::pdf::PDFiumFont* pFont;
1224
    const std::map<int, int>* pNameIndexToGlyph;
1225
1226
    // For the pdf provided mapping from the font to unicode
1227
    ToUnicodeData(const std::vector<uint8_t>& toUnicodeData, const vcl::pdf::PDFiumFont& pdfFont)
1228
0
        : pFont(&pdfFont)
1229
0
        , pNameIndexToGlyph(nullptr)
1230
0
    {
1231
0
        SvMemoryStream aInCMap(const_cast<uint8_t*>(toUnicodeData.data()), toUnicodeData.size(),
1232
0
                               StreamMode::READ);
1233
1234
0
        OString sLine;
1235
0
        while (aInCMap.ReadLine(sLine))
1236
0
        {
1237
0
            if (sLine.endsWith("beginbfchar"))
1238
0
            {
1239
0
                while (aInCMap.ReadLine(sLine))
1240
0
                {
1241
0
                    if (sLine.endsWith("endbfchar"))
1242
0
                        break;
1243
0
                    bfcharlines.push_back(sLine);
1244
0
                }
1245
0
            }
1246
0
            else if (sLine.endsWith("beginbfrange"))
1247
0
            {
1248
0
                while (aInCMap.ReadLine(sLine))
1249
0
                {
1250
0
                    if (sLine.endsWith("endbfrange"))
1251
0
                        break;
1252
0
                    bfcharranges.push_back(sLine);
1253
0
                }
1254
0
            }
1255
0
        }
1256
0
    }
1257
1258
    // For name keyed fonts without toUnicode data where we
1259
    // can assume the font encoding is Adobe Standard and
1260
    // reverse map from the name indexes so we can forward
1261
    // map to unicode
1262
    ToUnicodeData(std::map<int, int>& nameIndexToGlyph)
1263
0
        : pFont(nullptr)
1264
0
        , pNameIndexToGlyph(&nameIndexToGlyph)
1265
0
    {
1266
0
        for (const auto & [ adobe, glyphid ] : nameIndexToGlyph)
1267
0
        {
1268
0
            if (glyphid == 0)
1269
0
                continue;
1270
1271
0
            const char cChar(adobe);
1272
0
            OUString sUni(&cChar, 1, RTL_TEXTENCODING_ADOBE_STANDARD);
1273
0
            sal_Unicode mappedChar = sUni.toChar();
1274
1275
0
            OStringBuffer aBuffer("<");
1276
0
            appendFourByteHex(aBuffer, adobe);
1277
0
            aBuffer.append("> <");
1278
0
            appendFourByteHex(aBuffer, mappedChar);
1279
0
            aBuffer.append(">");
1280
0
            bfcharlines.push_back(aBuffer.toString());
1281
0
        }
1282
0
    }
1283
1284
    sal_uInt32 getGlyphIndexFromCharCode(sal_uInt32 nPDFCharCode)
1285
0
    {
1286
0
        if (pFont)
1287
0
            return pFont->getGlyphIndexFromCharCode(nPDFCharCode);
1288
0
        assert(pNameIndexToGlyph);
1289
0
        auto it = pNameIndexToGlyph->find(nPDFCharCode);
1290
0
        return it != pNameIndexToGlyph->end() ? it->second : 0;
1291
0
    }
1292
};
1293
}
1294
1295
static void buildCMapAndFeatures(const OUString& CMapUrl, SvFileStream& Features,
1296
                                 std::string_view FontName, ToUnicodeData& tud,
1297
                                 SubSetInfo& rSubSetInfo)
1298
0
{
1299
0
    SvFileStream CMap(CMapUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1300
1301
0
    CMap.WriteBytes(cmapprefix, std::size(cmapprefix) - 1);
1302
1303
0
    sal_Int32 mergeOffset = 1; //Leave space for notdef
1304
0
    for (const auto& count : rSubSetInfo.aComponents)
1305
0
        mergeOffset += count.nGlyphCount;
1306
1307
0
    std::map<sal_Int32, OString> ligatureGlyphToChars;
1308
0
    std::vector<sal_Int32> glyphs;
1309
1310
0
    if (!tud.bfcharranges.empty())
1311
0
    {
1312
0
        std::vector<OString> cidranges;
1313
1314
0
        for (const auto& charrange : tud.bfcharranges)
1315
0
        {
1316
0
            assert(charrange[0] == '<');
1317
0
            sal_Int32 nEnd = charrange.indexOf('>', 1);
1318
0
            assert(charrange[nEnd] == '>');
1319
0
            sal_Int32 nGlyphRangeStart = o3tl::toInt32(charrange.subView(1, nEnd - 1), 16);
1320
1321
0
            OString remainder(o3tl::trim(charrange.subView(nEnd + 1)));
1322
0
            assert(remainder[0] == '<');
1323
0
            nEnd = remainder.indexOf('>', 1);
1324
0
            assert(remainder[nEnd] == '>');
1325
0
            sal_Int32 nGlyphRangeEnd = o3tl::toInt32(remainder.subView(1, nEnd - 1), 16);
1326
1327
0
            sal_Int32 nGlyphRangeLen = nGlyphRangeEnd - nGlyphRangeStart;
1328
1329
0
            assert(nGlyphRangeLen >= 0);
1330
1331
0
            OString sChars(o3tl::trim(remainder.subView(nEnd + 1)));
1332
0
            assert(sChars[0] == '<' && sChars[sChars.getLength() - 1] == '>');
1333
1334
            // move simple single glyph->char ranges to bfcharlines instead
1335
0
            if (nGlyphRangeLen == 0)
1336
0
            {
1337
0
                OStringBuffer aBuffer("<");
1338
0
                appendFourByteHex(aBuffer, nGlyphRangeStart);
1339
0
                aBuffer.append("> " + sChars);
1340
0
                tud.bfcharlines.push_back(aBuffer.toString());
1341
0
                continue;
1342
0
            }
1343
1344
0
            OString sContents = sChars.copy(1, sChars.getLength() - 2);
1345
            //The assumption that is that cases of ligatures are with a range
1346
            //of a single glyph(?). In which case we have pushed such entries
1347
            //into bfcharlines above.
1348
0
            assert(sContents.getLength() == 4);
1349
0
            sal_Int32 nCharRangeStart = o3tl::toInt32(sContents, 16);
1350
0
            sal_Int32 nCharRangeEnd = nCharRangeStart + nGlyphRangeLen;
1351
1352
            // move simple single glyph->char ranges to bfcharlines instead
1353
0
            if (nCharRangeStart == nCharRangeEnd)
1354
0
            {
1355
0
                OStringBuffer aBuffer("<");
1356
0
                appendFourByteHex(aBuffer, nGlyphRangeStart);
1357
0
                aBuffer.append("> <");
1358
0
                appendFourByteHex(aBuffer, nCharRangeStart);
1359
0
                aBuffer.append(">");
1360
0
                tud.bfcharlines.push_back(aBuffer.toString());
1361
0
                continue;
1362
0
            }
1363
1364
0
            OStringBuffer aBuffer("<");
1365
0
            appendFourByteHex(aBuffer, nCharRangeStart);
1366
0
            aBuffer.append("> <");
1367
0
            appendFourByteHex(aBuffer, nCharRangeEnd);
1368
0
            aBuffer.append("> "_ostr + OString::number(nGlyphRangeStart));
1369
0
            OString cidrangeline = aBuffer.toString();
1370
0
            cidranges.push_back(cidrangeline);
1371
0
            rSubSetInfo.aComponents.back().glyphRangesToChars[nGlyphRangeStart]
1372
0
                = Range(nCharRangeStart, nCharRangeEnd);
1373
0
        }
1374
1375
0
        if (!cidranges.empty())
1376
0
        {
1377
            // searching for real world examples where this occurs
1378
0
            OString beginline = OString::number(cidranges.size()) + " begincidrange";
1379
0
            CMap.WriteLine(beginline);
1380
0
            for (const auto& rLine : cidranges)
1381
0
                CMap.WriteLine(rLine);
1382
0
            CMap.WriteLine("endcidrange");
1383
0
        }
1384
0
    }
1385
1386
0
    if (!tud.bfcharlines.empty())
1387
0
    {
1388
0
        std::set<OString> usedCodes;
1389
0
        std::vector<OString> outputlines;
1390
0
        for (const auto& charline : tud.bfcharlines)
1391
0
        {
1392
0
            assert(charline[0] == '<');
1393
0
            sal_Int32 nEnd = charline.indexOf('>', 1);
1394
0
            assert(charline[nEnd] == '>');
1395
0
            sal_Int32 nPDFCharCode = o3tl::toInt32(charline.subView(1, nEnd - 1), 16);
1396
0
            sal_Int32 nGlyphIndex = tud.getGlyphIndexFromCharCode(nPDFCharCode);
1397
0
            OString sChars(o3tl::trim(charline.subView(nEnd + 1)));
1398
0
            assert(sChars[0] == '<' && sChars[sChars.getLength() - 1] == '>');
1399
0
            OString sContents = sChars.copy(1, sChars.getLength() - 2);
1400
0
            assert(sContents.getLength() % 4 == 0);
1401
0
            sal_Int32 nCharsPerCode = sContents.getLength() / 4;
1402
0
            if (nCharsPerCode > 1)
1403
0
                ligatureGlyphToChars[nGlyphIndex] = sContents;
1404
0
            else
1405
0
            {
1406
0
                OString sLegacy
1407
0
                    = decomposeLegacyUnicodeLigature(static_cast<UChar32>(sContents.toUInt32(16)));
1408
0
                if (!sLegacy.isEmpty())
1409
0
                    ligatureGlyphToChars[nGlyphIndex] = sLegacy;
1410
0
            }
1411
0
            if (usedCodes.insert(sChars).second == false)
1412
0
            {
1413
                // This can happen with e.g. unicode '1' mapped to from two variants of that glyph,
1414
                // in the absense of a better idea, prefer the first one seen
1415
0
                SAL_INFO("sd.filter", "code: " << sChars << " for glyph " << nGlyphIndex
1416
0
                                               << " already used earlier");
1417
0
                continue;
1418
0
            }
1419
0
            OString cidcharline = sChars + " " + OString::number(nGlyphIndex);
1420
0
            glyphs.push_back(nGlyphIndex);
1421
0
            rSubSetInfo.aComponents.back().glyphToChars[nGlyphIndex] = sContents;
1422
0
            rSubSetInfo.aComponents.back().charsToGlyph[sContents] = nGlyphIndex;
1423
0
            outputlines.push_back(cidcharline);
1424
0
        }
1425
1426
0
        OString beginline = OString::number(outputlines.size()) + " begincidchar";
1427
0
        CMap.WriteLine(beginline);
1428
0
        for (const auto& cidcharline : outputlines)
1429
0
            CMap.WriteLine(cidcharline);
1430
0
        CMap.WriteLine("endcidchar");
1431
1432
0
        rSubSetInfo.aComponents.back().nGlyphCount = tud.bfcharlines.size();
1433
0
    }
1434
1435
0
    CMap.WriteBytes(cmapsuffix, std::size(cmapsuffix) - 1);
1436
1437
0
    const OUString& toMergedMapUrl = rSubSetInfo.aComponents.back().toMergedMapUrl;
1438
0
    SvFileStream toMergedMap(toMergedMapUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1439
0
    toMergedMap.WriteLine(Concat2View("mergefonts "_ostr + FontName + " 0"_ostr));
1440
0
    if (rSubSetInfo.aComponents.size() == 1)
1441
0
        toMergedMap.WriteLine("0\t0");
1442
0
    for (size_t i = 0; i < glyphs.size(); ++i)
1443
0
    {
1444
0
        OString sMapLine = OString::number(i + mergeOffset) + "\t" + OString::number(glyphs[i]);
1445
0
        toMergedMap.WriteLine(sMapLine);
1446
0
    }
1447
0
    toMergedMap.Close();
1448
1449
0
    CMap.Close();
1450
1451
0
    if (!ligatureGlyphToChars.empty())
1452
0
    {
1453
0
        std::map<OString, sal_Int32>& charsToGlyph = rSubSetInfo.aComponents.back().charsToGlyph;
1454
1455
0
        Features.WriteLine("languagesystem DFLT dflt;");
1456
0
        Features.WriteLine("feature liga {");
1457
0
        for (const auto& ligature : ligatureGlyphToChars)
1458
0
        {
1459
0
            sal_Int32 nLigatureGlyph = ligature.first;
1460
0
            OString sLigatureChars = ligature.second;
1461
0
            OStringBuffer ligatureLine("substitute");
1462
0
            for (sal_Int32 i = 0; i < sLigatureChars.getLength(); i += 4)
1463
0
            {
1464
0
                OString sLigatureChar = sLigatureChars.copy(i, 4);
1465
0
                sal_Int32 nCharGlyph = charsToGlyph[sLigatureChar];
1466
0
                ligatureLine.append(" \\" + OString::number(nCharGlyph));
1467
0
            }
1468
0
            ligatureLine.append(" by \\" + OString::number(nLigatureGlyph) + ";");
1469
0
            Features.WriteLine(ligatureLine);
1470
0
        }
1471
0
        Features.WriteLine("} liga;");
1472
0
    }
1473
0
}
1474
1475
static OUString buildFontMenuName(const OUString& FontMenuNameDBUrl,
1476
                                  std::u16string_view postScriptName, const OUString& fontName,
1477
                                  std::string_view Weight)
1478
0
{
1479
0
    OUString longFontName = fontName;
1480
1481
    // create FontMenuName
1482
0
    SvFileStream FontMenuNameDB(FontMenuNameDBUrl, StreamMode::READWRITE | StreamMode::TRUNC,
1483
0
                                RTL_TEXTENCODING_UTF8);
1484
0
    OUString postScriptFontName = u"["_ustr + postScriptName + u"]"_ustr;
1485
0
    FontMenuNameDB.WriteByteStringLine(postScriptFontName);
1486
0
    SAL_INFO("sd.filter",
1487
0
             "wrote basefont name: " << postScriptFontName << " to: " << FontMenuNameDBUrl);
1488
0
    OUString setFontName = "f=" + fontName;
1489
0
    FontMenuNameDB.WriteByteStringLine(setFontName);
1490
0
    SAL_INFO("sd.filter", "wrote family name: " << setFontName << " to: " << FontMenuNameDBUrl);
1491
0
    if (!isSimpleFamilyName(Weight))
1492
0
    {
1493
0
        longFontName = fontName + " " + OUString::createFromAscii(Weight);
1494
0
        OUString setLongFontName = "l=" + fontName + " " + OUString::createFromAscii(Weight);
1495
0
        FontMenuNameDB.WriteByteStringLine(setLongFontName);
1496
0
        SAL_INFO("sd.filter",
1497
0
                 "wrote long family name: " << setLongFontName << " to: " << FontMenuNameDBUrl);
1498
0
        OUString styleName = "s=" + OUString::createFromAscii(Weight);
1499
0
        FontMenuNameDB.WriteByteStringLine(styleName);
1500
0
        SAL_INFO("sd.filter", "wrote style name: " << styleName << " to: " << FontMenuNameDBUrl);
1501
0
    }
1502
0
    FontMenuNameDB.Close();
1503
1504
0
    return longFontName;
1505
0
}
1506
1507
// https://adobe-type-tools.github.io/font-tech-notes/pdfs/5900.RFMFAH_Tutorial.pdf
1508
static EmbeddedFontInfo mergeFontSubsets(std::u16string_view prefix, const OUString& mergedFontUrl,
1509
                                         const OUString& FontMenuNameDBUrl,
1510
                                         const OUString& postScriptName,
1511
                                         const OUString& longFontName, std::string_view Weight,
1512
                                         const SubSetInfo& rSubSetInfo)
1513
0
{
1514
0
    SAL_INFO("sd.filter", "merging " << rSubSetInfo.aComponents.size() << " font subsets of "
1515
0
                                     << postScriptName << " together to create: " << mergedFontUrl);
1516
0
    std::vector<std::pair<OUString, OUString>> fonts;
1517
0
    for (size_t i = 0; i < rSubSetInfo.aComponents.size(); ++i)
1518
0
    {
1519
        // Ignore subsets with no glyphs in them, except for the first one
1520
        // which can have notdef in it
1521
0
        if (i && !rSubSetInfo.aComponents[i].nGlyphCount)
1522
0
            continue;
1523
0
        fonts.push_back(std::make_pair(rSubSetInfo.aComponents[i].toMergedMapUrl,
1524
0
                                       rSubSetInfo.aComponents[i].pfaCIDUrl));
1525
0
    }
1526
0
    if (!EmbeddedFontsManager::mergefonts(rSubSetInfo.aComponents[0].cidFontInfoUrl, mergedFontUrl,
1527
0
                                          fonts))
1528
0
    {
1529
0
        SAL_WARN("sd.filter", "conversion failed");
1530
0
        return EmbeddedFontInfo();
1531
0
    }
1532
1533
0
    OUString mergedCMapUrl = mergedFontUrl + u".CMap";
1534
0
    OUString mergedFeaturesUrl = mergedFontUrl + u".Features";
1535
1536
0
    SvFileStream mergedCMap(mergedCMapUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1537
1538
0
    mergedCMap.WriteBytes(cmapprefix, std::size(cmapprefix) - 1);
1539
1540
0
    sal_Int32 cidcharcount(0), cidrangecount(0);
1541
0
    bool differentcidranges(false);
1542
0
    for (const auto& component : rSubSetInfo.aComponents)
1543
0
        cidcharcount += component.glyphToChars.size();
1544
1545
0
    for (size_t i = 0, size = rSubSetInfo.aComponents.size(); i < size; ++i)
1546
0
    {
1547
0
        const auto& component = rSubSetInfo.aComponents[i];
1548
0
        if (i == 0)
1549
0
        {
1550
0
            cidrangecount += component.glyphRangesToChars.size();
1551
0
            continue;
1552
0
        }
1553
1554
0
        if (component.glyphRangesToChars != rSubSetInfo.aComponents[i - 1].glyphRangesToChars)
1555
0
        {
1556
0
            cidrangecount += component.glyphRangesToChars.size();
1557
0
            differentcidranges = true;
1558
0
        }
1559
0
    }
1560
1561
0
    assert(!differentcidranges && "TODO: deal with this when an example arises");
1562
0
    if (differentcidranges)
1563
0
        return EmbeddedFontInfo();
1564
1565
0
    std::map<sal_Int32, OString> ligatureGlyphToChars;
1566
0
    std::map<OString, sal_Int32> charsToGlyph;
1567
1568
0
    if (cidrangecount)
1569
0
    {
1570
0
        OString beginline = OString::number(cidrangecount) + " begincidrange";
1571
0
        mergedCMap.WriteLine(beginline);
1572
1573
0
        const auto& component = rSubSetInfo.aComponents[0];
1574
0
        for (const auto& entry : component.glyphRangesToChars)
1575
0
        {
1576
0
            sal_Int32 glyphrangebegin = entry.first;
1577
0
            sal_Int32 charrangebegin = entry.second.Min();
1578
0
            sal_Int32 charrangeend = entry.second.Max();
1579
1580
0
            OStringBuffer aBuffer("<");
1581
0
            appendFourByteHex(aBuffer, charrangebegin);
1582
0
            aBuffer.append("> <");
1583
0
            appendFourByteHex(aBuffer, charrangeend);
1584
0
            aBuffer.append("> "_ostr + OString::number(glyphrangebegin));
1585
0
            OString cidrangeline = aBuffer.toString();
1586
1587
0
            mergedCMap.WriteLine(cidrangeline);
1588
0
        }
1589
1590
        // glyphoffset += component.nGlyphCount;
1591
0
        mergedCMap.WriteLine("endcidrange");
1592
0
    }
1593
1594
0
    if (cidcharcount)
1595
0
    {
1596
0
        sal_Int32 glyphoffset(0);
1597
0
        OString beginline = OString::number(cidcharcount) + " begincidchar";
1598
0
        mergedCMap.WriteLine(beginline);
1599
1600
0
        for (const auto& component : rSubSetInfo.aComponents)
1601
0
        {
1602
0
            for (const auto& entry : component.glyphToChars)
1603
0
            {
1604
0
                sal_Int32 glyph = entry.first + glyphoffset;
1605
0
                OString sCharContents = entry.second;
1606
0
                OString cidcharline = "<" + sCharContents + "> " + OString::number(glyph);
1607
1608
0
                sal_Int32 nCharsPerCode = sCharContents.getLength() / 4;
1609
0
                if (nCharsPerCode > 1)
1610
0
                    ligatureGlyphToChars[glyph] = sCharContents;
1611
0
                else
1612
0
                {
1613
0
                    OString sLegacy = decomposeLegacyUnicodeLigature(
1614
0
                        static_cast<UChar32>(sCharContents.toUInt32(16)));
1615
0
                    if (!sLegacy.isEmpty())
1616
0
                        ligatureGlyphToChars[glyph] = sLegacy;
1617
0
                }
1618
1619
0
                charsToGlyph[entry.second] = glyph;
1620
0
                mergedCMap.WriteLine(cidcharline);
1621
0
            }
1622
0
            glyphoffset += component.nGlyphCount;
1623
0
        }
1624
0
        mergedCMap.WriteLine("endcidchar");
1625
0
    }
1626
1627
0
    mergedCMap.WriteBytes(cmapsuffix, std::size(cmapsuffix) - 1);
1628
1629
0
    mergedCMap.Close();
1630
1631
0
    if (!ligatureGlyphToChars.empty())
1632
0
    {
1633
0
        SvFileStream Features(mergedFeaturesUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1634
0
        Features.WriteLine("languagesystem DFLT dflt;");
1635
0
        Features.WriteLine("feature liga {");
1636
0
        for (const auto& ligature : ligatureGlyphToChars)
1637
0
        {
1638
0
            sal_Int32 nLigatureGlyph = ligature.first;
1639
0
            OString sLigatureChars = ligature.second;
1640
0
            OStringBuffer ligatureLine("substitute");
1641
0
            for (sal_Int32 i = 0; i < sLigatureChars.getLength(); i += 4)
1642
0
            {
1643
0
                OString sLigatureChar = sLigatureChars.copy(i, 4);
1644
0
                sal_Int32 nCharGlyph = charsToGlyph[sLigatureChar];
1645
0
                ligatureLine.append(" \\" + OString::number(nCharGlyph));
1646
0
            }
1647
0
            ligatureLine.append(" by \\" + OString::number(nLigatureGlyph) + ";");
1648
0
            Features.WriteLine(ligatureLine);
1649
0
        }
1650
0
        Features.WriteLine("} liga;");
1651
0
        Features.Close();
1652
0
    }
1653
1654
0
    OUString otfUrl = getFileUrlForTemporaryFont(prefix, postScriptName, u".otf");
1655
0
    OUString features = !ligatureGlyphToChars.empty() ? mergedFeaturesUrl : OUString();
1656
0
    if (EmbeddedFontsManager::makeotf(mergedFontUrl, otfUrl, FontMenuNameDBUrl, mergedCMapUrl,
1657
0
                                      features))
1658
0
        return { longFontName, otfUrl, toOfficeWeight(Weight) };
1659
0
    SAL_WARN("sd.filter", "conversion failed");
1660
0
    return EmbeddedFontInfo();
1661
0
}
1662
1663
//static
1664
EmbeddedFontInfo ImpSdrPdfImport::convertToOTF(std::u16string_view prefix, SubSetInfo& rSubSetInfo,
1665
                                               const OUString& fileUrl, const OUString& fontName,
1666
                                               const OUString& postScriptName,
1667
                                               std::u16string_view fontFileName,
1668
                                               const std::vector<uint8_t>& toUnicodeData,
1669
                                               const vcl::pdf::PDFiumFont& font)
1670
0
{
1671
    // Convert to Type 1 CID keyed
1672
0
    std::map<int, int> nameIndexToGlyph;
1673
0
    bool bNameKeyed = false;
1674
0
    OString FontName, Weight, FSType;
1675
0
    if (!toPfaCID(rSubSetInfo, fileUrl, postScriptName, bNameKeyed, nameIndexToGlyph, FontName,
1676
0
                  Weight, FSType))
1677
0
    {
1678
0
        return EmbeddedFontInfo();
1679
0
    }
1680
1681
0
    OUString FeaturesUrl = fileUrl + u".Features";
1682
0
    SvFileStream Features(FeaturesUrl, StreamMode::READWRITE | StreamMode::TRUNC);
1683
1684
0
    const OUString& pfaCIDUrl = rSubSetInfo.aComponents.back().pfaCIDUrl;
1685
1686
    // Build CMap from pdfium toUnicodeData, etc.
1687
0
    OUString CMapUrl = fileUrl + u".CMap";
1688
0
    bool bCMap = true;
1689
0
    if (!toUnicodeData.empty())
1690
0
    {
1691
0
        ToUnicodeData tud(toUnicodeData, font);
1692
0
        buildCMapAndFeatures(CMapUrl, Features, FontName, tud, rSubSetInfo);
1693
0
    }
1694
0
    else if (bNameKeyed)
1695
0
    {
1696
0
        SAL_INFO("sd.filter", "There is no CMap, assuming Adobe Standard encoding.");
1697
0
        ToUnicodeData tud(nameIndexToGlyph);
1698
0
        buildCMapAndFeatures(CMapUrl, Features, FontName, tud, rSubSetInfo);
1699
0
    }
1700
0
    else
1701
0
    {
1702
0
        SAL_WARN("sd.filter", "There is no CMap, pdfium is missing unicodedata");
1703
0
        bCMap = false;
1704
0
    }
1705
1706
0
    Features.WriteLine("table OS/2 {");
1707
0
    Features.WriteLine(Concat2View("  FSType " + FSType + ";"));
1708
0
    Features.WriteLine("} OS/2;");
1709
1710
0
    Features.Close();
1711
1712
    // Create FontMenuName
1713
0
    OUString FontMenuNameDBUrl = fileUrl + u".FontMenuNameDBUrl";
1714
0
    OUString longFontName = buildFontMenuName(FontMenuNameDBUrl, postScriptName, fontName, Weight);
1715
1716
    // Merge multiple font subsets together
1717
0
    if (rSubSetInfo.aComponents.size() > 1)
1718
0
    {
1719
0
        OUString mergedFontUrl
1720
0
            = getFileUrlForTemporaryFont(prefix, postScriptName, u".merged.pfa.cid");
1721
0
        return mergeFontSubsets(prefix, mergedFontUrl, FontMenuNameDBUrl, postScriptName,
1722
0
                                longFontName, Weight, rSubSetInfo);
1723
0
    }
1724
1725
    // Otherwise not merged font, just a single subset
1726
0
    OUString otfUrl = getFileUrlForTemporaryFont(prefix, fontFileName, u".otf");
1727
0
    OUString cmap = bCMap ? CMapUrl : OUString();
1728
0
    if (EmbeddedFontsManager::makeotf(pfaCIDUrl, otfUrl, FontMenuNameDBUrl, cmap, FeaturesUrl))
1729
0
        return { longFontName, otfUrl, toOfficeWeight(Weight) };
1730
0
    SAL_WARN("sd.filter", "conversion failed");
1731
0
    return EmbeddedFontInfo();
1732
0
}
1733
1734
// There isn't, as far as I know, a way to stroke with a pattern at the moment,
1735
// so extract some sensible color if this is a stroke pattern
1736
Color ImpSdrPdfImport::getStrokeColor(
1737
    std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
1738
    std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage)
1739
705
{
1740
705
    if (std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap
1741
705
        = pPageObject->getRenderedStrokePattern(*mpPdfDocument, *pPage))
1742
0
    {
1743
0
        Bitmap aBitmap(bitmap->createBitmapFromBuffer());
1744
0
        if (!aBitmap.IsEmpty())
1745
0
            return aBitmap.GetPixelColor(aBitmap.GetSizePixel().Width() / 2,
1746
0
                                         aBitmap.GetSizePixel().Height() / 2);
1747
0
    }
1748
705
    return pPageObject->getStrokeColor();
1749
705
}
1750
1751
// Typically for a fill pattern you want to use some pattern fill equivalent
1752
// but if that's not possible then this fallback can be useful
1753
Color ImpSdrPdfImport::getFillColor(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
1754
                                    std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage)
1755
3.19k
{
1756
3.19k
    if (std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap
1757
3.19k
        = pPageObject->getRenderedFillPattern(*mpPdfDocument, *pPage))
1758
15
    {
1759
15
        Bitmap aBitmap(bitmap->createBitmapFromBuffer());
1760
15
        if (!aBitmap.IsEmpty())
1761
15
            return aBitmap.GetPixelColor(aBitmap.GetSizePixel().Width() / 2,
1762
15
                                         aBitmap.GetSizePixel().Height() / 2);
1763
15
    }
1764
3.17k
    return pPageObject->getFillColor();
1765
3.19k
}
1766
1767
static bool AllowDim(tools::Long nDim)
1768
13.2k
{
1769
13.2k
    if (nDim > 0x20000000 || nDim < -0x20000000)
1770
334
    {
1771
334
        SAL_WARN("sd.filter", "skipping huge dimension: " << nDim);
1772
334
        return false;
1773
334
    }
1774
12.9k
    return true;
1775
13.2k
}
1776
1777
6.72k
static bool AllowPoint(const Point& rPoint) { return AllowDim(rPoint.X()) && AllowDim(rPoint.Y()); }
1778
1779
static bool AllowRect(const tools::Rectangle& rRect)
1780
3.52k
{
1781
3.52k
    return AllowPoint(rRect.TopLeft()) && AllowPoint(rRect.BottomRight());
1782
3.52k
}
1783
1784
void ImpSdrPdfImport::ImportText(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
1785
                                 std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage,
1786
                                 std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage,
1787
                                 int /*nPageObjectIndex*/)
1788
3.52k
{
1789
3.52k
    basegfx::B2DRectangle aTextRect = pPageObject->getBounds();
1790
3.52k
    basegfx::B2DHomMatrix aMatrix = pPageObject->getMatrix();
1791
1792
3.52k
    basegfx::B2DHomMatrix aTextMatrix(maCurrentMatrix);
1793
1794
3.52k
    aTextRect *= aTextMatrix;
1795
1796
3.52k
    if (!std::isfinite(aTextRect.getMinX()) || !std::isfinite(aTextRect.getMaxX())
1797
3.52k
        || !std::isfinite(aTextRect.getMinY()) || !std::isfinite(aTextRect.getMaxY()))
1798
0
    {
1799
0
        SAL_WARN("sd.filter", "unusable text rectangle: " << aTextRect);
1800
0
        return;
1801
0
    }
1802
1803
3.52k
    const tools::Rectangle aRect = PointsToLogic(aTextRect.getMinX(), aTextRect.getMaxX(),
1804
3.52k
                                                 aTextRect.getMinY(), aTextRect.getMaxY());
1805
3.52k
    if (!AllowRect(aRect))
1806
334
        return;
1807
1808
3.19k
    OUString sText = pPageObject->getText(pTextPage);
1809
1810
3.19k
    OUString sFontName;
1811
3.19k
    FontWeight eFontWeight(WEIGHT_DONTKNOW);
1812
3.19k
    auto xFont = pPageObject->getFont();
1813
3.19k
    auto itImportedFont
1814
3.19k
        = xFont ? mxImportedFonts->find(xFont->getFontDictObjNum()) : mxImportedFonts->end();
1815
3.19k
    if (itImportedFont != mxImportedFonts->end())
1816
2.41k
    {
1817
        // We expand a name like "Foo" with non-traditional styles like
1818
        // "SemiBold" to "Foo SemiBold";
1819
2.41k
        sFontName = itImportedFont->sFontName;
1820
2.41k
        eFontWeight = itImportedFont->eFontWeight;
1821
2.41k
    }
1822
780
    else
1823
780
    {
1824
780
        sFontName = pPageObject->getFontName();
1825
780
        SAL_WARN("sd.filter", "font: " << sFontName << " wasn't collected");
1826
780
    }
1827
1828
3.19k
    const double dFontSize = pPageObject->getFontSize();
1829
3.19k
    double dFontSizeH = fabs(std::hypot(aMatrix.a(), aMatrix.c()) * dFontSize);
1830
3.19k
    double dFontSizeV = fabs(std::hypot(aMatrix.b(), aMatrix.d()) * dFontSize);
1831
1832
    // We will only really be able to squeeze a font size in whole units of
1833
    // twips through the various layers esp. export and reimport, so work in
1834
    // twips here and LogicToLogic so we don't end up using a value that cannot
1835
    // be roundtripped back.
1836
3.19k
    const Size aFontSizeTwips(dFontSizeH * 20, dFontSizeV * 20);
1837
3.19k
    const Size aFontSize(OutputDevice::LogicToLogic(aFontSizeTwips, MapMode(MapUnit::MapTwip),
1838
3.19k
                                                    MapMode(MapUnit::Map100thMM)));
1839
3.19k
    vcl::Font aFnt = mpVD->GetFont();
1840
3.19k
    aFnt.SetFontSize(aFontSize);
1841
1842
3.19k
    if (!sFontName.isEmpty())
1843
3.18k
        aFnt.SetFamilyName(sFontName);
1844
1845
3.19k
    int italicAngle = pPageObject->getFontAngle();
1846
3.19k
    {
1847
        // Decompose matrix to inspect shear
1848
3.19k
        basegfx::B2DVector aScale, aTranslate;
1849
3.19k
        double fRotate, fShearX;
1850
3.19k
        aMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
1851
3.19k
        int nTextRotation = basegfx::fround(basegfx::rad2deg<1>(atan(fShearX)));
1852
        // Add this additional shear to the reported italic angle, where
1853
        // rotation to the right is negative
1854
3.19k
        italicAngle += -nTextRotation;
1855
3.19k
    }
1856
3.19k
    aFnt.SetItalic(italicAngle == 0 ? ITALIC_NONE
1857
3.19k
                                    : (italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE));
1858
3.19k
    aFnt.SetWeight(eFontWeight);
1859
1860
3.19k
    if (aFnt != mpVD->GetFont())
1861
1.11k
    {
1862
1.11k
        mpVD->SetFont(aFnt);
1863
1.11k
        mbFntDirty = true;
1864
1.11k
    }
1865
1866
3.19k
    Color aTextColor(COL_TRANSPARENT);
1867
3.19k
    bool bFill = false;
1868
3.19k
    bool bUse = true;
1869
3.19k
    bool bInvisible = false;
1870
3.19k
    switch (pPageObject->getTextRenderMode())
1871
3.19k
    {
1872
3.19k
        case vcl::pdf::PDFTextRenderMode::Fill:
1873
3.19k
        case vcl::pdf::PDFTextRenderMode::FillClip:
1874
3.19k
        case vcl::pdf::PDFTextRenderMode::FillStroke:
1875
3.19k
        case vcl::pdf::PDFTextRenderMode::FillStrokeClip:
1876
3.19k
            bFill = true;
1877
3.19k
            break;
1878
0
        case vcl::pdf::PDFTextRenderMode::Stroke:
1879
0
        case vcl::pdf::PDFTextRenderMode::StrokeClip:
1880
0
        case vcl::pdf::PDFTextRenderMode::Unknown:
1881
0
            break;
1882
0
        case vcl::pdf::PDFTextRenderMode::Invisible:
1883
0
            bInvisible = true;
1884
0
            bUse = false;
1885
0
            break;
1886
0
        case vcl::pdf::PDFTextRenderMode::Clip:
1887
0
            bUse = false;
1888
0
            break;
1889
3.19k
    }
1890
3.19k
    if (bUse)
1891
3.19k
    {
1892
3.19k
        Color aColor
1893
3.19k
            = bFill ? getFillColor(pPageObject, pPage) : getStrokeColor(pPageObject, pPage);
1894
3.19k
        if (aColor != COL_TRANSPARENT)
1895
3.19k
            aTextColor = aColor.GetRGBColor();
1896
3.19k
    }
1897
1898
3.19k
    if (aTextColor != mpVD->GetTextColor())
1899
50
    {
1900
50
        mpVD->SetTextColor(aTextColor);
1901
50
        mbFntDirty = true;
1902
50
    }
1903
1904
3.19k
    InsertTextObject(aRect.TopLeft(), aRect.GetSize(), sText, bInvisible);
1905
3.19k
}
1906
1907
void ImpSdrPdfImport::InsertTextObject(const Point& rPos, const Size& rSize, const OUString& rStr,
1908
                                       bool bInvisible)
1909
3.19k
{
1910
3.19k
    FontMetric aFontMetric(mpVD->GetFontMetric());
1911
3.19k
    vcl::Font aFont(mpVD->GetFont());
1912
3.19k
    assert(aFont.GetAlignment() == ALIGN_BASELINE);
1913
1914
    /* Get our text bounds of this text, which is nominally relative to a 0
1915
       left margin, so we know where the inked rect begins relative to the
1916
       start of the text area, and we can adjust the pdf rectangle by that so
1917
       the SdrRectObj text will get rendered at the same x position.
1918
1919
       Similarly find the relative vertical distance from the inked bounds
1920
       to the baseline and adjust the pdf rect so the SdrRectObj will render
1921
       the text at the same y position.
1922
    */
1923
3.19k
    tools::Rectangle aOurRect;
1924
3.19k
    (void)mpVD->GetTextBoundRect(aOurRect, rStr);
1925
1926
3.19k
    auto nXDiff = aOurRect.Left();
1927
3.19k
    auto nYDiff = aFontMetric.GetDescent() - aOurRect.Bottom();
1928
1929
3.19k
    Point aPos(rPos.X() - nXDiff, rPos.Y() + nYDiff);
1930
1931
    // Natural advance width of the string in the resolved font with no
1932
    // extra character spacing.
1933
3.19k
    const tools::Long nVclTextWidth = mpVD->GetTextWidth(rStr);
1934
1935
    // To detect PDF character spacing, compare inked widths on both
1936
    // sides. FPDFPageObj_GetBounds (and therefore rSize) reports the
1937
    // rendered ink bounds, which exclude the left bearing of the first
1938
    // glyph and the right bearing of the last glyph. Compare that
1939
    // against VCL's ink bounds from GetTextBoundRect, not against the
1940
    // advance width, or the bearings show up as spurious spacing and
1941
    // produce excessive kerning on short runs like "ld".
1942
3.19k
    const tools::Long nVclInkedWidth = aOurRect.GetWidth();
1943
3.19k
    const tools::Long nPdfInkedWidth = rSize.Width();
1944
1945
3.19k
    short nExtraKerning = 0;
1946
3.19k
    const sal_Int32 nChars = rStr.getLength();
1947
3.19k
    if (nChars > 1 && nVclInkedWidth > 0 && nPdfInkedWidth > 0)
1948
1.81k
    {
1949
1.81k
        const tools::Long nKern = (nPdfInkedWidth - nVclInkedWidth) / (nChars - 1);
1950
1.81k
        nExtraKerning
1951
1.81k
            = static_cast<short>(std::clamp<tools::Long>(nKern, SAL_MIN_INT16, SAL_MAX_INT16));
1952
1.81k
    }
1953
1954
    // As per ImpEditEngine::CalcParaWidth the width of the text box has to be 1 unit wider than the text
1955
3.19k
    const tools::Long nTextWidth
1956
3.19k
        = nVclTextWidth + static_cast<tools::Long>(nExtraKerning) * (nChars - 1) + 1;
1957
3.19k
    Size aSize(nTextWidth, -aFontMetric.GetLineHeight());
1958
1959
3.19k
    Point aSdrPos(basegfx::fround<tools::Long>(aPos.X() * mfScaleX + maOfs.X()),
1960
3.19k
                  basegfx::fround<tools::Long>(aPos.Y() * mfScaleY + maOfs.Y()));
1961
3.19k
    Size aSdrSize(basegfx::fround<tools::Long>(aSize.Width() * mfScaleX),
1962
3.19k
                  basegfx::fround<tools::Long>(aSize.Height() * mfScaleY));
1963
1964
3.19k
    tools::Rectangle aTextRect(aSdrPos, aSdrSize);
1965
3.19k
    rtl::Reference<SdrRectObj> pText = new SdrRectObj(*mpModel, aTextRect, SdrObjKind::Text);
1966
1967
3.19k
    pText->SetMergedItem(makeSdrTextUpperDistItem(0));
1968
3.19k
    pText->SetMergedItem(makeSdrTextLowerDistItem(0));
1969
3.19k
    pText->SetMergedItem(makeSdrTextRightDistItem(0));
1970
3.19k
    pText->SetMergedItem(makeSdrTextLeftDistItem(0));
1971
1972
3.19k
    pText->SetMergedItem(makeSdrTextAutoGrowHeightItem(false));
1973
1974
3.19k
    if (bInvisible)
1975
0
        pText->SetVisible(false);
1976
1977
3.19k
    pText->SetLayer(mnLayer);
1978
3.19k
    pText->NbcSetText(rStr);
1979
3.19k
    SetAttributes(pText.get(), true);
1980
3.19k
    if (nExtraKerning != 0)
1981
1.67k
    {
1982
1.67k
        const tools::Long nKernSdr = basegfx::fround<tools::Long>(nExtraKerning * mfScaleX);
1983
1.67k
        pText->SetMergedItem(SvxKerningItem(
1984
1.67k
            static_cast<short>(std::clamp<tools::Long>(nKernSdr, SAL_MIN_INT16, SAL_MAX_INT16)),
1985
1.67k
            EE_CHAR_KERNING));
1986
1.67k
    }
1987
3.19k
    pText->SetSnapRect(aTextRect);
1988
1989
3.19k
    if (!aFont.IsTransparent())
1990
0
    {
1991
0
        SfxItemSet aAttr(SfxItemSet::makeFixedSfxItemSet<XATTR_FILL_FIRST, XATTR_FILL_LAST>(
1992
0
            *mpFillAttr->GetPool()));
1993
0
        aAttr.Put(XFillStyleItem(drawing::FillStyle_SOLID));
1994
0
        aAttr.Put(XFillColorItem(OUString(), aFont.GetFillColor()));
1995
0
        pText->SetMergedItemSet(aAttr);
1996
0
    }
1997
3.19k
    Degree100 nAngle = to<Degree100>(aFont.GetOrientation());
1998
3.19k
    if (nAngle)
1999
0
        pText->SdrAttrObj::NbcRotate(aSdrPos, nAngle);
2000
3.19k
    InsertObj(pText.get(), false);
2001
3.19k
}
2002
2003
void ImpSdrPdfImport::MapScaling()
2004
110
{
2005
110
    const size_t nCount(maTmpList.size());
2006
110
    const MapMode& rMap = mpVD->GetMapMode();
2007
110
    Point aMapOrg(rMap.GetOrigin());
2008
110
    bool bMov2(aMapOrg.X() != 0 || aMapOrg.Y() != 0);
2009
2010
110
    if (bMov2)
2011
0
    {
2012
0
        for (size_t i = mnMapScalingOfs; i < nCount; i++)
2013
0
        {
2014
0
            SdrObject* pObj = maTmpList[i].get();
2015
2016
0
            pObj->NbcMove(Size(aMapOrg.X(), aMapOrg.Y()));
2017
0
        }
2018
0
    }
2019
2020
110
    mnMapScalingOfs = nCount;
2021
110
}
2022
2023
basegfx::B2DPolyPolygon
2024
ImpSdrPdfImport::GetClip(const std::unique_ptr<vcl::pdf::PDFiumClipPath>& pClipPath,
2025
                         const basegfx::B2DHomMatrix& rPathMatrix,
2026
                         const basegfx::B2DHomMatrix& rTransform)
2027
431
{
2028
431
    basegfx::B2DPolyPolygon aClipPolyPoly;
2029
2030
431
    int nClipPathCount = pClipPath->getPathCount();
2031
832
    for (int i = 0; i < nClipPathCount; ++i)
2032
401
    {
2033
401
        std::vector<std::unique_ptr<vcl::pdf::PDFiumPathSegment>> aPathSegments;
2034
401
        const int nSegments = pClipPath->getPathSegmentCount(i);
2035
2.40k
        for (int nSegmentIndex = 0; nSegmentIndex < nSegments; ++nSegmentIndex)
2036
2.00k
            aPathSegments.push_back(pClipPath->getPathSegment(i, nSegmentIndex));
2037
2038
401
        appendSegmentsToPolyPoly(aClipPolyPoly, aPathSegments, rPathMatrix);
2039
401
    }
2040
2041
431
    aClipPolyPoly.transform(rTransform);
2042
2043
431
    return aClipPolyPoly;
2044
431
}
2045
2046
void ImpSdrPdfImport::ImportImage(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
2047
                                  int /*nPageObjectIndex*/)
2048
476
{
2049
476
    std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap = pPageObject->getImageBitmap();
2050
476
    if (!bitmap)
2051
45
    {
2052
45
        SAL_WARN("sd.filter", "Failed to get IMAGE");
2053
45
        return;
2054
45
    }
2055
2056
431
    const vcl::pdf::PDFBitmapType format = bitmap->getFormat();
2057
431
    if (format == vcl::pdf::PDFBitmapType::Unknown)
2058
0
    {
2059
0
        SAL_WARN("sd.filter", "Failed to get IMAGE format");
2060
0
        return;
2061
0
    }
2062
2063
431
    basegfx::B2DRectangle aBounds = pPageObject->getBounds();
2064
431
    float left = aBounds.getMinX();
2065
    // Upside down.
2066
431
    float bottom = aBounds.getMinY();
2067
431
    float right = aBounds.getMaxX();
2068
    // Upside down.
2069
431
    float top = aBounds.getMaxY();
2070
2071
431
    bool bEntirelyClippedOut = false;
2072
2073
431
    auto aPathMatrix = pPageObject->getMatrix();
2074
2075
431
    aPathMatrix *= maCurrentMatrix;
2076
2077
431
    const basegfx::B2DHomMatrix aTransform(
2078
431
        basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y()));
2079
2080
431
    basegfx::B2DPolyPolygon aClipPolyPoly
2081
431
        = GetClip(pPageObject->getClipPath(), aPathMatrix, aTransform);
2082
2083
431
    if (aClipPolyPoly.count())
2084
401
    {
2085
        // Clip against ClipRegion, use same conversions and transformation on
2086
        // the graphic bounds as were applied to the clipping polypolygon
2087
401
        const tools::Rectangle aRect = PointsToLogic(aBounds.getMinX(), aBounds.getMaxX(),
2088
401
                                                     aBounds.getMinY(), aBounds.getMaxY());
2089
401
        basegfx::B2DPolyPolygon aGraphicBounds(
2090
401
            basegfx::utils::createPolygonFromRect(vcl::unotools::b2DRectangleFromRectangle(aRect)));
2091
401
        aGraphicBounds.transform(aTransform);
2092
401
        const basegfx::B2DPolyPolygon aClippedBounds(basegfx::utils::clipPolyPolygonOnPolyPolygon(
2093
401
            aGraphicBounds, aClipPolyPoly, true, false));
2094
        // completely clipped out
2095
401
        bEntirelyClippedOut = aClippedBounds.getB2DRange().isEmpty();
2096
401
    }
2097
2098
431
    tools::Rectangle aRect = PointsToLogic(left, right, top, bottom);
2099
431
    aRect.AdjustRight(1);
2100
431
    aRect.AdjustBottom(1);
2101
2102
431
    Bitmap aBitmap = bitmap->createBitmapFromBuffer();
2103
431
    rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj(*mpModel, Graphic(aBitmap), aRect);
2104
2105
    // This action is not creating line and fill, set directly, do not use SetAttributes(..)
2106
431
    pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE));
2107
431
    pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE));
2108
2109
431
    if (bEntirelyClippedOut)
2110
355
        pGraf->SetVisible(false);
2111
2112
431
    InsertObj(pGraf.get());
2113
431
}
2114
2115
void ImpSdrPdfImport::appendSegmentsToPolyPoly(
2116
    basegfx::B2DPolyPolygon& rPolyPoly,
2117
    const std::vector<std::unique_ptr<vcl::pdf::PDFiumPathSegment>>& rPathSegments,
2118
    const basegfx::B2DHomMatrix& rPathMatrix)
2119
3.74k
{
2120
3.74k
    basegfx::B2DPolygon aPoly;
2121
3.74k
    std::vector<basegfx::B2DPoint> aBezier;
2122
2123
3.74k
    for (const auto& pPathSegment : rPathSegments)
2124
18.9k
    {
2125
18.9k
        if (pPathSegment != nullptr)
2126
18.9k
        {
2127
18.9k
            basegfx::B2DPoint aB2DPoint = pPathSegment->getPoint();
2128
18.9k
            aB2DPoint *= rPathMatrix;
2129
2130
18.9k
            const bool bClose = pPathSegment->isClosed();
2131
18.9k
            if (bClose)
2132
3.41k
                aPoly.setClosed(bClose); // TODO: Review
2133
2134
18.9k
            Point aPoint = PointsToLogic(aB2DPoint.getX(), aB2DPoint.getY());
2135
18.9k
            aB2DPoint.setX(aPoint.X());
2136
18.9k
            aB2DPoint.setY(aPoint.Y());
2137
2138
18.9k
            const vcl::pdf::PDFSegmentType eSegmentType = pPathSegment->getType();
2139
18.9k
            switch (eSegmentType)
2140
18.9k
            {
2141
14.1k
                case vcl::pdf::PDFSegmentType::Lineto:
2142
14.1k
                    aPoly.append(aB2DPoint);
2143
14.1k
                    break;
2144
2145
870
                case vcl::pdf::PDFSegmentType::Bezierto:
2146
870
                    aBezier.emplace_back(aB2DPoint.getX(), aB2DPoint.getY());
2147
870
                    if (aBezier.size() == 3)
2148
290
                    {
2149
290
                        aPoly.appendBezierSegment(aBezier[0], aBezier[1], aBezier[2]);
2150
290
                        aBezier.clear();
2151
290
                    }
2152
870
                    break;
2153
2154
4.00k
                case vcl::pdf::PDFSegmentType::Moveto:
2155
                    // New Poly.
2156
4.00k
                    if (aPoly.count() > 0)
2157
251
                    {
2158
251
                        rPolyPoly.append(aPoly, 1);
2159
251
                        aPoly.clear();
2160
251
                    }
2161
2162
4.00k
                    aPoly.append(aB2DPoint);
2163
4.00k
                    break;
2164
2165
0
                case vcl::pdf::PDFSegmentType::Unknown:
2166
0
                default:
2167
0
                    SAL_WARN("sd.filter", "Unknown path segment type in PDF: "
2168
0
                                              << static_cast<int>(eSegmentType));
2169
0
                    break;
2170
18.9k
            }
2171
18.9k
        }
2172
18.9k
    }
2173
2174
3.74k
    if (aBezier.size() == 3)
2175
0
    {
2176
0
        aPoly.appendBezierSegment(aBezier[0], aBezier[1], aBezier[2]);
2177
0
        aBezier.clear();
2178
0
    }
2179
2180
3.74k
    if (aPoly.count() > 0)
2181
3.74k
    {
2182
3.74k
        rPolyPoly.append(aPoly, 1);
2183
3.74k
        aPoly.clear();
2184
3.74k
    }
2185
3.74k
}
2186
2187
void ImpSdrPdfImport::ImportPath(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject,
2188
                                 std::unique_ptr<vcl::pdf::PDFiumPage> const& pPage,
2189
                                 int /*nPageObjectIndex*/)
2190
3.34k
{
2191
3.34k
    auto aPathMatrix = pPageObject->getMatrix();
2192
2193
3.34k
    aPathMatrix *= maCurrentMatrix;
2194
2195
3.34k
    basegfx::B2DPolyPolygon aPolyPoly;
2196
2197
3.34k
    std::vector<std::unique_ptr<vcl::pdf::PDFiumPathSegment>> aPathSegments;
2198
3.34k
    const int nSegments = pPageObject->getPathSegmentCount();
2199
20.3k
    for (int nSegmentIndex = 0; nSegmentIndex < nSegments; ++nSegmentIndex)
2200
16.9k
    {
2201
16.9k
        aPathSegments.push_back(pPageObject->getPathSegment(nSegmentIndex));
2202
16.9k
    }
2203
2204
3.34k
    appendSegmentsToPolyPoly(aPolyPoly, aPathSegments, aPathMatrix);
2205
2206
3.34k
    const basegfx::B2DHomMatrix aTransform(
2207
3.34k
        basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y()));
2208
3.34k
    aPolyPoly.transform(aTransform);
2209
2210
3.34k
    float fWidth = pPageObject->getStrokeWidth();
2211
3.34k
    const double dWidth = 0.5 * fabs(std::hypot(aPathMatrix.a(), aPathMatrix.c()) * fWidth);
2212
3.34k
    const double fLineWidth = convertPointToMm100(dWidth);
2213
3.34k
    if (std::isnan(fLineWidth) || fLineWidth < 0
2214
3.34k
        || fLineWidth > std::numeric_limits<sal_Int32>::max())
2215
75
    {
2216
75
        SAL_WARN("sd.filter", "Ignoring bogus line width: " << fLineWidth);
2217
75
        mnLineWidth = 0;
2218
75
    }
2219
3.27k
    else
2220
3.27k
        mnLineWidth = static_cast<sal_Int32>(fLineWidth);
2221
2222
3.34k
    vcl::pdf::PDFFillMode nFillMode = vcl::pdf::PDFFillMode::Alternate;
2223
3.34k
    bool bStroke = true; // Assume we have to draw, unless told otherwise.
2224
3.34k
    if (pPageObject->getDrawMode(nFillMode, bStroke))
2225
3.34k
    {
2226
3.34k
        if (nFillMode == vcl::pdf::PDFFillMode::Alternate)
2227
24
            mpVD->SetDrawMode(DrawModeFlags::Default);
2228
3.32k
        else if (nFillMode == vcl::pdf::PDFFillMode::Winding)
2229
2.75k
            mpVD->SetDrawMode(DrawModeFlags::Default);
2230
568
        else
2231
568
            mpVD->SetDrawMode(DrawModeFlags::NoFill);
2232
3.34k
    }
2233
2234
3.34k
    if (std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap
2235
3.34k
        = pPageObject->getRenderedFillPattern(*mpPdfDocument, *pPage))
2236
16
    {
2237
16
        moFillPattern = bitmap->createBitmapFromBuffer();
2238
16
        moFillColor.reset();
2239
16
    }
2240
3.33k
    else
2241
3.33k
    {
2242
3.33k
        moFillColor = pPageObject->getFillColor();
2243
3.33k
        moFillPattern.reset();
2244
3.33k
    }
2245
2246
3.34k
    if (bStroke)
2247
705
        mpVD->SetLineColor(getStrokeColor(pPageObject, pPage));
2248
2.64k
    else
2249
2.64k
        mpVD->SetLineColor(COL_TRANSPARENT);
2250
2251
3.34k
    if (!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(aPolyPoly))
2252
3.34k
    {
2253
3.34k
        rtl::Reference<SdrPathObj> pPath
2254
3.34k
            = new SdrPathObj(*mpModel, SdrObjKind::Polygon, std::move(aPolyPoly));
2255
3.34k
        SetAttributes(pPath.get());
2256
3.34k
        InsertObj(pPath.get(), false);
2257
3.34k
    }
2258
3.34k
}
2259
2260
Point ImpSdrPdfImport::PointsToLogic(double x, double y) const
2261
18.9k
{
2262
18.9k
    y = correctVertOrigin(y);
2263
2264
18.9k
    Point aPos(convertPointToMm100(x), convertPointToMm100(y));
2265
18.9k
    return aPos;
2266
18.9k
}
2267
2268
tools::Rectangle ImpSdrPdfImport::PointsToLogic(double left, double right, double top,
2269
                                                double bottom) const
2270
4.35k
{
2271
4.35k
    top = correctVertOrigin(top);
2272
4.35k
    bottom = correctVertOrigin(bottom);
2273
2274
4.35k
    Point aPos(convertPointToMm100(left), convertPointToMm100(top));
2275
4.35k
    Size aSize(convertPointToMm100(right - left), convertPointToMm100(bottom - top));
2276
2277
4.35k
    return tools::Rectangle(aPos, aSize);
2278
4.35k
}
2279
2280
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */