Files
docview/cmake/patches/pdfium-rendering-intent.patch
T
2026-09-21 13:41:40 +09:00

2402 lines
93 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
@@ -150,6 +150,7 @@
"cpdf_pageimagecache_unittest.cpp",
"cpdf_pageobjectholder_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_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_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"
@@ -97,6 +98,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 inherited_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_;
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
@@ -1094,7 +1094,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();
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
@@ -53,6 +53,8 @@
all_states.mutable_graph_state().Emplace();
all_states.mutable_text_state().Emplace();
all_states.mutable_general_state() = pPageObj->general_state();
+ all_states.mutable_color_state().SetRenderIntent(
+ all_states.general_state().GetRenderIntent());
form->ParseContent(&all_states, &matrix, nullptr);
bbox_ = dict->GetRectFor("BBox");
return form;
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_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_