| // 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; |
| } |
| } |