Add transformAnnotations and fix flattenRotations to use it

This commit is contained in:
Jay Berkenbilt 2021-02-21 16:06:58 -05:00
parent a76decd2d5
commit a9ae8cadc6
17 changed files with 10295 additions and 5 deletions

View File

@ -1,5 +1,11 @@
2021-02-21 Jay Berkenbilt <ejb@ql.org>
* Bug fix: --flatten-rotation now applies the required
transformation to annotations on the page.
* Add QPDFAcroFormDocumentHelper::transformAnnotations to apply a
transformation to a group of annotations.
* Add QPDFObjGen::unparse()
* Add QPDFObjectHandle::copyStream() for making a copy of a stream

View File

@ -113,6 +113,10 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
QPDF_DLL
void addFormField(QPDFFormFieldObjectHelper);
// Remove fields from the fields array
QPDF_DLL
void removeFormFields(std::set<QPDFObjGen> const&);
// Return a vector of all terminal fields in a document. Terminal
// fields are fields that have no children that are also fields.
// Terminal fields may still have children that are annotations.
@ -174,6 +178,32 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
QPDF_DLL
void generateAppearancesIfNeeded();
// Note: this method works on all annotations, not just ones with
// associated fields. For each annotation in old_annots, apply the
// given transformation matrix to create a new annotation. New
// annotations are appended to new_annots. If the annotation is
// associated with a form field, a new form field is created that
// points to the new annotation and is appended to new_fields, and
// the old field is added to old_fields.
//
// old_annots may belong to a different QPDF object. In that case,
// you should pass in from_qpdf, and copyForeignObject will be
// called automatically. If this is the case, for efficiency, you
// may pass in a QPDFAcroFormDocumentHelper for the other file to
// avoid the expensive process of creating one for each call to
// transformAnnotations. New fields and annotations are not added
// to the document or pages. You have to do that yourself after
// calling transformAnnotations.
QPDF_DLL
void transformAnnotations(
QPDFObjectHandle old_annots,
std::vector<QPDFObjectHandle>& new_annots,
std::vector<QPDFObjectHandle>& new_fields,
std::set<QPDFObjGen>& old_fields,
QPDFMatrix const& cm,
QPDF* from_qpdf = nullptr,
QPDFAcroFormDocumentHelper* from_afdh = nullptr);
private:
void analyze();
void traverseField(QPDFObjectHandle field,

View File

@ -40,6 +40,13 @@ class QPDFAnnotationObjectHelper: public QPDFObjectHelper
// This class provides helper methods for annotations. More
// functionality will likely be added in the future.
// Some functionality for annotations is also implemented in
// QPDFAcroFormDocumentHelper and QPDFFormFieldObjectHelper. In
// some cases, functions defined there work for other annotations
// besides widget annotations, but they are implemented with form
// fields so that they can properly handle form fields when
// needed.
// Return the subtype of the annotation as a string (e.g.
// "/Widget"). Returns the empty string if the subtype (which is
// required by the spec) is missing.

View File

@ -31,6 +31,8 @@
#include <qpdf/QPDFObjectHandle.hh>
#include <functional>
class QPDFAcroFormDocumentHelper;
class QPDFPageObjectHelper: public QPDFObjectHelper
{
// This is a helper class for page objects, but as of qpdf 10.1,
@ -323,9 +325,15 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
// various page bounding boxes (/MediaBox, etc.) so that the page
// will have the same semantics. This can be useful to work around
// problems with PDF applications that can't properly handle
// rotated pages.
// rotated pages. If a QPDFAcroFormDocumentHelper is provided, it
// will be used for resolving any form fields that have to be
// rotated. If not, one will be created inside the function, which
// is less efficient.
QPDF_DLL
void flattenRotation();
// ABI: merge versions and make afdh default to nullptr
QPDF_DLL
void flattenRotation(QPDFAcroFormDocumentHelper* afdh);
private:
static bool

View File

@ -54,6 +54,50 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited);
}
void
QPDFAcroFormDocumentHelper::removeFormFields(
std::set<QPDFObjGen> const& to_remove)
{
auto acroform = this->qpdf.getRoot().getKey("/AcroForm");
if (! acroform.isDictionary())
{
return;
}
auto fields = acroform.getKey("/Fields");
if (! fields.isArray())
{
return;
}
for (auto const& og: to_remove)
{
auto annotations = this->m->field_to_annotations.find(og);
if (annotations != this->m->field_to_annotations.end())
{
for (auto aoh: annotations->second)
{
this->m->annotation_to_field.erase(
aoh.getObjectHandle().getObjGen());
}
this->m->field_to_annotations.erase(og);
}
}
int i = 0;
while (i < fields.getArrayNItems())
{
auto field = fields.getArrayItem(i);
if (to_remove.count(field.getObjGen()))
{
fields.eraseItem(i);
}
else
{
++i;
}
}
}
std::vector<QPDFFormFieldObjectHelper>
QPDFAcroFormDocumentHelper::getFormFields()
{
@ -350,3 +394,273 @@ QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded()
}
setNeedAppearances(false);
}
void
QPDFAcroFormDocumentHelper::transformAnnotations(
QPDFObjectHandle old_annots,
std::vector<QPDFObjectHandle>& new_annots,
std::vector<QPDFObjectHandle>& new_fields,
std::set<QPDFObjGen>& old_fields,
QPDFMatrix const& cm,
QPDF* from_qpdf,
QPDFAcroFormDocumentHelper* from_afdh)
{
PointerHolder<QPDFAcroFormDocumentHelper> afdhph;
if (! from_qpdf)
{
// Assume these are from the same QPDF.
from_qpdf = &this->qpdf;
from_afdh = this;
}
else if ((from_qpdf != &this->qpdf) && (! from_afdh))
{
afdhph = new QPDFAcroFormDocumentHelper(*from_qpdf);
from_afdh = afdhph.getPointer();
}
bool foreign = (from_qpdf != &this->qpdf);
std::set<QPDFObjGen> added_new_fields;
// This helper prevents us from copying the same object
// multiple times.
std::map<QPDFObjGen, QPDFObjectHandle> copied_objects;
auto maybe_copy_object = [&](QPDFObjectHandle& to_copy) {
auto og = to_copy.getObjGen();
if (copied_objects.count(og))
{
to_copy = copied_objects[og];
return false;
}
else
{
to_copy = this->qpdf.makeIndirectObject(to_copy.shallowCopy());
copied_objects[og] = to_copy;
return true;
}
};
for (auto annot: QPDFArrayItems(old_annots))
{
if (annot.isStream())
{
annot.warnIfPossible("ignoring annotation that's a stream");
continue;
}
// Make copies of annotations and fields down to the
// appearance streams, preserving all internal referential
// integrity. When the incoming annotations are from a
// different file, we first copy them locally. Then, whether
// local or foreign, we copy them again so that if we bring
// the same annotation in multiple times (e.g. overlaying a
// foreign page onto multiple local pages or a local page onto
// multiple other local pages), we don't create annotations
// that are referenced in more than one place. If we did that,
// the effect of applying transformations would be cumulative,
// which is definitely not what we want. Besides, annotations
// and fields are not intended to be referenced in multiple
// places.
// Determine if this annotation is attached to a form field.
// If so, the annotation may be the same object as the form
// field, or the form field may have the annotation as a kid.
// In either case, we have to walk up the field structure to
// find the top-level field. Within one iteration through a
// set of annotations, we don't want to copy the same item
// more than once. For example, suppose we have field A with
// kids B, C, and D, each of which has annotations BA, CA, and
// DA. When we get to BA, we will find that BA is a kid of B
// which is under A. When we do a copyForeignObject of A, it
// will also copy everything else because of the indirect
// references. When we clone BA, we will want to clone A and
// then update A's clone's kid to point B's clone and B's
// clone's parent to point to A's clone. The same thing holds
// for annotatons. Next, when we get to CA, we will again
// discover that A is the top, but we don't want to re-copy A.
// We want CA's clone to be linked to the same clone as BA's.
// Failure to do this will break up things like radio button
// groups, which all have to kids of the same parent.
auto ffield = from_afdh->getFieldForAnnotation(annot);
auto ffield_oh = ffield.getObjectHandle();
QPDFObjectHandle top_field;
bool have_field = false;
bool have_parent = false;
if (ffield_oh.isStream())
{
ffield_oh.warnIfPossible("ignoring form field that's a stream");
}
else if ((! ffield_oh.isNull()) && (! ffield_oh.isIndirect()))
{
ffield_oh.warnIfPossible("ignoring form field not indirect");
}
else if (! ffield_oh.isNull())
{
// A field and its associated annotation can be the same
// object. This matters because we don't want to clone the
// annotation and field separately in this case.
have_field = true;
// Find the top-level field. It may be the field itself.
top_field = ffield_oh;
std::set<QPDFObjGen> seen;
while (! top_field.getKey("/Parent").isNull())
{
top_field = top_field.getKey("/Parent");
have_parent = true;
auto og = top_field.getObjGen();
if (seen.count(og))
{
break;
}
seen.insert(og);
}
if (foreign)
{
// copyForeignObject returns the same value if called
// multiple times with the same field. Create/retrieve
// the local copy of the original field. This pulls
// over everything the field references including
// annotations and appearance streams, but it's
// harmless to call copyForeignObject on them too.
// They will already be copied, so we'll get the right
// object back.
top_field = this->qpdf.copyForeignObject(top_field);
ffield_oh = this->qpdf.copyForeignObject(ffield_oh);
}
old_fields.insert(top_field.getObjGen());
// Traverse the field, copying kids, and preserving
// integrity.
std::list<QPDFObjectHandle> queue;
if (maybe_copy_object(top_field))
{
queue.push_back(top_field);
}
seen.clear();
while (! queue.empty())
{
QPDFObjectHandle obj = queue.front();
queue.pop_front();
auto orig_og = obj.getObjGen();
if (seen.count(orig_og))
{
// loop
break;
}
seen.insert(orig_og);
auto parent = obj.getKey("/Parent");
if (parent.isIndirect())
{
auto parent_og = parent.getObjGen();
if (copied_objects.count(parent_og))
{
obj.replaceKey("/Parent", copied_objects[parent_og]);
}
else
{
parent.warnIfPossible(
"while traversing field " +
obj.getObjGen().unparse() +
", found parent (" + parent_og.unparse() +
") that had not been seen, indicating likely"
" invalid field structure");
}
}
auto kids = obj.getKey("/Kids");
if (kids.isArray())
{
for (int i = 0; i < kids.getArrayNItems(); ++i)
{
auto kid = kids.getArrayItem(i);
if (maybe_copy_object(kid))
{
kids.setArrayItem(i, kid);
queue.push_back(kid);
}
}
}
}
// Now switch to copies. We already switched for top_field
maybe_copy_object(ffield_oh);
ffield = QPDFFormFieldObjectHelper(ffield_oh);
}
QTC::TC("qpdf", "QPDFAcroFormDocumentHelper copy annotation",
(have_field ? 1 : 0) | (foreign ? 2 : 0));
if (have_field)
{
QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field with parent",
(have_parent ? 1 : 0) | (foreign ? 2 : 0));
}
if (foreign)
{
annot = this->qpdf.copyForeignObject(annot);
}
maybe_copy_object(annot);
// Now we have copies, so we can safely mutate.
if (have_field && ! added_new_fields.count(top_field.getObjGen()))
{
new_fields.push_back(top_field);
added_new_fields.insert(top_field.getObjGen());
}
new_annots.push_back(annot);
// Identify and copy any appearance streams
auto ah = QPDFAnnotationObjectHelper(annot);
auto apdict = ah.getAppearanceDictionary();
std::vector<QPDFObjectHandle> streams;
auto replace_stream = [](auto& dict, auto& key, auto& old) {
auto new_stream = old.copyStream();
dict.replaceKey(key, new_stream);
return new_stream;
};
if (apdict.isDictionary())
{
for (auto& ap: QPDFDictItems(apdict))
{
if (ap.second.isStream())
{
streams.push_back(
replace_stream(apdict, ap.first, ap.second));
}
else if (ap.second.isDictionary())
{
for (auto& ap2: QPDFDictItems(ap.second))
{
if (ap2.second.isStream())
{
streams.push_back(
replace_stream(
ap.second, ap2.first, ap2.second));
}
}
}
}
}
// Now we can safely mutate the annotation and its appearance
// streams.
for (auto& stream: streams)
{
auto omatrix = stream.getDict().getKey("/Matrix");
QPDFMatrix apcm;
if (omatrix.isArray())
{
QTC::TC("qpdf", "QPDFAcroFormDocumentHelper modify ap matrix");
auto m1 = omatrix.getArrayAsMatrix();
apcm = QPDFMatrix(m1);
}
apcm.concat(cm);
auto new_matrix = QPDFObjectHandle::newFromMatrix(apcm);
stream.getDict().replaceKey("/Matrix", new_matrix);
}
auto rect = cm.transformRectangle(
annot.getKey("/Rect").getArrayAsRectangle());
annot.replaceKey(
"/Rect", QPDFObjectHandle::newFromRectangle(rect));
}
}

