/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: */ |