2901 lines
115 KiB
Diff
2901 lines
115 KiB
Diff
diff --git a/core/fpdfapi/font/BUILD.gn b/core/fpdfapi/font/BUILD.gn
|
|
--- a/core/fpdfapi/font/BUILD.gn
|
|
+++ b/core/fpdfapi/font/BUILD.gn
|
|
@@ -62,10 +62,12 @@
|
|
"cpdf_simplefont_unittest.cpp",
|
|
"cpdf_tounicodemap_unittest.cpp",
|
|
"cpdf_truetypefont_unittest.cpp",
|
|
+ "cpdf_type3font_unittest.cpp",
|
|
]
|
|
deps = [
|
|
":font",
|
|
"../../fxge",
|
|
+ "../page",
|
|
"../page:unit_test_support",
|
|
"../parser",
|
|
"../parser:unit_test_support",
|
|
diff --git a/core/fpdfapi/font/cpdf_font.h b/core/fpdfapi/font/cpdf_font.h
|
|
--- a/core/fpdfapi/font/cpdf_font.h
|
|
+++ b/core/fpdfapi/font/cpdf_font.h
|
|
@@ -20,6 +20,7 @@
|
|
#include "core/fxcrt/fx_coordinates.h"
|
|
#include "core/fxcrt/fx_string.h"
|
|
#include "core/fxcrt/observed_ptr.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
#include "core/fxge/cfx_font.h"
|
|
@@ -43,7 +44,8 @@
|
|
public:
|
|
virtual ~FormIface() = default;
|
|
|
|
- virtual void ParseContentForType3Char(CPDF_Type3Char* pChar) = 0;
|
|
+ virtual void ParseContentForType3Char(CPDF_Type3Char* pChar,
|
|
+ RenderingIntent intent) = 0;
|
|
virtual bool HasPageObjects() const = 0;
|
|
virtual CFX_FloatRect CalcBoundingBox() const = 0;
|
|
virtual std::optional<std::pair<RetainPtr<CFX_DIBitmap>, CFX_Matrix>>
|
|
diff --git a/core/fpdfapi/font/cpdf_type3font.cpp b/core/fpdfapi/font/cpdf_type3font.cpp
|
|
--- a/core/fpdfapi/font/cpdf_type3font.cpp
|
|
+++ b/core/fpdfapi/font/cpdf_type3font.cpp
|
|
@@ -96,12 +96,17 @@
|
|
CheckFontMetrics();
|
|
}
|
|
|
|
-CPDF_Type3Char* CPDF_Type3Font::LoadChar(uint32_t charcode) {
|
|
- if (char_loading_depth_ >= kMaxType3FormLevel) {
|
|
+CPDF_Type3Char* CPDF_Type3Font::LoadChar(uint32_t charcode,
|
|
+ RenderingIntent intent) {
|
|
+ if (charcode >= char_width_l_.size() ||
|
|
+ static_cast<unsigned>(intent) >
|
|
+ static_cast<unsigned>(RenderingIntent::kAbsoluteColorimetric) ||
|
|
+ char_loading_depth_ >= kMaxType3FormLevel) {
|
|
return nullptr;
|
|
}
|
|
|
|
- auto it = cache_map_.find(charcode);
|
|
+ const GlyphKey key{charcode, intent};
|
|
+ auto it = cache_map_.find(key);
|
|
if (it != cache_map_.end()) {
|
|
return it->second.get();
|
|
}
|
|
@@ -132,9 +137,9 @@
|
|
{
|
|
AutoRestorer<int> restorer(&char_loading_depth_);
|
|
char_loading_depth_++;
|
|
- pForm->ParseContentForType3Char(pNewChar.get());
|
|
+ pForm->ParseContentForType3Char(pNewChar.get(), intent);
|
|
}
|
|
- it = cache_map_.find(charcode);
|
|
+ it = cache_map_.find(key);
|
|
if (it != cache_map_.end()) {
|
|
return it->second.get();
|
|
}
|
|
@@ -145,7 +150,7 @@
|
|
}
|
|
|
|
CPDF_Type3Char* pCachedChar = pNewChar.get();
|
|
- cache_map_[charcode] = std::move(pNewChar);
|
|
+ cache_map_[key] = std::move(pNewChar);
|
|
return pCachedChar;
|
|
}
|
|
|
|
diff --git a/core/fpdfapi/font/cpdf_type3font.h b/core/fpdfapi/font/cpdf_type3font.h
|
|
--- a/core/fpdfapi/font/cpdf_type3font.h
|
|
+++ b/core/fpdfapi/font/cpdf_type3font.h
|
|
@@ -12,6 +12,7 @@
|
|
#include <array>
|
|
#include <map>
|
|
#include <memory>
|
|
+#include <utility>
|
|
|
|
#include "core/fpdfapi/font/cpdf_simplefont.h"
|
|
#include "core/fxcrt/fx_coordinates.h"
|
|
@@ -36,7 +37,9 @@
|
|
void SetPageResources(CPDF_Dictionary* pResources) {
|
|
page_resources_.Reset(pResources);
|
|
}
|
|
- CPDF_Type3Char* LoadChar(uint32_t charcode);
|
|
+ CPDF_Type3Char* LoadChar(
|
|
+ uint32_t charcode,
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric);
|
|
void CheckType3FontMetrics();
|
|
|
|
CFX_Matrix& GetFontMatrix() { return font_matrix_; }
|
|
@@ -56,7 +59,10 @@
|
|
RetainPtr<CPDF_Dictionary> char_procs_;
|
|
RetainPtr<CPDF_Dictionary> page_resources_;
|
|
RetainPtr<CPDF_Dictionary> font_resources_;
|
|
- std::map<uint32_t, std::unique_ptr<CPDF_Type3Char>> cache_map_;
|
|
+ // Type 3 uses one-byte character codes. At most four parsed variants per
|
|
+ // character preserve inherited RI while keeping glyph-local ri/q/Q intact.
|
|
+ using GlyphKey = std::pair<uint32_t, RenderingIntent>;
|
|
+ std::map<GlyphKey, std::unique_ptr<CPDF_Type3Char>> cache_map_;
|
|
std::array<int, 256> char_width_l_ = {};
|
|
};
|
|
|
|
diff --git a/core/fpdfapi/font/cpdf_type3font_unittest.cpp b/core/fpdfapi/font/cpdf_type3font_unittest.cpp
|
|
--- /dev/null
|
|
+++ b/core/fpdfapi/font/cpdf_type3font_unittest.cpp
|
|
@@ -0,0 +1,147 @@
|
|
+// Copyright 2026 The PDFium Authors
|
|
+// Use of this source code is governed by a BSD-style license that can be
|
|
+// found in the LICENSE file.
|
|
+
|
|
+#include "core/fpdfapi/font/cpdf_type3font.h"
|
|
+
|
|
+#include <array>
|
|
+#include <memory>
|
|
+#include <string_view>
|
|
+#include <utility>
|
|
+
|
|
+#include "core/fpdfapi/font/cpdf_type3char.h"
|
|
+#include "core/fpdfapi/page/cpdf_form.h"
|
|
+#include "core/fpdfapi/page/cpdf_pageobject.h"
|
|
+#include "core/fpdfapi/page/test_with_page_module.h"
|
|
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
|
|
+#include "core/fpdfapi/parser/cpdf_name.h"
|
|
+#include "core/fpdfapi/parser/cpdf_reference.h"
|
|
+#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
+#include "core/fpdfapi/parser/cpdf_test_document.h"
|
|
+#include "core/fpdfapi/render/cpdf_type3cache.h"
|
|
+#include "core/fxcrt/data_vector.h"
|
|
+#include "testing/gtest/include/gtest/gtest.h"
|
|
+
|
|
+namespace {
|
|
+
|
|
+using CPDFType3FontTest = TestWithPageModule;
|
|
+
|
|
+constexpr RenderingIntent kIntents[] = {
|
|
+ RenderingIntent::kPerceptual,
|
|
+ RenderingIntent::kRelativeColorimetric,
|
|
+ RenderingIntent::kSaturation,
|
|
+ RenderingIntent::kAbsoluteColorimetric,
|
|
+};
|
|
+
|
|
+class CountingFormFactory final : public CPDF_Font::FormFactoryIface {
|
|
+ public:
|
|
+ std::unique_ptr<CPDF_Font::FormIface> CreateForm(
|
|
+ CPDF_Document* document,
|
|
+ RetainPtr<CPDF_Dictionary> resources,
|
|
+ RetainPtr<CPDF_Stream> stream) override {
|
|
+ ++count;
|
|
+ return std::make_unique<CPDF_Form>(document, std::move(resources),
|
|
+ std::move(stream));
|
|
+ }
|
|
+
|
|
+ int count = 0;
|
|
+};
|
|
+
|
|
+RetainPtr<CPDF_Font> MakeType3Font(CPDF_TestDocument* document,
|
|
+ CountingFormFactory* factory,
|
|
+ std::string_view content) {
|
|
+ auto stream = document->NewIndirect<CPDF_Stream>(
|
|
+ DataVector<uint8_t>(content.begin(), content.end()),
|
|
+ pdfium::MakeRetain<CPDF_Dictionary>());
|
|
+ auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
|
|
+ dict->SetNewFor<CPDF_Name>("Type", "Font");
|
|
+ dict->SetNewFor<CPDF_Name>("Subtype", "Type3");
|
|
+ dict->SetNewFor<CPDF_Name>("Encoding", "WinAnsiEncoding");
|
|
+ dict->SetMatrixFor("FontMatrix", CFX_Matrix(0.001f, 0, 0, 0.001f, 0, 0));
|
|
+ dict->SetRectFor("FontBBox", CFX_FloatRect(0, 0, 600, 600));
|
|
+ auto char_procs = dict->SetNewFor<CPDF_Dictionary>("CharProcs");
|
|
+ char_procs->SetNewFor<CPDF_Reference>("A", document, stream->GetObjNum());
|
|
+ return CPDF_Font::Create(document, std::move(dict), factory);
|
|
+}
|
|
+
|
|
+} // namespace
|
|
+
|
|
+TEST_F(CPDFType3FontTest, IntentVariantsPreserveExplicitIntentAndSavedState) {
|
|
+ CPDF_TestDocument document;
|
|
+ CountingFormFactory factory;
|
|
+ auto font = MakeType3Font(
|
|
+ &document, &factory,
|
|
+ "600 0 d0 0 0 1 rg 0 0 20 20 re f "
|
|
+ "q /AbsoluteColorimetric ri 30 0 20 20 re f Q 60 0 20 20 re f");
|
|
+ ASSERT_TRUE(font);
|
|
+ CPDF_Type3Font* type3 = font->AsType3Font();
|
|
+ ASSERT_TRUE(type3);
|
|
+ std::array<CPDF_Type3Char*, 4> variants{};
|
|
+ for (size_t i = 0; i < std::size(kIntents); ++i) {
|
|
+ variants[i] = type3->LoadChar(65, kIntents[i]);
|
|
+ ASSERT_TRUE(variants[i]);
|
|
+ const auto* form = static_cast<const CPDF_Form*>(variants[i]->form());
|
|
+ ASSERT_TRUE(form);
|
|
+ ASSERT_EQ(form->GetPageObjectCount(), 3u);
|
|
+ EXPECT_EQ(form->GetPageObjectByIndex(0)->general_state().GetRenderIntent(),
|
|
+ kIntents[i]);
|
|
+ EXPECT_EQ(form->GetPageObjectByIndex(1)->general_state().GetRenderIntent(),
|
|
+ RenderingIntent::kAbsoluteColorimetric);
|
|
+ EXPECT_EQ(form->GetPageObjectByIndex(2)->general_state().GetRenderIntent(),
|
|
+ kIntents[i]);
|
|
+ for (size_t j = 0; j < i; ++j) {
|
|
+ EXPECT_NE(variants[j], variants[i]);
|
|
+ }
|
|
+ }
|
|
+ EXPECT_EQ(factory.count, 4);
|
|
+ for (int repeat = 0; repeat < 32; ++repeat) {
|
|
+ for (size_t i = 0; i < std::size(kIntents); ++i) {
|
|
+ EXPECT_EQ(type3->LoadChar(65, kIntents[i]), variants[i]);
|
|
+ }
|
|
+ }
|
|
+ EXPECT_EQ(factory.count, 4);
|
|
+ // Metrics use the default variant and do not create a fifth entry.
|
|
+ EXPECT_EQ(type3->GetCharWidth(65), variants[1]->width());
|
|
+ EXPECT_EQ(type3->GetCharBBox(65), variants[1]->bbox());
|
|
+ EXPECT_EQ(factory.count, 4);
|
|
+ EXPECT_FALSE(type3->LoadChar(256, RenderingIntent::kPerceptual));
|
|
+ EXPECT_FALSE(type3->LoadChar(65, static_cast<RenderingIntent>(4)));
|
|
+ EXPECT_EQ(factory.count, 4);
|
|
+ // Earlier variants remain alive and retain their own inherited state.
|
|
+ const auto* first_form = static_cast<const CPDF_Form*>(variants[0]->form());
|
|
+ EXPECT_EQ(first_form->GetPageObjectByIndex(2)->general_state().GetRenderIntent(),
|
|
+ RenderingIntent::kPerceptual);
|
|
+}
|
|
+
|
|
+TEST_F(CPDFType3FontTest, BitmapCacheUsesTheSameIntentVariantAsFontCache) {
|
|
+ CPDF_TestDocument document;
|
|
+ CountingFormFactory factory;
|
|
+ auto font = MakeType3Font(
|
|
+ &document, &factory,
|
|
+ "600 0 0 0 10 10 d1 10 0 0 10 0 0 cm "
|
|
+ "BI /W 1 /H 1 /IM true /BPC 1 ID \xff EI");
|
|
+ ASSERT_TRUE(font);
|
|
+ CPDF_Type3Font* type3 = font->AsType3Font();
|
|
+ ASSERT_TRUE(type3);
|
|
+ auto cache = pdfium::MakeRetain<CPDF_Type3Cache>(type3);
|
|
+ const CFX_Matrix matrix(10, 0, 0, 10, 0, 0);
|
|
+ std::array<const CFX_GlyphBitmap*, 4> bitmaps{};
|
|
+ for (size_t i = 0; i < std::size(kIntents); ++i) {
|
|
+ auto* glyph = type3->LoadChar(65, kIntents[i]);
|
|
+ ASSERT_TRUE(glyph);
|
|
+ ASSERT_TRUE(glyph->LoadBitmapFromSoleImageOfForm());
|
|
+ bitmaps[i] = cache->LoadGlyphBitmap(65, matrix, kIntents[i]);
|
|
+ ASSERT_TRUE(bitmaps[i]);
|
|
+ EXPECT_EQ(cache->LoadGlyphBitmap(65, matrix, kIntents[i]), bitmaps[i]);
|
|
+ for (size_t j = 0; j < i; ++j) {
|
|
+ EXPECT_NE(bitmaps[j], bitmaps[i]);
|
|
+ }
|
|
+ }
|
|
+ EXPECT_EQ(factory.count, 4);
|
|
+ for (size_t i = 0; i < std::size(kIntents); ++i) {
|
|
+ EXPECT_EQ(cache->LoadGlyphBitmap(65, matrix, kIntents[i]), bitmaps[i]);
|
|
+ }
|
|
+ EXPECT_EQ(factory.count, 4);
|
|
+ EXPECT_FALSE(cache->LoadGlyphBitmap(256, matrix, kIntents[0]));
|
|
+ EXPECT_FALSE(cache->LoadGlyphBitmap(65, matrix, static_cast<RenderingIntent>(4)));
|
|
+}
|
|
diff --git a/core/fpdfapi/page/BUILD.gn b/core/fpdfapi/page/BUILD.gn
|
|
--- a/core/fpdfapi/page/BUILD.gn
|
|
+++ b/core/fpdfapi/page/BUILD.gn
|
|
@@ -149,7 +149,9 @@
|
|
"cpdf_page_unittest.cpp",
|
|
"cpdf_pageimagecache_unittest.cpp",
|
|
"cpdf_pageobjectholder_unittest.cpp",
|
|
+ "cpdf_patterncontext_unittest.cpp",
|
|
"cpdf_psengine_unittest.cpp",
|
|
+ "cpdf_renderingintent_unittest.cpp",
|
|
"cpdf_streamcontentparser_unittest.cpp",
|
|
"cpdf_streamparser_unittest.cpp",
|
|
]
|
|
diff --git a/core/fpdfapi/page/cpdf_allstates.cpp b/core/fpdfapi/page/cpdf_allstates.cpp
|
|
--- a/core/fpdfapi/page/cpdf_allstates.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_allstates.cpp
|
|
@@ -27,6 +27,11 @@
|
|
|
|
CPDF_AllStates::~CPDF_AllStates() = default;
|
|
|
|
+void CPDF_AllStates::SetRenderIntent(const ByteString& name) {
|
|
+ mutable_general_state().SetRenderIntent(name);
|
|
+ mutable_color_state().SetRenderIntent(general_state().GetRenderIntent());
|
|
+}
|
|
+
|
|
void CPDF_AllStates::SetDefaultStates() {
|
|
graphic_states_.SetDefaultStates();
|
|
}
|
|
@@ -76,7 +81,9 @@
|
|
break;
|
|
}
|
|
case FXBSTR_ID('R', 'I', 0, 0):
|
|
- mutable_general_state().SetRenderIntent(pObject->GetString());
|
|
+ if (pObject->IsName()) {
|
|
+ SetRenderIntent(pObject->GetString());
|
|
+ }
|
|
break;
|
|
case FXBSTR_ID('F', 'o', 'n', 't'): {
|
|
const CPDF_Array* font = pObject->AsArray();
|
|
diff --git a/core/fpdfapi/page/cpdf_allstates.h b/core/fpdfapi/page/cpdf_allstates.h
|
|
--- a/core/fpdfapi/page/cpdf_allstates.h
|
|
+++ b/core/fpdfapi/page/cpdf_allstates.h
|
|
@@ -22,6 +22,7 @@
|
|
~CPDF_AllStates();
|
|
|
|
void SetDefaultStates();
|
|
+ void SetRenderIntent(const ByteString& name);
|
|
|
|
void ProcessExtGS(const CPDF_Dictionary* pGS,
|
|
CPDF_StreamContentParser* pParser);
|
|
diff --git a/core/fpdfapi/page/cpdf_color.cpp b/core/fpdfapi/page/cpdf_color.cpp
|
|
--- a/core/fpdfapi/page/cpdf_color.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_color.cpp
|
|
@@ -100,8 +100,9 @@
|
|
CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceGray);
|
|
}
|
|
|
|
-std::optional<FX_COLORREF> CPDF_Color::GetColorRef() const {
|
|
- std::optional<FX_RGB_STRUCT<float>> maybe_rgb = GetRGB();
|
|
+std::optional<FX_COLORREF> CPDF_Color::GetColorRef(
|
|
+ RenderingIntent intent) const {
|
|
+ std::optional<FX_RGB_STRUCT<float>> maybe_rgb = GetRGB(intent);
|
|
if (!maybe_rgb.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
@@ -113,17 +114,18 @@
|
|
FXSYS_roundf(r * 255.0f));
|
|
}
|
|
|
|
-std::optional<FX_RGB_STRUCT<float>> CPDF_Color::GetRGB() const {
|
|
+std::optional<FX_RGB_STRUCT<float>> CPDF_Color::GetRGB(
|
|
+ RenderingIntent intent) const {
|
|
if (IsPatternInternal()) {
|
|
if (std::holds_alternative<std::unique_ptr<PatternValue>>(color_data_)) {
|
|
const auto& pattern_value =
|
|
std::get<std::unique_ptr<PatternValue>>(color_data_);
|
|
- return cs_->AsPatternCS()->GetPatternRGB(*pattern_value);
|
|
+ return cs_->AsPatternCS()->GetPatternRGB(*pattern_value, intent);
|
|
}
|
|
} else {
|
|
if (std::holds_alternative<std::vector<float>>(color_data_)) {
|
|
const auto& buffer = std::get<std::vector<float>>(color_data_);
|
|
- return cs_->GetRGB(buffer);
|
|
+ return cs_->GetRGB(buffer, intent);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
diff --git a/core/fpdfapi/page/cpdf_color.h b/core/fpdfapi/page/cpdf_color.h
|
|
--- a/core/fpdfapi/page/cpdf_color.h
|
|
+++ b/core/fpdfapi/page/cpdf_color.h
|
|
@@ -14,6 +14,7 @@
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/span.h"
|
|
#include "core/fxge/dib/fx_dib.h"
|
|
@@ -44,8 +45,10 @@
|
|
// Wrapper around GetRGB() that returns the RGB value as FX_COLORREF. The
|
|
// GetRGB() return value is clamped to fit into FX_COLORREF, where the color
|
|
// components are 8-bit fields within an unsigned integer.
|
|
- std::optional<FX_COLORREF> GetColorRef() const;
|
|
- std::optional<FX_RGB_STRUCT<float>> GetRGB() const;
|
|
+ std::optional<FX_COLORREF> GetColorRef(
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric) const;
|
|
+ std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric) const;
|
|
|
|
// Should only be called if IsPattern() returns true.
|
|
RetainPtr<CPDF_Pattern> GetPattern() const;
|
|
diff --git a/core/fpdfapi/page/cpdf_colorspace.cpp b/core/fpdfapi/page/cpdf_colorspace.cpp
|
|
--- a/core/fpdfapi/page/cpdf_colorspace.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_colorspace.cpp
|
|
@@ -126,7 +126,8 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
std::set<const CPDF_Object*>* pVisited) override;
|
|
@@ -135,7 +136,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const override;
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const override;
|
|
|
|
private:
|
|
static constexpr float kDefaultGamma = 1.0f;
|
|
@@ -154,13 +156,15 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const override;
|
|
void TranslateImageLine(pdfium::span<uint8_t> dest_span,
|
|
pdfium::span<const uint8_t> src_span,
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const override;
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
std::set<const CPDF_Object*>* pVisited) override;
|
|
@@ -184,7 +188,8 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const override;
|
|
void GetDefaultValue(int iComponent,
|
|
float* value,
|
|
float* min,
|
|
@@ -194,7 +199,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const override;
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
std::set<const CPDF_Object*>* pVisited) override;
|
|
@@ -216,14 +222,16 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const override;
|
|
RetainPtr<CPDF_IccProfile> GetIccProfile() const override;
|
|
void TranslateImageLine(pdfium::span<uint8_t> dest_span,
|
|
pdfium::span<const uint8_t> src_span,
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const override;
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const override;
|
|
bool IsNormal() const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
@@ -253,7 +261,8 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const override;
|
|
void GetDefaultValue(int iComponent,
|
|
float* value,
|
|
float* min,
|
|
@@ -276,7 +285,8 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const override;
|
|
void GetDefaultValue(int iComponent,
|
|
float* value,
|
|
float* min,
|
|
@@ -638,7 +648,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const {
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const {
|
|
// Only applies to CMYK colorspaces. None of the colorspaces that use this
|
|
// generic base implementation are CMYK.
|
|
CHECK(!bTransMask);
|
|
@@ -652,7 +663,7 @@
|
|
for (uint32_t j = 0; j < components_; j++) {
|
|
src[j] = static_cast<float>(*src_buf++) / divisor;
|
|
}
|
|
- auto rgb = GetRGBOrZerosOnError(src);
|
|
+ auto rgb = GetRGBOrZerosOnError(src, intent);
|
|
*dest_buf++ = static_cast<int32_t>(rgb.blue * 255);
|
|
*dest_buf++ = static_cast<int32_t>(rgb.green * 255);
|
|
*dest_buf++ = static_cast<int32_t>(rgb.red * 255);
|
|
@@ -717,7 +728,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_CalGray::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
const float gray = pBuf[0];
|
|
return FX_RGB_STRUCT<float>{gray, gray, gray};
|
|
}
|
|
@@ -727,7 +739,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const {
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const {
|
|
CHECK(!bTransMask); // Only applies to CMYK colorspaces.
|
|
|
|
auto gray_span = src_span.first(static_cast<size_t>(pixels));
|
|
@@ -778,7 +791,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_CalRGB::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
float a = pBuf[0];
|
|
float b = pBuf[1];
|
|
float c = pBuf[2];
|
|
@@ -810,7 +824,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const {
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const {
|
|
CHECK(!bTransMask); // Only applies to CMYK colorspaces.
|
|
fxcodec::ReverseRGB(dest_span, src_span, pixels);
|
|
}
|
|
@@ -865,7 +880,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_LabCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
float Lstar = pBuf[0];
|
|
float astar = pBuf[1];
|
|
float bstar = pBuf[2];
|
|
@@ -901,7 +917,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const {
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const {
|
|
CHECK(!bTransMask); // Only applies to CMYK colorspaces.
|
|
|
|
auto bgr_span = fxcrt::reinterpret_span<FX_BGR_STRUCT<uint8_t>>(dest_span);
|
|
@@ -917,7 +934,7 @@
|
|
// Better code than the equivalent GetRGBOrZerosOnError() since that
|
|
// is implemented in a base class and can't devirtualize the GetRGB()
|
|
// call despite this class being marked final.
|
|
- auto rgb = GetRGB(lab).value_or(FX_RGB_STRUCT<float>{});
|
|
+ auto rgb = GetRGB(lab, intent).value_or(FX_RGB_STRUCT<float>{});
|
|
bgr_ref.blue = static_cast<int32_t>(rgb.blue * 255);
|
|
bgr_ref.green = static_cast<int32_t>(rgb.green * 255);
|
|
bgr_ref.red = static_cast<int32_t>(rgb.red * 255);
|
|
@@ -969,17 +986,20 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_ICCBasedCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
if (profile_->IsSRGB()) {
|
|
return FX_RGB_STRUCT<float>{pBuf[0], pBuf[1], pBuf[2]};
|
|
}
|
|
if (profile_->IsSupported()) {
|
|
float rgb[3];
|
|
- profile_->Translate(pBuf.first(ComponentCount()), rgb);
|
|
- return FX_RGB_STRUCT<float>{rgb[0], rgb[1], rgb[2]};
|
|
+ if (profile_->Translate(pBuf.first(ComponentCount()), rgb, intent)) {
|
|
+ return FX_RGB_STRUCT<float>{rgb[0], rgb[1], rgb[2]};
|
|
+ }
|
|
+ return std::nullopt;
|
|
}
|
|
if (base_cs_) {
|
|
- return base_cs_->GetRGB(pBuf);
|
|
+ return base_cs_->GetRGB(pBuf, intent);
|
|
}
|
|
return FX_RGB_STRUCT<float>{};
|
|
}
|
|
@@ -993,7 +1013,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const {
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const {
|
|
CHECK(!bTransMask); // Only applies to CMYK colorspaces.
|
|
|
|
if (profile_->IsSRGB()) {
|
|
@@ -1003,12 +1024,14 @@
|
|
if (!profile_->IsSupported()) {
|
|
if (base_cs_) {
|
|
base_cs_->TranslateImageLine(dest_span, src_span, pixels, image_width,
|
|
- image_height, false);
|
|
+ image_height, false, intent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
- profile_->TranslateScanline(dest_span, src_span, pixels);
|
|
+ if (!profile_->TranslateScanline(dest_span, src_span, pixels, intent)) {
|
|
+ std::ranges::fill(dest_span.first(static_cast<size_t>(pixels) * 3), 0);
|
|
+ }
|
|
}
|
|
|
|
bool CPDF_ICCBasedCS::IsNormal() const {
|
|
@@ -1131,7 +1154,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_SeparationCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
if (is_none_type_) {
|
|
return std::nullopt;
|
|
}
|
|
@@ -1140,7 +1164,7 @@
|
|
return std::nullopt;
|
|
}
|
|
std::vector<float> results(base_cs_->ComponentCount(), pBuf[0]);
|
|
- return base_cs_->GetRGB(results);
|
|
+ return base_cs_->GetRGB(results, intent);
|
|
}
|
|
|
|
// Using at least 16 elements due to the call alt_cs_->GetRGB() below.
|
|
@@ -1150,7 +1174,7 @@
|
|
return std::nullopt;
|
|
}
|
|
if (base_cs_) {
|
|
- return base_cs_->GetRGB(results);
|
|
+ return base_cs_->GetRGB(results, intent);
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
@@ -1199,7 +1223,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_DeviceNCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
if (!func_) {
|
|
return std::nullopt;
|
|
}
|
|
@@ -1211,5 +1236,5 @@
|
|
if (nresults == 0) {
|
|
return std::nullopt;
|
|
}
|
|
- return base_cs_->GetRGB(results);
|
|
-}
|
|
+ return base_cs_->GetRGB(results, intent);
|
|
+}
|
|
diff --git a/core/fpdfapi/page/cpdf_colorspace.h b/core/fpdfapi/page/cpdf_colorspace.h
|
|
--- a/core/fpdfapi/page/cpdf_colorspace.h
|
|
+++ b/core/fpdfapi/page/cpdf_colorspace.h
|
|
@@ -21,6 +21,7 @@
|
|
#include "core/fpdfapi/parser/cpdf_object.h"
|
|
#include "core/fxcrt/bytestring.h"
|
|
#include "core/fxcrt/observed_ptr.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/span.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
@@ -103,13 +104,16 @@
|
|
// Wrapper around GetRGB() that returns black (0, 0, 0) when an actual value
|
|
// can not be determined.
|
|
FX_RGB_STRUCT<float> GetRGBOrZerosOnError(
|
|
- pdfium::span<const float> pBuf) const {
|
|
- return GetRGB(pBuf).value_or(FX_RGB_STRUCT<float>{});
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric) const {
|
|
+ return GetRGB(pBuf, intent).value_or(FX_RGB_STRUCT<float>{});
|
|
}
|
|
|
|
// Use CPDF_Pattern::GetPatternColorRef() instead of GetRGB() for patterns.
|
|
virtual std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const = 0;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric) const = 0;
|
|
|
|
virtual RetainPtr<CPDF_IccProfile> GetIccProfile() const;
|
|
|
|
@@ -118,12 +122,14 @@
|
|
float* min,
|
|
float* max) const;
|
|
|
|
- virtual void TranslateImageLine(pdfium::span<uint8_t> dest_span,
|
|
- pdfium::span<const uint8_t> src_span,
|
|
- int pixels,
|
|
- int image_width,
|
|
- int image_height,
|
|
- bool bTransMask) const;
|
|
+ virtual void TranslateImageLine(
|
|
+ pdfium::span<uint8_t> dest_span,
|
|
+ pdfium::span<const uint8_t> src_span,
|
|
+ int pixels,
|
|
+ int image_width,
|
|
+ int image_height,
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric) const;
|
|
virtual void EnableStdConversion(bool bEnabled);
|
|
virtual bool IsNormal() const;
|
|
|
|
diff --git a/core/fpdfapi/page/cpdf_colorstate.cpp b/core/fpdfapi/page/cpdf_colorstate.cpp
|
|
--- a/core/fpdfapi/page/cpdf_colorstate.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_colorstate.cpp
|
|
@@ -26,6 +26,24 @@
|
|
|
|
void CPDF_ColorState::SetDefault() {
|
|
ref_.GetPrivateCopy()->SetDefault();
|
|
+}
|
|
+
|
|
+void CPDF_ColorState::SetRenderIntent(RenderingIntent intent) {
|
|
+ if (ref_ && ref_.GetObject()->render_intent_ == intent) {
|
|
+ return;
|
|
+ }
|
|
+ ColorData* data = ref_.GetPrivateCopy();
|
|
+ data->render_intent_ = intent;
|
|
+ if (!data->fill_color_.IsNull()) {
|
|
+ if (auto color = data->fill_color_.GetColorRef(intent)) {
|
|
+ data->fill_color_ref_ = *color;
|
|
+ }
|
|
+ }
|
|
+ if (!data->stroke_color_.IsNull()) {
|
|
+ if (auto color = data->stroke_color_.GetColorRef(intent)) {
|
|
+ data->stroke_color_ref_ = *color;
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
FX_COLORREF CPDF_ColorState::GetFillColorRef() const {
|
|
@@ -127,14 +145,16 @@
|
|
if (!color.IsPattern()) {
|
|
color.SetValueForNonPattern(std::move(values));
|
|
}
|
|
- return color.GetColorRef().value_or(0xFFFFFFFF);
|
|
+ return color.GetColorRef(ref_.GetObject()->render_intent_)
|
|
+ .value_or(0xFFFFFFFF);
|
|
}
|
|
|
|
FX_COLORREF CPDF_ColorState::SetPattern(RetainPtr<CPDF_Pattern> pattern,
|
|
pdfium::span<float> values,
|
|
CPDF_Color& color) {
|
|
color.SetValueForPattern(pattern, values);
|
|
- std::optional<FX_COLORREF> colorref = color.GetColorRef();
|
|
+ std::optional<FX_COLORREF> colorref =
|
|
+ color.GetColorRef(ref_.GetObject()->render_intent_);
|
|
if (colorref.has_value()) {
|
|
return colorref.value();
|
|
}
|
|
@@ -146,7 +166,8 @@
|
|
CPDF_ColorState::ColorData::ColorData() = default;
|
|
|
|
CPDF_ColorState::ColorData::ColorData(const ColorData& src)
|
|
- : fill_color_ref_(src.fill_color_ref_),
|
|
+ : render_intent_(src.render_intent_),
|
|
+ fill_color_ref_(src.fill_color_ref_),
|
|
stroke_color_ref_(src.stroke_color_ref_),
|
|
fill_color_(src.fill_color_),
|
|
stroke_color_(src.stroke_color_) {}
|
|
@@ -154,6 +175,7 @@
|
|
CPDF_ColorState::ColorData::~ColorData() = default;
|
|
|
|
void CPDF_ColorState::ColorData::SetDefault() {
|
|
+ render_intent_ = RenderingIntent::kRelativeColorimetric;
|
|
fill_color_ref_ = 0;
|
|
stroke_color_ref_ = 0;
|
|
fill_color_.SetColorSpace(
|
|
diff --git a/core/fpdfapi/page/cpdf_colorstate.h b/core/fpdfapi/page/cpdf_colorstate.h
|
|
--- a/core/fpdfapi/page/cpdf_colorstate.h
|
|
+++ b/core/fpdfapi/page/cpdf_colorstate.h
|
|
@@ -27,6 +27,7 @@
|
|
|
|
void Emplace();
|
|
void SetDefault();
|
|
+ void SetRenderIntent(RenderingIntent intent);
|
|
|
|
FX_COLORREF GetFillColorRef() const;
|
|
void SetFillColorRef(FX_COLORREF colorref);
|
|
@@ -62,6 +63,7 @@
|
|
|
|
void SetDefault();
|
|
|
|
+ RenderingIntent render_intent_ = RenderingIntent::kRelativeColorimetric;
|
|
FX_COLORREF fill_color_ref_ = 0;
|
|
FX_COLORREF stroke_color_ref_ = 0;
|
|
CPDF_Color fill_color_;
|
|
diff --git a/core/fpdfapi/page/cpdf_devicecs.cpp b/core/fpdfapi/page/cpdf_devicecs.cpp
|
|
--- a/core/fpdfapi/page/cpdf_devicecs.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_devicecs.cpp
|
|
@@ -44,7 +44,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_DeviceCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
switch (GetFamily()) {
|
|
case Family::kDeviceGray: {
|
|
const float pix = NormalizeChannel(pBuf.front());
|
|
@@ -83,7 +84,8 @@
|
|
int pixels,
|
|
int image_width,
|
|
int image_height,
|
|
- bool bTransMask) const {
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent) const {
|
|
auto rgb_out = fxcrt::reinterpret_span<FX_RGB_STRUCT<uint8_t>>(dest_span);
|
|
switch (GetFamily()) {
|
|
case Family::kDeviceGray:
|
|
diff --git a/core/fpdfapi/page/cpdf_devicecs.h b/core/fpdfapi/page/cpdf_devicecs.h
|
|
--- a/core/fpdfapi/page/cpdf_devicecs.h
|
|
+++ b/core/fpdfapi/page/cpdf_devicecs.h
|
|
@@ -19,13 +19,18 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
- void TranslateImageLine(pdfium::span<uint8_t> dest_span,
|
|
- pdfium::span<const uint8_t> src_span,
|
|
- int pixels,
|
|
- int image_width,
|
|
- int image_height,
|
|
- bool bTransMask) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric) const override;
|
|
+ void TranslateImageLine(
|
|
+ pdfium::span<uint8_t> dest_span,
|
|
+ pdfium::span<const uint8_t> src_span,
|
|
+ int pixels,
|
|
+ int image_width,
|
|
+ int image_height,
|
|
+ bool bTransMask,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric) const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
std::set<const CPDF_Object*>* pVisited) override;
|
|
diff --git a/core/fpdfapi/page/cpdf_dib.cpp b/core/fpdfapi/page/cpdf_dib.cpp
|
|
--- a/core/fpdfapi/page/cpdf_dib.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_dib.cpp
|
|
@@ -138,7 +138,36 @@
|
|
|
|
CPDF_DIB::JpxSMaskInlineData::~JpxSMaskInlineData() = default;
|
|
|
|
+// static
|
|
+RenderingIntent CPDF_DIB::ResolveRenderingIntent(
|
|
+ const CPDF_Dictionary* dict, RenderingIntent inherited_intent) {
|
|
+ if (!dict) {
|
|
+ return inherited_intent;
|
|
+ }
|
|
+ RetainPtr<const CPDF_Object> value = dict->GetDirectObjectFor("Intent");
|
|
+ if (!value || !value->IsName()) {
|
|
+ return inherited_intent;
|
|
+ }
|
|
+ const ByteString name = value->GetString();
|
|
+ if (name == "Perceptual") {
|
|
+ return RenderingIntent::kPerceptual;
|
|
+ }
|
|
+ if (name == "RelativeColorimetric") {
|
|
+ return RenderingIntent::kRelativeColorimetric;
|
|
+ }
|
|
+ if (name == "Saturation") {
|
|
+ return RenderingIntent::kSaturation;
|
|
+ }
|
|
+ if (name == "AbsoluteColorimetric") {
|
|
+ return RenderingIntent::kAbsoluteColorimetric;
|
|
+ }
|
|
+ // An unrecognized intent name falls back to the PDF default (not the
|
|
+ // inherited state); an absent or non-name entry above keeps inheritance.
|
|
+ return RenderingIntent::kRelativeColorimetric;
|
|
+}
|
|
+
|
|
bool CPDF_DIB::Load() {
|
|
+ rendering_intent_ = RenderingIntent::kRelativeColorimetric;
|
|
if (!LoadInternal(nullptr, nullptr)) {
|
|
return false;
|
|
}
|
|
@@ -204,7 +233,9 @@
|
|
bool bStdCS,
|
|
CPDF_ColorSpace::Family GroupFamily,
|
|
bool bLoadMask,
|
|
- const CFX_Size& max_size_required) {
|
|
+ const CFX_Size& max_size_required,
|
|
+ RenderingIntent intent) {
|
|
+ rendering_intent_ = intent;
|
|
std_cs_ = bStdCS;
|
|
has_mask_ = bHasMask;
|
|
group_family_ = GroupFamily;
|
|
@@ -813,6 +844,7 @@
|
|
}
|
|
|
|
dict_ = stream_->GetDict();
|
|
+ rendering_intent_ = ResolveRenderingIntent(dict_.Get(), rendering_intent_);
|
|
SetWidth(dict_->GetIntegerFor("Width"));
|
|
SetHeight(dict_->GetIntegerFor("Height"));
|
|
if (!IsValidDimension(GetWidth()) || !IsValidDimension(GetHeight())) {
|
|
@@ -873,7 +905,7 @@
|
|
std::vector<float> colors =
|
|
ReadArrayElementsToVector(pMatte.Get(), components_);
|
|
|
|
- auto rgb = color_space_->GetRGBOrZerosOnError(colors);
|
|
+ auto rgb = color_space_->GetRGBOrZerosOnError(colors, rendering_intent_);
|
|
matte_color_ =
|
|
ArgbEncode(0, FXSYS_roundf(rgb.red * 255),
|
|
FXSYS_roundf(rgb.green * 255), FXSYS_roundf(rgb.blue * 255));
|
|
@@ -915,7 +947,8 @@
|
|
mask_ = pdfium::MakeRetain<CPDF_DIB>(document_, std::move(mask_stream));
|
|
LoadState ret =
|
|
mask_->StartLoadDIBBase(false, nullptr, nullptr, true,
|
|
- CPDF_ColorSpace::Family::kUnknown, false, {0, 0});
|
|
+ CPDF_ColorSpace::Family::kUnknown, false, {0, 0},
|
|
+ rendering_intent_);
|
|
if (ret == LoadState::kContinue) {
|
|
if (status_ == LoadState::kFail) {
|
|
status_ = LoadState::kContinue;
|
|
@@ -957,7 +990,7 @@
|
|
float color_values[3];
|
|
std::ranges::fill(color_values, comp_data_[0].decode_min_);
|
|
|
|
- auto rgb = color_space_->GetRGBOrZerosOnError(color_values);
|
|
+ auto rgb = color_space_->GetRGBOrZerosOnError(color_values, rendering_intent_);
|
|
FX_ARGB argb0 =
|
|
ArgbEncode(255, FXSYS_roundf(rgb.red * 255),
|
|
FXSYS_roundf(rgb.green * 255), FXSYS_roundf(rgb.blue * 255));
|
|
@@ -972,7 +1005,7 @@
|
|
color_values[0] += comp_data_[0].decode_step_;
|
|
color_values[1] += comp_data_[0].decode_step_;
|
|
color_values[2] += comp_data_[0].decode_step_;
|
|
- auto result = color_space_->GetRGBOrZerosOnError(color_values);
|
|
+ auto result = color_space_->GetRGBOrZerosOnError(color_values, rendering_intent_);
|
|
argb1 = ArgbEncode(255, FXSYS_roundf(result.red * 255),
|
|
FXSYS_roundf(result.green * 255),
|
|
FXSYS_roundf(result.blue * 255));
|
|
@@ -1006,9 +1039,9 @@
|
|
color_space_->ComponentCount() > 1) {
|
|
const size_t nComponents = color_space_->ComponentCount();
|
|
std::vector<float> temp_buf(nComponents, color_values[0]);
|
|
- rgb = color_space_->GetRGBOrZerosOnError(temp_buf);
|
|
+ rgb = color_space_->GetRGBOrZerosOnError(temp_buf, rendering_intent_);
|
|
} else {
|
|
- rgb = color_space_->GetRGBOrZerosOnError(color_values);
|
|
+ rgb = color_space_->GetRGBOrZerosOnError(color_values, rendering_intent_);
|
|
}
|
|
SetPaletteArgb(i, ArgbEncode(255, FXSYS_roundf(rgb.red * 255),
|
|
FXSYS_roundf(rgb.green * 255),
|
|
@@ -1078,7 +1111,7 @@
|
|
rgb.green = (1.0f - color_values[1]) * k;
|
|
rgb.blue = (1.0f - color_values[2]) * k;
|
|
} else if (family_ != CPDF_ColorSpace::Family::kPattern) {
|
|
- rgb = color_space_->GetRGBOrZerosOnError(color_values);
|
|
+ rgb = color_space_->GetRGBOrZerosOnError(color_values, rendering_intent_);
|
|
}
|
|
const float R = std::clamp(rgb.red, 0.0f, 1.0f);
|
|
const float G = std::clamp(rgb.green, 0.0f, 1.0f);
|
|
@@ -1105,7 +1138,8 @@
|
|
|
|
if (ComponentCountMatchesColorSpace()) {
|
|
color_space_->TranslateImageLine(dest_scan, src_scan, GetWidth(),
|
|
- GetWidth(), GetHeight(), TransMask());
|
|
+ GetWidth(), GetHeight(), TransMask(),
|
|
+ rendering_intent_);
|
|
}
|
|
return true;
|
|
}
|
|
diff --git a/core/fpdfapi/page/cpdf_dib.h b/core/fpdfapi/page/cpdf_dib.h
|
|
--- a/core/fpdfapi/page/cpdf_dib.h
|
|
+++ b/core/fpdfapi/page/cpdf_dib.h
|
|
@@ -14,6 +14,7 @@
|
|
|
|
#include "core/fpdfapi/page/cpdf_colorspace.h"
|
|
#include "core/fxcrt/data_vector.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/span.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
@@ -56,6 +57,11 @@
|
|
uint32_t GetMatteColor() const { return matte_color_; }
|
|
bool IsJBigImage() const;
|
|
|
|
+ // Missing or non-name image Intent preserves graphics-state inheritance.
|
|
+ // Standard names select that intent; unknown names use the PDF default.
|
|
+ static RenderingIntent ResolveRenderingIntent(
|
|
+ const CPDF_Dictionary* dict, RenderingIntent inherited_intent);
|
|
+
|
|
bool Load();
|
|
LoadState StartLoadDIBBase(bool bHasMask,
|
|
const CPDF_Dictionary* pFormResources,
|
|
@@ -63,7 +69,9 @@
|
|
bool bStdCS,
|
|
CPDF_ColorSpace::Family GroupFamily,
|
|
bool bLoadMask,
|
|
- const CFX_Size& max_size_required);
|
|
+ const CFX_Size& max_size_required,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
LoadState ContinueLoadDIBBase(PauseIndicatorIface* pPause);
|
|
RetainPtr<CPDF_DIB> DetachMask();
|
|
|
|
@@ -129,6 +137,7 @@
|
|
uint32_t components_ = 0;
|
|
CPDF_ColorSpace::Family family_ = CPDF_ColorSpace::Family::kUnknown;
|
|
CPDF_ColorSpace::Family group_family_ = CPDF_ColorSpace::Family::kUnknown;
|
|
+ RenderingIntent rendering_intent_ = RenderingIntent::kRelativeColorimetric;
|
|
uint32_t matte_color_ = 0;
|
|
LoadState status_ = LoadState::kFail;
|
|
bool load_mask_ = false;
|
|
diff --git a/core/fpdfapi/page/cpdf_dib_unittest.cpp b/core/fpdfapi/page/cpdf_dib_unittest.cpp
|
|
--- a/core/fpdfapi/page/cpdf_dib_unittest.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_dib_unittest.cpp
|
|
@@ -13,6 +13,7 @@
|
|
#include "core/fpdfapi/parser/cpdf_name.h"
|
|
#include "core/fpdfapi/parser/cpdf_number.h"
|
|
#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
+#include "core/fpdfapi/parser/cpdf_string.h"
|
|
#include "core/fpdfapi/parser/cpdf_test_document.h"
|
|
#include "core/fxcrt/data_vector.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
@@ -90,3 +91,39 @@
|
|
EXPECT_THAT(dib->GetScanline(0),
|
|
testing::ElementsAre(0x55, 0x33, 0x11, 0xbb, 0x99, 0x77));
|
|
}
|
|
+
|
|
+TEST_F(CPDFDIBTest, ImageIntentDistinguishesUnknownNameFromMissingOrInvalidType) {
|
|
+ auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
|
|
+ constexpr RenderingIntent inherited = RenderingIntent::kSaturation;
|
|
+ EXPECT_EQ(CPDF_DIB::ResolveRenderingIntent(nullptr, inherited), inherited);
|
|
+ EXPECT_EQ(CPDF_DIB::ResolveRenderingIntent(dict.Get(), inherited), inherited);
|
|
+ struct IntentCase {
|
|
+ const char* name;
|
|
+ RenderingIntent intent;
|
|
+ };
|
|
+ const IntentCase cases[] = {
|
|
+ {"Perceptual", RenderingIntent::kPerceptual},
|
|
+ {"RelativeColorimetric", RenderingIntent::kRelativeColorimetric},
|
|
+ {"Saturation", RenderingIntent::kSaturation},
|
|
+ {"AbsoluteColorimetric", RenderingIntent::kAbsoluteColorimetric},
|
|
+ };
|
|
+ for (const auto& test : cases) {
|
|
+ dict->SetNewFor<CPDF_Name>("Intent", test.name);
|
|
+ for (const auto& source : cases) {
|
|
+ EXPECT_EQ(CPDF_DIB::ResolveRenderingIntent(dict.Get(), source.intent),
|
|
+ test.intent);
|
|
+ }
|
|
+ }
|
|
+ // Unknown names fall back to RelativeColorimetric, including old
|
|
+ // four-character prefix matches. PDF names are case sensitive.
|
|
+ for (const char* name : {"Unknown", "Abso", "AbsoluteColorimetricSuffix",
|
|
+ "perceptual", ""}) {
|
|
+ dict->SetNewFor<CPDF_Name>("Intent", name);
|
|
+ EXPECT_EQ(CPDF_DIB::ResolveRenderingIntent(dict.Get(), inherited),
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
+ }
|
|
+ dict->SetNewFor<CPDF_Number>("Intent", 3);
|
|
+ EXPECT_EQ(CPDF_DIB::ResolveRenderingIntent(dict.Get(), inherited), inherited);
|
|
+ dict->SetNewFor<CPDF_String>("Intent", "Perceptual");
|
|
+ EXPECT_EQ(CPDF_DIB::ResolveRenderingIntent(dict.Get(), inherited), inherited);
|
|
+}
|
|
diff --git a/core/fpdfapi/page/cpdf_docpagedata.cpp b/core/fpdfapi/page/cpdf_docpagedata.cpp
|
|
--- a/core/fpdfapi/page/cpdf_docpagedata.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_docpagedata.cpp
|
|
@@ -382,27 +382,36 @@
|
|
|
|
RetainPtr<CPDF_Pattern> CPDF_DocPageData::GetPattern(
|
|
RetainPtr<CPDF_Object> pPatternObj,
|
|
- const CFX_Matrix& matrix) {
|
|
+ const CFX_Matrix& matrix,
|
|
+ RenderingIntent initial_render_intent) {
|
|
CHECK(pPatternObj->IsDictionary() || pPatternObj->IsStream());
|
|
|
|
auto it = pattern_map_.find(pPatternObj);
|
|
- if (it != pattern_map_.end() && it->second) {
|
|
- return pdfium::WrapRetain(it->second.Get());
|
|
+ if (it != pattern_map_.end() && it->second &&
|
|
+ it->second->MatchesContext(matrix, initial_render_intent)) {
|
|
+ auto* shading = it->second->AsShadingPattern();
|
|
+ if (!shading || !shading->IsShadingObject()) {
|
|
+ return pdfium::WrapRetain(it->second.Get());
|
|
+ }
|
|
}
|
|
|
|
RetainPtr<CPDF_Pattern> pattern;
|
|
switch (pPatternObj->GetDict()->GetIntegerFor("PatternType")) {
|
|
case CPDF_Pattern::kTiling:
|
|
pattern = pdfium::MakeRetain<CPDF_TilingPattern>(GetDocument(),
|
|
- pPatternObj, matrix);
|
|
+ pPatternObj, matrix,
|
|
+ initial_render_intent);
|
|
break;
|
|
case CPDF_Pattern::kShading:
|
|
pattern = pdfium::MakeRetain<CPDF_ShadingPattern>(
|
|
- GetDocument(), pPatternObj, false, matrix);
|
|
+ GetDocument(), pPatternObj, false, matrix, initial_render_intent);
|
|
break;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
+ // Retain only the most recently requested context per source object. Existing
|
|
+ // page objects own their immutable pattern through PatternValue, so replacing
|
|
+ // this cache entry cannot change an already parsed page/Form's interpretation.
|
|
pattern_map_[pPatternObj].Reset(pattern.Get());
|
|
return pattern;
|
|
}
|
|
@@ -413,8 +422,13 @@
|
|
CHECK(pPatternObj->IsDictionary() || pPatternObj->IsStream());
|
|
|
|
auto it = pattern_map_.find(pPatternObj);
|
|
- if (it != pattern_map_.end() && it->second) {
|
|
- return pdfium::WrapRetain(it->second->AsShadingPattern());
|
|
+ if (it != pattern_map_.end() && it->second &&
|
|
+ it->second->MatchesContext(matrix,
|
|
+ RenderingIntent::kRelativeColorimetric)) {
|
|
+ auto* shading = it->second->AsShadingPattern();
|
|
+ if (shading && shading->IsShadingObject()) {
|
|
+ return pdfium::WrapRetain(shading);
|
|
+ }
|
|
}
|
|
|
|
auto pPattern = pdfium::MakeRetain<CPDF_ShadingPattern>(
|
|
diff --git a/core/fpdfapi/page/cpdf_docpagedata.h b/core/fpdfapi/page/cpdf_docpagedata.h
|
|
--- a/core/fpdfapi/page/cpdf_docpagedata.h
|
|
+++ b/core/fpdfapi/page/cpdf_docpagedata.h
|
|
@@ -78,7 +78,8 @@
|
|
std::set<const CPDF_Object*>* pVisited);
|
|
|
|
RetainPtr<CPDF_Pattern> GetPattern(RetainPtr<CPDF_Object> pPatternObj,
|
|
- const CFX_Matrix& matrix);
|
|
+ const CFX_Matrix& matrix,
|
|
+ RenderingIntent initial_render_intent);
|
|
RetainPtr<CPDF_ShadingPattern> GetShading(RetainPtr<CPDF_Object> pPatternObj,
|
|
const CFX_Matrix& matrix);
|
|
|
|
diff --git a/core/fpdfapi/page/cpdf_form.cpp b/core/fpdfapi/page/cpdf_form.cpp
|
|
--- a/core/fpdfapi/page/cpdf_form.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_form.cpp
|
|
@@ -9,6 +9,7 @@
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
+#include "core/fpdfapi/page/cpdf_allstates.h"
|
|
#include "core/fpdfapi/page/cpdf_contentparser.h"
|
|
#include "core/fpdfapi/page/cpdf_imageobject.h"
|
|
#include "core/fpdfapi/page/cpdf_pageobject.h"
|
|
@@ -70,8 +71,19 @@
|
|
ParseContentInternal(pGraphicStates, pParentMatrix, nullptr, recursion_state);
|
|
}
|
|
|
|
-void CPDF_Form::ParseContentForType3Char(CPDF_Type3Char* pType3Char) {
|
|
- ParseContentInternal(nullptr, nullptr, pType3Char, nullptr);
|
|
+void CPDF_Form::ParseContentForType3Char(CPDF_Type3Char* pType3Char,
|
|
+ RenderingIntent intent) {
|
|
+ // Retain the existing empty glyph color/graphics state, inheriting only RI.
|
|
+ // Parsing with that initial RI lets ordinary ri and q/Q operators override
|
|
+ // and restore it, without mutating an already-cached glyph object graph.
|
|
+ CPDF_AllStates states;
|
|
+ states.mutable_general_state().Emplace();
|
|
+ states.mutable_graph_state().Emplace();
|
|
+ states.mutable_text_state().Emplace();
|
|
+ states.mutable_color_state().Emplace();
|
|
+ states.mutable_general_state().SetRenderIntent(intent);
|
|
+ states.mutable_color_state().SetRenderIntent(intent);
|
|
+ ParseContentInternal(&states, nullptr, pType3Char, nullptr);
|
|
}
|
|
|
|
void CPDF_Form::ParseContentInternal(const CPDF_AllStates* pGraphicStates,
|
|
diff --git a/core/fpdfapi/page/cpdf_form.h b/core/fpdfapi/page/cpdf_form.h
|
|
--- a/core/fpdfapi/page/cpdf_form.h
|
|
+++ b/core/fpdfapi/page/cpdf_form.h
|
|
@@ -46,7 +46,8 @@
|
|
~CPDF_Form() override;
|
|
|
|
// CPDF_Font::FormIface:
|
|
- void ParseContentForType3Char(CPDF_Type3Char* pType3Char) override;
|
|
+ void ParseContentForType3Char(CPDF_Type3Char* pType3Char,
|
|
+ RenderingIntent intent) override;
|
|
bool HasPageObjects() const override;
|
|
CFX_FloatRect CalcBoundingBox() const override;
|
|
std::optional<std::pair<RetainPtr<CFX_DIBitmap>, CFX_Matrix>>
|
|
diff --git a/core/fpdfapi/page/cpdf_generalstate.cpp b/core/fpdfapi/page/cpdf_generalstate.cpp
|
|
--- a/core/fpdfapi/page/cpdf_generalstate.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_generalstate.cpp
|
|
@@ -13,23 +13,6 @@
|
|
#include "core/fpdfapi/parser/cpdf_object.h"
|
|
|
|
namespace {
|
|
-
|
|
-int RI_StringToId(const ByteString& ri) {
|
|
- uint32_t id = ri.GetID();
|
|
- if (id == FXBSTR_ID('A', 'b', 's', 'o')) {
|
|
- return 1;
|
|
- }
|
|
-
|
|
- if (id == FXBSTR_ID('S', 'a', 't', 'u')) {
|
|
- return 2;
|
|
- }
|
|
-
|
|
- if (id == FXBSTR_ID('P', 'e', 'r', 'c')) {
|
|
- return 3;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
|
|
BlendMode GetBlendTypeInternal(const ByteString& mode) {
|
|
switch (mode.GetID()) {
|
|
@@ -80,8 +63,32 @@
|
|
|
|
CPDF_GeneralState::~CPDF_GeneralState() = default;
|
|
|
|
+RenderingIntent CPDF_GeneralState::GetRenderIntent() const {
|
|
+ return ref_ ? ref_.GetObject()->render_intent_
|
|
+ : RenderingIntent::kRelativeColorimetric;
|
|
+}
|
|
+
|
|
void CPDF_GeneralState::SetRenderIntent(const ByteString& ri) {
|
|
- ref_.GetPrivateCopy()->render_intent_ = RI_StringToId(ri);
|
|
+ RenderingIntent intent;
|
|
+ if (ri == "Perceptual") {
|
|
+ intent = RenderingIntent::kPerceptual;
|
|
+ } else if (ri == "RelativeColorimetric") {
|
|
+ intent = RenderingIntent::kRelativeColorimetric;
|
|
+ } else if (ri == "Saturation") {
|
|
+ intent = RenderingIntent::kSaturation;
|
|
+ } else if (ri == "AbsoluteColorimetric") {
|
|
+ intent = RenderingIntent::kAbsoluteColorimetric;
|
|
+ } else {
|
|
+ // PDF requires unknown intent names to use RelativeColorimetric.
|
|
+ intent = RenderingIntent::kRelativeColorimetric;
|
|
+ }
|
|
+ SetRenderIntent(intent);
|
|
+}
|
|
+
|
|
+void CPDF_GeneralState::SetRenderIntent(RenderingIntent intent) {
|
|
+ if (intent != GetRenderIntent()) {
|
|
+ ref_.GetPrivateCopy()->render_intent_ = intent;
|
|
+ }
|
|
}
|
|
|
|
ByteString CPDF_GeneralState::GetBlendMode() const {
|
|
diff --git a/core/fpdfapi/page/cpdf_generalstate.h b/core/fpdfapi/page/cpdf_generalstate.h
|
|
--- a/core/fpdfapi/page/cpdf_generalstate.h
|
|
+++ b/core/fpdfapi/page/cpdf_generalstate.h
|
|
@@ -12,6 +12,7 @@
|
|
#include "constants/transparency.h"
|
|
#include "core/fxcrt/bytestring.h"
|
|
#include "core/fxcrt/fx_coordinates.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/shared_copy_on_write.h"
|
|
#include "core/fxcrt/span.h"
|
|
@@ -30,7 +31,9 @@
|
|
void Emplace() { ref_.Emplace(); }
|
|
bool HasRef() const { return !!ref_; }
|
|
|
|
+ RenderingIntent GetRenderIntent() const;
|
|
void SetRenderIntent(const ByteString& ri);
|
|
+ void SetRenderIntent(RenderingIntent intent);
|
|
|
|
ByteString GetBlendMode() const;
|
|
BlendMode GetBlendType() const;
|
|
@@ -98,7 +101,7 @@
|
|
float fill_alpha_ = 1.0f;
|
|
RetainPtr<const CPDF_Object> tr_;
|
|
RetainPtr<CPDF_TransferFunc> transfer_func_;
|
|
- int render_intent_ = 0;
|
|
+ RenderingIntent render_intent_ = RenderingIntent::kRelativeColorimetric;
|
|
bool stroke_adjust_ = false;
|
|
bool alpha_source_ = false;
|
|
bool text_knockout_ = false;
|
|
diff --git a/core/fpdfapi/page/cpdf_iccprofile.cpp b/core/fpdfapi/page/cpdf_iccprofile.cpp
|
|
--- a/core/fpdfapi/page/cpdf_iccprofile.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_iccprofile.cpp
|
|
@@ -42,24 +42,51 @@
|
|
}
|
|
|
|
src_components_ = components;
|
|
- transform_ = std::move(transform);
|
|
+ const size_t index =
|
|
+ static_cast<size_t>(RenderingIntent::kRelativeColorimetric);
|
|
+ transforms_[index] = std::move(transform);
|
|
+ transform_attempted_[index] = true;
|
|
}
|
|
|
|
CPDF_IccProfile::~CPDF_IccProfile() = default;
|
|
|
|
bool CPDF_IccProfile::IsNormal() const {
|
|
- return transform_->IsNormal();
|
|
+ return transforms_[static_cast<size_t>(
|
|
+ RenderingIntent::kRelativeColorimetric)]
|
|
+ ->IsNormal();
|
|
}
|
|
|
|
-void CPDF_IccProfile::Translate(pdfium::span<const float> src_values,
|
|
- pdfium::span<float, 3> dest_values) {
|
|
- transform_->Translate(src_values, dest_values);
|
|
+fxcodec::IccTransform* CPDF_IccProfile::GetTransform(RenderingIntent intent) {
|
|
+ const size_t index = static_cast<size_t>(intent);
|
|
+ if (!transform_attempted_[index]) {
|
|
+ transform_attempted_[index] = true;
|
|
+ transforms_[index] = fxcodec::IccTransform::CreateTransformSRGB(
|
|
+ stream_acc_->GetSpan(), intent);
|
|
+ }
|
|
+ return transforms_[index].get();
|
|
}
|
|
|
|
-void CPDF_IccProfile::TranslateScanline(pdfium::span<uint8_t> pDest,
|
|
- pdfium::span<const uint8_t> pSrc,
|
|
- int pixels) {
|
|
- transform_->TranslateScanline(pDest, pSrc, pixels);
|
|
+bool CPDF_IccProfile::Translate(pdfium::span<const float> src_values,
|
|
+ pdfium::span<float, 3> dest_values,
|
|
+ RenderingIntent intent) {
|
|
+ auto* transform = GetTransform(intent);
|
|
+ if (!transform) {
|
|
+ return false;
|
|
+ }
|
|
+ transform->Translate(src_values, dest_values);
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool CPDF_IccProfile::TranslateScanline(pdfium::span<uint8_t> dest,
|
|
+ pdfium::span<const uint8_t> src,
|
|
+ int pixels,
|
|
+ RenderingIntent intent) {
|
|
+ auto* transform = GetTransform(intent);
|
|
+ if (!transform) {
|
|
+ return false;
|
|
+ }
|
|
+ transform->TranslateScanline(dest, src, pixels);
|
|
+ return true;
|
|
}
|
|
|
|
RetainPtr<const CPDF_StreamAcc> CPDF_IccProfile::GetStreamAcc() const {
|
|
diff --git a/core/fpdfapi/page/cpdf_iccprofile.h b/core/fpdfapi/page/cpdf_iccprofile.h
|
|
--- a/core/fpdfapi/page/cpdf_iccprofile.h
|
|
+++ b/core/fpdfapi/page/cpdf_iccprofile.h
|
|
@@ -9,8 +9,10 @@
|
|
|
|
#include <stdint.h>
|
|
|
|
+#include <array>
|
|
#include <memory>
|
|
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/span.h"
|
|
|
|
@@ -26,15 +28,20 @@
|
|
|
|
bool IsValid() const { return IsSRGB() || IsSupported(); }
|
|
bool IsSRGB() const { return is_srgb_; }
|
|
- bool IsSupported() const { return !!transform_; }
|
|
+ bool IsSupported() const {
|
|
+ return !!transforms_[static_cast<size_t>(
|
|
+ RenderingIntent::kRelativeColorimetric)];
|
|
+ }
|
|
uint32_t GetComponents() const { return src_components_; }
|
|
|
|
bool IsNormal() const;
|
|
- void Translate(pdfium::span<const float> src_values,
|
|
- pdfium::span<float, 3> dest_values);
|
|
- void TranslateScanline(pdfium::span<uint8_t> pDest,
|
|
+ bool Translate(pdfium::span<const float> src_values,
|
|
+ pdfium::span<float, 3> dest_values,
|
|
+ RenderingIntent intent);
|
|
+ bool TranslateScanline(pdfium::span<uint8_t> pDest,
|
|
pdfium::span<const uint8_t> pSrc,
|
|
- int pixels);
|
|
+ int pixels,
|
|
+ RenderingIntent intent);
|
|
|
|
RetainPtr<const CPDF_StreamAcc> GetStreamAcc() const;
|
|
|
|
@@ -43,11 +50,14 @@
|
|
uint32_t expected_components);
|
|
~CPDF_IccProfile() override;
|
|
|
|
- // Keeps stream alive for the lifetime of this object, so `transform_` can
|
|
+ fxcodec::IccTransform* GetTransform(RenderingIntent intent);
|
|
+
|
|
+ // Keeps stream alive for the lifetime of this object, so `transforms_` can
|
|
// safely access the stream data.
|
|
RetainPtr<const CPDF_StreamAcc> const stream_acc_;
|
|
// Uses data from `stream_acc_`.
|
|
- std::unique_ptr<fxcodec::IccTransform> transform_;
|
|
+ std::array<std::unique_ptr<fxcodec::IccTransform>, 4> transforms_;
|
|
+ std::array<bool, 4> transform_attempted_ = {};
|
|
const bool is_srgb_;
|
|
uint32_t src_components_ = 0;
|
|
};
|
|
diff --git a/core/fpdfapi/page/cpdf_image.cpp b/core/fpdfapi/page/cpdf_image.cpp
|
|
--- a/core/fpdfapi/page/cpdf_image.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_image.cpp
|
|
@@ -363,11 +363,12 @@
|
|
bool bStdCS,
|
|
CPDF_ColorSpace::Family GroupFamily,
|
|
bool bLoadMask,
|
|
- const CFX_Size& max_size_required) {
|
|
+ const CFX_Size& max_size_required,
|
|
+ RenderingIntent intent) {
|
|
RetainPtr<CPDF_DIB> source = CreateNewDIB();
|
|
CPDF_DIB::LoadState ret =
|
|
source->StartLoadDIBBase(true, pFormResource, pPageResource, bStdCS,
|
|
- GroupFamily, bLoadMask, max_size_required);
|
|
+ GroupFamily, bLoadMask, max_size_required, intent);
|
|
if (ret == CPDF_DIB::LoadState::kFail) {
|
|
dibbase_.Reset();
|
|
return false;
|
|
diff --git a/core/fpdfapi/page/cpdf_image.h b/core/fpdfapi/page/cpdf_image.h
|
|
--- a/core/fpdfapi/page/cpdf_image.h
|
|
+++ b/core/fpdfapi/page/cpdf_image.h
|
|
@@ -10,6 +10,7 @@
|
|
#include <stdint.h>
|
|
|
|
#include "core/fpdfapi/page/cpdf_colorspace.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/span.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
@@ -66,7 +67,9 @@
|
|
bool bStdCS,
|
|
CPDF_ColorSpace::Family GroupFamily,
|
|
bool bLoadMask,
|
|
- const CFX_Size& max_size_required);
|
|
+ const CFX_Size& max_size_required,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
|
|
// Returns whether to Continue() or not.
|
|
bool Continue(PauseIndicatorIface* pPause);
|
|
diff --git a/core/fpdfapi/page/cpdf_imageloader.cpp b/core/fpdfapi/page/cpdf_imageloader.cpp
|
|
--- a/core/fpdfapi/page/cpdf_imageloader.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_imageloader.cpp
|
|
@@ -30,15 +30,16 @@
|
|
const CFX_Size& max_size_required) {
|
|
cache_ = pPageImageCache;
|
|
image_object_ = pImage;
|
|
+ const RenderingIntent intent = pImage->general_state().GetRenderIntent();
|
|
bool should_continue;
|
|
if (cache_) {
|
|
should_continue = cache_->StartGetCachedBitmap(
|
|
image_object_->GetImage(), pFormResource, pPageResource, bStdCS,
|
|
- eFamily, bLoadMask, max_size_required);
|
|
+ eFamily, bLoadMask, max_size_required, intent);
|
|
} else {
|
|
should_continue = image_object_->GetImage()->StartLoadDIBBase(
|
|
pFormResource, pPageResource, bStdCS, eFamily, bLoadMask,
|
|
- max_size_required);
|
|
+ max_size_required, intent);
|
|
}
|
|
if (!should_continue) {
|
|
Finish();
|
|
diff --git a/core/fpdfapi/page/cpdf_indexedcs.cpp b/core/fpdfapi/page/cpdf_indexedcs.cpp
|
|
--- a/core/fpdfapi/page/cpdf_indexedcs.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_indexedcs.cpp
|
|
@@ -89,7 +89,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_IndexedCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
int32_t index = static_cast<int32_t>(pBuf[0]);
|
|
if (index < 0 || index > max_index_) {
|
|
return std::nullopt;
|
|
@@ -112,5 +113,5 @@
|
|
comp.min +
|
|
comp.max * lookup_table_[index * component_min_max_.size() + i] / 255;
|
|
}
|
|
- return base_cs_->GetRGB(comps);
|
|
+ return base_cs_->GetRGB(comps, intent);
|
|
}
|
|
diff --git a/core/fpdfapi/page/cpdf_indexedcs.h b/core/fpdfapi/page/cpdf_indexedcs.h
|
|
--- a/core/fpdfapi/page/cpdf_indexedcs.h
|
|
+++ b/core/fpdfapi/page/cpdf_indexedcs.h
|
|
@@ -29,7 +29,9 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric) const override;
|
|
const CPDF_IndexedCS* AsIndexedCS() const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
diff --git a/core/fpdfapi/page/cpdf_meshstream.cpp b/core/fpdfapi/page/cpdf_meshstream.cpp
|
|
--- a/core/fpdfapi/page/cpdf_meshstream.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_meshstream.cpp
|
|
@@ -102,11 +102,13 @@
|
|
ShadingType type,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
RetainPtr<const CPDF_Stream> pShadingStream,
|
|
- RetainPtr<CPDF_ColorSpace> pCS)
|
|
+ RetainPtr<CPDF_ColorSpace> pCS,
|
|
+ RenderingIntent rendering_intent)
|
|
: type_(type),
|
|
funcs_(funcs),
|
|
shading_stream_(std::move(pShadingStream)),
|
|
cs_(std::move(pCS)),
|
|
+ rendering_intent_(rendering_intent),
|
|
stream_(pdfium::MakeRetain<CPDF_StreamAcc>(shading_stream_)) {}
|
|
|
|
CPDF_MeshStream::~CPDF_MeshStream() = default;
|
|
@@ -215,7 +217,7 @@
|
|
component_max_;
|
|
}
|
|
if (funcs_.empty()) {
|
|
- return cs_->GetRGBOrZerosOnError(color_value);
|
|
+ return cs_->GetRGBOrZerosOnError(color_value, rendering_intent_);
|
|
}
|
|
return {color_value[0], 0.0f, 0.0f};
|
|
}
|
|
diff --git a/core/fpdfapi/page/cpdf_meshstream.h b/core/fpdfapi/page/cpdf_meshstream.h
|
|
--- a/core/fpdfapi/page/cpdf_meshstream.h
|
|
+++ b/core/fpdfapi/page/cpdf_meshstream.h
|
|
@@ -14,6 +14,7 @@
|
|
#include <vector>
|
|
|
|
#include "core/fpdfapi/page/cpdf_shadingpattern.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxge/dib/fx_dib.h"
|
|
|
|
@@ -40,7 +41,9 @@
|
|
CPDF_MeshStream(ShadingType type,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
RetainPtr<const CPDF_Stream> pShadingStream,
|
|
- RetainPtr<CPDF_ColorSpace> pCS);
|
|
+ RetainPtr<CPDF_ColorSpace> pCS,
|
|
+ RenderingIntent rendering_intent =
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
~CPDF_MeshStream();
|
|
|
|
bool Load();
|
|
@@ -79,6 +82,7 @@
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs_;
|
|
RetainPtr<const CPDF_Stream> const shading_stream_;
|
|
RetainPtr<CPDF_ColorSpace> const cs_;
|
|
+ const RenderingIntent rendering_intent_;
|
|
uint32_t coord_bits_ = 0;
|
|
uint32_t component_bits_ = 0;
|
|
uint32_t flag_bits_ = 0;
|
|
diff --git a/core/fpdfapi/page/cpdf_pageimagecache.cpp b/core/fpdfapi/page/cpdf_pageimagecache.cpp
|
|
--- a/core/fpdfapi/page/cpdf_pageimagecache.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_pageimagecache.cpp
|
|
@@ -174,19 +174,32 @@
|
|
bool bStdCS,
|
|
CPDF_ColorSpace::Family eFamily,
|
|
bool bLoadMask,
|
|
- const CFX_Size& max_size_required) {
|
|
+ const CFX_Size& max_size_required,
|
|
+ RenderingIntent intent) {
|
|
// A cross-document image may have come from the embedder.
|
|
if (page_->GetDocument() != pImage->GetDocument()) {
|
|
return false;
|
|
}
|
|
|
|
RetainPtr<const CPDF_Stream> pStream = pImage->GetStream();
|
|
- const auto it = image_cache_.find(pStream);
|
|
+ const RenderingIntent effective_intent =
|
|
+ CPDF_DIB::ResolveRenderingIntent(pImage->GetDict().Get(), intent);
|
|
+ auto it = image_cache_.find(pStream);
|
|
+ if (it != image_cache_.end() &&
|
|
+ it->second->GetRenderingIntent() != effective_intent) {
|
|
+ // Keep one decoded variant per stream. ClearImageCacheEntry also accounts
|
|
+ // for its bytes and clears any borrowed current-entry pointer. Bitmap
|
|
+ // consumers retain their own references, so replacing this entry cannot
|
|
+ // alter pixels already being rendered.
|
|
+ ClearImageCacheEntry(pStream.Get());
|
|
+ it = image_cache_.end();
|
|
+ }
|
|
cur_find_cache_ = it != image_cache_.end();
|
|
if (cur_find_cache_) {
|
|
cur_image_cache_entry_ = it->second.get();
|
|
} else {
|
|
- cur_image_cache_entry_ = std::make_unique<Entry>(std::move(pImage));
|
|
+ cur_image_cache_entry_ =
|
|
+ std::make_unique<Entry>(std::move(pImage), effective_intent);
|
|
}
|
|
CPDF_DIB::LoadState ret = cur_image_cache_entry_->StartGetCachedBitmap(
|
|
this, pFormResources, pPageResources, bStdCS, eFamily, bLoadMask,
|
|
@@ -247,8 +260,9 @@
|
|
return cur_image_cache_entry_->DetachMask();
|
|
}
|
|
|
|
-CPDF_PageImageCache::Entry::Entry(RetainPtr<CPDF_Image> pImage)
|
|
- : image_(std::move(pImage)) {}
|
|
+CPDF_PageImageCache::Entry::Entry(RetainPtr<CPDF_Image> pImage,
|
|
+ RenderingIntent intent)
|
|
+ : image_(std::move(pImage)), rendering_intent_(intent) {}
|
|
|
|
CPDF_PageImageCache::Entry::~Entry() = default;
|
|
|
|
@@ -282,7 +296,7 @@
|
|
cur_bitmap_ = image_->CreateNewDIB();
|
|
CPDF_DIB::LoadState ret = cur_bitmap_.AsRaw<CPDF_DIB>()->StartLoadDIBBase(
|
|
true, pFormResources, pPageResources, bStdCS, eFamily, bLoadMask,
|
|
- max_size_required);
|
|
+ max_size_required, rendering_intent_);
|
|
cached_set_max_size_required_ =
|
|
(max_size_required.width != 0 && max_size_required.height != 0);
|
|
if (ret == CPDF_DIB::LoadState::kContinue) {
|
|
diff --git a/core/fpdfapi/page/cpdf_pageimagecache.h b/core/fpdfapi/page/cpdf_pageimagecache.h
|
|
--- a/core/fpdfapi/page/cpdf_pageimagecache.h
|
|
+++ b/core/fpdfapi/page/cpdf_pageimagecache.h
|
|
@@ -40,7 +40,9 @@
|
|
bool bStdCS,
|
|
CPDF_ColorSpace::Family eFamily,
|
|
bool bLoadMask,
|
|
- const CFX_Size& max_size_required);
|
|
+ const CFX_Size& max_size_required,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
|
|
bool Continue(PauseIndicatorIface* pPause);
|
|
|
|
@@ -51,7 +53,7 @@
|
|
private:
|
|
class Entry {
|
|
public:
|
|
- explicit Entry(RetainPtr<CPDF_Image> pImage);
|
|
+ Entry(RetainPtr<CPDF_Image> pImage, RenderingIntent intent);
|
|
~Entry();
|
|
|
|
void Reset();
|
|
@@ -60,6 +62,7 @@
|
|
uint32_t GetTimeCount() const { return time_count_; }
|
|
void SetTimeCount(uint32_t count) { time_count_ = count; }
|
|
CPDF_Image* GetImage() const { return image_.Get(); }
|
|
+ RenderingIntent GetRenderingIntent() const { return rendering_intent_; }
|
|
|
|
CPDF_DIB::LoadState StartGetCachedBitmap(
|
|
CPDF_PageImageCache* pPageImageCache,
|
|
@@ -86,6 +89,7 @@
|
|
uint32_t matte_color_ = 0;
|
|
uint32_t cache_size_ = 0;
|
|
RetainPtr<CPDF_Image> const image_;
|
|
+ const RenderingIntent rendering_intent_;
|
|
RetainPtr<CFX_DIBBase> cur_bitmap_;
|
|
RetainPtr<CFX_DIBBase> cur_mask_;
|
|
RetainPtr<CFX_DIBBase> cached_bitmap_;
|
|
diff --git a/core/fpdfapi/page/cpdf_pageimagecache_unittest.cpp b/core/fpdfapi/page/cpdf_pageimagecache_unittest.cpp
|
|
--- a/core/fpdfapi/page/cpdf_pageimagecache_unittest.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_pageimagecache_unittest.cpp
|
|
@@ -14,6 +14,12 @@
|
|
#include "core/fpdfapi/page/cpdf_page.h"
|
|
#include "core/fpdfapi/page/cpdf_pagemodule.h"
|
|
#include "core/fpdfapi/page/test_with_page_module.h"
|
|
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
|
|
+#include "core/fpdfapi/parser/cpdf_name.h"
|
|
+#include "core/fpdfapi/parser/cpdf_number.h"
|
|
+#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
+#include "core/fpdfapi/parser/cpdf_test_document.h"
|
|
+#include "core/fxcrt/data_vector.h"
|
|
#include "core/fpdfapi/parser/cpdf_parser.h"
|
|
#include "core/fpdfapi/render/cpdf_docrenderdata.h"
|
|
#include "core/fxcrt/cfx_fileaccess_stream.h"
|
|
@@ -252,3 +258,74 @@
|
|
}
|
|
|
|
} // namespace pdfium
|
|
+
|
|
+namespace pdfium {
|
|
+
|
|
+TEST_F(CPDFPageImageCacheTest, ImageIntentReplacesSingleStreamCacheVariant) {
|
|
+ CPDF_TestDocument document;
|
|
+ auto page = pdfium::MakeRetain<CPDF_Page>(
|
|
+ &document, pdfium::MakeRetain<CPDF_Dictionary>());
|
|
+ page->AddPageImageCache();
|
|
+ CPDF_PageImageCache* cache = page->GetPageImageCache();
|
|
+ auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
|
|
+ dict->SetNewFor<CPDF_Name>("Type", "XObject");
|
|
+ dict->SetNewFor<CPDF_Name>("Subtype", "Image");
|
|
+ dict->SetNewFor<CPDF_Name>("ColorSpace", "DeviceRGB");
|
|
+ dict->SetNewFor<CPDF_Number>("Width", 1);
|
|
+ dict->SetNewFor<CPDF_Number>("Height", 1);
|
|
+ dict->SetNewFor<CPDF_Number>("BitsPerComponent", 8);
|
|
+ auto stream = pdfium::MakeRetain<CPDF_Stream>(
|
|
+ DataVector<uint8_t>{0x11, 0x22, 0x33}, dict);
|
|
+ auto image = pdfium::MakeRetain<CPDF_Image>(&document, stream);
|
|
+ const auto load = [&](RenderingIntent intent) {
|
|
+ bool more = cache->StartGetCachedBitmap(
|
|
+ image, nullptr, nullptr, false, CPDF_ColorSpace::Family::kUnknown,
|
|
+ false, {0, 0}, intent);
|
|
+ while (more) {
|
|
+ more = cache->Continue(nullptr);
|
|
+ }
|
|
+ return cache->DetachCurBitmap();
|
|
+ };
|
|
+
|
|
+ RetainPtr<CFX_DIBBase> relative = load(RenderingIntent::kRelativeColorimetric);
|
|
+ ASSERT_TRUE(relative);
|
|
+ EXPECT_EQ(load(RenderingIntent::kRelativeColorimetric).Get(), relative.Get());
|
|
+ RetainPtr<CFX_DIBBase> perceptual = load(RenderingIntent::kPerceptual);
|
|
+ ASSERT_TRUE(perceptual);
|
|
+ EXPECT_NE(perceptual.Get(), relative.Get());
|
|
+ RetainPtr<CFX_DIBBase> relative_again =
|
|
+ load(RenderingIntent::kRelativeColorimetric);
|
|
+ ASSERT_TRUE(relative_again);
|
|
+ EXPECT_NE(relative_again.Get(), relative.Get());
|
|
+ EXPECT_NE(relative_again.Get(), perceptual.Get());
|
|
+ // Entry replacement must not invalidate a bitmap held by an earlier loader.
|
|
+ EXPECT_EQ(relative->GetScanline(0)[0], 0x33);
|
|
+ EXPECT_EQ(perceptual->GetScanline(0)[2], 0x11);
|
|
+
|
|
+ for (int i = 0; i < 128; ++i) {
|
|
+ const RenderingIntent intent =
|
|
+ i % 2 ? RenderingIntent::kPerceptual : RenderingIntent::kSaturation;
|
|
+ RetainPtr<CFX_DIBBase> bitmap = load(intent);
|
|
+ ASSERT_TRUE(bitmap);
|
|
+ // The budget should contain exactly the active single-stream variant,
|
|
+ // without charging every old intent again or underflowing on eviction.
|
|
+ cache->CacheOptimization(
|
|
+ static_cast<int32_t>(bitmap->GetEstimatedImageMemoryBurden()));
|
|
+ EXPECT_EQ(load(intent).Get(), bitmap.Get());
|
|
+ }
|
|
+
|
|
+ // Explicit /Intent takes priority. Different graphics-state intents now
|
|
+ // resolve to the same variant and therefore reuse its decoded bitmap.
|
|
+ dict->SetNewFor<CPDF_Name>("Intent", "AbsoluteColorimetric");
|
|
+ RetainPtr<CFX_DIBBase> explicit_intent = load(RenderingIntent::kPerceptual);
|
|
+ ASSERT_TRUE(explicit_intent);
|
|
+ EXPECT_EQ(load(RenderingIntent::kSaturation).Get(), explicit_intent.Get());
|
|
+ EXPECT_EQ(load(RenderingIntent::kRelativeColorimetric).Get(),
|
|
+ explicit_intent.Get());
|
|
+ cache->CacheOptimization(0);
|
|
+ EXPECT_NE(load(RenderingIntent::kPerceptual).Get(), explicit_intent.Get());
|
|
+ EXPECT_EQ(explicit_intent->GetScanline(0)[1], 0x22);
|
|
+ page->ClearView();
|
|
+}
|
|
+
|
|
+} // namespace pdfium
|
|
diff --git a/core/fpdfapi/page/cpdf_pattern.cpp b/core/fpdfapi/page/cpdf_pattern.cpp
|
|
--- a/core/fpdfapi/page/cpdf_pattern.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_pattern.cpp
|
|
@@ -13,10 +13,12 @@
|
|
|
|
CPDF_Pattern::CPDF_Pattern(CPDF_Document* doc,
|
|
RetainPtr<CPDF_Object> pObj,
|
|
- const CFX_Matrix& parentMatrix)
|
|
+ const CFX_Matrix& parentMatrix,
|
|
+ RenderingIntent initial_render_intent)
|
|
: document_(doc),
|
|
pattern_obj_(std::move(pObj)),
|
|
- parent_matrix_(parentMatrix) {
|
|
+ parent_matrix_(parentMatrix),
|
|
+ initial_render_intent_(initial_render_intent) {
|
|
DCHECK(document_);
|
|
DCHECK(pattern_obj_);
|
|
}
|
|
diff --git a/core/fpdfapi/page/cpdf_pattern.h b/core/fpdfapi/page/cpdf_pattern.h
|
|
--- a/core/fpdfapi/page/cpdf_pattern.h
|
|
+++ b/core/fpdfapi/page/cpdf_pattern.h
|
|
@@ -10,6 +10,7 @@
|
|
#include "core/fpdfapi/parser/cpdf_object.h"
|
|
#include "core/fxcrt/fx_coordinates.h"
|
|
#include "core/fxcrt/observed_ptr.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
|
|
@@ -26,11 +27,16 @@
|
|
virtual CPDF_ShadingPattern* AsShadingPattern();
|
|
|
|
const CFX_Matrix& pattern_to_form() const { return pattern_to_form_; }
|
|
+ RenderingIntent initial_render_intent() const { return initial_render_intent_; }
|
|
+ bool MatchesContext(const CFX_Matrix& matrix, RenderingIntent intent) const {
|
|
+ return parent_matrix_ == matrix && initial_render_intent_ == intent;
|
|
+ }
|
|
|
|
protected:
|
|
CPDF_Pattern(CPDF_Document* doc,
|
|
RetainPtr<CPDF_Object> pObj,
|
|
- const CFX_Matrix& parentMatrix);
|
|
+ const CFX_Matrix& parentMatrix,
|
|
+ RenderingIntent initial_render_intent);
|
|
~CPDF_Pattern() override;
|
|
|
|
// All the getters that return pointers return non-NULL pointers.
|
|
@@ -45,6 +51,7 @@
|
|
RetainPtr<CPDF_Object> const pattern_obj_;
|
|
CFX_Matrix pattern_to_form_;
|
|
const CFX_Matrix parent_matrix_;
|
|
+ const RenderingIntent initial_render_intent_;
|
|
};
|
|
|
|
#endif // CORE_FPDFAPI_PAGE_CPDF_PATTERN_H_
|
|
diff --git a/core/fpdfapi/page/cpdf_patterncontext_unittest.cpp b/core/fpdfapi/page/cpdf_patterncontext_unittest.cpp
|
|
--- /dev/null
|
|
+++ b/core/fpdfapi/page/cpdf_patterncontext_unittest.cpp
|
|
@@ -0,0 +1,240 @@
|
|
+// Copyright 2026 The PDFium Authors
|
|
+// Use of this source code is governed by a BSD-style license that can be
|
|
+// found in the LICENSE file.
|
|
+
|
|
+#include "core/fpdfapi/page/cpdf_pattern.h"
|
|
+
|
|
+#include <memory>
|
|
+#include <string_view>
|
|
+#include <utility>
|
|
+#include <vector>
|
|
+
|
|
+#include "core/fpdfapi/page/cpdf_allstates.h"
|
|
+#include "core/fpdfapi/page/cpdf_docpagedata.h"
|
|
+#include "core/fpdfapi/page/cpdf_form.h"
|
|
+#include "core/fpdfapi/page/cpdf_shadingobject.h"
|
|
+#include "core/fpdfapi/page/cpdf_shadingpattern.h"
|
|
+#include "core/fpdfapi/page/cpdf_tilingpattern.h"
|
|
+#include "core/fpdfapi/page/test_with_page_module.h"
|
|
+#include "core/fpdfapi/parser/cpdf_array.h"
|
|
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
|
|
+#include "core/fpdfapi/parser/cpdf_name.h"
|
|
+#include "core/fpdfapi/parser/cpdf_number.h"
|
|
+#include "core/fpdfapi/parser/cpdf_reference.h"
|
|
+#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
+#include "core/fpdfapi/parser/cpdf_test_document.h"
|
|
+#include "core/fxcrt/data_vector.h"
|
|
+#include "core/fxcrt/observed_ptr.h"
|
|
+#include "testing/gtest/include/gtest/gtest.h"
|
|
+
|
|
+namespace {
|
|
+
|
|
+using CPDFPatternContextTest = TestWithPageModule;
|
|
+
|
|
+RetainPtr<CPDF_Stream> MakeStream(CPDF_TestDocument* document,
|
|
+ RetainPtr<CPDF_Dictionary> dict,
|
|
+ std::string_view content) {
|
|
+ return document->NewIndirect<CPDF_Stream>(
|
|
+ DataVector<uint8_t>(content.begin(), content.end()), std::move(dict));
|
|
+}
|
|
+
|
|
+RetainPtr<CPDF_Stream> MakeTiling(CPDF_TestDocument* document) {
|
|
+ auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
|
|
+ dict->SetNewFor<CPDF_Number>("PatternType", 1);
|
|
+ dict->SetNewFor<CPDF_Number>("PaintType", 1);
|
|
+ dict->SetNewFor<CPDF_Number>("TilingType", 1);
|
|
+ dict->SetNewFor<CPDF_Number>("XStep", 100);
|
|
+ dict->SetNewFor<CPDF_Number>("YStep", 100);
|
|
+ dict->SetRectFor("BBox", CFX_FloatRect(0, 0, 100, 100));
|
|
+ return MakeStream(document, std::move(dict),
|
|
+ "0 0 1 rg 0 0 20 20 re f "
|
|
+ "q /AbsoluteColorimetric ri 30 0 20 20 re f "
|
|
+ "Q 60 0 20 20 re f");
|
|
+}
|
|
+
|
|
+RetainPtr<CPDF_Dictionary> MakeShading(CPDF_TestDocument* document) {
|
|
+ auto shading = document->NewIndirect<CPDF_Dictionary>();
|
|
+ shading->SetNewFor<CPDF_Number>("ShadingType", 2);
|
|
+ shading->SetNewFor<CPDF_Name>("ColorSpace", "DeviceRGB");
|
|
+ auto coords = shading->SetNewFor<CPDF_Array>("Coords");
|
|
+ for (int value : {0, 0, 100, 0}) {
|
|
+ coords->AppendNew<CPDF_Number>(value);
|
|
+ }
|
|
+ auto function = shading->SetNewFor<CPDF_Dictionary>("Function");
|
|
+ function->SetNewFor<CPDF_Number>("FunctionType", 2);
|
|
+ function->SetNewFor<CPDF_Number>("N", 1);
|
|
+ auto domain = function->SetNewFor<CPDF_Array>("Domain");
|
|
+ domain->AppendNew<CPDF_Number>(0);
|
|
+ domain->AppendNew<CPDF_Number>(1);
|
|
+ auto c0 = function->SetNewFor<CPDF_Array>("C0");
|
|
+ auto c1 = function->SetNewFor<CPDF_Array>("C1");
|
|
+ for (int i = 0; i < 3; ++i) {
|
|
+ c0->AppendNew<CPDF_Number>(0);
|
|
+ c1->AppendNew<CPDF_Number>(1);
|
|
+ }
|
|
+ return shading;
|
|
+}
|
|
+
|
|
+RetainPtr<CPDF_Stream> MakeParent(CPDF_TestDocument* document,
|
|
+ const CPDF_Object* pattern,
|
|
+ const CPDF_Object* shading,
|
|
+ std::string_view content) {
|
|
+ auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
|
|
+ dict->SetRectFor("BBox", CFX_FloatRect(0, 0, 100, 100));
|
|
+ auto resources = dict->SetNewFor<CPDF_Dictionary>("Resources");
|
|
+ auto patterns = resources->SetNewFor<CPDF_Dictionary>("Pattern");
|
|
+ patterns->SetNewFor<CPDF_Reference>("P", document, pattern->GetObjNum());
|
|
+ if (shading) {
|
|
+ resources->SetNewFor<CPDF_Dictionary>("Shading")->SetNewFor<CPDF_Reference>(
|
|
+ "S", document, shading->GetObjNum());
|
|
+ }
|
|
+ return MakeStream(document, std::move(dict), content);
|
|
+}
|
|
+
|
|
+} // namespace
|
|
+
|
|
+TEST_F(CPDFPatternContextTest,
|
|
+ ReplacingContextRetainsOldUsersWithoutCacheGrowth) {
|
|
+ CPDF_TestDocument document;
|
|
+ auto source = MakeTiling(&document);
|
|
+ auto* data = CPDF_DocPageData::FromDocument(&document);
|
|
+ const CFX_Matrix matrix;
|
|
+ auto first = data->GetPattern(source, matrix, RenderingIntent::kPerceptual);
|
|
+ ASSERT_TRUE(first);
|
|
+ EXPECT_EQ(data->GetPattern(source, matrix, RenderingIntent::kPerceptual),
|
|
+ first);
|
|
+ auto second = data->GetPattern(source, matrix, RenderingIntent::kSaturation);
|
|
+ ASSERT_TRUE(second);
|
|
+ EXPECT_NE(second, first);
|
|
+ auto third = data->GetPattern(source, matrix, RenderingIntent::kPerceptual);
|
|
+ EXPECT_NE(third, second);
|
|
+ EXPECT_EQ(first->initial_render_intent(), RenderingIntent::kPerceptual);
|
|
+ EXPECT_EQ(second->initial_render_intent(), RenderingIntent::kSaturation);
|
|
+ EXPECT_EQ(third->initial_render_intent(), RenderingIntent::kPerceptual);
|
|
+
|
|
+ const CFX_Matrix translated(1, 0, 0, 1, 17, 29);
|
|
+ auto moved =
|
|
+ data->GetPattern(source, translated, RenderingIntent::kPerceptual);
|
|
+ EXPECT_NE(moved, third);
|
|
+ EXPECT_EQ(moved->pattern_to_form(), translated);
|
|
+ EXPECT_EQ(third->pattern_to_form(), matrix);
|
|
+
|
|
+ // Once a user releases an old context, replacement releases the cache's last
|
|
+ // reference. Repeating with many distinct matrices does not retain variants.
|
|
+ for (int i = 0; i < 128; ++i) {
|
|
+ ObservedPtr<CPDF_Pattern> previous(
|
|
+ data->GetPattern(source, CFX_Matrix(1, 0, 0, 1, i, 0),
|
|
+ RenderingIntent::kAbsoluteColorimetric)
|
|
+ .Get());
|
|
+ ASSERT_TRUE(previous);
|
|
+ data->GetPattern(source, matrix, RenderingIntent::kRelativeColorimetric);
|
|
+ EXPECT_FALSE(previous);
|
|
+ }
|
|
+ EXPECT_EQ(first->initial_render_intent(), RenderingIntent::kPerceptual);
|
|
+ EXPECT_EQ(moved->pattern_to_form(), translated);
|
|
+}
|
|
+
|
|
+TEST_F(CPDFPatternContextTest, ParserKeepsInitialIntentAcrossRiAndSavedState) {
|
|
+ CPDF_TestDocument document;
|
|
+ auto source = MakeTiling(&document);
|
|
+ auto parent =
|
|
+ MakeParent(&document, source.Get(), nullptr,
|
|
+ "/AbsoluteColorimetric ri /Pattern cs /P scn 0 0 20 20 re f "
|
|
+ "q /Saturation ri /P scn 30 0 20 20 re f "
|
|
+ "Q /P scn 60 0 20 20 re f");
|
|
+ std::vector<std::unique_ptr<CPDF_Form>> retained_forms;
|
|
+ for (RenderingIntent initial :
|
|
+ {RenderingIntent::kPerceptual, RenderingIntent::kSaturation,
|
|
+ RenderingIntent::kPerceptual}) {
|
|
+ CPDF_AllStates state;
|
|
+ state.SetDefaultStates();
|
|
+ state.mutable_general_state().SetRenderIntent(initial);
|
|
+ state.mutable_color_state().SetRenderIntent(initial);
|
|
+ auto form = std::make_unique<CPDF_Form>(&document, nullptr, parent);
|
|
+ form->ParseContent(&state, nullptr, nullptr);
|
|
+ ASSERT_EQ(form->GetPageObjectCount(), 3u);
|
|
+ for (size_t i = 0; i < 3; ++i) {
|
|
+ auto* paint = form->GetPageObjectByIndex(i);
|
|
+ const RenderingIntent paint_intent =
|
|
+ i == 1 ? RenderingIntent::kSaturation
|
|
+ : RenderingIntent::kAbsoluteColorimetric;
|
|
+ EXPECT_EQ(paint->general_state().GetRenderIntent(), paint_intent);
|
|
+ auto pattern = paint->color_state().GetFillColor()->GetPattern();
|
|
+ ASSERT_TRUE(pattern);
|
|
+ EXPECT_EQ(pattern->initial_render_intent(), initial);
|
|
+ auto cell = pattern->AsTilingPattern()->Load(paint);
|
|
+ ASSERT_TRUE(cell);
|
|
+ ASSERT_EQ(cell->GetPageObjectCount(), 3u);
|
|
+ EXPECT_EQ(
|
|
+ cell->GetPageObjectByIndex(0)->general_state().GetRenderIntent(),
|
|
+ initial);
|
|
+ EXPECT_EQ(
|
|
+ cell->GetPageObjectByIndex(1)->general_state().GetRenderIntent(),
|
|
+ RenderingIntent::kAbsoluteColorimetric);
|
|
+ EXPECT_EQ(
|
|
+ cell->GetPageObjectByIndex(2)->general_state().GetRenderIntent(),
|
|
+ initial);
|
|
+ }
|
|
+ retained_forms.push_back(std::move(form));
|
|
+ }
|
|
+ // Replacing the shared-source cache must not recolor the first parsed Form.
|
|
+ auto* original = retained_forms[0]->GetPageObjectByIndex(0);
|
|
+ auto original_pattern = original->color_state().GetFillColor()->GetPattern();
|
|
+ EXPECT_EQ(original_pattern->initial_render_intent(),
|
|
+ RenderingIntent::kPerceptual);
|
|
+ EXPECT_EQ(original->general_state().GetRenderIntent(),
|
|
+ RenderingIntent::kAbsoluteColorimetric);
|
|
+ CPDF_Form default_form(&document, nullptr, parent);
|
|
+ default_form.ParseContent();
|
|
+ EXPECT_EQ(default_form.GetPageObjectByIndex(0)
|
|
+ ->color_state()
|
|
+ .GetFillColor()
|
|
+ ->GetPattern()
|
|
+ ->initial_render_intent(),
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
+}
|
|
+
|
|
+TEST_F(CPDFPatternContextTest, ShadingPatternAndShUseDifferentInheritedStates) {
|
|
+ CPDF_TestDocument document;
|
|
+ auto shading = MakeShading(&document);
|
|
+ auto pattern = document.NewIndirect<CPDF_Dictionary>();
|
|
+ pattern->SetNewFor<CPDF_Number>("PatternType", 2);
|
|
+ pattern->SetNewFor<CPDF_Reference>("Shading", &document,
|
|
+ shading->GetObjNum());
|
|
+ auto parent = MakeParent(
|
|
+ &document, pattern.Get(), shading.Get(),
|
|
+ "/AbsoluteColorimetric ri /Pattern cs /P scn 0 0 20 20 re f /S sh");
|
|
+ CPDF_AllStates state;
|
|
+ state.SetDefaultStates();
|
|
+ state.SetRenderIntent("Perceptual");
|
|
+ CPDF_Form form(&document, nullptr, parent);
|
|
+ form.ParseContent(&state, nullptr, nullptr);
|
|
+ ASSERT_EQ(form.GetPageObjectCount(), 2u);
|
|
+ auto* painted = form.GetPageObjectByIndex(0);
|
|
+ auto selected = painted->color_state().GetFillColor()->GetPattern();
|
|
+ ASSERT_TRUE(selected->AsShadingPattern());
|
|
+ EXPECT_EQ(selected->AsShadingPattern()->GetRenderIntent(
|
|
+ painted->general_state().GetRenderIntent()),
|
|
+ RenderingIntent::kPerceptual);
|
|
+ auto* direct = form.GetPageObjectByIndex(1)->AsShading();
|
|
+ ASSERT_TRUE(direct);
|
|
+ EXPECT_EQ(direct->general_state().GetRenderIntent(),
|
|
+ RenderingIntent::kAbsoluteColorimetric);
|
|
+ EXPECT_EQ(direct->pattern()->GetRenderIntent(
|
|
+ direct->general_state().GetRenderIntent()),
|
|
+ RenderingIntent::kAbsoluteColorimetric);
|
|
+
|
|
+ auto gs = pattern->SetNewFor<CPDF_Dictionary>("ExtGState");
|
|
+ gs->SetNewFor<CPDF_Name>("RI", "Saturation");
|
|
+ EXPECT_EQ(selected->AsShadingPattern()->GetRenderIntent(
|
|
+ RenderingIntent::kAbsoluteColorimetric),
|
|
+ RenderingIntent::kSaturation);
|
|
+ gs->SetNewFor<CPDF_Number>("RI", 42);
|
|
+ EXPECT_EQ(selected->AsShadingPattern()->GetRenderIntent(
|
|
+ RenderingIntent::kAbsoluteColorimetric),
|
|
+ RenderingIntent::kPerceptual);
|
|
+ gs->SetNewFor<CPDF_Name>("RI", "UnrecognizedIntent");
|
|
+ EXPECT_EQ(selected->AsShadingPattern()->GetRenderIntent(
|
|
+ RenderingIntent::kAbsoluteColorimetric),
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
+}
|
|
diff --git a/core/fpdfapi/page/cpdf_patterncs.cpp b/core/fpdfapi/page/cpdf_patterncs.cpp
|
|
--- a/core/fpdfapi/page/cpdf_patterncs.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_patterncs.cpp
|
|
@@ -48,7 +48,8 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_PatternCS::GetRGB(
|
|
- pdfium::span<const float> pBuf) const {
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent) const {
|
|
NOTREACHED();
|
|
}
|
|
|
|
@@ -57,10 +58,11 @@
|
|
}
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> CPDF_PatternCS::GetPatternRGB(
|
|
- const PatternValue& value) const {
|
|
+ const PatternValue& value,
|
|
+ RenderingIntent intent) const {
|
|
if (!base_cs_) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
- return base_cs_->GetRGB(value.GetComps());
|
|
+ return base_cs_->GetRGB(value.GetComps(), intent);
|
|
}
|
|
diff --git a/core/fpdfapi/page/cpdf_patterncs.h b/core/fpdfapi/page/cpdf_patterncs.h
|
|
--- a/core/fpdfapi/page/cpdf_patterncs.h
|
|
+++ b/core/fpdfapi/page/cpdf_patterncs.h
|
|
@@ -27,14 +27,17 @@
|
|
|
|
// CPDF_ColorSpace:
|
|
std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
- pdfium::span<const float> pBuf) const override;
|
|
+ pdfium::span<const float> pBuf,
|
|
+ RenderingIntent intent =
|
|
+ RenderingIntent::kRelativeColorimetric) const override;
|
|
const CPDF_PatternCS* AsPatternCS() const override;
|
|
uint32_t v_Load(CPDF_Document* doc,
|
|
const CPDF_Array* pArray,
|
|
std::set<const CPDF_Object*>* pVisited) override;
|
|
|
|
std::optional<FX_RGB_STRUCT<float>> GetPatternRGB(
|
|
- const PatternValue& value) const;
|
|
+ const PatternValue& value,
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric) const;
|
|
|
|
private:
|
|
CPDF_PatternCS();
|
|
diff --git a/core/fpdfapi/page/cpdf_renderingintent_unittest.cpp b/core/fpdfapi/page/cpdf_renderingintent_unittest.cpp
|
|
--- /dev/null
|
|
+++ b/core/fpdfapi/page/cpdf_renderingintent_unittest.cpp
|
|
@@ -0,0 +1,92 @@
|
|
+// Copyright 2026 The PDFium Authors
|
|
+// Use of this source code is governed by a BSD-style license that can be
|
|
+// found in the LICENSE file.
|
|
+
|
|
+#include "core/fpdfapi/page/cpdf_allstates.h"
|
|
+#include "core/fpdfapi/page/cpdf_colorspace.h"
|
|
+#include "core/fpdfapi/page/test_with_page_module.h"
|
|
+#include "testing/gtest/include/gtest/gtest.h"
|
|
+
|
|
+namespace {
|
|
+
|
|
+// A deterministic color space makes the color-cache and copy-on-write behavior
|
|
+// observable without relying on ICC tables or their precision.
|
|
+class IntentColorSpace final : public CPDF_ColorSpace {
|
|
+ public:
|
|
+ IntentColorSpace() : CPDF_ColorSpace(Family::kDeviceRGB) {}
|
|
+ std::optional<FX_RGB_STRUCT<float>> GetRGB(
|
|
+ pdfium::span<const float> values,
|
|
+ RenderingIntent intent) const override {
|
|
+ return FX_RGB_STRUCT<float>{static_cast<int>(intent) / 3.0f, 0, 0};
|
|
+ }
|
|
+
|
|
+ private:
|
|
+ uint32_t v_Load(CPDF_Document*,
|
|
+ const CPDF_Array*,
|
|
+ std::set<const CPDF_Object*>*) override {
|
|
+ return 3;
|
|
+ }
|
|
+};
|
|
+
|
|
+} // namespace
|
|
+
|
|
+using CPDFRenderingIntentTest = TestWithPageModule;
|
|
+
|
|
+TEST_F(CPDFRenderingIntentTest, DefaultAndExactNames) {
|
|
+ CPDF_GeneralState state;
|
|
+ EXPECT_EQ(state.GetRenderIntent(), RenderingIntent::kRelativeColorimetric);
|
|
+ state.SetRenderIntent("Perceptual");
|
|
+ EXPECT_EQ(state.GetRenderIntent(), RenderingIntent::kPerceptual);
|
|
+ state.SetRenderIntent("Saturation");
|
|
+ EXPECT_EQ(state.GetRenderIntent(), RenderingIntent::kSaturation);
|
|
+ state.SetRenderIntent("AbsoluteColorimetric");
|
|
+ EXPECT_EQ(state.GetRenderIntent(), RenderingIntent::kAbsoluteColorimetric);
|
|
+ for (const char* name : {"AbsoluteColorimetricSuffix", "Rela", ""}) {
|
|
+ state.SetRenderIntent("AbsoluteColorimetric");
|
|
+ state.SetRenderIntent(name);
|
|
+ EXPECT_EQ(state.GetRenderIntent(), RenderingIntent::kRelativeColorimetric);
|
|
+ }
|
|
+ state.SetRenderIntent("RelativeColorimetric");
|
|
+ EXPECT_EQ(state.GetRenderIntent(), RenderingIntent::kRelativeColorimetric);
|
|
+}
|
|
+
|
|
+TEST_F(CPDFRenderingIntentTest, ColorCacheAndSavedStateAreIndependent) {
|
|
+ CPDF_AllStates state;
|
|
+ state.SetDefaultStates();
|
|
+ auto colorspace = pdfium::MakeRetain<IntentColorSpace>();
|
|
+ state.mutable_color_state().SetFillColor(colorspace, {0, 0, 0});
|
|
+ state.mutable_color_state().SetStrokeColor(colorspace, {0, 0, 0});
|
|
+ const auto initial_fill = state.color_state().GetFillColorRef();
|
|
+ const auto initial_stroke = state.color_state().GetStrokeColorRef();
|
|
+ CPDF_AllStates saved(state);
|
|
+ state.SetRenderIntent("Perceptual");
|
|
+ EXPECT_NE(state.color_state().GetFillColorRef(), initial_fill);
|
|
+ EXPECT_NE(state.color_state().GetStrokeColorRef(), initial_stroke);
|
|
+ EXPECT_EQ(saved.color_state().GetFillColorRef(), initial_fill);
|
|
+ EXPECT_EQ(saved.color_state().GetStrokeColorRef(), initial_stroke);
|
|
+ EXPECT_EQ(saved.general_state().GetRenderIntent(),
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
+ state = saved;
|
|
+ EXPECT_EQ(state.color_state().GetFillColorRef(), initial_fill);
|
|
+ state.SetRenderIntent("Saturation");
|
|
+ const auto saturation_fill = state.color_state().GetFillColorRef();
|
|
+ state.mutable_color_state().SetFillColor(colorspace, {1, 0, 0});
|
|
+ EXPECT_EQ(state.color_state().GetFillColorRef(), saturation_fill);
|
|
+ state.SetRenderIntent("PerceptualInvalid");
|
|
+ EXPECT_EQ(state.color_state().GetFillColorRef(), initial_fill);
|
|
+}
|
|
+
|
|
+TEST_F(CPDFRenderingIntentTest, EmptyColorsSurviveIntentChanges) {
|
|
+ CPDF_ColorState state;
|
|
+ state.SetRenderIntent(RenderingIntent::kPerceptual);
|
|
+ EXPECT_FALSE(state.HasFillColor());
|
|
+ EXPECT_FALSE(state.HasStrokeColor());
|
|
+ state.Emplace();
|
|
+ state.SetRenderIntent(RenderingIntent::kAbsoluteColorimetric);
|
|
+ EXPECT_FALSE(state.HasFillColor());
|
|
+ EXPECT_FALSE(state.HasStrokeColor());
|
|
+ auto colorspace = pdfium::MakeRetain<IntentColorSpace>();
|
|
+ state.SetFillColor(colorspace, {0, 0, 0});
|
|
+ EXPECT_TRUE(state.HasFillColor());
|
|
+ EXPECT_EQ(state.GetFillColorRef(), FXSYS_BGR(0, 0, 255));
|
|
+}
|
|
diff --git a/core/fpdfapi/page/cpdf_shadingpattern.cpp b/core/fpdfapi/page/cpdf_shadingpattern.cpp
|
|
--- a/core/fpdfapi/page/cpdf_shadingpattern.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_shadingpattern.cpp
|
|
@@ -11,6 +11,7 @@
|
|
|
|
#include "core/fpdfapi/page/cpdf_docpagedata.h"
|
|
#include "core/fpdfapi/page/cpdf_function.h"
|
|
+#include "core/fpdfapi/page/cpdf_generalstate.h"
|
|
#include "core/fpdfapi/parser/cpdf_array.h"
|
|
#include "core/fpdfapi/parser/cpdf_dictionary.h"
|
|
#include "core/fpdfapi/parser/cpdf_document.h"
|
|
@@ -33,8 +34,10 @@
|
|
CPDF_ShadingPattern::CPDF_ShadingPattern(CPDF_Document* doc,
|
|
RetainPtr<CPDF_Object> pPatternObj,
|
|
bool bShading,
|
|
- const CFX_Matrix& parentMatrix)
|
|
- : CPDF_Pattern(doc, std::move(pPatternObj), parentMatrix),
|
|
+ const CFX_Matrix& parentMatrix,
|
|
+ RenderingIntent initial_render_intent)
|
|
+ : CPDF_Pattern(doc, std::move(pPatternObj), parentMatrix,
|
|
+ initial_render_intent),
|
|
shading_(bShading) {
|
|
DCHECK(document());
|
|
if (!bShading) {
|
|
@@ -97,6 +100,26 @@
|
|
: pattern_obj()->GetDict()->GetDirectObjectFor("Shading");
|
|
}
|
|
|
|
+RenderingIntent CPDF_ShadingPattern::GetRenderIntent(
|
|
+ RenderingIntent inherited_intent) const {
|
|
+ if (shading_) {
|
|
+ return inherited_intent;
|
|
+ }
|
|
+
|
|
+ RetainPtr<const CPDF_Dictionary> dict = pattern_obj()->GetDict();
|
|
+ RetainPtr<const CPDF_Dictionary> state_dict =
|
|
+ dict ? dict->GetDictFor("ExtGState") : nullptr;
|
|
+ RetainPtr<const CPDF_Object> intent =
|
|
+ state_dict ? state_dict->GetDirectObjectFor("RI") : nullptr;
|
|
+ if (!intent || !intent->IsName()) {
|
|
+ return initial_render_intent();
|
|
+ }
|
|
+
|
|
+ CPDF_GeneralState state;
|
|
+ state.SetRenderIntent(intent->GetString());
|
|
+ return state.GetRenderIntent();
|
|
+}
|
|
+
|
|
bool CPDF_ShadingPattern::Validate() const {
|
|
if (shading_type_ == kInvalidShading) {
|
|
return false;
|
|
diff --git a/core/fpdfapi/page/cpdf_shadingpattern.h b/core/fpdfapi/page/cpdf_shadingpattern.h
|
|
--- a/core/fpdfapi/page/cpdf_shadingpattern.h
|
|
+++ b/core/fpdfapi/page/cpdf_shadingpattern.h
|
|
@@ -55,6 +55,7 @@
|
|
ShadingType GetShadingType() const { return shading_type_; }
|
|
bool IsShadingObject() const { return shading_; }
|
|
RetainPtr<const CPDF_Object> GetShadingObject() const;
|
|
+ RenderingIntent GetRenderIntent(RenderingIntent inherited_intent) const;
|
|
RetainPtr<CPDF_ColorSpace> GetCS() const { return cs_; }
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& GetFuncs() const {
|
|
return functions_;
|
|
@@ -64,7 +65,9 @@
|
|
CPDF_ShadingPattern(CPDF_Document* doc,
|
|
RetainPtr<CPDF_Object> pPatternObj,
|
|
bool bShading,
|
|
- const CFX_Matrix& parentMatrix);
|
|
+ const CFX_Matrix& parentMatrix,
|
|
+ RenderingIntent initial_render_intent =
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
CPDF_ShadingPattern(const CPDF_ShadingPattern&) = delete;
|
|
CPDF_ShadingPattern& operator=(const CPDF_ShadingPattern&) = delete;
|
|
|
|
diff --git a/core/fpdfapi/page/cpdf_streamcontentparser.cpp b/core/fpdfapi/page/cpdf_streamcontentparser.cpp
|
|
--- a/core/fpdfapi/page/cpdf_streamcontentparser.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_streamcontentparser.cpp
|
|
@@ -406,6 +406,8 @@
|
|
object_holder_(pObjHolder),
|
|
recursion_state_(recursion_state),
|
|
bbox_(rcBBox),
|
|
+ initial_render_intent_(pStates ? pStates->general_state().GetRenderIntent()
|
|
+ : RenderingIntent::kRelativeColorimetric),
|
|
cur_states_(std::make_unique<CPDF_AllStates>()) {
|
|
if (pmtContentToUser) {
|
|
mt_content_to_user_ = *pmtContentToUser;
|
|
@@ -1094,7 +1096,12 @@
|
|
GetNumbers(3));
|
|
}
|
|
|
|
-void CPDF_StreamContentParser::Handle_SetRenderIntent() {}
|
|
+void CPDF_StreamContentParser::Handle_SetRenderIntent() {
|
|
+ RetainPtr<CPDF_Object> name = GetObject(0);
|
|
+ if (name && name->IsName()) {
|
|
+ cur_states_->SetRenderIntent(name->GetString());
|
|
+ }
|
|
+}
|
|
|
|
void CPDF_StreamContentParser::Handle_CloseStrokePath() {
|
|
Handle_ClosePath();
|
|
@@ -1299,7 +1306,7 @@
|
|
return nullptr;
|
|
}
|
|
return CPDF_DocPageData::FromDocument(document_)->GetPattern(
|
|
- std::move(pPattern), cur_states_->parent_matrix());
|
|
+ std::move(pPattern), cur_states_->parent_matrix(), initial_render_intent_);
|
|
}
|
|
|
|
RetainPtr<CPDF_ShadingPattern> CPDF_StreamContentParser::FindShading(
|
|
diff --git a/core/fpdfapi/page/cpdf_streamcontentparser.h b/core/fpdfapi/page/cpdf_streamcontentparser.h
|
|
--- a/core/fpdfapi/page/cpdf_streamcontentparser.h
|
|
+++ b/core/fpdfapi/page/cpdf_streamcontentparser.h
|
|
@@ -20,6 +20,7 @@
|
|
#include "core/fxcrt/bytestring.h"
|
|
#include "core/fxcrt/fx_coordinates.h"
|
|
#include "core/fxcrt/fx_number.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/span.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
@@ -216,6 +217,8 @@
|
|
UnownedPtr<CPDF_Form::RecursionState> const recursion_state_;
|
|
CFX_Matrix mt_content_to_user_;
|
|
const CFX_FloatRect bbox_;
|
|
+ // Remains unchanged by ri, gs, q/Q and cm within this content stream.
|
|
+ const RenderingIntent initial_render_intent_;
|
|
uint32_t param_start_pos_ = 0;
|
|
uint32_t param_count_ = 0;
|
|
std::unique_ptr<CPDF_StreamParser> syntax_;
|
|
diff --git a/core/fpdfapi/page/cpdf_tilingpattern.cpp b/core/fpdfapi/page/cpdf_tilingpattern.cpp
|
|
--- a/core/fpdfapi/page/cpdf_tilingpattern.cpp
|
|
+++ b/core/fpdfapi/page/cpdf_tilingpattern.cpp
|
|
@@ -20,8 +20,10 @@
|
|
|
|
CPDF_TilingPattern::CPDF_TilingPattern(CPDF_Document* doc,
|
|
RetainPtr<CPDF_Object> pPatternObj,
|
|
- const CFX_Matrix& parentMatrix)
|
|
- : CPDF_Pattern(doc, std::move(pPatternObj), parentMatrix) {
|
|
+ const CFX_Matrix& parentMatrix,
|
|
+ RenderingIntent initial_render_intent)
|
|
+ : CPDF_Pattern(doc, std::move(pPatternObj), parentMatrix,
|
|
+ initial_render_intent) {
|
|
DCHECK(document());
|
|
colored_ = pattern_obj()->GetDict()->GetIntegerFor("PaintType") == 1;
|
|
SetPatternToFormMatrix();
|
|
@@ -53,6 +55,11 @@
|
|
all_states.mutable_graph_state().Emplace();
|
|
all_states.mutable_text_state().Emplace();
|
|
all_states.mutable_general_state() = pPageObj->general_state();
|
|
+ // Pattern cells inherit the initial state of their defining content stream,
|
|
+ // not the state of the object later painted with the pattern (PDF 1.7, 4.6.2).
|
|
+ // Explicit ri/gs and q/Q inside the cell are then handled by the parser.
|
|
+ all_states.mutable_general_state().SetRenderIntent(initial_render_intent());
|
|
+ all_states.mutable_color_state().SetRenderIntent(initial_render_intent());
|
|
form->ParseContent(&all_states, &matrix, nullptr);
|
|
bbox_ = dict->GetRectFor("BBox");
|
|
return form;
|
|
diff --git a/core/fpdfapi/page/cpdf_tilingpattern.h b/core/fpdfapi/page/cpdf_tilingpattern.h
|
|
--- a/core/fpdfapi/page/cpdf_tilingpattern.h
|
|
+++ b/core/fpdfapi/page/cpdf_tilingpattern.h
|
|
@@ -36,7 +36,9 @@
|
|
private:
|
|
CPDF_TilingPattern(CPDF_Document* doc,
|
|
RetainPtr<CPDF_Object> pPatternObj,
|
|
- const CFX_Matrix& parentMatrix);
|
|
+ const CFX_Matrix& parentMatrix,
|
|
+ RenderingIntent initial_render_intent =
|
|
+ RenderingIntent::kRelativeColorimetric);
|
|
CPDF_TilingPattern(const CPDF_TilingPattern&) = delete;
|
|
CPDF_TilingPattern& operator=(const CPDF_TilingPattern&) = delete;
|
|
|
|
diff --git a/core/fpdfapi/render/cpdf_rendershading.cpp b/core/fpdfapi/render/cpdf_rendershading.cpp
|
|
--- a/core/fpdfapi/render/cpdf_rendershading.cpp
|
|
+++ b/core/fpdfapi/render/cpdf_rendershading.cpp
|
|
@@ -67,6 +67,7 @@
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
const RetainPtr<CPDF_ColorSpace>& pCS,
|
|
int alpha,
|
|
+ RenderingIntent rendering_intent,
|
|
std::array<FX_ARGB, kShadingSteps>* output) {
|
|
const uint32_t results_count = GetValidatedOutputsCount(funcs, pCS);
|
|
if (results_count == 0) {
|
|
@@ -91,7 +92,7 @@
|
|
result_span = result_span.subspan(nresults.value());
|
|
}
|
|
}
|
|
- auto rgb = pCS->GetRGBOrZerosOnError(result_array);
|
|
+ auto rgb = pCS->GetRGBOrZerosOnError(result_array, rendering_intent);
|
|
shading_steps[i] =
|
|
ArgbEncode(alpha, FXSYS_roundf(rgb.red * 255),
|
|
FXSYS_roundf(rgb.green * 255), FXSYS_roundf(rgb.blue * 255));
|
|
@@ -111,7 +112,8 @@
|
|
const CPDF_Dictionary* dict,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
const RetainPtr<CPDF_ColorSpace>& pCS,
|
|
- int alpha) {
|
|
+ int alpha,
|
|
+ RenderingIntent rendering_intent) {
|
|
DCHECK_EQ(pBitmap->GetFormat(), FXDIB_Format::kBgra);
|
|
|
|
RetainPtr<const CPDF_Array> pCoords = dict->GetArrayFor("Coords");
|
|
@@ -141,7 +143,7 @@
|
|
float axis_len_square = (x_span * x_span) + (y_span * y_span);
|
|
|
|
std::array<FX_ARGB, kShadingSteps> shading_steps;
|
|
- if (!GetShadingSteps(t_min, t_max, funcs, pCS, alpha, &shading_steps)) {
|
|
+ if (!GetShadingSteps(t_min, t_max, funcs, pCS, alpha, rendering_intent, &shading_steps)) {
|
|
return;
|
|
}
|
|
|
|
@@ -179,7 +181,8 @@
|
|
const CPDF_Dictionary* dict,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
const RetainPtr<CPDF_ColorSpace>& pCS,
|
|
- int alpha) {
|
|
+ int alpha,
|
|
+ RenderingIntent rendering_intent) {
|
|
DCHECK_EQ(pBitmap->GetFormat(), FXDIB_Format::kBgra);
|
|
|
|
RetainPtr<const CPDF_Array> pCoords = dict->GetArrayFor("Coords");
|
|
@@ -205,7 +208,7 @@
|
|
const bool bEndExtend = pArray && pArray->GetBooleanAt(1, false);
|
|
|
|
std::array<FX_ARGB, kShadingSteps> shading_steps;
|
|
- if (!GetShadingSteps(t_min, t_max, funcs, pCS, alpha, &shading_steps)) {
|
|
+ if (!GetShadingSteps(t_min, t_max, funcs, pCS, alpha, rendering_intent, &shading_steps)) {
|
|
return;
|
|
}
|
|
|
|
@@ -279,7 +282,8 @@
|
|
const CPDF_Dictionary* dict,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
const RetainPtr<CPDF_ColorSpace>& pCS,
|
|
- int alpha) {
|
|
+ int alpha,
|
|
+ RenderingIntent rendering_intent) {
|
|
DCHECK_EQ(pBitmap->GetFormat(), FXDIB_Format::kBgra);
|
|
|
|
const uint32_t total_results = GetValidatedOutputsCount(funcs, pCS);
|
|
@@ -327,7 +331,7 @@
|
|
result_span = result_span.subspan(nresults.value());
|
|
}
|
|
}
|
|
- auto rgb = pCS->GetRGBOrZerosOnError(result_array);
|
|
+ auto rgb = pCS->GetRGBOrZerosOnError(result_array, rendering_intent);
|
|
dib_buf[column] = ArgbEncode(alpha, static_cast<int32_t>(rgb.red * 255),
|
|
static_cast<int32_t>(rgb.green * 255),
|
|
static_cast<int32_t>(rgb.blue * 255));
|
|
@@ -461,11 +465,12 @@
|
|
RetainPtr<const CPDF_Stream> pShadingStream,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
RetainPtr<CPDF_ColorSpace> pCS,
|
|
- int alpha) {
|
|
+ int alpha,
|
|
+ RenderingIntent rendering_intent) {
|
|
DCHECK_EQ(pBitmap->GetFormat(), FXDIB_Format::kBgra);
|
|
|
|
CPDF_MeshStream stream(kFreeFormGouraudTriangleMeshShading, funcs,
|
|
- std::move(pShadingStream), pCS);
|
|
+ std::move(pShadingStream), pCS, rendering_intent);
|
|
if (!stream.Load()) {
|
|
return;
|
|
}
|
|
@@ -473,7 +478,7 @@
|
|
std::array<FX_ARGB, kShadingSteps> shading_steps;
|
|
if (!funcs.empty()) {
|
|
if (!GetShadingSteps(stream.component_min(0), stream.component_max(0),
|
|
- funcs, pCS, alpha, &shading_steps)) {
|
|
+ funcs, pCS, alpha, rendering_intent, &shading_steps)) {
|
|
return;
|
|
}
|
|
}
|
|
@@ -522,7 +527,8 @@
|
|
RetainPtr<const CPDF_Stream> pShadingStream,
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
RetainPtr<CPDF_ColorSpace> pCS,
|
|
- int alpha) {
|
|
+ int alpha,
|
|
+ RenderingIntent rendering_intent) {
|
|
DCHECK_EQ(pBitmap->GetFormat(), FXDIB_Format::kBgra);
|
|
|
|
int row_verts = pShadingStream->GetDict()->GetIntegerFor("VerticesPerRow");
|
|
@@ -531,7 +537,7 @@
|
|
}
|
|
|
|
CPDF_MeshStream stream(kLatticeFormGouraudTriangleMeshShading, funcs,
|
|
- std::move(pShadingStream), pCS);
|
|
+ std::move(pShadingStream), pCS, rendering_intent);
|
|
if (!stream.Load()) {
|
|
return;
|
|
}
|
|
@@ -539,7 +545,7 @@
|
|
std::array<FX_ARGB, kShadingSteps> shading_steps;
|
|
if (!funcs.empty()) {
|
|
if (!GetShadingSteps(stream.component_min(0), stream.component_max(0),
|
|
- funcs, pCS, alpha, &shading_steps)) {
|
|
+ funcs, pCS, alpha, rendering_intent, &shading_steps)) {
|
|
return;
|
|
}
|
|
}
|
|
@@ -853,7 +859,8 @@
|
|
const std::vector<std::unique_ptr<CPDF_Function>>& funcs,
|
|
RetainPtr<CPDF_ColorSpace> pCS,
|
|
bool bNoPathSmooth,
|
|
- int alpha) {
|
|
+ int alpha,
|
|
+ RenderingIntent rendering_intent) {
|
|
DCHECK_EQ(pBitmap->GetFormat(), FXDIB_Format::kBgra);
|
|
DCHECK(type == kCoonsPatchMeshShading ||
|
|
type == kTensorProductPatchMeshShading);
|
|
@@ -864,7 +871,7 @@
|
|
return;
|
|
}
|
|
|
|
- CPDF_MeshStream stream(type, funcs, std::move(pShadingStream), pCS);
|
|
+ CPDF_MeshStream stream(type, funcs, std::move(pShadingStream), pCS, rendering_intent);
|
|
if (!stream.Load()) {
|
|
return;
|
|
}
|
|
@@ -872,7 +879,7 @@
|
|
std::array<FX_ARGB, kShadingSteps> shading_steps;
|
|
if (!funcs.empty()) {
|
|
if (!GetShadingSteps(stream.component_min(0), stream.component_max(0),
|
|
- funcs, pCS, alpha, &shading_steps)) {
|
|
+ funcs, pCS, alpha, rendering_intent, &shading_steps)) {
|
|
return;
|
|
}
|
|
}
|
|
@@ -1012,7 +1019,8 @@
|
|
const CFX_Matrix& mtMatrix,
|
|
const FX_RECT& clip_rect,
|
|
int alpha,
|
|
- const CPDF_RenderOptions& options) {
|
|
+ const CPDF_RenderOptions& options,
|
|
+ RenderingIntent rendering_intent) {
|
|
RetainPtr<CPDF_ColorSpace> pColorSpace = pPattern->GetCS();
|
|
if (!pColorSpace) {
|
|
return;
|
|
@@ -1027,7 +1035,7 @@
|
|
std::vector<float> comps = ReadArrayElementsToVector(
|
|
pBackColor.Get(), pColorSpace->ComponentCount());
|
|
|
|
- auto rgb = pColorSpace->GetRGBOrZerosOnError(comps);
|
|
+ auto rgb = pColorSpace->GetRGBOrZerosOnError(comps, rendering_intent);
|
|
background = ArgbEncode(255, static_cast<int32_t>(rgb.red * 255),
|
|
static_cast<int32_t>(rgb.green * 255),
|
|
static_cast<int32_t>(rgb.blue * 255));
|
|
@@ -1039,7 +1047,10 @@
|
|
mtMatrix.TransformRect(dict->GetRectFor("BBox")).GetOuterRect());
|
|
}
|
|
#if defined(PDF_USE_SKIA)
|
|
- if (pDevice->RenderCapShading() &&
|
|
+ // The native shading API has no rendering-intent argument. Use the
|
|
+ // color-managed bitmap path when its default intent is not appropriate.
|
|
+ if (rendering_intent == RenderingIntent::kRelativeColorimetric &&
|
|
+ pDevice->RenderCapShading() &&
|
|
pDevice->DrawShading(*pPattern, mtMatrix, clip_rect_bbox, alpha)) {
|
|
return;
|
|
}
|
|
@@ -1061,15 +1072,15 @@
|
|
return;
|
|
case kFunctionBasedShading:
|
|
DrawFuncShading(pBitmap, final_matrix, dict.Get(), funcs, pColorSpace,
|
|
- alpha);
|
|
+ alpha, rendering_intent);
|
|
break;
|
|
case kAxialShading:
|
|
DrawAxialShading(pBitmap, final_matrix, dict.Get(), funcs, pColorSpace,
|
|
- alpha);
|
|
+ alpha, rendering_intent);
|
|
break;
|
|
case kRadialShading:
|
|
DrawRadialShading(pBitmap, final_matrix, dict.Get(), funcs, pColorSpace,
|
|
- alpha);
|
|
+ alpha, rendering_intent);
|
|
break;
|
|
case kFreeFormGouraudTriangleMeshShading: {
|
|
// The shading object can be a stream or a dictionary. We do not handle
|
|
@@ -1078,7 +1089,7 @@
|
|
ToStream(pPattern->GetShadingObject());
|
|
if (pStream) {
|
|
DrawFreeGouraudShading(pBitmap, final_matrix, std::move(pStream), funcs,
|
|
- pColorSpace, alpha);
|
|
+ pColorSpace, alpha, rendering_intent);
|
|
}
|
|
break;
|
|
}
|
|
@@ -1089,7 +1100,7 @@
|
|
ToStream(pPattern->GetShadingObject());
|
|
if (pStream) {
|
|
DrawLatticeGouraudShading(pBitmap, final_matrix, std::move(pStream),
|
|
- funcs, pColorSpace, alpha);
|
|
+ funcs, pColorSpace, alpha, rendering_intent);
|
|
}
|
|
break;
|
|
}
|
|
@@ -1102,7 +1113,8 @@
|
|
if (pStream) {
|
|
DrawCoonPatchMeshes(pPattern->GetShadingType(), pBitmap, final_matrix,
|
|
std::move(pStream), funcs, pColorSpace,
|
|
- options.GetOptions().bNoPathSmooth, alpha);
|
|
+ options.GetOptions().bNoPathSmooth, alpha,
|
|
+ rendering_intent);
|
|
}
|
|
break;
|
|
}
|
|
diff --git a/core/fpdfapi/render/cpdf_rendershading.h b/core/fpdfapi/render/cpdf_rendershading.h
|
|
--- a/core/fpdfapi/render/cpdf_rendershading.h
|
|
+++ b/core/fpdfapi/render/cpdf_rendershading.h
|
|
@@ -6,6 +6,8 @@
|
|
|
|
#ifndef CORE_FPDFAPI_RENDER_CPDF_RENDERSHADING_H_
|
|
#define CORE_FPDFAPI_RENDER_CPDF_RENDERSHADING_H_
|
|
+
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
|
|
class CFX_Matrix;
|
|
class CFX_RenderDevice;
|
|
@@ -24,7 +26,8 @@
|
|
const CFX_Matrix& mtMatrix,
|
|
const FX_RECT& clip_rect,
|
|
int alpha,
|
|
- const CPDF_RenderOptions& options);
|
|
+ const CPDF_RenderOptions& options,
|
|
+ RenderingIntent rendering_intent);
|
|
|
|
CPDF_RenderShading() = delete;
|
|
CPDF_RenderShading(const CPDF_RenderShading&) = delete;
|
|
diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp
|
|
--- a/core/fpdfapi/render/cpdf_renderstatus.cpp
|
|
+++ b/core/fpdfapi/render/cpdf_renderstatus.cpp
|
|
@@ -20,6 +20,7 @@
|
|
#include "core/fpdfapi/font/cpdf_font.h"
|
|
#include "core/fpdfapi/font/cpdf_type3char.h"
|
|
#include "core/fpdfapi/font/cpdf_type3font.h"
|
|
+#include "core/fpdfapi/page/cpdf_allstates.h"
|
|
#include "core/fpdfapi/page/cpdf_docpagedata.h"
|
|
#include "core/fpdfapi/page/cpdf_form.h"
|
|
#include "core/fpdfapi/page/cpdf_formobject.h"
|
|
@@ -962,7 +963,8 @@
|
|
continue;
|
|
}
|
|
|
|
- CPDF_Type3Char* pType3Char = pType3Font->LoadChar(charcode);
|
|
+ const RenderingIntent intent = textobj->general_state().GetRenderIntent();
|
|
+ CPDF_Type3Char* pType3Char = pType3Font->LoadChar(charcode, intent);
|
|
if (!pType3Char) {
|
|
continue;
|
|
}
|
|
@@ -1064,7 +1066,7 @@
|
|
CPDF_DocRenderData::FromDocument(doc)->GetCachedType3(pType3Font);
|
|
|
|
const CFX_GlyphBitmap* pBitmap =
|
|
- pCache->LoadGlyphBitmap(charcode, matrix);
|
|
+ pCache->LoadGlyphBitmap(charcode, matrix, intent);
|
|
if (!pBitmap) {
|
|
continue;
|
|
}
|
|
@@ -1164,6 +1166,7 @@
|
|
CPDF_PathObject path;
|
|
path.mutable_graph_state() = textobj->graph_state();
|
|
path.mutable_color_state() = textobj->color_state();
|
|
+ path.mutable_general_state() = textobj->general_state();
|
|
|
|
CFX_Matrix matrix = charpos.GetEffectiveMatrix(CFX_Matrix(
|
|
font_size, 0, 0, font_size, charpos.origin_.x, charpos.origin_.y));
|
|
@@ -1200,7 +1203,8 @@
|
|
FXSYS_roundf(255 * (stroke ? pPageObj->general_state().GetStrokeAlpha()
|
|
: pPageObj->general_state().GetFillAlpha()));
|
|
CPDF_RenderShading::Draw(device_, context_, cur_obj_, pattern, matrix, rect,
|
|
- alpha, options_);
|
|
+ alpha, options_, pattern->GetRenderIntent(
|
|
+ pPageObj->general_state().GetRenderIntent()));
|
|
}
|
|
|
|
void CPDF_RenderStatus::ProcessShading(const CPDF_ShadingObject* pShadingObj,
|
|
@@ -1214,7 +1218,7 @@
|
|
CPDF_RenderShading::Draw(
|
|
device_, context_, cur_obj_, pShadingObj->pattern(), matrix, rect,
|
|
FXSYS_roundf(255 * pShadingObj->general_state().GetFillAlpha()),
|
|
- options_);
|
|
+ options_, pShadingObj->general_state().GetRenderIntent());
|
|
}
|
|
|
|
void CPDF_RenderStatus::DrawTilingPattern(CPDF_TilingPattern* pattern,
|
|
@@ -1448,7 +1452,20 @@
|
|
|
|
CPDF_Form form(context_->GetDocument(), context_->GetMutablePageResources(),
|
|
pGroup);
|
|
- form.ParseContent();
|
|
+ // Keep the parser's ordinary empty-stream initial state, inheriting only the
|
|
+ // color-conversion intent. In particular, do not inherit the active mask,
|
|
+ // alpha, clipping path, or current colors into the mask's content stream.
|
|
+ CPDF_AllStates mask_states;
|
|
+ mask_states.mutable_general_state().Emplace();
|
|
+ mask_states.mutable_graph_state().Emplace();
|
|
+ mask_states.mutable_text_state().Emplace();
|
|
+ mask_states.mutable_color_state().Emplace();
|
|
+ const RenderingIntent rendering_intent =
|
|
+ cur_obj_ ? cur_obj_->general_state().GetRenderIntent()
|
|
+ : initial_states_.general_state().GetRenderIntent();
|
|
+ mask_states.mutable_general_state().SetRenderIntent(rendering_intent);
|
|
+ mask_states.mutable_color_state().SetRenderIntent(rendering_intent);
|
|
+ form.ParseContent(&mask_states, nullptr, nullptr);
|
|
|
|
bool bLuminosity =
|
|
smask_dict->GetByteStringFor(pdfium::transparency::kSoftMaskSubType) !=
|
|
@@ -1574,7 +1591,10 @@
|
|
std::vector<float> floats = ReadArrayElementsToVector(pBC.Get(), count);
|
|
floats.resize(comps);
|
|
|
|
- auto rgb = pCS->GetRGBOrZerosOnError(floats);
|
|
+ const RenderingIntent rendering_intent =
|
|
+ cur_obj_ ? cur_obj_->general_state().GetRenderIntent()
|
|
+ : initial_states_.general_state().GetRenderIntent();
|
|
+ auto rgb = pCS->GetRGBOrZerosOnError(floats, rendering_intent);
|
|
return ArgbEncode(255, static_cast<int>(rgb.red * 255),
|
|
static_cast<int>(rgb.green * 255),
|
|
static_cast<int>(rgb.blue * 255));
|
|
diff --git a/core/fpdfapi/render/cpdf_renderstatus.h b/core/fpdfapi/render/cpdf_renderstatus.h
|
|
--- a/core/fpdfapi/render/cpdf_renderstatus.h
|
|
+++ b/core/fpdfapi/render/cpdf_renderstatus.h
|
|
@@ -106,6 +106,7 @@
|
|
RetainPtr<const CPDF_Object> pObject) const;
|
|
|
|
FX_ARGB GetFillArgb(CPDF_PageObject* pObj) const;
|
|
+ FX_ARGB GetStrokeArgb(CPDF_PageObject* pObj) const;
|
|
FX_ARGB GetFillArgbForType3(CPDF_PageObject* pObj) const;
|
|
|
|
void DrawTilingPattern(CPDF_TilingPattern* pattern,
|
|
@@ -185,7 +186,6 @@
|
|
FX_ARGB GetBackgroundColor(const CPDF_Dictionary* pSMaskDict,
|
|
const CPDF_Dictionary* group_dict,
|
|
CPDF_ColorSpace::Family* pCSFamily);
|
|
- FX_ARGB GetStrokeArgb(CPDF_PageObject* pObj) const;
|
|
FX_RECT GetObjectClippedRect(const CPDF_PageObject* pObj,
|
|
const CFX_Matrix& mtObj2Device) const;
|
|
// Returns the format that is compatible with `device_`.
|
|
diff --git a/core/fpdfapi/render/cpdf_rendertiling.cpp b/core/fpdfapi/render/cpdf_rendertiling.cpp
|
|
--- a/core/fpdfapi/render/cpdf_rendertiling.cpp
|
|
+++ b/core/fpdfapi/render/cpdf_rendertiling.cpp
|
|
@@ -234,7 +234,8 @@
|
|
pPatternBitmap->ConvertColorScale(/*is_white_on_black=*/false);
|
|
}
|
|
|
|
- FX_ARGB fill_argb = pRenderStatus->GetFillArgb(pPageObj);
|
|
+ FX_ARGB fill_argb = bStroke ? pRenderStatus->GetStrokeArgb(pPageObj)
|
|
+ : pRenderStatus->GetFillArgb(pPageObj);
|
|
int clip_width = clip_box.right - clip_box.left;
|
|
int clip_height = clip_box.bottom - clip_box.top;
|
|
auto pScreen = pdfium::MakeRetain<CFX_DIBitmap>();
|
|
diff --git a/core/fpdfapi/render/cpdf_type3cache.cpp b/core/fpdfapi/render/cpdf_type3cache.cpp
|
|
--- a/core/fpdfapi/render/cpdf_type3cache.cpp
|
|
+++ b/core/fpdfapi/render/cpdf_type3cache.cpp
|
|
@@ -82,12 +82,19 @@
|
|
|
|
const CFX_GlyphBitmap* CPDF_Type3Cache::LoadGlyphBitmap(
|
|
uint32_t charcode,
|
|
- const CFX_Matrix& mtMatrix) {
|
|
+ const CFX_Matrix& mtMatrix,
|
|
+ RenderingIntent intent) {
|
|
+ if (charcode >= 256 || static_cast<unsigned>(intent) >
|
|
+ static_cast<unsigned>(
|
|
+ RenderingIntent::kAbsoluteColorimetric)) {
|
|
+ return nullptr;
|
|
+ }
|
|
SizeKey keygen = {
|
|
FXSYS_roundf(mtMatrix.a * 10000),
|
|
FXSYS_roundf(mtMatrix.b * 10000),
|
|
FXSYS_roundf(mtMatrix.c * 10000),
|
|
FXSYS_roundf(mtMatrix.d * 10000),
|
|
+ intent,
|
|
};
|
|
CPDF_Type3GlyphMap* pSizeCache;
|
|
auto it = size_map_.find(keygen);
|
|
@@ -104,7 +111,7 @@
|
|
}
|
|
|
|
std::unique_ptr<CFX_GlyphBitmap> pNewBitmap =
|
|
- RenderGlyph(pSizeCache, charcode, mtMatrix);
|
|
+ RenderGlyph(pSizeCache, charcode, mtMatrix, intent);
|
|
CFX_GlyphBitmap* pGlyphBitmap = pNewBitmap.get();
|
|
pSizeCache->SetBitmap(charcode, std::move(pNewBitmap));
|
|
return pGlyphBitmap;
|
|
@@ -113,8 +120,9 @@
|
|
std::unique_ptr<CFX_GlyphBitmap> CPDF_Type3Cache::RenderGlyph(
|
|
CPDF_Type3GlyphMap* pSize,
|
|
uint32_t charcode,
|
|
- const CFX_Matrix& mtMatrix) {
|
|
- CPDF_Type3Char* pChar = font_->LoadChar(charcode);
|
|
+ const CFX_Matrix& mtMatrix,
|
|
+ RenderingIntent intent) {
|
|
+ CPDF_Type3Char* pChar = font_->LoadChar(charcode, intent);
|
|
if (!pChar) {
|
|
return nullptr;
|
|
}
|
|
diff --git a/core/fpdfapi/render/cpdf_type3cache.h b/core/fpdfapi/render/cpdf_type3cache.h
|
|
--- a/core/fpdfapi/render/cpdf_type3cache.h
|
|
+++ b/core/fpdfapi/render/cpdf_type3cache.h
|
|
@@ -15,6 +15,7 @@
|
|
|
|
#include "core/fxcrt/bytestring.h"
|
|
#include "core/fxcrt/observed_ptr.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
|
|
class CFX_GlyphBitmap;
|
|
@@ -27,17 +28,20 @@
|
|
CONSTRUCT_VIA_MAKE_RETAIN;
|
|
|
|
const CFX_GlyphBitmap* LoadGlyphBitmap(uint32_t charcode,
|
|
- const CFX_Matrix& mtMatrix);
|
|
+ const CFX_Matrix& mtMatrix,
|
|
+ RenderingIntent intent);
|
|
|
|
private:
|
|
- using SizeKey = std::tuple<int, int, int, int>;
|
|
+ // Preserve the existing size key, with at most four RI variants per size.
|
|
+ using SizeKey = std::tuple<int, int, int, int, RenderingIntent>;
|
|
|
|
explicit CPDF_Type3Cache(CPDF_Type3Font* font);
|
|
~CPDF_Type3Cache() override;
|
|
|
|
std::unique_ptr<CFX_GlyphBitmap> RenderGlyph(CPDF_Type3GlyphMap* pSize,
|
|
uint32_t charcode,
|
|
- const CFX_Matrix& mtMatrix);
|
|
+ const CFX_Matrix& mtMatrix,
|
|
+ RenderingIntent intent);
|
|
|
|
RetainPtr<CPDF_Type3Font> const font_;
|
|
std::map<SizeKey, std::unique_ptr<CPDF_Type3GlyphMap>> size_map_;
|
|
diff --git a/core/fxcodec/icc/icc_transform.cpp b/core/fxcodec/icc/icc_transform.cpp
|
|
--- a/core/fxcodec/icc/icc_transform.cpp
|
|
+++ b/core/fxcodec/icc/icc_transform.cpp
|
|
@@ -54,7 +54,8 @@
|
|
|
|
// static
|
|
std::unique_ptr<IccTransform> IccTransform::CreateTransformSRGB(
|
|
- pdfium::span<const uint8_t> span) {
|
|
+ pdfium::span<const uint8_t> span,
|
|
+ RenderingIntent intent) {
|
|
ScopedCmsProfile srcProfile(cmsOpenProfileFromMem(
|
|
span.data(), pdfium::checked_cast<cmsUInt32Number>(span.size())));
|
|
if (!srcProfile) {
|
|
@@ -96,9 +97,9 @@
|
|
cmsHTRANSFORM hTransform = nullptr;
|
|
switch (dstCS) {
|
|
case cmsSigRgbData:
|
|
- hTransform =
|
|
- cmsCreateTransform(srcProfile.get(), srcFormat, dstProfile.get(),
|
|
- TYPE_BGR_8, INTENT_PERCEPTUAL, /*dwFlags=*/0);
|
|
+ hTransform = cmsCreateTransform(
|
|
+ srcProfile.get(), srcFormat, dstProfile.get(), TYPE_BGR_8,
|
|
+ static_cast<cmsUInt32Number>(intent), /*dwFlags=*/0);
|
|
break;
|
|
case cmsSigGrayData:
|
|
case cmsSigCmykData:
|
|
diff --git a/core/fxcodec/icc/icc_transform.h b/core/fxcodec/icc/icc_transform.h
|
|
--- a/core/fxcodec/icc/icc_transform.h
|
|
+++ b/core/fxcodec/icc/icc_transform.h
|
|
@@ -12,6 +12,7 @@
|
|
#include <memory>
|
|
|
|
#include "core/fxcodec/fx_codec_def.h"
|
|
+#include "core/fxcrt/rendering_intent.h"
|
|
#include "core/fxcrt/span.h"
|
|
|
|
#if defined(USE_SYSTEM_LCMS2)
|
|
@@ -25,7 +26,8 @@
|
|
class IccTransform {
|
|
public:
|
|
static std::unique_ptr<IccTransform> CreateTransformSRGB(
|
|
- pdfium::span<const uint8_t> span);
|
|
+ pdfium::span<const uint8_t> span,
|
|
+ RenderingIntent intent = RenderingIntent::kRelativeColorimetric);
|
|
|
|
~IccTransform();
|
|
|
|
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
|
|
--- a/core/fxcrt/BUILD.gn
|
|
+++ b/core/fxcrt/BUILD.gn
|
|
@@ -113,6 +113,7 @@
|
|
"ptr_util.h",
|
|
"raw_span.h",
|
|
"retain_ptr.h",
|
|
+ "rendering_intent.h",
|
|
"scoped_set_insertion.h",
|
|
"shared_copy_on_write.h",
|
|
"span.h",
|
|
diff --git a/core/fxcrt/rendering_intent.h b/core/fxcrt/rendering_intent.h
|
|
--- /dev/null
|
|
+++ b/core/fxcrt/rendering_intent.h
|
|
@@ -0,0 +1,16 @@
|
|
+// Copyright 2026 The PDFium Authors
|
|
+// Use of this source code is governed by a BSD-style license that can be
|
|
+// found in the LICENSE file.
|
|
+
|
|
+#ifndef CORE_FXCRT_RENDERING_INTENT_H_
|
|
+#define CORE_FXCRT_RENDERING_INTENT_H_
|
|
+
|
|
+// Values match the ICC rendering intents, as used by LittleCMS.
|
|
+enum class RenderingIntent {
|
|
+ kPerceptual = 0,
|
|
+ kRelativeColorimetric = 1,
|
|
+ kSaturation = 2,
|
|
+ kAbsoluteColorimetric = 3,
|
|
+};
|
|
+
|
|
+#endif // CORE_FXCRT_RENDERING_INTENT_H_
|