View File

@ -7,6 +7,7 @@
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDFMatrix.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
class ContentProvider: public QPDFObjectHandle::StreamDataProvider
{
@ -1080,6 +1081,12 @@ QPDFPageObjectHelper::placeFormXObject(
void
QPDFPageObjectHelper::flattenRotation()
{
flattenRotation(nullptr);
}
void
QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh)
{
QPDF* qpdf = this->oh.getOwningQPDF();
if (! qpdf)
@ -1206,4 +1213,26 @@ QPDFPageObjectHelper::flattenRotation()
QTC::TC("qpdf", "QPDFPageObjectHelper flatten inherit rotate");
this->oh.replaceKey("/Rotate", QPDFObjectHandle::newInteger(0));
}
QPDFObjectHandle annots = this->oh.getKey("/Annots");
if (annots.isArray())
{
std::vector<QPDFObjectHandle> new_annots;
std::vector<QPDFObjectHandle> new_fields;
std::set<QPDFObjGen> old_fields;
PointerHolder<QPDFAcroFormDocumentHelper> afdhph;
if (! afdh)
{
afdhph = new QPDFAcroFormDocumentHelper(*qpdf);
afdh = afdhph.getPointer();
}
afdh->transformAnnotations(
annots, new_annots, new_fields, old_fields, cm);
afdh->removeFormFields(old_fields);
for (auto const& f: new_fields)
{
afdh->addFormField(QPDFFormFieldObjectHelper(f));
}
this->oh.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots));
}
}

