blob: e2d704cb33545c025186d8c741870225cc69ab08 [file] [log] [blame] [edit]
// Copyright 2016 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fpdfdoc/cpdf_generateap.h"
#include <algorithm>
#include <sstream>
#include <utility>
#include "constants/annotation_common.h"
#include "constants/appearance.h"
#include "constants/font_encodings.h"
#include "constants/form_fields.h"
#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
#include "core/fpdfapi/font/cpdf_font.h"
#include "core/fpdfapi/page/cpdf_docpagedata.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_boolean.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.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_string.h"
#include "core/fpdfapi/parser/fpdf_parser_decode.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "core/fpdfdoc/cpdf_annot.h"
#include "core/fpdfdoc/cpdf_color_utils.h"
#include "core/fpdfdoc/cpdf_defaultappearance.h"
#include "core/fpdfdoc/cpdf_formfield.h"
#include "core/fpdfdoc/cpvt_fontmap.h"
#include "core/fpdfdoc/cpvt_variabletext.h"
#include "core/fpdfdoc/cpvt_word.h"
#include "core/fxcrt/fx_string_wrappers.h"
#include "core/fxcrt/notreached.h"
#include "core/fxge/cfx_renderdevice.h"
namespace {
constexpr char kGSDictName[] = "GS";
struct CPVT_Dash {
CPVT_Dash(int32_t dash, int32_t gap, int32_t phase)
: dash(dash), gap(gap), phase(phase) {}
int32_t dash;
int32_t gap;
int32_t phase;
};
enum class PaintOperation { kStroke, kFill };
ByteString GetPDFWordString(IPVT_FontMap* font_map,
int32_t font_index,
uint16_t word,
uint16_t sub_word) {
if (sub_word > 0) {
return ByteString::Format("%c", sub_word);
}
if (!font_map) {
return ByteString();
}
RetainPtr<CPDF_Font> pdf_font = font_map->GetPDFFont(font_index);
if (!pdf_font) {
return ByteString();
}
if (pdf_font->GetBaseFontName() == "Symbol" ||
pdf_font->GetBaseFontName() == "ZapfDingbats") {
return ByteString::Format("%c", word);
}
ByteString word_string;
uint32_t char_code = pdf_font->CharCodeFromUnicode(word);
if (char_code != CPDF_Font::kInvalidCharCode) {
pdf_font->AppendChar(&word_string, char_code);
}
return word_string;
}
ByteString GetWordRenderString(ByteStringView words) {
if (words.IsEmpty()) {
return ByteString();
}
return PDF_EncodeString(words) + " Tj\n";
}
ByteString GetFontSetString(IPVT_FontMap* font_map,
int32_t font_index,
float font_size) {
fxcrt::ostringstream font_stream;
if (font_map) {
ByteString font_alias = font_map->GetPDFFontAlias(font_index);
if (font_alias.GetLength() > 0 && font_size > 0) {
font_stream << "/" << font_alias << " ";
WriteFloat(font_stream, font_size) << " Tf\n";
}
}
return ByteString(font_stream);
}
// ISO 32000-1:2008 spec, table 166.
// ISO 32000-2:2020 spec, table 168.
struct BorderStyleInfo {
float width = 1;
BorderStyle style = BorderStyle::kSolid;
CPVT_Dash dash_pattern{3, 0, 0};
};
BorderStyleInfo GetBorderStyleInfo(const CPDF_Dictionary* border_style_dict) {
BorderStyleInfo border_style_info;
if (!border_style_dict) {
return border_style_info;
}
if (border_style_dict->KeyExist("W")) {
border_style_info.width = border_style_dict->GetFloatFor("W");
}
const ByteString border_style_string =
border_style_dict->GetByteStringFor("S");
if (border_style_string.GetLength()) {
switch (border_style_string[0]) {
case 'S':
border_style_info.style = BorderStyle::kSolid;
break;
case 'D':
border_style_info.style = BorderStyle::kDash;
break;
case 'B':
border_style_info.style = BorderStyle::kBeveled;
border_style_info.width *= 2;
break;
case 'I':
border_style_info.style = BorderStyle::kInset;
border_style_info.width *= 2;
break;
case 'U':
border_style_info.style = BorderStyle::kUnderline;
break;
}
}
RetainPtr<const CPDF_Array> dash_array = border_style_dict->GetArrayFor("D");
if (dash_array) {
border_style_info.dash_pattern =
CPVT_Dash(dash_array->GetIntegerAt(0), dash_array->GetIntegerAt(1),
dash_array->GetIntegerAt(2));
}
return border_style_info;
}
// ISO 32000-1:2008 spec, table 189.
// ISO 32000-2:2020 spec, table 192.
struct AppearanceCharacteristics {
int rotation = 0; // In degrees.
CFX_Color border_color;
CFX_Color background_color;
};
AppearanceCharacteristics GetAppearanceCharacteristics(
const CPDF_Dictionary* mk_dict) {
AppearanceCharacteristics appearance_characteristics;
if (!mk_dict) {
return appearance_characteristics;
}
appearance_characteristics.rotation =
mk_dict->GetIntegerFor(pdfium::appearance::kR);
RetainPtr<const CPDF_Array> border_color_array =
mk_dict->GetArrayFor(pdfium::appearance::kBC);
if (border_color_array) {
appearance_characteristics.border_color =
fpdfdoc::CFXColorFromArray(*border_color_array);
}
RetainPtr<const CPDF_Array> background_color_array =
mk_dict->GetArrayFor(pdfium::appearance::kBG);
if (background_color_array) {
appearance_characteristics.background_color =
fpdfdoc::CFXColorFromArray(*background_color_array);
}
return appearance_characteristics;
}
struct AnnotationDimensionsAndColor {
CFX_FloatRect bbox;
CFX_Matrix matrix;
CFX_Color border_color;
CFX_Color background_color;
};
AnnotationDimensionsAndColor GetAnnotationDimensionsAndColor(
const CPDF_Dictionary* annot_dict) {
const AppearanceCharacteristics appearance_characteristics =
GetAppearanceCharacteristics(annot_dict->GetDictFor("MK"));
const CFX_FloatRect annot_rect =
annot_dict->GetRectFor(pdfium::annotation::kRect);
CFX_FloatRect bbox_rect;
CFX_Matrix matrix;
switch (appearance_characteristics.rotation % 360) {
case 0:
bbox_rect = CFX_FloatRect(0, 0, annot_rect.right - annot_rect.left,
annot_rect.top - annot_rect.bottom);
break;
case 90:
matrix = CFX_Matrix(0, 1, -1, 0, annot_rect.right - annot_rect.left, 0);
bbox_rect = CFX_FloatRect(0, 0, annot_rect.top - annot_rect.bottom,
annot_rect.right - annot_rect.left);
break;
case 180:
matrix = CFX_Matrix(-1, 0, 0, -1, annot_rect.right - annot_rect.left,
annot_rect.top - annot_rect.bottom);
bbox_rect = CFX_FloatRect(0, 0, annot_rect.right - annot_rect.left,
annot_rect.top - annot_rect.bottom);
break;
case 270:
matrix = CFX_Matrix(0, -1, 1, 0, 0, annot_rect.top - annot_rect.bottom);
bbox_rect = CFX_FloatRect(0, 0, annot_rect.top - annot_rect.bottom,
annot_rect.right - annot_rect.left);
break;
}
return {
.bbox = bbox_rect,
.matrix = matrix,
.border_color = appearance_characteristics.border_color,
.background_color = appearance_characteristics.background_color,
};
}
ByteString GetDefaultAppearanceString(CPDF_Dictionary* annot_dict,
CPDF_Dictionary* form_dict) {
ByteString default_appearance_string;
RetainPtr<const CPDF_Object> default_appearance_object =
CPDF_FormField::GetFieldAttrForDict(annot_dict, "DA");
if (default_appearance_object) {
default_appearance_string = default_appearance_object->GetString();
}
if (default_appearance_string.IsEmpty()) {
default_appearance_string = form_dict->GetByteStringFor("DA");
}
return default_appearance_string;
}
bool CloneResourcesDictIfMissingFromStream(CPDF_Dictionary* stream_dict,
const CPDF_Dictionary* dr_dict) {
RetainPtr<CPDF_Dictionary> resources_dict =
stream_dict->GetMutableDictFor("Resources");
if (resources_dict) {
return false;
}
stream_dict->SetFor("Resources", dr_dict->Clone());
return true;
}
bool ValidateOrCreateFontResources(CPDF_Document* doc,
CPDF_Dictionary* stream_dict,
const CPDF_Dictionary* font_dict,
const ByteString& font_name) {
RetainPtr<CPDF_Dictionary> resources_dict =
stream_dict->GetMutableDictFor("Resources");
RetainPtr<CPDF_Dictionary> font_resource_dict =
resources_dict->GetMutableDictFor("Font");
if (!font_resource_dict) {
font_resource_dict = resources_dict->SetNewFor<CPDF_Dictionary>("Font");
}
if (!ValidateFontResourceDict(font_resource_dict.Get())) {
return false;
}
if (!font_resource_dict->KeyExist(font_name)) {
font_resource_dict->SetNewFor<CPDF_Reference>(font_name, doc,
font_dict->GetObjNum());
}
return true;
}
ByteString GenerateEditAP(IPVT_FontMap* font_map,
CPVT_VariableText::Iterator* vt_iterator,
const CFX_PointF& offset,
bool continuous,
uint16_t sub_word) {
fxcrt::ostringstream edit_stream;
fxcrt::ostringstream line_stream;
CFX_PointF old_point;
CFX_PointF new_point;
int32_t current_font_index = -1;
CPVT_WordPlace oldplace;
ByteString words;
vt_iterator->SetAt(0);
while (vt_iterator->NextWord()) {
CPVT_WordPlace place = vt_iterator->GetWordPlace();
if (continuous) {
if (place.LineCmp(oldplace) != 0) {
if (!words.IsEmpty()) {
line_stream << GetWordRenderString(words.AsStringView());
edit_stream << line_stream.str();
line_stream.str("");
words.clear();
}
CPVT_Word word;
if (vt_iterator->GetWord(word)) {
new_point =
CFX_PointF(word.ptWord.x + offset.x, word.ptWord.y + offset.y);
} else {
CPVT_Line line;
vt_iterator->GetLine(line);
new_point =
CFX_PointF(line.ptLine.x + offset.x, line.ptLine.y + offset.y);
}
if (new_point != old_point) {
WritePoint(line_stream, new_point - old_point) << " Td\n";
old_point = new_point;
}
}
CPVT_Word word;
if (vt_iterator->GetWord(word)) {
if (word.nFontIndex != current_font_index) {
if (!words.IsEmpty()) {
line_stream << GetWordRenderString(words.AsStringView());
words.clear();
}
line_stream << GetFontSetString(font_map, word.nFontIndex,
word.fFontSize);
current_font_index = word.nFontIndex;
}
words +=
GetPDFWordString(font_map, current_font_index, word.Word, sub_word);
}
oldplace = place;
} else {
CPVT_Word word;
if (vt_iterator->GetWord(word)) {
new_point =
CFX_PointF(word.ptWord.x + offset.x, word.ptWord.y + offset.y);
if (new_point != old_point) {
WritePoint(edit_stream, new_point - old_point) << " Td\n";
old_point = new_point;
}
if (word.nFontIndex != current_font_index) {
edit_stream << GetFontSetString(font_map, word.nFontIndex,
word.fFontSize);
current_font_index = word.nFontIndex;
}
edit_stream << GetWordRenderString(
GetPDFWordString(font_map, current_font_index, word.Word, sub_word)
.AsStringView());
}
}
}
if (!words.IsEmpty()) {
line_stream << GetWordRenderString(words.AsStringView());
edit_stream << line_stream.str();
}
return ByteString(edit_stream);
}
ByteString GenerateColorAP(const CFX_Color& color, PaintOperation operation) {
fxcrt::ostringstream color_stream;
switch (color.nColorType) {
case CFX_Color::Type::kRGB:
WriteFloat(color_stream, color.fColor1) << " ";
WriteFloat(color_stream, color.fColor2) << " ";
WriteFloat(color_stream, color.fColor3) << " ";
color_stream << (operation == PaintOperation::kStroke ? "RG" : "rg")
<< "\n";
return ByteString(color_stream);
case CFX_Color::Type::kGray:
WriteFloat(color_stream, color.fColor1) << " ";
color_stream << (operation == PaintOperation::kStroke ? "G" : "g")
<< "\n";
return ByteString(color_stream);
case CFX_Color::Type::kCMYK:
WriteFloat(color_stream, color.fColor1) << " ";
WriteFloat(color_stream, color.fColor2) << " ";
WriteFloat(color_stream, color.fColor3) << " ";
WriteFloat(color_stream, color.fColor4) << " ";
color_stream << (operation == PaintOperation::kStroke ? "K" : "k")
<< "\n";
return ByteString(color_stream);
case CFX_Color::Type::kTransparent:
return ByteString();
}
NOTREACHED();
}
ByteString GenerateBorderAP(const CFX_FloatRect& rect,
const BorderStyleInfo& border_style_info,
const CFX_Color& border_color) {
const float width = border_style_info.width;
if (width <= 0) {
return ByteString();
}
fxcrt::ostringstream app_stream;
const float left = rect.left;
const float bottom = rect.bottom;
const float right = rect.right;
const float top = rect.top;
const float half_width = width / 2.0f;
switch (border_style_info.style) {
case BorderStyle::kSolid: {
ByteString color_string =
GenerateColorAP(border_color, PaintOperation::kFill);
if (color_string.GetLength() > 0) {
app_stream << color_string;
WriteRect(app_stream, rect) << " re\n";
CFX_FloatRect inner_rect = rect;
inner_rect.Deflate(width, width);
WriteRect(app_stream, inner_rect) << " re f*\n";
}
return ByteString(app_stream);
}
case BorderStyle::kDash: {
ByteString color_string =
GenerateColorAP(border_color, PaintOperation::kStroke);
if (color_string.GetLength() > 0) {
const auto& dash = border_style_info.dash_pattern;
app_stream << color_string;
WriteFloat(app_stream, width) << " w [" << dash.dash << " " << dash.gap
<< "] " << dash.phase << " d\n";
WritePoint(app_stream, {left + half_width, bottom + half_width})
<< " m\n";
WritePoint(app_stream, {left + half_width, top - half_width}) << " l\n";
WritePoint(app_stream, {right - half_width, top - half_width})
<< " l\n";
WritePoint(app_stream, {right - half_width, bottom + half_width})
<< " l\n";
WritePoint(app_stream, {left + half_width, bottom + half_width})
<< " l S\n";
}
return ByteString(app_stream);
}
case BorderStyle::kBeveled:
case BorderStyle::kInset: {
const float left_top_gray_value =
border_style_info.style == BorderStyle::kBeveled ? 1.0f : 0.5f;
app_stream << GenerateColorAP(
CFX_Color(CFX_Color::Type::kGray, left_top_gray_value),
PaintOperation::kFill);
WritePoint(app_stream, {left + half_width, bottom + half_width})
<< " m\n";
WritePoint(app_stream, {left + half_width, top - half_width}) << " l\n";
WritePoint(app_stream, {right - half_width, top - half_width}) << " l\n";
WritePoint(app_stream, {right - width, top - width}) << " l\n";
WritePoint(app_stream, {left + width, top - width}) << " l\n";
WritePoint(app_stream, {left + width, bottom + width}) << " l f\n";
const float right_bottom_gray_value =
border_style_info.style == BorderStyle::kBeveled ? 0.5f : 0.75f;
app_stream << GenerateColorAP(
CFX_Color(CFX_Color::Type::kGray, right_bottom_gray_value),
PaintOperation::kFill);
WritePoint(app_stream, {right - half_width, top - half_width}) << " m\n";
WritePoint(app_stream, {right - half_width, bottom + half_width})
<< " l\n";
WritePoint(app_stream, {left + half_width, bottom + half_width})
<< " l\n";
WritePoint(app_stream, {left + width, bottom + width}) << " l\n";
WritePoint(app_stream, {right - width, bottom + width}) << " l\n";
WritePoint(app_stream, {right - width, top - width}) << " l f\n";
ByteString color_string =
GenerateColorAP(border_color, PaintOperation::kFill);
if (color_string.GetLength() > 0) {
app_stream << color_string;
WriteRect(app_stream, rect) << " re\n";
CFX_FloatRect inner_rect = rect;
inner_rect.Deflate(half_width, half_width);
WriteRect(app_stream, inner_rect) << " re f*\n";
}
return ByteString(app_stream);
}
case BorderStyle::kUnderline: {
ByteString color_string =
GenerateColorAP(border_color, PaintOperation::kStroke);
if (color_string.GetLength() > 0) {
app_stream << color_string;
WriteFloat(app_stream, width) << " w\n";
WritePoint(app_stream, {left, bottom + half_width}) << " m\n";
WritePoint(app_stream, {right, bottom + half_width}) << " l S\n";
}
return ByteString(app_stream);
}
}
NOTREACHED();
}
ByteString GetColorStringWithDefault(const CPDF_Array* color_array,
const CFX_Color& default_color,
PaintOperation operation) {
if (color_array) {
CFX_Color color = fpdfdoc::CFXColorFromArray(*color_array);
return GenerateColorAP(color, operation);
}
return GenerateColorAP(default_color, operation);
}
float GetBorderWidth(const CPDF_Dictionary* dict) {
RetainPtr<const CPDF_Dictionary> border_style_dict = dict->GetDictFor("BS");
if (border_style_dict && border_style_dict->KeyExist("W")) {
return border_style_dict->GetFloatFor("W");
}
auto border_array = dict->GetArrayFor(pdfium::annotation::kBorder);
if (border_array && border_array->size() > 2) {
return border_array->GetFloatAt(2);
}
return 1;
}
RetainPtr<const CPDF_Array> GetDashArray(const CPDF_Dictionary* dict) {
RetainPtr<const CPDF_Dictionary> border_style_dict = dict->GetDictFor("BS");
if (border_style_dict && border_style_dict->GetByteStringFor("S") == "D") {
return border_style_dict->GetArrayFor("D");
}
RetainPtr<const CPDF_Array> border_array =
dict->GetArrayFor(pdfium::annotation::kBorder);
if (border_array && border_array->size() == 4) {
return border_array->GetArrayAt(3);
}
return nullptr;
}
ByteString GetDashPatternString(const CPDF_Dictionary* dict) {
RetainPtr<const CPDF_Array> dash_array = GetDashArray(dict);
if (!dash_array || dash_array->IsEmpty()) {
return ByteString();
}
// Support maximum of ten elements in the dash array.
size_t dash_arrayCount = std::min<size_t>(dash_array->size(), 10);
fxcrt::ostringstream dash_stream;
dash_stream << "[";
for (size_t i = 0; i < dash_arrayCount; ++i) {
WriteFloat(dash_stream, dash_array->GetFloatAt(i)) << " ";
}
dash_stream << "] 0 d\n";
return ByteString(dash_stream);
}
ByteString GetPopupContentsString(CPDF_Document* doc,
const CPDF_Dictionary& annot_dict,
RetainPtr<CPDF_Font> default_font,
const ByteString& font_name) {
WideString value(annot_dict.GetUnicodeTextFor(pdfium::form_fields::kT));
value += L'\n';
value += annot_dict.GetUnicodeTextFor(pdfium::annotation::kContents);
CPVT_FontMap map(doc, nullptr, std::move(default_font), font_name);
CPVT_VariableText::Provider prd(&map);
CPVT_VariableText vt(&prd);
vt.SetPlateRect(annot_dict.GetRectFor(pdfium::annotation::kRect));
vt.SetFontSize(12);
vt.SetAutoReturn(true);
vt.SetMultiLine(true);
vt.Initialize();
vt.SetText(value);
vt.RearrangeAll();
CFX_PointF offset(3.0f, -3.0f);
ByteString content = GenerateEditAP(&map, vt.GetIterator(), offset, false, 0);
if (content.IsEmpty()) {
return ByteString();
}
ByteString color = GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0),
PaintOperation::kFill);
return ByteString{"BT\n", color.AsStringView(), content.AsStringView(),
"ET\n", "Q\n"};
}
RetainPtr<CPDF_Dictionary> GenerateFallbackFontDict(CPDF_Document* doc) {
auto font_dict = doc->NewIndirect<CPDF_Dictionary>();
font_dict->SetNewFor<CPDF_Name>("Type", "Font");
font_dict->SetNewFor<CPDF_Name>("Subtype", "Type1");
font_dict->SetNewFor<CPDF_Name>("BaseFont", CFX_Font::kDefaultAnsiFontName);
font_dict->SetNewFor<CPDF_Name>("Encoding",
pdfium::font_encodings::kWinAnsiEncoding);
return font_dict;
}
RetainPtr<CPDF_Dictionary> GenerateResourceFontDict(
CPDF_Document* doc,
const ByteString& font_name,
uint32_t font_dict_obj_num) {
auto resource_font_dict = doc->New<CPDF_Dictionary>();
resource_font_dict->SetNewFor<CPDF_Reference>(font_name, doc,
font_dict_obj_num);
return resource_font_dict;
}
ByteString GetPaintOperatorString(bool is_stroke_rect, bool is_fill_rect) {
if (is_stroke_rect) {
return is_fill_rect ? "b" : "s";
}
return is_fill_rect ? "f" : "n";
}
ByteString GenerateTextSymbolAP(const CFX_FloatRect& rect) {
fxcrt::ostringstream app_stream;
app_stream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 1, 1, 0),
PaintOperation::kFill);
app_stream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0),
PaintOperation::kStroke);
static constexpr int kBorderWidth = 1;
app_stream << kBorderWidth << " w\n";
static constexpr float kHalfWidth = kBorderWidth / 2.0f;
static constexpr int kTipDelta = 4;
CFX_FloatRect outer_rect1 = rect;
outer_rect1.Deflate(kHalfWidth, kHalfWidth);
outer_rect1.bottom += kTipDelta;
CFX_FloatRect outer_rect2 = outer_rect1;
outer_rect2.left += kTipDelta;
outer_rect2.right = outer_rect2.left + kTipDelta;
outer_rect2.top = outer_rect2.bottom - kTipDelta;
float outer_rect2_middle = (outer_rect2.left + outer_rect2.right) / 2;
// Draw outer boxes.
WritePoint(app_stream, {outer_rect1.left, outer_rect1.bottom}) << " m\n";
WritePoint(app_stream, {outer_rect1.left, outer_rect1.top}) << " l\n";
WritePoint(app_stream, {outer_rect1.right, outer_rect1.top}) << " l\n";
WritePoint(app_stream, {outer_rect1.right, outer_rect1.bottom}) << " l\n";
WritePoint(app_stream, {outer_rect2.right, outer_rect2.bottom}) << " l\n";
WritePoint(app_stream, {outer_rect2_middle, outer_rect2.top}) << " l\n";
WritePoint(app_stream, {outer_rect2.left, outer_rect2.bottom}) << " l\n";
WritePoint(app_stream, {outer_rect1.left, outer_rect1.bottom}) << " l\n";
// Draw inner lines.
CFX_FloatRect line_rect = outer_rect1;
const float delta_x = 2;
const float delta_y = (line_rect.top - line_rect.bottom) / 4;
line_rect.left += delta_x;
line_rect.right -= delta_x;
for (int i = 0; i < 3; ++i) {
line_rect.top -= delta_y;
WritePoint(app_stream, {line_rect.left, line_rect.top}) << " m\n";
WritePoint(app_stream, {line_rect.right, line_rect.top}) << " l\n";
}
app_stream << "B*\n";
return ByteString(app_stream);
}
RetainPtr<CPDF_Dictionary> GenerateExtGStateDict(
const CPDF_Dictionary& annot_dict,
const ByteString& blend_mode) {
auto gs_dict =
pdfium::MakeRetain<CPDF_Dictionary>(annot_dict.GetByteStringPool());
gs_dict->SetNewFor<CPDF_Name>("Type", "ExtGState");
float opacity = annot_dict.KeyExist("CA") ? annot_dict.GetFloatFor("CA") : 1;
gs_dict->SetNewFor<CPDF_Number>("CA", opacity);
gs_dict->SetNewFor<CPDF_Number>("ca", opacity);
gs_dict->SetNewFor<CPDF_Boolean>("AIS", false);
gs_dict->SetNewFor<CPDF_Name>("BM", blend_mode);
auto resources_dict =
pdfium::MakeRetain<CPDF_Dictionary>(annot_dict.GetByteStringPool());
resources_dict->SetFor(kGSDictName, std::move(gs_dict));
return resources_dict;
}
RetainPtr<CPDF_Dictionary> GenerateResourcesDict(
CPDF_Document* doc,
RetainPtr<CPDF_Dictionary> gs_dict,
RetainPtr<CPDF_Dictionary> font_resource_dict) {
auto resources_dict = doc->New<CPDF_Dictionary>();
if (gs_dict) {
resources_dict->SetFor("ExtGState", gs_dict);
}
if (font_resource_dict) {
resources_dict->SetFor("Font", font_resource_dict);
}
return resources_dict;
}
void GenerateAndSetAPDict(CPDF_Document* doc,
CPDF_Dictionary* annot_dict,
fxcrt::ostringstream* app_stream,
RetainPtr<CPDF_Dictionary> resource_dict,
bool is_text_markup_annotation) {
auto stream_dict = pdfium::MakeRetain<CPDF_Dictionary>();
stream_dict->SetNewFor<CPDF_Number>("FormType", 1);
stream_dict->SetNewFor<CPDF_Name>("Type", "XObject");
stream_dict->SetNewFor<CPDF_Name>("Subtype", "Form");
stream_dict->SetMatrixFor("Matrix", CFX_Matrix());
CFX_FloatRect rect = is_text_markup_annotation
? CPDF_Annot::BoundingRectFromQuadPoints(annot_dict)
: annot_dict->GetRectFor(pdfium::annotation::kRect);
stream_dict->SetRectFor("BBox", rect);
stream_dict->SetFor("Resources", std::move(resource_dict));
auto normal_stream = doc->NewIndirect<CPDF_Stream>(std::move(stream_dict));
normal_stream->SetDataFromStringstream(app_stream);
RetainPtr<CPDF_Dictionary> ap_dict =
annot_dict->GetOrCreateDictFor(pdfium::annotation::kAP);
ap_dict->SetNewFor<CPDF_Reference>("N", doc, normal_stream->GetObjNum());
}
ByteString GenerateTextFieldAP(const CPDF_Dictionary* annot_dict,
const CFX_FloatRect& body_rect,
float font_size,
CPVT_VariableText& vt) {
RetainPtr<const CPDF_Object> v_field =
CPDF_FormField::GetFieldAttrForDict(annot_dict, pdfium::form_fields::kV);
WideString value = v_field ? v_field->GetUnicodeText() : WideString();
RetainPtr<const CPDF_Object> q_field =
CPDF_FormField::GetFieldAttrForDict(annot_dict, "Q");
const int32_t align = q_field ? q_field->GetInteger() : 0;
RetainPtr<const CPDF_Object> ff_field =
CPDF_FormField::GetFieldAttrForDict(annot_dict, pdfium::form_fields::kFf);
const uint32_t flags = ff_field ? ff_field->GetInteger() : 0;
RetainPtr<const CPDF_Object> max_len_field =
CPDF_FormField::GetFieldAttrForDict(annot_dict, "MaxLen");
const uint32_t max_len = max_len_field ? max_len_field->GetInteger() : 0;
vt.SetPlateRect(body_rect);
vt.SetAlignment(align);
if (FXSYS_IsFloatZero(font_size)) {
vt.SetAutoFontSize(true);
} else {
vt.SetFontSize(font_size);
}
bool is_multi_line = (flags >> 12) & 1;
if (is_multi_line) {
vt.SetMultiLine(true);
vt.SetAutoReturn(true);
}
uint16_t sub_word = 0;
if ((flags >> 13) & 1) {
sub_word = '*';
vt.SetPasswordChar(sub_word);
}
bool is_char_array = (flags >> 24) & 1;
if (is_char_array) {
vt.SetCharArray(max_len);
} else {
vt.SetLimitChar(max_len);
}
vt.Initialize();
vt.SetText(value);
vt.RearrangeAll();
CFX_PointF offset;
if (!is_multi_line) {
offset = CFX_PointF(
0.0f, (vt.GetContentRect().Height() - body_rect.Height()) / 2.0f);
}
return GenerateEditAP(vt.GetProvider()->GetFontMap(), vt.GetIterator(),
offset, !is_char_array, sub_word);
}
ByteString GenerateComboBoxAP(const CPDF_Dictionary* annot_dict,
const CFX_FloatRect& body_rect,
const CFX_Color& text_color,
float font_size,
CPVT_VariableText::Provider& provider) {
fxcrt::ostringstream body_stream;
RetainPtr<const CPDF_Object> v_field =
CPDF_FormField::GetFieldAttrForDict(annot_dict, pdfium::form_fields::kV);
WideString value = v_field ? v_field->GetUnicodeText() : WideString();
CPVT_VariableText vt(&provider);
CFX_FloatRect button_rect = body_rect;
button_rect.left = button_rect.right - 13;
button_rect.Normalize();
CFX_FloatRect edit_rect = body_rect;
edit_rect.right = button_rect.left;
edit_rect.Normalize();
vt.SetPlateRect(edit_rect);
if (FXSYS_IsFloatZero(font_size)) {
vt.SetAutoFontSize(true);
} else {
vt.SetFontSize(font_size);
}
vt.Initialize();
vt.SetText(value);
vt.RearrangeAll();
CFX_FloatRect content_rect = vt.GetContentRect();
CFX_PointF offset =
CFX_PointF(0.0f, (content_rect.Height() - edit_rect.Height()) / 2.0f);
ByteString edit =
GenerateEditAP(provider.GetFontMap(), vt.GetIterator(), offset, true, 0);
if (edit.GetLength() > 0) {
body_stream << "/Tx BMC\nq\n";
WriteRect(body_stream, edit_rect) << " re\nW\nn\n";
body_stream << "BT\n"
<< GenerateColorAP(text_color, PaintOperation::kFill) << edit
<< "ET\n"
<< "Q\nEMC\n";
}
ByteString button =
GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 220.0f / 255.0f,
220.0f / 255.0f, 220.0f / 255.0f),
PaintOperation::kFill);
if (button.GetLength() > 0 && !button_rect.IsEmpty()) {
body_stream << "q\n" << button;
WriteRect(body_stream, button_rect) << " re f\n";
body_stream << "Q\n";
static const BorderStyleInfo kButtonBorderStyleInfo{
.width = 2, .style = BorderStyle::kBeveled, .dash_pattern{3, 0, 0}};
ByteString button_border =
GenerateBorderAP(button_rect, kButtonBorderStyleInfo,
CFX_Color(CFX_Color::Type::kGray, 0));
if (button_border.GetLength() > 0) {
body_stream << "q\n" << button_border << "Q\n";
}
CFX_PointF center((button_rect.left + button_rect.right) / 2,
(button_rect.top + button_rect.bottom) / 2);
if (FXSYS_IsFloatBigger(button_rect.Width(), 6) &&
FXSYS_IsFloatBigger(button_rect.Height(), 6)) {
body_stream << "q\n0 g\n";
WritePoint(body_stream, {center.x - 3, center.y + 1.5f}) << " m\n";
WritePoint(body_stream, {center.x + 3, center.y + 1.5f}) << " l\n";
WritePoint(body_stream, {center.x, center.y - 1.5f}) << " l\n";
WritePoint(body_stream, {center.x - 3, center.y + 1.5f}) << " l f\n";
body_stream << button << "Q\n";
}
}
return ByteString(body_stream);
}
ByteString GenerateListBoxAP(const CPDF_Dictionary* annot_dict,
const CFX_FloatRect& body_rect,
const CFX_Color& text_color,
float font_size,
CPVT_VariableText::Provider& provider) {
RetainPtr<const CPDF_Array> opts =
ToArray(CPDF_FormField::GetFieldAttrForDict(annot_dict, "Opt"));
if (!opts) {
return ByteString();
}
RetainPtr<const CPDF_Array> selections =
ToArray(CPDF_FormField::GetFieldAttrForDict(annot_dict, "I"));
RetainPtr<const CPDF_Object> top_index =
CPDF_FormField::GetFieldAttrForDict(annot_dict, "TI");
const int32_t top = top_index ? top_index->GetInteger() : 0;
fxcrt::ostringstream body_stream;
float fy = body_rect.top;
for (size_t i = top, sz = opts->size(); i < sz; i++) {
if (FXSYS_IsFloatSmaller(fy, body_rect.bottom)) {
break;
}
if (RetainPtr<const CPDF_Object> opt = opts->GetDirectObjectAt(i)) {
WideString item;
if (opt->IsString()) {
item = opt->GetUnicodeText();
} else if (const CPDF_Array* opt_array = opt->AsArray()) {
RetainPtr<const CPDF_Object> opt_item = opt_array->GetDirectObjectAt(1);
if (opt_item) {
item = opt_item->GetUnicodeText();
}
}
bool is_selected = false;
if (selections) {
for (size_t s = 0, ssz = selections->size(); s < ssz; s++) {
int value = selections->GetIntegerAt(s);
if (value >= 0 && i == static_cast<size_t>(value)) {
is_selected = true;
break;
}
}
}
CPVT_VariableText vt(&provider);
vt.SetPlateRect(
CFX_FloatRect(body_rect.left, 0.0f, body_rect.right, 0.0f));
vt.SetFontSize(FXSYS_IsFloatZero(font_size) ? 12.0f : font_size);
vt.Initialize();
vt.SetText(item);
vt.RearrangeAll();
const float item_height = vt.GetContentRect().Height();
if (is_selected) {
CFX_FloatRect item_rect = CFX_FloatRect(
body_rect.left, fy - item_height, body_rect.right, fy);
body_stream << "q\n"
<< GenerateColorAP(
CFX_Color(CFX_Color::Type::kRGB, 0, 51.0f / 255.0f,
113.0f / 255.0f),
PaintOperation::kFill);
WriteRect(body_stream, item_rect) << " re f\nQ\n";
body_stream << "BT\n"
<< GenerateColorAP(CFX_Color(CFX_Color::Type::kGray, 1),
PaintOperation::kFill)
<< GenerateEditAP(provider.GetFontMap(), vt.GetIterator(),
CFX_PointF(0.0f, fy), true, 0)
<< "ET\n";
} else {
body_stream << "BT\n"
<< GenerateColorAP(text_color, PaintOperation::kFill)
<< GenerateEditAP(provider.GetFontMap(), vt.GetIterator(),
CFX_PointF(0.0f, fy), true, 0)
<< "ET\n";
}
fy -= item_height;
}
}
return ByteString(body_stream);
}
bool GenerateCircleAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
RetainPtr<const CPDF_Array> interior_color = annot_dict->GetArrayFor("IC");
app_stream << GetColorStringWithDefault(
interior_color.Get(), CFX_Color(CFX_Color::Type::kTransparent),
PaintOperation::kFill);
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);
float border_width = GetBorderWidth(annot_dict);
bool is_stroke_rect = border_width > 0;
if (is_stroke_rect) {
app_stream << border_width << " w ";
app_stream << GetDashPatternString(annot_dict);
}
CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
rect.Normalize();
if (is_stroke_rect) {
// Deflating rect because stroking a path entails painting all points
// whose perpendicular distance from the path in user space is less than
// or equal to half the line width.
rect.Deflate(border_width / 2, border_width / 2);
}
const float middle_x = (rect.left + rect.right) / 2;
const float middle_y = (rect.top + rect.bottom) / 2;
// `kL` is precalculated approximate value of 4 * tan((3.14 / 2) / 4) / 3,
// where `kL` * radius is a good approximation of control points for
// arc with 90 degrees.
static constexpr float kL = 0.5523f;
const float delta_x = kL * rect.Width() / 2.0;
const float delta_y = kL * rect.Height() / 2.0;
// Starting point
app_stream << middle_x << " " << rect.top << " m\n";
// First Bezier Curve
app_stream << middle_x + delta_x << " " << rect.top << " " << rect.right
<< " " << middle_y + delta_y << " " << rect.right << " "
<< middle_y << " c\n";
// Second Bezier Curve
app_stream << rect.right << " " << middle_y - delta_y << " "
<< middle_x + delta_x << " " << rect.bottom << " " << middle_x
<< " " << rect.bottom << " c\n";
// Third Bezier Curve
app_stream << middle_x - delta_x << " " << rect.bottom << " " << rect.left
<< " " << middle_y - delta_y << " " << rect.left << " " << middle_y
<< " c\n";
// Fourth Bezier Curve
app_stream << rect.left << " " << middle_y + delta_y << " "
<< middle_x - delta_x << " " << rect.top << " " << middle_x << " "
<< rect.top << " c\n";
bool is_fill_rect = interior_color && !interior_color->IsEmpty();
app_stream << GetPaintOperatorString(is_stroke_rect, is_fill_rect) << "\n";
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
false /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateHighlightAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 1, 1, 0), PaintOperation::kFill);
RetainPtr<const CPDF_Array> quad_points_array =
annot_dict->GetArrayFor("QuadPoints");
if (quad_points_array) {
const size_t quad_point_count =
CPDF_Annot::QuadPointCount(quad_points_array.Get());
for (size_t i = 0; i < quad_point_count; ++i) {
CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(annot_dict, i);
rect.Normalize();
app_stream << rect.left << " " << rect.top << " m " << rect.right << " "
<< rect.top << " l " << rect.right << " " << rect.bottom
<< " l " << rect.left << " " << rect.bottom << " l h f\n";
}
}
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Multiply");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
true /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateInkAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
RetainPtr<const CPDF_Array> ink_list = annot_dict->GetArrayFor("InkList");
if (!ink_list || ink_list->IsEmpty()) {
return false;
}
float border_width = GetBorderWidth(annot_dict);
const bool is_stroke = border_width > 0;
if (!is_stroke) {
return false;
}
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);
app_stream << border_width << " w ";
app_stream << GetDashPatternString(annot_dict);
// Set inflated rect as a new rect because paths near the border with large
// width should not be clipped to the original rect.
CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
rect.Inflate(border_width / 2, border_width / 2);
annot_dict->SetRectFor(pdfium::annotation::kRect, rect);
for (size_t i = 0; i < ink_list->size(); i++) {
RetainPtr<const CPDF_Array> coordinates_array = ink_list->GetArrayAt(i);
if (!coordinates_array || coordinates_array->size() < 2) {
continue;
}
app_stream << coordinates_array->GetFloatAt(0) << " "
<< coordinates_array->GetFloatAt(1) << " m ";
for (size_t j = 0; j < coordinates_array->size() - 1; j += 2) {
app_stream << coordinates_array->GetFloatAt(j) << " "
<< coordinates_array->GetFloatAt(j + 1) << " l ";
}
app_stream << "S\n";
}
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
false /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateTextAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
const float note_length = 20;
CFX_FloatRect note_rect(rect.left, rect.bottom, rect.left + note_length,
rect.bottom + note_length);
annot_dict->SetRectFor(pdfium::annotation::kRect, note_rect);
app_stream << GenerateTextSymbolAP(note_rect);
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
false /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateUnderlineAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);
RetainPtr<const CPDF_Array> quad_points_array =
annot_dict->GetArrayFor("QuadPoints");
if (quad_points_array) {
static constexpr int kLineWidth = 1;
app_stream << kLineWidth << " w ";
const size_t quad_point_count =
CPDF_Annot::QuadPointCount(quad_points_array.Get());
for (size_t i = 0; i < quad_point_count; ++i) {
CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(annot_dict, i);
rect.Normalize();
app_stream << rect.left << " " << rect.bottom + kLineWidth << " m "
<< rect.right << " " << rect.bottom + kLineWidth << " l S\n";
}
}
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
true /*IsTextMarkupAnnotation*/);
return true;
}
bool GeneratePopupAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs\n";
app_stream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 1, 1, 0),
PaintOperation::kFill);
app_stream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0),
PaintOperation::kStroke);
const float border_width = 1;
app_stream << border_width << " w\n";
CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
rect.Normalize();
rect.Deflate(border_width / 2, border_width / 2);
app_stream << rect.left << " " << rect.bottom << " " << rect.Width() << " "
<< rect.Height() << " re b\n";
RetainPtr<CPDF_Dictionary> font_dict = GenerateFallbackFontDict(doc);
auto* doc_page_data = CPDF_DocPageData::FromDocument(doc);
RetainPtr<CPDF_Font> default_font = doc_page_data->GetFont(font_dict);
if (!default_font) {
return false;
}
const ByteString font_name = "FONT";
RetainPtr<CPDF_Dictionary> resource_font_dict =
GenerateResourceFontDict(doc, font_name, font_dict->GetObjNum());
RetainPtr<CPDF_Dictionary> gs_dict =
GenerateExtGStateDict(*annot_dict, "Normal");
RetainPtr<CPDF_Dictionary> resources_dict = GenerateResourcesDict(
doc, std::move(gs_dict), std::move(resource_font_dict));
app_stream << GetPopupContentsString(doc, *annot_dict,
std::move(default_font), font_name);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
false /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateSquareAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
RetainPtr<const CPDF_Array> interior_color = annot_dict->GetArrayFor("IC");
app_stream << GetColorStringWithDefault(
interior_color.Get(), CFX_Color(CFX_Color::Type::kTransparent),
PaintOperation::kFill);
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);
float border_width = GetBorderWidth(annot_dict);
const bool is_stroke_rect = border_width > 0;
if (is_stroke_rect) {
app_stream << border_width << " w ";
app_stream << GetDashPatternString(annot_dict);
}
CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
rect.Normalize();
if (is_stroke_rect) {
// Deflating rect because stroking a path entails painting all points
// whose perpendicular distance from the path in user space is less than
// or equal to half the line width.
rect.Deflate(border_width / 2, border_width / 2);
}
const bool is_fill_rect = interior_color && (interior_color->size() > 0);
app_stream << rect.left << " " << rect.bottom << " " << rect.Width() << " "
<< rect.Height() << " re "
<< GetPaintOperatorString(is_stroke_rect, is_fill_rect) << "\n";
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
false /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateSquigglyAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);
RetainPtr<const CPDF_Array> quad_points_array =
annot_dict->GetArrayFor("QuadPoints");
if (quad_points_array) {
static constexpr int kLineWidth = 1;
static constexpr int kDelta = 2;
app_stream << kLineWidth << " w ";
const size_t quad_point_count =
CPDF_Annot::QuadPointCount(quad_points_array.Get());
for (size_t i = 0; i < quad_point_count; ++i) {
CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(annot_dict, i);
rect.Normalize();
const float top = rect.bottom + kDelta;
const float bottom = rect.bottom;
app_stream << rect.left << " " << top << " m ";
float x = rect.left + kDelta;
bool isUpwards = false;
while (x < rect.right) {
app_stream << x << " " << (isUpwards ? top : bottom) << " l ";
x += kDelta;
isUpwards = !isUpwards;
}
float remainder = rect.right - (x - kDelta);
if (isUpwards)
app_stream << rect.right << " " << bottom + remainder << " l ";
else
app_stream << rect.right << " " << top - remainder << " l ";
app_stream << "S\n";
}
}
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
true /*IsTextMarkupAnnotation*/);
return true;
}
bool GenerateStrikeOutAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
fxcrt::ostringstream app_stream;
app_stream << "/" << kGSDictName << " gs ";
app_stream << GetColorStringWithDefault(
annot_dict->GetArrayFor(pdfium::annotation::kC).Get(),
CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);
RetainPtr<const CPDF_Array> quad_points_array =
annot_dict->GetArrayFor("QuadPoints");
if (quad_points_array) {
const size_t quad_point_count =
CPDF_Annot::QuadPointCount(quad_points_array.Get());
for (size_t i = 0; i < quad_point_count; ++i) {
CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(annot_dict, i);
rect.Normalize();
float y = (rect.top + rect.bottom) / 2;
static constexpr int kLineWidth = 1;
app_stream << kLineWidth << " w " << rect.left << " " << y << " m "
<< rect.right << " " << y << " l S\n";
}
}
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
GenerateAndSetAPDict(doc, annot_dict, &app_stream, std::move(resources_dict),
true /*IsTextMarkupAnnotation*/);
return true;
}
} // namespace
// static
void CPDF_GenerateAP::GenerateFormAP(CPDF_Document* doc,
CPDF_Dictionary* annot_dict,
FormType type) {
RetainPtr<CPDF_Dictionary> root_dict = doc->GetMutableRoot();
if (!root_dict) {
return;
}
RetainPtr<CPDF_Dictionary> form_dict =
root_dict->GetMutableDictFor("AcroForm");
if (!form_dict) {
return;
}
const ByteString default_appearance_string =
GetDefaultAppearanceString(annot_dict, form_dict);
if (default_appearance_string.IsEmpty()) {
return;
}
CPDF_DefaultAppearance appearance(default_appearance_string);
float font_size = 0;
std::optional<ByteString> font = appearance.GetFont(&font_size);
if (!font.has_value())
return;
ByteString font_name = font.value();
CFX_Color text_color = fpdfdoc::CFXColorFromString(default_appearance_string);
RetainPtr<CPDF_Dictionary> dr_dict = form_dict->GetMutableDictFor("DR");
if (!dr_dict) {
return;
}
RetainPtr<CPDF_Dictionary> dr_font_dict = dr_dict->GetMutableDictFor("Font");
if (!ValidateFontResourceDict(dr_font_dict.Get())) {
return;
}
RetainPtr<CPDF_Dictionary> font_dict =
dr_font_dict->GetMutableDictFor(font_name);
if (!font_dict) {
font_dict = GenerateFallbackFontDict(doc);
dr_font_dict->SetNewFor<CPDF_Reference>(font_name, doc,
font_dict->GetObjNum());
}
auto* doc_page_data = CPDF_DocPageData::FromDocument(doc);
RetainPtr<CPDF_Font> default_font = doc_page_data->GetFont(font_dict);
if (!default_font) {
return;
}
const AnnotationDimensionsAndColor annot_dimensions_and_color =
GetAnnotationDimensionsAndColor(annot_dict);
fxcrt::ostringstream app_stream;
const ByteString background = GenerateColorAP(
annot_dimensions_and_color.background_color, PaintOperation::kFill);
if (background.GetLength() > 0) {
app_stream << "q\n" << background;
WriteRect(app_stream, annot_dimensions_and_color.bbox) << " re f\nQ\n";
}
const BorderStyleInfo border_style_info =
GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
const ByteString border_stream =
GenerateBorderAP(annot_dimensions_and_color.bbox, border_style_info,
annot_dimensions_and_color.border_color);
if (border_stream.GetLength() > 0) {
app_stream << "q\n" << border_stream << "Q\n";
}
CFX_FloatRect body_rect = annot_dimensions_and_color.bbox;
body_rect.Deflate(border_style_info.width, border_style_info.width);
RetainPtr<CPDF_Dictionary> ap_dict =
annot_dict->GetOrCreateDictFor(pdfium::annotation::kAP);
RetainPtr<CPDF_Stream> normal_stream = ap_dict->GetMutableStreamFor("N");
RetainPtr<CPDF_Dictionary> resources_dict;
if (normal_stream) {
RetainPtr<CPDF_Dictionary> stream_dict = normal_stream->GetMutableDict();
const bool cloned =
CloneResourcesDictIfMissingFromStream(stream_dict, dr_dict);
if (!cloned) {
if (!ValidateOrCreateFontResources(doc, stream_dict, font_dict,
font_name)) {
return;
}
}
resources_dict = stream_dict->GetMutableDictFor("Resources");
} else {
normal_stream =
doc->NewIndirect<CPDF_Stream>(pdfium::MakeRetain<CPDF_Dictionary>());
ap_dict->SetNewFor<CPDF_Reference>("N", doc, normal_stream->GetObjNum());
}
CPVT_FontMap map(doc, std::move(resources_dict), std::move(default_font),
font_name);
CPVT_VariableText::Provider provider(&map);
switch (type) {
case CPDF_GenerateAP::kTextField: {
CPVT_VariableText vt(&provider);
ByteString body =
GenerateTextFieldAP(annot_dict, body_rect, font_size, vt);
if (body.GetLength() > 0) {
const CFX_FloatRect content_rect = vt.GetContentRect();
app_stream << "/Tx BMC\n" << "q\n";
if (content_rect.Width() > body_rect.Width() ||
content_rect.Height() > body_rect.Height()) {
WriteRect(app_stream, body_rect) << " re\nW\nn\n";
}
app_stream << "BT\n"
<< GenerateColorAP(text_color, PaintOperation::kFill) << body
<< "ET\n"
<< "Q\nEMC\n";
}
break;
}
case CPDF_GenerateAP::kComboBox: {
app_stream << GenerateComboBoxAP(annot_dict, body_rect, text_color,
font_size, provider);
break;
}
case CPDF_GenerateAP::kListBox: {
const ByteString body = GenerateListBoxAP(
annot_dict, body_rect, text_color, font_size, provider);
if (body.GetLength() > 0) {
app_stream << "/Tx BMC\nq\n";
WriteRect(app_stream, body_rect) << " re\nW\nn\n" << body << "Q\nEMC\n";
}
break;
}
}
normal_stream->SetDataFromStringstreamAndRemoveFilter(&app_stream);
RetainPtr<CPDF_Dictionary> stream_dict = normal_stream->GetMutableDict();
stream_dict->SetMatrixFor("Matrix", annot_dimensions_and_color.matrix);
stream_dict->SetRectFor("BBox", annot_dimensions_and_color.bbox);
const bool cloned =
CloneResourcesDictIfMissingFromStream(stream_dict, dr_dict);
if (cloned) {
return;
}
ValidateOrCreateFontResources(doc, stream_dict, font_dict, font_name);
}
// static
void CPDF_GenerateAP::GenerateEmptyAP(CPDF_Document* doc,
CPDF_Dictionary* annot_dict) {
auto gs_dict = GenerateExtGStateDict(*annot_dict, "Normal");
auto resources_dict = GenerateResourcesDict(doc, std::move(gs_dict), nullptr);
fxcrt::ostringstream stream;
GenerateAndSetAPDict(doc, annot_dict, &stream, std::move(resources_dict),
false);
}
// static
bool CPDF_GenerateAP::GenerateAnnotAP(CPDF_Document* doc,
CPDF_Dictionary* annot_dict,
CPDF_Annot::Subtype subtype) {
switch (subtype) {
case CPDF_Annot::Subtype::CIRCLE:
return GenerateCircleAP(doc, annot_dict);
case CPDF_Annot::Subtype::HIGHLIGHT:
return GenerateHighlightAP(doc, annot_dict);
case CPDF_Annot::Subtype::INK:
return GenerateInkAP(doc, annot_dict);
case CPDF_Annot::Subtype::POPUP:
return GeneratePopupAP(doc, annot_dict);
case CPDF_Annot::Subtype::SQUARE:
return GenerateSquareAP(doc, annot_dict);
case CPDF_Annot::Subtype::SQUIGGLY:
return GenerateSquigglyAP(doc, annot_dict);
case CPDF_Annot::Subtype::STRIKEOUT:
return GenerateStrikeOutAP(doc, annot_dict);
case CPDF_Annot::Subtype::TEXT:
return GenerateTextAP(doc, annot_dict);
case CPDF_Annot::Subtype::UNDERLINE:
return GenerateUnderlineAP(doc, annot_dict);
default:
return false;
}
}