Commit 930eade6d36060b1bdc5934e74952fa6bcfdb07f

Authored by Jay Berkenbilt
1 parent 65ef0bf3

Fix omissions in text appearance generation

When generating appearance streams for variable text annotations,
properly handle the cases of there being no appearance dictionary, no
appearance stream, or an appearance stream with no BMC..EMC marker.
ChangeLog
1 2019-01-20 Jay Berkenbilt <ejb@ql.org> 1 2019-01-20 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * When generating appearance streams for variable text
  4 + annotations, properly handle the cases of there being no
  5 + appearance dictionary, no appearance stream, or an appearance
  6 + stream with no BMC..EMC marker.
  7 +
3 * When flattening annotations, remove annotations from the file 8 * When flattening annotations, remove annotations from the file
4 that don't have appearance streams. These were previously being 9 that don't have appearance streams. These were previously being
5 preserved, but since they are invisible, there is no reason to 10 preserved, but since they are invisible, there is no reason to
libqpdf/QPDFFormFieldObjectHelper.cc
@@ -506,6 +506,7 @@ class ValueSetter: public QPDFObjectHandle::TokenFilter @@ -506,6 +506,7 @@ class ValueSetter: public QPDFObjectHandle::TokenFilter
506 { 506 {
507 } 507 }
508 virtual void handleToken(QPDFTokenizer::Token const&); 508 virtual void handleToken(QPDFTokenizer::Token const&);
  509 + virtual void handleEOF();
509 void writeAppearance(); 510 void writeAppearance();
510 511
511 private: 512 private:
@@ -515,6 +516,7 @@ class ValueSetter: public QPDFObjectHandle::TokenFilter @@ -515,6 +516,7 @@ class ValueSetter: public QPDFObjectHandle::TokenFilter
515 double tf; 516 double tf;
516 QPDFObjectHandle::Rectangle bbox; 517 QPDFObjectHandle::Rectangle bbox;
517 enum { st_top, st_bmc, st_emc, st_end } state; 518 enum { st_top, st_bmc, st_emc, st_end } state;
  519 + bool replaced;
518 }; 520 };
519 521
520 ValueSetter::ValueSetter(std::string const& DA, std::string const& V, 522 ValueSetter::ValueSetter(std::string const& DA, std::string const& V,
@@ -525,7 +527,8 @@ ValueSetter::ValueSetter(std::string const&amp; DA, std::string const&amp; V, @@ -525,7 +527,8 @@ ValueSetter::ValueSetter(std::string const&amp; DA, std::string const&amp; V,
525 opt(opt), 527 opt(opt),
526 tf(tf), 528 tf(tf),
527 bbox(bbox), 529 bbox(bbox),
528 - state(st_top) 530 + state(st_top),
  531 + replaced(false)
529 { 532 {
530 } 533 }
531 534
@@ -575,8 +578,22 @@ ValueSetter::handleToken(QPDFTokenizer::Token const&amp; token) @@ -575,8 +578,22 @@ ValueSetter::handleToken(QPDFTokenizer::Token const&amp; token)
575 } 578 }
576 } 579 }
577 580
578 -void ValueSetter::writeAppearance() 581 +void
  582 +ValueSetter::handleEOF()