View File

@ -5313,6 +5313,13 @@ print "\n";
dictionary if needed.
</para>
</listitem>
<listitem>
<para>
Add method
<function>QPDFAcroFormDocumentHelper::transformAnnotations</function>,
which applies a transformation to each annotation on a page.
</para>
</listitem>
<listitem>
<para>
Add <function>QUtil::path_basename</function> to return the
@ -5341,6 +5348,12 @@ print "\n";
Bug Fixes
</para>
<itemizedlist>
<listitem>
<para>
The <option>--flatten-rotations</option> option applies
transformations to any annotations that may be on the page.
</para>
</listitem>
<listitem>
<para>
If a form XObject lacks a resources dictionary, consider any

View File

@ -5362,6 +5362,13 @@ static void copy_attachments(QPDF& pdf, Options& o, int& exit_code)
static void handle_transformations(QPDF& pdf, Options& o, int& exit_code)
{
QPDFPageDocumentHelper dh(pdf);
PointerHolder<QPDFAcroFormDocumentHelper> afdh;
auto make_afdh = [&]() {
if (! afdh.getPointer())
{
afdh = new QPDFAcroFormDocumentHelper(pdf);
}
};
if (o.externalize_inline_images)
{
std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
@ -5408,8 +5415,8 @@ static void handle_transformations(QPDF& pdf, Options& o, int& exit_code)
}
if (o.generate_appearances)
{
QPDFAcroFormDocumentHelper afdh(pdf);
afdh.generateAppearancesIfNeeded();
make_afdh();
afdh->generateAppearancesIfNeeded();
}
if (o.flatten_annotations)
{
@ -5427,9 +5434,10 @@ static void handle_transformations(QPDF& pdf, Options& o, int& exit_code)
}
if (o.flatten_rotation)
{
make_afdh();
for (auto& page: dh.getAllPages())
{
page.flattenRotation();
page.flattenRotation(afdh.getPointer());
}
}
if (o.remove_page_labels)

View File

@ -572,3 +572,6 @@ qpdf password file 0
QPDFFileSpecObjectHelper empty compat_name 0
QPDFFileSpecObjectHelper non-empty compat_name 0
QPDFPageObjectHelper flatten inherit rotate 0
QPDFAcroFormDocumentHelper copy annotation 1
QPDFAcroFormDocumentHelper field with parent 1
QPDFAcroFormDocumentHelper modify ap matrix 0

View File

@ -2248,7 +2248,7 @@ $td->runtest("explicit keep files open = n",
show_ntests();
# ----------
$td->notify("--- Rotate Pages ---");
$n_tests += 8;
$n_tests += 18;
# Do absolute, positive, and negative on ranges that include
# inherited and non-inherited.
# Pages 11-15 inherit /Rotate 90
@ -2290,6 +2290,49 @@ $td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "inherited-flattened.pdf"});
foreach my $angle (qw(90 180 270))
{
$td->runtest("rotate annotations",
{$td->COMMAND =>
"qpdf --static-id --qdf --rotate=$angle" .
" --flatten-rotation --no-original-object-ids" .
" form-fields-and-annotations.pdf a.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("check output (flatten $angle)",
{$td->FILE => "a.pdf"},
{$td->FILE => "annotations-rotated-$angle.pdf"});
}
# The file form-fields-and-annotations-shared.pdf contains some
# annotations that appear in multiple pages /Annots, some non-shared
# things that share appearance streams, some form fields appear on
# multiple pages, and an indirect /Annotations array. It is out of
# spec in several ways but still works in most viewers. These test
# make sure we don't make anything worse and also end up exercising
# some cases of things being copied more than once, though we also
# exercise that with legitimate test cases using overlay.
$td->runtest("shared annotations 1 page",
{$td->COMMAND =>
"qpdf --qdf --no-original-object-ids --static-id" .
" --rotate=90:1 form-fields-and-annotations-shared.pdf" .
" a.pdf --flatten-rotation"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "rotated-shared-annotations-1.pdf"});
$td->runtest("shared annotations 2 pages",
{$td->COMMAND =>
"qpdf --qdf --no-original-object-ids --static-id" .
" --rotate=90:1,2 form-fields-and-annotations-shared.pdf" .
" a.pdf --flatten-rotation"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "rotated-shared-annotations-2.pdf"});
show_ntests();
# ----------
$td->notify("--- Flatten Form/Annotations ---");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,942 @@
%PDF-1.6
%¿÷¢þ
%QDF-1.0
1 0 obj
<<
/AcroForm <<
/DR 2 0 R
/Fields [
3 0 R
4 0 R
5 0 R
]
>>
/Names <<
/EmbeddedFiles 6 0 R
>>
/Pages 7 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Font <<
/F1 8 0 R
>>
>>
endobj
3 0 obj
<<
/AP <<
/N 9 0 R
>>
/DA (0 0.4 0 rg /F1 18 Tf)
/DR 2 0 R
/DV ()
/FT /Tx
/Ff 0
/Rect [
72
470.774
190.8
484.922
]
/Subtype /Widget
/T (Text Box 1)
/Type /Annot
/V (Formy field)
>>
endobj
4 0 obj
<<
/AP <<
/N 11 0 R
>>
/DA (0 0.4 0 rg /F1 18 Tf)
/DR 2 0 R
/DV ()
/FT /Tx
/Ff 0
/Rect [
372
330.774
386.148
470.374
]
/Subtype /Widget
/T (Text Box 1)
/Type /Annot
/V (Rot-ccw field)
>>
endobj
5 0 obj
<<
/DV /1
/FT /Btn
/Ff 49152
/Kids [
13 0 R
14 0 R
15 0 R
]
/T (r1)
/V /2
>>
endobj
6 0 obj
<<
/Names [
(attachment1.txt)
16 0 R
]
>>
endobj
7 0 obj
<<
/Count 1
/Kids [
17 0 R
]
/Type /Pages
>>
endobj
8 0 obj
<<
/BaseFont /Courier
/Encoding /WinAnsiEncoding
/Subtype /Type1
/Type /Font
>>
endobj
9 0 obj
<<
/BBox [
0
-2.826
118.8
11.322
]
/Resources 2 0 R
/Subtype /Form
/Type /XObject
/Length 10 0 R
>>
stream
/Tx BMC
q
BT
/F1 18 Tf
(Formy field) Tj
ET
Q
EMC
endstream
endobj
10 0 obj
53
endobj
11 0 obj
<<
/BBox [
0
-2.826
140.4
11.322
]
/Matrix [
0
1
-1
0
0
0
]
/Resources 2 0 R
/Subtype /Form
/Type /XObject
/Length 12 0 R
>>
stream
/Tx BMC
q
BT
/F1 18 Tf
(Rot-ccw field) Tj
ET
Q
EMC
endstream
endobj
12 0 obj
55
endobj
13 0 obj
<<
/AP <<
/N <<
/1 18 0 R
/Off 20 0 R
>>
>>
/AS /1
/DA (0.18039 0.20392 0.21176 rg /ZaDi 0 Tf)
/DR <<
/Font <<
/ZaDi 22 0 R
>>
>>
/F 4
/FT /Btn
/MK <<
/CA (l)
>>
/Parent 5 0 R
/Rect [
152.749
648.501
164.801
660.549
]
/Subtype /Widget
/Type /Annot
>>
endobj
14 0 obj
<<
/AP <<
/N <<
/2 23 0 R
/Off 25 0 R
>>
>>
/AS /2
/DA (0.18039 0.20392 0.21176 rg /ZaDi 0 Tf)
/DR <<
/Font <<
/ZaDi 22 0 R
>>
>>
/F 4
/FT /Btn
/MK <<
/CA (l)
>>
/Parent 5 0 R
/Rect [
152.749
627.301
164.801
639.349
]
/Subtype /Widget
/Type /Annot
>>
endobj
15 0 obj
<<
/AP <<
/N <<
/3 27 0 R
/Off 29 0 R
>>
>>
/AS /3
/DA (0.18039 0.20392 0.21176 rg /ZaDi 0 Tf)
/DR <<
/Font <<
/ZaDi 22 0 R
>>
>>
/F 4
/FT /Btn
/MK <<
/CA (l)
>>
/Parent 5 0 R
/Rect [
151.399
606.501
163.451
618.549
]
/Subtype /Widget
/Type /Annot
>>
endobj
16 0 obj
<<
/EF <<
/F 31 0 R
/UF 31 0 R
>>
/F (attachment1.txt)
/Type /Filespec
/UF (attachment1.txt)
>>
endobj
%% Page 1
17 0 obj
<<
/Annots [
33 0 R
3 0 R
34 0 R
4 0 R
35 0 R
36 0 R
37 0 R
38 0 R
13 0 R
14 0 R
15 0 R
]
/Contents 39 0 R
/MediaBox [
0
0
612
792
]
/Parent 7 0 R
/Resources 2 0 R
/Type /Page
>>
endobj
18 0 obj
<<
/BBox [
0
0
12.05
12.05
]
/Resources 41 0 R
/Subtype /Form
/Type /XObject
/Length 19 0 R
>>
stream
/Tx BMC
q BT
0.18039 0.20392 0.21176 rg /ZaDi 12.05 Tf
0 0 Td
ET
Q
1 0 0 rg
6 8.4 m 7.35 8.4 8.45 7.35 8.45 6 c
8.45 4.65 7.35 3.55 6 3.55 c
4.65 3.55 3.6 4.65 3.6 6 c
3.6 7.35 4.65 8.4 6 8.4 c f*
EMC
endstream
endobj
19 0 obj
202
endobj
20 0 obj
<<
/BBox [
0
0
12.05
12.05
]
/Resources 41 0 R
/Subtype /Form
/Type /XObject
/Length 21 0 R
>>
stream
/Tx BMC
EMC
endstream
endobj
21 0 obj
12
endobj
22 0 obj
<<
/BaseFont /ZapfDingbats
/Subtype /Type1
/Type /Font
>>
endobj
23 0 obj
<<
/BBox [
0
0
12.05
12.05
]
/Resources 41 0 R
/Subtype /Form
/Type /XObject
/Length 24 0 R
>>
stream
/Tx BMC
q BT
0.18039 0.20392 0.21176 rg /ZaDi 12.05 Tf
0 0 Td
ET
Q
0 1 0 rg
6 8.4 m 7.35 8.4 8.45 7.35 8.45 6 c
8.45 4.65 7.35 3.55 6 3.55 c
4.65 3.55 3.6 4.65 3.6 6 c
3.6 7.35 4.65 8.4 6 8.4 c f*
EMC
endstream
endobj
24 0 obj
202
endobj
25 0 obj
<<
/BBox [
0
0
12.05
12.05
]
/Resources 41 0 R
/Subtype /Form
/Type /XObject
/Length 26 0 R
>>
stream
/Tx BMC
EMC
endstream
endobj
26 0 obj
12
endobj
27 0 obj
<<
/BBox [
0
0
12.05
12.05
]
/Resources 41 0 R
/Subtype /Form
/Type /XObject
/Length 28 0 R
>>
stream
/Tx BMC
q BT
0.18039 0.20392 0.21176 rg /ZaDi 12.05 Tf
0 0 Td
ET
Q
0 0 1 rg
6 8.4 m 7.35 8.4 8.45 7.35 8.45 6 c
8.45 4.65 7.35 3.55 6 3.55 c
4.65 3.55 3.6 4.65 3.6 6 c
3.6 7.35 4.65 8.4 6 8.4 c f*
EMC
endstream
endobj
28 0 obj
202
endobj
29 0 obj
<<
/BBox [
0
0
12.05
12.05
]
/Resources 41 0 R
/Subtype /Form
/Type /XObject
/Length 30 0 R
>>
stream
/Tx BMC
EMC
endstream
endobj
30 0 obj
12
endobj
31 0 obj
<<
/Params <<
/CheckSum <80a33fc110b5a7b8b4d58b8d57e814bc>
/Size 22
/Subtype /text#2fplain
>>
/Type /EmbeddedFile
/Length 32 0 R
>>
stream
content of attachment
endstream
endobj
32 0 obj
22
endobj
33 0 obj
<<
/A <<
/S /URI
/URI (https://www.qbilt.org/)
>>
/Border [
0
0
.4
]
/C [
.8
.6
.6
]
/H /I
/Rect [
72
501.832
374.4
520.696
]
/Subtype /Link
/Type /Annot
>>
endobj
34 0 obj
<<
/AP <<
/N 42 0 R
>>
/Contents (attachment1.txt)
/FS 16 0 R
/NM (attachment1.txt)
/Rect [
72
400
92
420
]
/Subtype /FileAttachment
/Type /Annot
>>
endobj
35 0 obj
<<
/AP <<
/N 44 0 R
>>
/DA ()
/Rect [
72
350
92
360
]
/Subtype /FreeText
/Type /Annot
>>
endobj
36 0 obj
<<
/AP <<
/N 46 0 R
>>
/DA ()
/Rect [
102
350
112
370
]
/Subtype /FreeText
/Type /Annot
>>
endobj
37 0 obj
<<
/AP <<
/N 48 0 R
>>
/DA ()
/Rect [
122
350
142
360
]
/Subtype /FreeText
/Type /Annot
>>
endobj
38 0 obj
<<
/AP <<
/N 50 0 R
>>
/DA ()
/Rect [
152
350
162
370
]
/Subtype /FreeText
/Type /Annot
>>
endobj
%% Contents for page 1
39 0 obj
<<
/Length 40 0 R
>>
stream
q
1 1 .7 rg
.5 .5 0 RG
72 470.77 118.8 14.15 re
B
Q
q
0 .5 .5 RG
0 1 1 rg
372 330.77 14.15 139.4 re
B
Q
q
1 0 0 RG
72 310 20 10 re
72 310 5 10 re
S
0 1 0 RG
102 310 10 20 re
102 310 10 5 re
S
0 0 1 RG
122 310 20 10 re
137 310 5 10 re
S
0.5 0 1 RG
152 310 10 20 re
152 325 10 5 re
S
10 w
0.14 .33 .18 RG
5 5 602 782 re
S
Q
BT
/F1 16 Tf
20.6 TL
170 650 Td
(radio button 1) Tj
(radio button 2) '
(radio button 3) '
1 0 0 1 72 546 Tm
/F1 20 Tf
(Thick green border surrounds page.) Tj
0 -40 Td
/F1 24 Tf
0 0 1 rg
(https://www.qbilt.org) Tj
/F1 12 Tf
1 0 0 1 202 474 Tm
(<- Formy field in yellow) Tj
1 0 0 1 392 410 Tm
14.4 TL
(<- Rot-ccw field) Tj
(with "Rot" at bottom) '
(and text going up) '
0 g
1 0 0 1 102 405 Tm
(Arrow to the left points down.) Tj
1 0 0 1 182 310 Tm
(<- Drawn rectangles appear below annotations.) Tj
ET
endstream
endobj
40 0 obj
874
endobj
41 0 obj
<<
/Font 52 0 R
/ProcSet [
/PDF
/Text
]
>>
endobj
42 0 obj
<<
/BBox [
0
0
20
20
]
/Resources <<
>>
/Subtype /Form
/Type /XObject
/Length 43 0 R
>>
stream
0 10 m
10 0 l
20 10 l
10 0 m
10 20 l
0 0 20 20 re
S
endstream
endobj
43 0 obj
52
endobj
44 0 obj
<<
/BBox [
0
0
20
10
]
/Resources 2 0 R
/Subtype /Form
/Type /XObject
/Length 45 0 R
>>
stream
1 0 0 RG
0 0 20 10 re
0 0 5 10 re
S
endstream
endobj
45 0 obj
36
endobj
46 0 obj
<<
/BBox [
0
0
20
10
]
/Matrix [
0
1
-1
0
0
0
]
/Resources 2 0 R
/Subtype /Form
/Type /XObject
/Length 47 0 R
>>
stream
0 1 0 RG
0 0 20 10 re
0 0 5 10 re
S
endstream
endobj
47 0 obj
36
endobj
48 0 obj
<<
/BBox [
0
0
20
10
]
/Matrix [
-1
0
0
-1
0
0
]
/Resources 2 0 R
/Subtype /Form
/Type /XObject
/Length 49 0 R
>>
stream
0 0 1 RG
0 0 20 10 re
0 0 5 10 re
S
endstream
endobj
49 0 obj
36
endobj
50 0 obj
<<
/BBox [
0
0
20
10
]
/Matrix [
0
-1
1
0
0
0
]
/Resources 2 0 R
/Subtype /Form
/Type /XObject
/Length 51 0 R
>>
stream
0.5 0 1 RG
0 0 20 10 re
0 0 5 10 re
S
endstream
endobj
51 0 obj
38
endobj
52 0 obj
<<
/ZaDi 22 0 R
>>
endobj
xref
0 53
0000000000 65535 f
0000000025 00000 n
0000000211 00000 n
0000000263 00000 n
0000000506 00000 n
0000000755 00000 n
0000000874 00000 n
0000000944 00000 n
0000001017 00000 n
0000001121 00000 n
0000001335 00000 n
0000001355 00000 n
0000001625 00000 n
0000001645 00000 n
0000001997 00000 n
0000002349 00000 n
0000002701 00000 n
0000002842 00000 n
0000003114 00000 n
0000003473 00000 n
0000003494 00000 n
0000003663 00000 n
0000003683 00000 n
0000003764 00000 n
0000004123 00000 n
0000004144 00000 n
0000004313 00000 n
0000004333 00000 n
0000004692 00000 n
0000004713 00000 n
0000004882 00000 n
0000004902 00000 n
0000005110 00000 n
0000005130 00000 n
0000005374 00000 n
0000005578 00000 n
0000005718 00000 n
0000005860 00000 n
0000006002 00000 n
0000006167 00000 n
0000007098 00000 n
0000007119 00000 n
0000007193 00000 n
0000007397 00000 n
0000007417 00000 n
0000007603 00000 n
0000007623 00000 n
0000007862 00000 n
0000007882 00000 n
0000008122 00000 n
0000008142 00000 n
0000008383 00000 n
0000008403 00000 n
trailer <<
/Root 1 0 R
/Size 53
/ID [<a2f146daeb6d814a742556489dab9882><7b639c67bfc16b5e891fa5468aac3a14>]
>>
startxref
8441
%%EOF

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff