Skip to content

Commit 4346c51

Browse files
authored
Merge pull request #114 from embedpdf/embedpdf/feature/signature-field-box
Draw a signature field's box
2 parents eeac787 + 0b3eb09 commit 4346c51

8 files changed

Lines changed: 152 additions & 22 deletions

File tree

‎core/fpdfdoc/cpdf_appearancegenerator.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class CPDF_AppearanceGenerator {
5151
FormType type,
5252
const WideString& value_override);
5353

54+
// A widget that shows only its box, a signature field's or one in no
55+
// field: its background (/MK /BG) and border (/MK /BC, /BS) as a text
56+
// field draws them, as a new normal appearance.
57+
static void GenerateBoxFormAP(CPDF_Document* doc,
58+
CPDF_Dictionary* annot_dict);
59+
5460
// A check box's Off and on appearances: the box, and when on, the symbol
5561
// its /MK /CA names (a check by default) in its /DA colour (black by
5662
// default).

‎core/fpdfdoc/cpdf_appearancegenerator_forms.cpp‎

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -253,21 +253,18 @@ ByteString GenerateListBoxAP(const CPDF_Dictionary* annot_dict,
253253
return ByteString(body_stream);
254254
}
255255

256-
void GenerateTextFieldFormAP(fxcrt::ostringstream& app_stream,
257-
const CPDF_Dictionary* annot_dict,
258-
const CFX_FloatRect& bbox,
259-
const DefaultAppearanceInfo& da_info,
260-
CPDF_AnnotFontMap& map,
261-
const CPDF_RichTextLayout::Options& options,
262-
const WideString* value_override) {
263-
const AppearanceCharacteristics mk =
264-
GetAppearanceCharacteristics(annot_dict->GetDictFor("MK"));
256+
// A field's background and border, in isolated graphics state: what a text
257+
// field draws under its text, and all a signature field's box is. A solid
258+
// border is a stroked rectangle; the other styles draw as a combo box's and
259+
// a list box's do.
260+
void WriteFieldBox(fxcrt::ostringstream& app_stream,
261+
const AppearanceCharacteristics& mk,
262+
const BorderStyleInfo& bs,
263+
const CFX_FloatRect& bbox) {
265264
const bool has_bg =
266265
mk.background_color.nColorType != CFX_Color::Type::kTransparent;
267266
const bool has_bc =
268267
mk.border_color.nColorType != CFX_Color::Type::kTransparent;
269-
270-
const BorderStyleInfo bs = GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
271268
const float bw = bs.width;
272269
const float half_bw = bw / 2.0f;
273270

@@ -276,12 +273,6 @@ void GenerateTextFieldFormAP(fxcrt::ostringstream& app_stream,
276273
stroke_rect.Deflate(half_bw, half_bw);
277274
}
278275

279-
CFX_FloatRect body_rect = bbox;
280-
body_rect.Deflate(2.0f * bw, bw);
281-
282-
// Background + border in isolated graphics state. A solid border is a
283-
// stroked rectangle; the other styles draw as a combo box's and a list
284-
// box's do.
285276
app_stream << "q\n";
286277
if (has_bg) {
287278
app_stream << GenerateColorAP(mk.background_color, PaintOperation::kFill);
@@ -297,6 +288,24 @@ void GenerateTextFieldFormAP(fxcrt::ostringstream& app_stream,
297288
app_stream << GenerateBorderAP(bbox, bs, mk.border_color);
298289
}
299290
app_stream << "Q\n";
291+
}
292+
293+
void GenerateTextFieldFormAP(fxcrt::ostringstream& app_stream,
294+
const CPDF_Dictionary* annot_dict,
295+
const CFX_FloatRect& bbox,
296+
const DefaultAppearanceInfo& da_info,
297+
CPDF_AnnotFontMap& map,
298+
const CPDF_RichTextLayout::Options& options,
299+
const WideString* value_override) {
300+
const AppearanceCharacteristics mk =
301+
GetAppearanceCharacteristics(annot_dict->GetDictFor("MK"));
302+
const BorderStyleInfo bs = GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
303+
const float bw = bs.width;
304+
305+
CFX_FloatRect body_rect = bbox;
306+
body_rect.Deflate(2.0f * bw, bw);
307+
308+
WriteFieldBox(app_stream, mk, bs, bbox);
300309

301310
// Text content in isolated graphics state with proper clipping
302311
app_stream << "/Tx BMC\nq\n";
@@ -988,6 +997,25 @@ CPDF_AppearanceGenerator::GenerateEphemeralFormAP(
988997
std::move(target.normal_stream)};
989998
}
990999

1000+
// static
1001+
void CPDF_AppearanceGenerator::GenerateBoxFormAP(CPDF_Document* doc,
1002+
CPDF_Dictionary* annot_dict) {
1003+
const AppearanceCharacteristics mk =
1004+
GetAppearanceCharacteristics(annot_dict->GetDictFor("MK"));
1005+
const BorderStyleInfo bs = GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
1006+
const AnnotationDimensionsAndColor dims =
1007+
GetAnnotationDimensionsAndColor(annot_dict);
1008+
1009+
fxcrt::ostringstream content;
1010+
WriteFieldBox(content, mk, bs, dims.bbox);
1011+
// A new stream, as a check box's are: the old /N may be another widget's
1012+
// too, or a drawing someone placed in this one.
1013+
const uint32_t normal_obj_num =
1014+
CreateFormXObjectStream(doc, content, dims.bbox, dims.matrix);
1015+
annot_dict->GetOrCreateDictFor(pdfium::annotation::kAP)
1016+
->SetNewFor<CPDF_Reference>("N", doc, normal_obj_num);
1017+
}
1018+
9911019
// static
9921020
void CPDF_AppearanceGenerator::GenerateCheckboxFormAP(
9931021
CPDF_Document* doc,

‎fpdfsdk/epdf_annot.cpp‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4605,7 +4605,7 @@ FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
46054605
EPDFAnnot_GenerateFormFieldAP(FPDF_ANNOTATION annot) {
46064606
ScopedFPDFAnnotationView annot_view(annot);
46074607
CPDF_AnnotContext* pContext = CPDFAnnotContextFromFPDFAnnotation(annot);
4608-
if (!pContext) {
4608+
if (!pContext || FPDFAnnot_GetSubtype(annot) != FPDF_ANNOT_WIDGET) {
46094609
return false;
46104610
}
46114611

@@ -4631,8 +4631,10 @@ EPDFAnnot_GenerateFormFieldAP(FPDF_ANNOTATION annot) {
46314631
pLookup = pLookup->GetDictFor("Parent");
46324632
}
46334633

4634-
if (ft.IsEmpty()) {
4635-
return false;
4634+
// A signature field's widget and one in no field show only their box.
4635+
if (ft.IsEmpty() || ft == "Sig") {
4636+
CPDF_AppearanceGenerator::GenerateBoxFormAP(pDoc, pAnnotDict.Get());
4637+
return true;
46364638
}
46374639

46384640
uint32_t ff = 0;

‎fpdfsdk/epdf_form.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,6 +2450,9 @@ void BakeWidgetAppearance(CPDF_Document* doc,
24502450
CPDF_AppearanceGenerator::GenerateFormAP(
24512451
doc, widget, CPDF_AppearanceGenerator::kListBox);
24522452
break;
2453+
case 7: // signature: its box, until a signature is drawn into it
2454+
CPDF_AppearanceGenerator::GenerateBoxFormAP(doc, widget);
2455+
break;
24532456
default:
24542457
break;
24552458
}

‎fpdfsdk/epdf_form_embeddertest.cpp‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,88 @@ TEST_F(EPDFFormEmbedderTest, AttachWidgetsFormsARadioGroup) {
14781478
UnloadPage(page);
14791479
}
14801480

1481+
TEST_F(EPDFFormEmbedderTest, AttachToSignatureFieldDrawsItsBox) {
1482+
ASSERT_TRUE(OpenDocument("hello_world.pdf"));
1483+
FPDF_PAGE page = LoadPage(0);
1484+
ASSERT_TRUE(page);
1485+
1486+
const uint32_t field = EPDFForm_CreateField(
1487+
document(), EPDF_FORMFIELD_FAMILY_SIGNATURE,
1488+
GetFPDFWideString(L"approval").get(), 0);
1489+
ASSERT_GT(field, 0u);
1490+
const uint32_t widget = CreateWidgetAnnot(page, 20, 200, 180, 240);
1491+
ASSERT_GT(widget, 0u);
1492+
{
1493+
ScopedFPDFAnnotation annot(EPDFPage_GetAnnotByObjectNumber(page, widget));
1494+
ASSERT_TRUE(annot);
1495+
ASSERT_TRUE(EPDFAnnot_SetMKColor(annot.get(), EPDF_MK_COLOR_BG, 255, 0, 0));
1496+
ASSERT_TRUE(EPDFAnnot_SetMKColor(annot.get(), EPDF_MK_COLOR_BC, 0, 0, 255));
1497+
}
1498+
ASSERT_TRUE(EPDFForm_AttachWidget(document(), field, widget, nullptr, 0));
1499+
1500+
// The box: the background filled, the border stroked inside it.
1501+
const std::wstring box = GetEffectiveWidgetAppearance(document(), widget);
1502+
EXPECT_NE(std::wstring::npos, box.find(L"1 0 0 rg"));
1503+
EXPECT_NE(std::wstring::npos, box.find(L"re f*"));
1504+
EXPECT_NE(std::wstring::npos, box.find(L"0 0 1 RG"));
1505+
EXPECT_NE(std::wstring::npos, box.find(L"re s"));
1506+
UnloadPage(page);
1507+
}
1508+
1509+
TEST_F(EPDFFormEmbedderTest, SignatureFieldBoxRedrawsInItsNewStyle) {
1510+
ASSERT_TRUE(OpenDocument("hello_world.pdf"));
1511+
FPDF_PAGE page = LoadPage(0);
1512+
ASSERT_TRUE(page);
1513+
1514+
const uint32_t field = EPDFForm_CreateField(
1515+
document(), EPDF_FORMFIELD_FAMILY_SIGNATURE,
1516+
GetFPDFWideString(L"approval").get(), 0);
1517+
ASSERT_GT(field, 0u);
1518+
const uint32_t widget = CreateWidgetAnnot(page, 20, 200, 180, 240);
1519+
ASSERT_GT(widget, 0u);
1520+
ASSERT_TRUE(EPDFForm_AttachWidget(document(), field, widget, nullptr, 0));
1521+
// No colours: an empty box.
1522+
EXPECT_EQ(std::wstring::npos,
1523+
GetEffectiveWidgetAppearance(document(), widget).find(L"rg"));
1524+
1525+
ScopedFPDFAnnotation annot(EPDFPage_GetAnnotByObjectNumber(page, widget));
1526+
ASSERT_TRUE(annot);
1527+
ASSERT_TRUE(EPDFAnnot_SetMKColor(annot.get(), EPDF_MK_COLOR_BG, 0, 255, 0));
1528+
ASSERT_TRUE(EPDFAnnot_GenerateFormFieldAP(annot.get()));
1529+
EXPECT_NE(std::wstring::npos,
1530+
GetEffectiveWidgetAppearance(document(), widget).find(L"0 1 0 rg"));
1531+
UnloadPage(page);
1532+
}
1533+
1534+
TEST_F(EPDFFormEmbedderTest, WidgetInNoFieldDrawsItsBox) {
1535+
ASSERT_TRUE(OpenDocument("hello_world.pdf"));
1536+
FPDF_PAGE page = LoadPage(0);
1537+
ASSERT_TRUE(page);
1538+
1539+
const uint32_t widget = CreateWidgetAnnot(page, 20, 200, 180, 240);
1540+
ASSERT_GT(widget, 0u);
1541+
ScopedFPDFAnnotation annot(EPDFPage_GetAnnotByObjectNumber(page, widget));
1542+
ASSERT_TRUE(annot);
1543+
ASSERT_TRUE(EPDFAnnot_SetMKColor(annot.get(), EPDF_MK_COLOR_BG, 0, 0, 255));
1544+
ASSERT_TRUE(EPDFAnnot_GenerateFormFieldAP(annot.get()));
1545+
EXPECT_NE(std::wstring::npos,
1546+
GetEffectiveWidgetAppearance(document(), widget).find(L"0 0 1 rg"));
1547+
UnloadPage(page);
1548+
}
1549+
1550+
TEST_F(EPDFFormEmbedderTest, GenerateFormFieldAPRefusesAnotherSubtype) {
1551+
ASSERT_TRUE(OpenDocument("hello_world.pdf"));
1552+
FPDF_PAGE page = LoadPage(0);
1553+
ASSERT_TRUE(page);
1554+
1555+
ScopedFPDFAnnotation square(EPDFPage_CreateAnnot(page, FPDF_ANNOT_SQUARE));
1556+
ASSERT_TRUE(square);
1557+
EXPECT_FALSE(EPDFAnnot_GenerateFormFieldAP(square.get()));
1558+
EXPECT_FALSE(EPDFAnnot_HasAppearanceStream(square.get(),
1559+
FPDF_ANNOT_APPEARANCEMODE_NORMAL));
1560+
UnloadPage(page);
1561+
}
1562+
14811563
TEST_F(EPDFFormEmbedderTest, AttachToLegacyMergedFieldKeepsFieldId) {
14821564
ASSERT_TRUE(OpenDocument("toggle_fields.pdf"));
14831565
FPDF_PAGE page = LoadPage(0);

‎public/epdf_annot.h‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1626,10 +1626,14 @@ FPDF_EXPORT int FPDF_CALLCONV EPDFAnnot_GetMKRotation(FPDF_ANNOTATION annot);
16261626
// symbol /MK /CA names in the /DA colour. A push button: its
16271627
// background and border, and its caption (/MK /CA) centred in
16281628
// the /DA font and colour; an icon (/MK /I) is not drawn.
1629+
// - /Sig, and a widget in no field -> its box: the background and border
1630+
// as a text field draws them. It replaces whatever the widget
1631+
// shows, a signature drawn into it included.
16291632
//
16301633
// annot - handle to a widget annotation.
16311634
//
1632-
// Returns true on success, false if the annotation is not a form field.
1635+
// Returns true on success, false for another subtype or a field type it
1636+
// doesn't draw.
16331637
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
16341638
EPDFAnnot_GenerateFormFieldAP(FPDF_ANNOTATION annot);
16351639

‎public/epdf_form.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ EPDFForm_CreateField(FPDF_DOCUMENT document,
641641
// merged field). For checkbox/radio fields |on_state| names the widget's
642642
// checked appearance state and must be non-empty; for other families pass
643643
// NULL. Adoption wires /Parent + /Kids, seeds toggle /AP states and /AS,
644-
// and bakes the family-correct appearance stream. Adopting into a legacy
644+
// and bakes the family-correct appearance stream (a signature field's: its
645+
// box, EPDFAnnot_GenerateFormFieldAP()). Adopting into a legacy
645646
// MERGED field first splits it (the field keeps its object number; the
646647
// previously merged widget becomes a new kid annotation, whose /P names its
647648
// page - widget identity changes, field identity never does). |split_objnum| is

‎testing/embedpdf_layer_mode_writers.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ EPDFFontEmbedderTest.WidgetDrawsEachClusterInAFontThatHasIt 20
214214
EPDFFontEmbedderTest.WidgetDrawsWhatNoFontHasWithTheLastResort 20
215215
EPDFFontEmbedderTest.WidgetWritesWithADocumentProgram 52
216216
EPDFFormEmbedderTest.AttachToLegacyMergedFieldKeepsFieldId 6
217+
EPDFFormEmbedderTest.AttachToSignatureFieldDrawsItsBox 7
217218
EPDFFormEmbedderTest.AttachWidgetsFormsARadioGroup 11
218219
EPDFFormEmbedderTest.CalculationOrderSetReplaceAndRemove 1
219220
EPDFFormEmbedderTest.ChoiceValueAndIndicesAreWrittenAsGiven 3
@@ -230,6 +231,7 @@ EPDFFormEmbedderTest.FieldEmbedMetadataIsSnapshotted 1
230231
EPDFFormEmbedderTest.FieldEmbedMetadataReadsAndWrites 2
231232
EPDFFormEmbedderTest.FieldEmbedMetadataWritesTheFieldNotItsParent 1
232233
EPDFFormEmbedderTest.FieldSettersValidateAndApply 3
234+
EPDFFormEmbedderTest.GenerateFormFieldAPRefusesAnotherSubtype 2
233235
EPDFFormEmbedderTest.ListBoxTopIndexIsRead 1
234236
EPDFFormEmbedderTest.ListBoxValuesAreAnArray 1
235237
EPDFFormEmbedderTest.MalformedDefaultReadsAsUnsupported 1
@@ -242,13 +244,15 @@ EPDFFormEmbedderTest.RepairCreatesAcroFormAndLinksKids 4
242244
EPDFFormEmbedderTest.RepairLinksRecoveredFields 1
243245
EPDFFormEmbedderTest.RequiredMultiSelectArrayIsNotSkippedOnExport 2
244246
EPDFFormEmbedderTest.SetFieldOptionsWritesOnlyTheOptions 1
247+
EPDFFormEmbedderTest.SignatureFieldBoxRedrawsInItsNewStyle 8
245248
EPDFFormEmbedderTest.SplitKeepsEachPlanesActions 5
246249
EPDFFormEmbedderTest.TextValueAndItsPicture 2
247250
EPDFFormEmbedderTest.ToggleDefaultIsTheNameItIsGiven 1
248251
EPDFFormEmbedderTest.ToggleValueIsTheNameItIsGiven 1
249252
EPDFFormEmbedderTest.ToggleWidgetsTurnOnTheirOwnState 3
250253
EPDFFormEmbedderTest.TwoPlaneTwinWidgetsFillTogether 9
251254
EPDFFormEmbedderTest.TypedValueSnapshotPreservesPdfShapes 3
255+
EPDFFormEmbedderTest.WidgetInNoFieldDrawsItsBox 3
252256
EPDFFormEmbedderTest.WidgetRotationTurnsItsAppearance 2
253257
EPDFInkAppearanceEmbedderTest.AZigzagIsOneCubicPerPair 4
254258
EPDFInkAppearanceEmbedderTest.DrawsAcrobatsCurves 10

0 commit comments

Comments
 (0)