579 { 583 {
  584 + if (! this->replaced)
  585 + {
  586 + QTC::TC("qpdf", "QPDFFormFieldObjectHelper replaced BMC at EOF");
  587 + write("/Tx BMC\n");
  588 + writeAppearance();
  589 + }
  590 +}
  591 +
  592 +void
  593 +ValueSetter::writeAppearance()
  594 +{
  595 + this->replaced = true;
  596 +
580 // This code does not take quadding into consideration because 597 // This code does not take quadding into consideration because
581 // doing so requires font metric information, which we don't 598 // doing so requires font metric information, which we don't
582 // have in many cases. 599 // have in many cases.
@@ -768,6 +785,29 @@ QPDFFormFieldObjectHelper::generateTextAppearance( @@ -768,6 +785,29 @@ QPDFFormFieldObjectHelper::generateTextAppearance(
768 QPDFAnnotationObjectHelper& aoh) 785 QPDFAnnotationObjectHelper& aoh)
769 { 786 {
770 QPDFObjectHandle AS = aoh.getAppearanceStream("/N"); 787 QPDFObjectHandle AS = aoh.getAppearanceStream("/N");
  788 + if (AS.isNull())
  789 + {
  790 + QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AS from scratch");
  791 + QPDFObjectHandle::Rectangle rect = aoh.getRect();
  792 + QPDFObjectHandle::Rectangle bbox(
  793 + 0, 0, rect.urx - rect.llx, rect.ury - rect.lly);
  794 + QPDFObjectHandle dict = QPDFObjectHandle::parse(
  795 + "<< /Resources << /ProcSet [ /PDF /Text ] >>"
  796 + " /Type /XObject /Subtype /Form >>");
  797 + dict.replaceKey("/BBox", QPDFObjectHandle::newFromRectangle(bbox));
  798 + AS = QPDFObjectHandle::newStream(
  799 + this->oh.getOwningQPDF(), "/Tx BMC\nEMC\n");
  800 + AS.replaceDict(dict);
  801 + QPDFObjectHandle AP = aoh.getAppearanceDictionary();
  802 + if (AP.isNull())
  803 + {
  804 + QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AP from scratch");
  805 + aoh.getObjectHandle().replaceKey(
  806 + "/AP", QPDFObjectHandle::newDictionary());
  807 + AP = aoh.getAppearanceDictionary();
  808 + }
  809 + AP.replaceKey("/N", AS);
  810 + }
771 if (! AS.isStream()) 811 if (! AS.isStream())
772 { 812 {
773 aoh.getObjectHandle().warnIfPossible( 813 aoh.getObjectHandle().warnIfPossible(
qpdf/qpdf.testcov
@@ -422,3 +422,6 @@ qpdf bytes fallback warning 0 @@ -422,3 +422,6 @@ qpdf bytes fallback warning 0
422 qpdf invalid utf-8 in auto 0 422 qpdf invalid utf-8 in auto 0
423 qpdf input password hex-bytes 0 423 qpdf input password hex-bytes 0
424 QPDFPageDocumentHelper ignore annotation with no appearance 0 424 QPDFPageDocumentHelper ignore annotation with no appearance 0
  425 +QPDFFormFieldObjectHelper create AS from scratch 0
  426 +QPDFFormFieldObjectHelper create AP from scratch 0
  427 +QPDFFormFieldObjectHelper replaced BMC at EOF 0
qpdf/qtest/qpdf.test
@@ -261,18 +261,29 @@ $td-&gt;runtest(&quot;compare files&quot;, @@ -261,18 +261,29 @@ $td-&gt;runtest(&quot;compare files&quot;,
261 show_ntests(); 261 show_ntests();
262 # ---------- 262 # ----------
263 $td->notify("--- Appearance Streams ---"); 263 $td->notify("--- Appearance Streams ---");
264 -$n_tests += 4; 264 +$n_tests += 8;
265 265
266 -$td->runtest("generate appearances and flatten",  
267 - {$td->COMMAND =>  
268 - "qpdf --qdf --no-original-object-ids --static-id" .  
269 - " --generate-appearances --flatten-annotations=all" .  
270 - " need-appearances.pdf a.pdf"},  
271 - {$td->STRING => "", $td->EXIT_STATUS => 0},  
272 - $td->NORMALIZE_NEWLINES);  
273 -$td->runtest("compare files",  
274 - {$td->FILE => "a.pdf"},  
275 - {$td->FILE => "appearances-a.pdf"}); 266 +foreach my $f ('need-appearances',
  267 + 'need-appearances-more',
  268 + 'need-appearances-more2')
  269 +{
  270 + $td->runtest("generate appearances and flatten ($f)",
  271 + {$td->COMMAND =>
  272 + "qpdf --qdf --no-original-object-ids --static-id" .
  273 + " --generate-appearances --flatten-annotations=all" .
  274 + " $f.pdf a.pdf"},
  275 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  276 + $td->NORMALIZE_NEWLINES);
  277 + my $exp = 'appearances-a';
  278 + if ($f =~ m/appearances(-.*)$/)
  279 + {
  280 + $exp .= $1;
  281 + }
  282 + $exp .= '.pdf';
  283 + $td->runtest("compare files",
  284 + {$td->FILE => "a.pdf"},
  285 + {$td->FILE => $exp});
  286 +}
276 287
277 $td->runtest("more choices", 288 $td->runtest("more choices",
278 {$td->COMMAND => 289 {$td->COMMAND =>
qpdf/qtest/qpdf/appearances-a-more.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/appearances-a-more2.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/need-appearances-more2.pdf 0 โ†’ 100644
No preview for this file type