-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathOptionalContent.cs
1480 lines (1370 loc) · 51 KB
/
OptionalContent.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ===========================================================================
// ©2013-2021 WebSupergoo. All rights reserved.
//
// This source code is for use exclusively with the ABCpdf product under
// the terms of the license for that product. Details can be found at
//
// http://www.websupergoo.com/
//
// This copyright notice must not be deleted and must be reproduced alongside
// any sections of code extracted from this module.
// ===========================================================================
using System;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Net;
using WebSupergoo.ABCpdf12;
using WebSupergoo.ABCpdf12.Objects;
using WebSupergoo.ABCpdf12.Operations;
using WebSupergoo.ABCpdf12.Atoms;
namespace OptionalContent {
/// <summary>
/// This class represents the Optional Content Properties Dictionary in a PDF document.
/// This is the top level container of all Optional Content Groups (OCGs)
/// in a document. OCGs are referenced at the top level in a global dictionary and their
/// visibility states are held in a global configuration dictionary. These OCGs may then
/// be referenced by one or more pages in the document.
/// Here we refer to OCGs simply as Groups.
/// </summary>
class Properties {
/// <summary>
/// Create a Properties object for the Doc.
/// </summary>
/// <param name="doc">The Doc on which to operate.</param>
/// <param name="create">Whether to create a new properties entry if one is not already present.</param>
/// <returns>The Properties object. This may be null if layers were not present in the document and the create parameter is false.</returns>
public static Properties FromDoc(Doc doc, bool create) {
Catalog catalog = doc.ObjectSoup.Catalog;
Atom atom = catalog.Resolve(Atom.GetItem(catalog.Atom, "OCProperties"));
if ((atom == null) && (create)) {
int id = doc.AddObject("<< /OCGs [] /D << /BaseState /ON >> >>");
IndirectObject io = doc.ObjectSoup[id];
Atom.SetItem(catalog.Atom, "OCProperties", new RefAtom(io));
atom = io.Atom;
}
if (atom == null)
return null;
return new Properties(catalog, atom);
}
private Catalog _catalog;
private Atom _atom;
private Properties(Catalog catalog, Atom atom) {
_catalog = catalog;
_atom = atom;
}
/// <summary>
/// The Catalog from the Doc.
/// </summary>
public Catalog Catalog { get { return _catalog; } }
/// <summary>
/// Get the Groups for the Doc.
/// </summary>
/// <returns>A list of the Groups.</returns>
public List<Group> GetGroups() {
List<Group> list = new List<Group>();
ArrayAtom array = EntryOCGs;
if (array != null) {
foreach (Atom item in array) {
IndirectObject io = _catalog.ResolveObj(item);
Group group = Group.FromIndirectObject(this, io);
if (group != null)
list.Add(group);
}
}
return list;
}
/// <summary>
/// Get the Optional Content Groups (OCGs) for the Page.
/// </summary>
/// <param name="page">The page from which OCGs should be found.</param>
/// <returns>A list of the OCGs.</returns>
public List<Group> GetGroups(Page page) {
List<Group> list = new List<Group>();
ISet<Atom> props = page.GetResourcesByType("Properties", true, true, true, true, null);
foreach (Atom item in props) {
IndirectObject io = page.ResolveObj(item);
Group ocg = Group.FromIndirectObject(this, io);
if (ocg != null) {
list.Add(ocg);
continue;
}
MembershipGroup mg = MembershipGroup.FromIndirectObject(this, io);
if (mg != null) {
foreach (var ocg2 in mg.PolicyGroups)
list.Add(ocg2);
continue;
}
}
return list;
}
/// <summary>
/// Sort Groups for presentation in a UI. Remove any Groups which should not appear in UI.
/// Construct a list of indents for those Groups that should be presented nested.
/// </summary>
/// <param name="groups">The OCGs to order.</param>
/// <param name="indents">A list of indents to indicate nestedness.</param>
public void SortGroupsForPresentation(List<Group> groups, List<int> indents) {
Dictionary<int, Group> lookup = new Dictionary<int, Group>();
foreach (Group group in groups)
lookup[group.IndirectObject.ID] = group;
groups.Clear();
indents.Clear();
Configuration config = GetDefault();
ArrayAtom order = config != null ? config.EntryOrder : null;
if (order == null)
return;
SortGroupsForPresentation(order, lookup, groups, indents, 0);
}
private void SortGroupsForPresentation(ArrayAtom order, Dictionary<int, Group> lookup, List<Group> groups, List<int> indents, int depth) {
if (depth > 100)
throw new Exception("OCG order nesting depth unfeasibly large.");
foreach (Atom atom in order) {
Atom resAtom = _catalog.Resolve(atom);
if (resAtom is ArrayAtom) { // OCG group
SortGroupsForPresentation((ArrayAtom)resAtom, lookup, groups, indents, depth + 1);
}
else if (resAtom is DictAtom) { // OCG
RefAtom refAtom = _catalog.ResolveRef(atom);
if (refAtom != null) {
Group group = null;
lookup.TryGetValue(refAtom.ID, out group);
if (group != null) {
groups.Add(group);
indents.Add(depth);
}
}
}
}
}
/// <summary>
/// Add a Group to the Doc.
/// </summary>
/// <param name="name">The name for the Group</param>
/// <param name="parent">The parent for the Group to indicate nested visibility. This may be null if nested visibility is not required.</param>
/// <returns>The newly added Group.</returns>
public Group AddGroup(string name, Group parent) {
Group ocg = Group.NewGroup(this);
ocg.EntryName = new StringAtom(name);
if (true) { // we need to list the ocg in the global database
ArrayAtom array = EntryOCGs;
if (array == null) {
array = new ArrayAtom();
EntryOCGs = array;
}
array.Add(new RefAtom(ocg.IndirectObject));
}
if (true) { // we also need to list it in the visible entries
Configuration config = GetDefault();
ArrayAtom array = config.EntryOrder;
if (array == null) {
array = new ArrayAtom();
config.EntryOrder = array;
}
if (parent != null) {
Tuple<ArrayAtom, int> entry = FindArrayEntry(parent.IndirectObject, array, 0);
if (entry == null)
throw new ArgumentException("Parent OCG not present in configuration dictionary.");
array = entry.Item1;
int index = entry.Item2;
ArrayAtom next = index < array.Count - 1 ? _catalog.Resolve(array[index + 1]) as ArrayAtom : null;
if (next == null) {
next = new ArrayAtom();
array.Insert(index + 1, next);
}
array = next;
}
array.Add(new RefAtom(ocg.IndirectObject));
}
return ocg;
}
/// <summary>
/// Add a Membership Group to the Doc.
/// </summary>
/// <returns>The newly added Membership Group.</returns>
public MembershipGroup AddMembershipGroup() {
return MembershipGroup.New(this);
}
/// <summary>
/// Get the default Configuration to indicate which layers are visible or invisible.
/// </summary>
/// <returns>The Configuration.</returns>
public Configuration GetDefault() {
Atom defaultConfig = EntryD;
return defaultConfig != null ? Configuration.FromAtom(this, defaultConfig) : null;
}
/// <summary>
/// Get the alternate Configurations that may be used to indicate which layers are visible or invisible.
/// Alternate configurations are indended for use under different circumstances - they represent alternate
/// views of the pages.
/// </summary>
/// <returns>A list of Configurations.</returns>
public List<Configuration> GetConfigs() {
List<Configuration> list = new List<Configuration>();
ArrayAtom configs = EntryConfigs;
if (configs != null) {
foreach (Atom item in configs) {
Configuration occd = Configuration.FromAtom(this, item);
list.Add(occd);
}
}
return list;
}
/// <summary>
/// The Optional Content Properties Dictionary OCG entry.
/// </summary>
public ArrayAtom EntryOCGs {
get { return _catalog.Resolve(Atom.GetItem(_atom, "OCGs")) as ArrayAtom; }
set { Atom.SetItem(_atom, "OCGs", value); }
}
/// <summary>
/// The Optional Content Properties Dictionary D entry.
/// </summary>
public DictAtom EntryD {
get { return _catalog.Resolve(Atom.GetItem(_atom, "D")) as DictAtom; }
set { Atom.SetItem(_atom, "D", value); }
}
/// <summary>
/// The Optional Content Properties Dictionary Configs entry.
/// </summary>
public ArrayAtom EntryConfigs {
get { return _catalog.Resolve(Atom.GetItem(_atom, "Configs")) as ArrayAtom; }
set { Atom.SetItem(_atom, "Configs", value); }
}
private static Tuple<ArrayAtom, int> FindArrayEntry(IndirectObject io, ArrayAtom array, int depth) {
if (depth > 100)
return null;
int n = array.Count;
for (int i = 0; i < n; i++) {
Atom atom = array[i];
ArrayAtom subArray = io.Resolve(atom) as ArrayAtom;
if (subArray != null) {
Tuple<ArrayAtom, int> result = FindArrayEntry(io, subArray, depth + 1);
if (result != null)
return result;
}
else {
RefAtom refAtom = io.ResolveRef(atom);
if ((refAtom != null) && (refAtom.ID == io.ID))
return new Tuple<ArrayAtom, int>(array, i);
}
}
return null;
}
public static RefAtom MakeResourceIndirect(Page page, IndirectObject container, string resourceType, string resourceName, Atom resourceAtom, ref Atom cache) {
RefAtom refAtom = container.ResolveRef(resourceAtom);
if (refAtom == null) {
if (cache == null)
cache = GetResourceDict(page, container, resourceType, true);
IndirectObject prop = new IndirectObject();
prop.Atom = resourceAtom;
container.Doc.ObjectSoup.Add(prop);
refAtom = (RefAtom)Atom.SetItem(cache, resourceName, new RefAtom(prop));
}
return refAtom;
}
public static DictAtom GetResourceDict(Page page, IndirectObject container, string resourceType, bool create) {
DictAtom dict = null;
Atom resAtom = null;
if (container is FormXObject) {
resAtom = container.Resolve(Atom.GetItem(container.Atom, "Resources"));
if (resAtom == null)
resAtom = Atom.SetItem(container.Atom, "Resources", new DictAtom());
}
else {
resAtom = container.Resolve(Atom.GetItem(page.Atom, "Resources"));
Debug.Assert(resAtom != null, "Page does not contain Resources.");
}
dict = container.Resolve(Atom.GetItem(resAtom, resourceType)) as DictAtom;
if ((dict == null) && (create))
dict = (DictAtom)Atom.SetItem(resAtom, resourceType, new DictAtom());
return dict;
}
}
/// <summary>
/// This class represents an Optional Content Group (OCG) in a PDF document.
/// An OCG is a layer-like object that may be visible or invisible. Items on the page
/// may belong to one or more than one OCG. Only if all OCGs that they belong to are visible
/// are the items visible. OCGs are held at the Doc level in a global dictionary
/// and then referenced at a Page level.
/// </summary>
class Group {
/// <summary>
/// Create a new Group for the document.
/// </summary>
/// <param name="oc">The Properties object for the Doc.</param>
/// <returns>The new Group.</returns>
public static Group NewGroup(Properties oc) {
IndirectObject io = IndirectObject.FromString("<< /Type /OCG /Name () >>");
oc.Catalog.Doc.ObjectSoup.Add(io);
return FromIndirectObject(oc, io);
}
/// <summary>
/// Create a list of Groups from a list of atoms that reference Optional Content Groups (OCGs) already existing in the Doc.
/// Only those atoms that reference OCGs will be included in the list. Other atoms will be ignored.
/// </summary>
/// <param name="oc">The Properties object for the Doc.</param>
/// <param name="atoms">The list of atoms.</param>
/// <returns>A list of Groups.</returns>
public static List<Group> FromAtoms(Properties oc, IEnumerable<Atom> atoms) {
List<Group> list = new List<Group>();
foreach (Atom atom in atoms) {
IndirectObject io = oc.Catalog.ResolveObj(atom);
Group ocg = Group.FromIndirectObject(oc, io);
if (ocg != null)
list.Add(ocg);
}
return list;
}
/// <summary>
/// Create a Group from an IndirectObject that already exists in the Doc.
/// If the IndirectObject is not an Optional Content Group then null will be returned.
/// </summary>
/// <param name="oc">The Properties object for the Doc.</param>
/// <param name="io">The IndirectObject.</param>
/// <returns>The Group.</returns>
public static Group FromIndirectObject(Properties oc, IndirectObject io) {
if (io == null)
return null;
Group ocg = new Group(oc, io);
return ocg.AllOk() ? ocg : null;
}
private Properties _oc;
private IndirectObject _io;
private Group(Properties oc, IndirectObject io) {
_oc = oc;
_io = io;
}
private bool AllOk() {
// check type
NameAtom type = _io.Resolve(Atom.GetItem(_io.Atom, "Type")) as NameAtom;
if ((type == null) || (type.Text != "OCG"))
return false; // malformed entry - corrupt PDF
// The optional intent can be either a name or an array of names.
// To keep things simple we always use the array form.
Atom intent = _io.Resolve(Atom.GetItem(_io.Atom, "Intent"));
if (intent is NameAtom) {
ArrayAtom array = new ArrayAtom();
array.Add(intent);
Atom.SetItem(_io.Atom, "Intent", array);
intent = array;
}
return true;
}
/// <summary>
/// The IndirectObject representing the Group.
/// </summary>
public IndirectObject IndirectObject {
get { return _io; }
}
/// <summary>
/// Indicates whether the Group is visible or not.
/// </summary>
public bool Visible {
get { return _oc.GetDefault().GetVisibility(true, this); }
set { _oc.GetDefault().SetVisibility(value, this); }
}
/// <summary>
/// Adds the Group to a page.
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
public string AddToPage(Page page) {
return page.AddResource(_io, "Properties", "OC" + _io.ID.ToString());
}
/// <summary>
/// The Optional Content Group Dictionary Name entry.
/// </summary>
public StringAtom EntryName {
get { return _io.Resolve(Atom.GetItem(_io.Atom, "Name")) as StringAtom; }
set { Atom.SetItem(_io.Atom, "Name", value); }
}
/// <summary>
/// The Optional Content Group Dictionary Intent entry.
/// </summary>
public ArrayAtom EntryIntent {
get { return _io.Resolve(Atom.GetItem(_io.Atom, "Intent")) as ArrayAtom; }
set { Atom.SetItem(_io.Atom, "Intent", value); }
}
/// <summary>
/// The Optional Content Group Dictionary Usage entry.
/// </summary>
public DictAtom EntryUsage {
get { return _io.Resolve(Atom.GetItem(_io.Atom, "Usage")) as DictAtom; }
set { Atom.SetItem(_io.Atom, "Usage", value); }
}
}
/// <summary>
/// This class represents an Optional Content Membership Dictionary (OCMD) in a PDF document.
/// This is a bit of a mouthful and doesn't actually express what it is, which is a
/// kind of metagroup. It expresses visibility dependent on the visibility of a
/// set of groups rather than directly being switched on and off by itself. OCMDs are not
/// referenced at the document level - only at a page level. However of course the OCMD references
/// OCGs which are themselves referenced at a document level.
/// Membership Groups may express visibility either using a Policy or using a Visibility Expression.
/// Policies are simpler to implement but limited in scope. Visibility Expressions are more complex
/// but also more powerful.
/// OCGs always have to be IndirectObjects since they are referenced from more than one place.
/// OCMGs do not have to be IndirectObject - they can be simple Atoms - since they are only referenced from
/// the Resources of the Page. However to make things simple we convert them to IndiretObjects when we
/// find them.
/// </summary>
class MembershipGroup {
/// <summary>
/// An enumeration representing the possibilities for a Policy based Membership Group.
/// </summary>
public enum PolicyEnum { AllOn, AnyOn, AllOff, AnyOff };
/// <summary>
/// An enumeration representing the possibilities for a Logic based Membership Group.
/// </summary>
public enum LogicEnum { And, Or, Not };
/// <summary>
/// Create a new Membership Group.
/// </summary>
/// <param name="oc">The Properties object for the Doc</param>
/// <returns>The Membership Group</returns>
public static MembershipGroup New(Properties oc) {
IndirectObject io = IndirectObject.FromString("<< /Type /OCMD >>");
oc.Catalog.Doc.ObjectSoup.Add(io);
return FromIndirectObject(oc, io);
}
/// <summary>
/// Create a list of Membership Groups from a list of atoms that reference Optional Content Membership Groups (OCMGs) already existing in the Doc.
/// Only those atoms that reference OCMGs will be included in the list. Other atoms will be ignored.
/// </summary>
/// <param name="oc">The Properties object for the Doc.</param>
/// <param name="atoms">The list of atoms.</param>
/// <returns>A list of Membership Groups.</returns>
public static List<MembershipGroup> FromAtoms(Properties oc, IEnumerable<Atom> atoms) {
// Here we assume that all atoms passed in are references to an OCMG IndirectObject.
// In our code this is always the case. However if you modify the code to take Atoms
// read from another PDF then you need to tak account of the fact that OCMGs (unlike
// OCGs) need not be IndirectObjects - they can be plain DictAtoms. If this is the case
// you will need to use Properties.MakeResourceIndirect to convert these Atoms into
// IndirectObjects.
List<MembershipGroup> list = new List<MembershipGroup>();
foreach (Atom atom in atoms) {
IndirectObject io = oc.Catalog.ResolveObj(atom);
MembershipGroup ocmg = MembershipGroup.FromIndirectObject(oc, io);
if (ocmg != null)
list.Add(ocmg);
}
return list;
}
/// <summary>
/// Create a new Membership Group from an IndirectObject that already exists in the Doc.
/// If the IndirectObject is not a Membership Group then null will be returned.
/// </summary>
/// <param name="oc">The Properties object for the Doc</param>
/// <param name="io">The IndirectObject.</param>
/// <returns>The Group.</returns>
public static MembershipGroup FromIndirectObject(Properties oc, IndirectObject io) {
if (io == null)
return null;
MembershipGroup ocm = new MembershipGroup(oc, io);
return ocm.AllOk() ? ocm : null;
}
private Properties _oc;
private IndirectObject _io;
private MembershipGroup(Properties oc, IndirectObject io) {
_oc = oc;
_io = io;
}
private bool AllOk() {
// check type
NameAtom type = _io.Resolve(Atom.GetItem(_io.Atom, "Type")) as NameAtom;
if ((type == null) || (type.Text != "OCMD"))
return false; // malformed entry - corrupt PDF
// The optional OCGs can be either a dict or an array of dicts.
// To keep things simple we always use the array form.
Atom ocgs = _io.Resolve(Atom.GetItem(_io.Atom, "OCGs"));
if (ocgs is DictAtom) {
ArrayAtom array = new ArrayAtom();
array.Add(ocgs);
Atom.SetItem(_io.Atom, "OCGs", array);
ocgs = array;
}
return true;
}
/// <summary>
/// The IndirectObject representing the Membership Group.
/// </summary>
public IndirectObject IndirectObject {
get { return _io; }
}
/// <summary>
/// Indicates whether the Membership Group is visible or not.
/// </summary>
public bool Visible {
get { return EntryVE != null ? EvaluateVisibility(EntryVE, 0) : EvaluateVisibility(Policy, PolicyGroups); }
}
private bool EvaluateVisibility(Atom ve, int depth) {
if (depth > 100)
return true;
ve = _oc.Catalog.Resolve(ve);
if (ve is ArrayAtom) {
ArrayAtom array = (ArrayAtom)ve;
int n = array.Count;
string op = n > 0 ? Atom.GetName(_oc.Catalog.Resolve(Atom.GetItem(array, 0))) : "";
if (op == LogicEnum.And.ToString()) {
for(int i = 1; i < n; i++) {
IndirectObject io = _oc.Catalog.ResolveObj(array[i]);
if (io != null) {
if (!EvaluateVisibility(io))
return false;
}
else {
if (!EvaluateVisibility(array[i], depth + 1))
return false;
}
}
return true;
}
else if (op == LogicEnum.Or.ToString()) {
for(int i = 1; i < n; i++) {
IndirectObject io = _oc.Catalog.ResolveObj(array[i]);
if (io != null) {
if (EvaluateVisibility(io))
return true;
}
else {
if (EvaluateVisibility(array[i], depth + 1))
return true;
}
}
return false;
}
else if ((op == LogicEnum.Not.ToString()) && (n > 1)) {
IndirectObject io = _oc.Catalog.ResolveObj(array[1]);
if (io != null)
return !EvaluateVisibility(io);
else
return !EvaluateVisibility(array[1], depth + 1);
}
}
return true;
}
private bool EvaluateVisibility(PolicyEnum? policy, IEnumerable<Group> groups) {
bool visible = true;
if (policy == null)
policy = PolicyEnum.AnyOn;
if (groups == null)
groups = new Group[0];
if (policy == PolicyEnum.AnyOn) {
visible = false;
foreach (Group group in groups) {
if (group.Visible) {
visible = true;
break;
}
}
}
else if (policy == PolicyEnum.AnyOff) {
visible = false;
foreach (Group group in groups) {
if (!group.Visible) {
visible = true;
break;
}
}
}
else if (policy == PolicyEnum.AllOn) {
visible = true;
foreach (Group group in groups) {
if (!group.Visible) {
visible = false;
break;
}
}
}
else if (policy == PolicyEnum.AllOff) {
visible = true;
foreach (Group group in groups) {
if (group.Visible) {
visible = false;
break;
}
}
}
return visible;
}
private bool EvaluateVisibility(IndirectObject io) {
Group group = Group.FromIndirectObject(_oc, io);
return group != null ? group.Visible : true;
}
/// <summary>
/// The Policy for the Membership Group. Null indicates that there is no Policy.
/// </summary>
public PolicyEnum? Policy {
get { NameAtom a = EntryP; return a != null ? (PolicyEnum?)Enum.Parse(typeof(PolicyEnum), a.Text) : null; }
set { EntryP = value.HasValue ? new NameAtom(value.ToString()) : null; }
}
/// <summary>
/// The Groups on which the Policy will operate.
/// </summary>
public IEnumerable<Group> PolicyGroups {
get {
ArrayAtom array = EntryOCGs;
return array != null ? Group.FromAtoms(_oc, EntryOCGs) : new List<Group>();
}
set {
ArrayAtom array = new ArrayAtom();
foreach (Group item in value)
array.Add(new RefAtom(item.IndirectObject));
EntryOCGs = array;
}
}
/// <summary>
/// Create a Visibility Expression showing how the visibility of a set of Groups should be combined.
/// </summary>
/// <param name="op">The logic operator used to combine the visibility.</param>
/// <param name="groups">The set of Groups on which to operate. Visible Groups are true and invisible ones are false.</param>
/// <returns>An ArrayAtom containing the Visibility Expression.</returns>
public ArrayAtom MakeVisibilityExpression(LogicEnum op, IEnumerable<Group> groups) {
ArrayAtom array = new ArrayAtom();
array.Add(new NameAtom(op.ToString()));
foreach (var group in groups)
array.Add(new RefAtom(group.IndirectObject));
return array;
}
/// <summary>
/// Create a Visibility Expression showing how the visibility of a set of Visibility Expressions should be combined.
/// </summary>
/// <param name="op">The logic operator used to combine the visibility.</param>
/// <param name="atoms">The set of groups on which to operate.</param>
/// <returns>An ArrayAtom containing the Visibility Expression.</returns>
public ArrayAtom MakeVisibilityExpression(LogicEnum op, IEnumerable<ArrayAtom> atoms) {
ArrayAtom array = new ArrayAtom();
array.Add(new NameAtom(op.ToString()));
foreach (var atom in atoms)
array.Add(atom);
return array;
}
/// <summary>
/// All the Groups on which the Membership Group depends.
/// </summary>
public IEnumerable<Atom> GetGroupReferences() {
List<Atom> list = new List<Atom>();
GetGroupReferences(EntryVE, list, 0);
GetGroupReferences(EntryOCGs, list, 0);
return list;
}
private IEnumerable<Atom> GetGroupReferences(ArrayAtom array, List<Atom> list, int depth) {
if ((array == null) || (depth > 100))
return list;
foreach (Atom atom in array) {
Atom item = _oc.Catalog.Resolve(atom);
if (item is ArrayAtom)
GetGroupReferences((ArrayAtom)item, list, depth + 1);
else {
RefAtom refAtom =_oc.Catalog.ResolveRef(atom);
if (refAtom != null)
list.Add(refAtom);
}
}
return list;
}
/// <summary>
/// Add the Membership Group to a Page.
/// </summary>
/// <param name="page">The Page to which this should be added.</param>
/// <returns>The resource name used for this Membership Group.</returns>
public string AddToPage(Page page) {
return page.AddResource(_io, "Properties", "OC" + _io.ID.ToString());
}
/// <summary>
/// The Optional Content Membership Dictionary OCGs entry.
/// </summary>
public ArrayAtom EntryOCGs {
get { return _io.Resolve(Atom.GetItem(_io.Atom, "OCGs")) as ArrayAtom; }
set { Atom.SetItem(_io.Atom, "OCGs", value); }
}
/// <summary>
/// The Optional Content Membership Dictionary P entry.
/// </summary>
public NameAtom EntryP {
get { return _io.Resolve(Atom.GetItem(_io.Atom, "P")) as NameAtom; }
set { Atom.SetItem(_io.Atom, "P", value); }
}
/// <summary>
/// The Optional Content Membership Dictionary VE entry.
/// </summary>
public ArrayAtom EntryVE {
get { return _io.Resolve(Atom.GetItem(_io.Atom, "VE")) as ArrayAtom; }
set { Atom.SetItem(_io.Atom, "VE", value); }
}
}
/// <summary>
/// This class represents an Optional Content Configuration Dictionary (OCCD) in a PDF document.
/// An OCCD expresses the visibility of the Groups in the document and also how they should be
/// presented in a User Interface (UI).
/// </summary>
class Configuration {
/// <summary>
/// Create a Configuration from an Optional Content Configuration Dictionary (OCCD) Atom already existing in the Doc.
/// If the Atom is not an OCCD then null will be returned.
/// </summary>
/// <param name="oc">The Properties object for the Doc.</param>
/// <param name="atom">The Atom.</param>
/// <returns>The Configuration.</returns>
public static Configuration FromAtom(Properties oc, Atom atom) {
if ((atom == null) || (oc == null))
return null;
return new Configuration(oc, atom);
}
private Properties _oc;
private Atom _atom;
private Configuration(Properties oc, Atom atom) {
_oc = oc;
_atom = atom;
}
/// <summary>
/// The Atom representing the Membership Group.
/// </summary>
public Atom Atom {
get { return _atom; }
}
private enum Visibility { On, Off, Unchanged };
/// <summary>
/// Evaluate the visibility of the Group.
/// </summary>
/// <param name="isDefault">Whether this is the default configuration.</param>
/// <param name="layer">The Group for which visibility should be evaluated.</param>
/// <returns>Whether visible.</returns>
public bool GetVisibility(bool isDefault, Group layer) {
Visibility visibility = Visibility.On;
NameAtom baseState = isDefault ? null : EntryBaseState;
if (baseState != null) {
switch (baseState.Text) {
case "OFF": visibility = Visibility.Off; break;
case "Unchanged": visibility = Visibility.Unchanged; break;
}
}
if (visibility != Visibility.On) {
ArrayAtom array = EntryON;
if (array != null) {
foreach (Atom item in array) {
IndirectObject io = _oc.Catalog.ResolveObj(item);
if ((io != null) && (io.ID == layer.IndirectObject.ID)) {
visibility = Visibility.On;
break;
}
}
}
}
else if (visibility != Visibility.Off) {
ArrayAtom array = EntryOFF;
if (array != null) {
foreach (Atom item in array) {
IndirectObject io = _oc.Catalog.ResolveObj(item);
if ((io != null) && (io.ID == layer.IndirectObject.ID)) {
visibility = Visibility.Off;
break;
}
}
}
}
return visibility != Visibility.Off;
}
/// <summary>
/// Set the visibility of a Group.
/// </summary>
/// <param name="visible">Whether the Group should be visible.</param>
/// <param name="layer">The Group for which visibility should be evaluated.</param>
public void SetVisibility(bool visible, Group layer) {
RemoveEntries(EntryOFF, layer.IndirectObject.ID);
RemoveEntries(EntryON, layer.IndirectObject.ID);
if (visible) {
if (EntryON == null)
EntryON = new ArrayAtom();
EntryON.Add(new RefAtom(layer.IndirectObject));
}
else {
if (EntryOFF == null)
EntryOFF = new ArrayAtom();
EntryOFF.Add(new RefAtom(layer.IndirectObject));
}
//layer.IndirectObject.Atom = layer.IndirectObject.Atom.Clone();
}
private void RemoveEntries(ArrayAtom array, int id) {
int n = array != null ? array.Count : 0;
for (int i = 0; i < n; i++) {
IndirectObject item = _oc.Catalog.ResolveObj(array[i]);
if ((item != null) && (item.ID == id)) {
array.RemoveAt(i);
i--;
n--;
}
}
}
/// <summary>
/// The Optional Content Configuration Dictionary Name entry.
/// </summary>
public StringAtom EntryName {
get { return _oc.Catalog.Resolve(Atom.GetItem(_atom, "Name")) as StringAtom; }
set { Atom.SetItem(_atom, "Name", value); }
}
/// <summary>
/// The Optional Content Configuration Dictionary Creator entry.
/// </summary>
public StringAtom EntryCreator {
get { return _oc.Catalog.Resolve(Atom.GetItem(_atom, "Creator")) as StringAtom; }
set { Atom.SetItem(_atom, "Creator", value); }
}
/// <summary>
/// The Optional Content Configuration Dictionary BaseState entry.
/// </summary>
public NameAtom EntryBaseState {
get { return _oc.Catalog.Resolve(Atom.GetItem(_atom, "BaseState")) as NameAtom; }
set { Atom.SetItem(_atom, "BaseState", value); }
}
/// <summary>
/// The Optional Content Configuration Dictionary ON entry.
/// </summary>
public ArrayAtom EntryON {
get { return _oc.Catalog.Resolve(Atom.GetItem(_atom, "ON")) as ArrayAtom; }
set { Atom.SetItem(_atom, "ON", value); }
}
/// <summary>
/// The Optional Content Configuration Dictionary OFF entry.
/// </summary>
public ArrayAtom EntryOFF {
get { return _oc.Catalog.Resolve(Atom.GetItem(_atom, "OFF")) as ArrayAtom; }
set { Atom.SetItem(_atom, "OFF", value); }
}
/// <summary>
/// The Optional Content Configuration Dictionary Order entry.
/// </summary>
public ArrayAtom EntryOrder {
get { return _oc.Catalog.Resolve(Atom.GetItem(_atom, "Order")) as ArrayAtom; }
set { Atom.SetItem(_atom, "Order", value); }
}
}
/// <summary>
/// Class for evaluating which parts of a page are visible and which parts are members
/// of which Optional Content Groups or Membership dictionaries. Includes facilities
/// for redacting elements that are invisible.
/// </summary>
class Reader {
/// <summary>
/// Create a Reader for a particular Page.
/// </summary>
/// <param name="oc">The Properties object for the Doc.</param>
/// <param name="page">The page to operate on.</param>
/// <returns></returns>
public static Reader FromPage(Properties oc, Page page) {
Reader content = new Reader(oc, page);
return content.AllOk() ? content : null;
}
[DebuggerDisplay("\\{ Offset = {Offset} Length = {Length} Command = {Command} \\}")]
private class Op : IComparable {
public Op(int offset, int length, string command, string textWidth) {
Offset = offset;
Length = length;
Command = command;
TextWidth = textWidth;
}
public int CompareTo(Object obj) {
return this.Offset - ((Op)obj).Offset;
}
public int Offset;
public int Length;
public string Command;
public string TextWidth;
}
private Properties _oc;
private Page _page;
private Dictionary<int, List<Op>> _ops;
private Dictionary<int, Dictionary<int, Atom[]>> _states; // first int is stream ID, second is position in stream
private Dictionary<int, IDictionary<string, Atom>> _rezMap;
private Dictionary<int, List<int>> _lookups; // fast lookup list
private Reader() {
Debug.Assert(false);
}
private Reader(Properties oc, Page page) {
_oc = oc;
_page = page;
_ops = null;
_states = null;
_rezMap = null;
_lookups = null;
}
private bool AllOk() {
_page.DeInline(true);
_page.Flatten(true, false);
// Theoretically a FormXObject could have different visibilty
// at different times it is drawn. So you should really call
// _page.StampFormXObjects(true) to separate them all out. However
// this might have quite an impact on size and it seems unlikely
// so we don't currently do this.
ScanPage(_page.GetText(Page.TextType.SvgPlus2, true));
return true;
}
private void ScanPage(string svg) {
_ops = new Dictionary<int, List<Op>>();
_states = new Dictionary<int, Dictionary<int, Atom[]>>();
_rezMap = new Dictionary<int, IDictionary<string, Atom>>();
_lookups = new Dictionary<int, List<int>>();
Stack<Atom> names = new Stack<Atom>();
Stack<int> depths = new Stack<int>();
depths.Push(0);
using (StringReader reader = new StringReader(svg)) {
for (string item; (item = reader.ReadLine()) != null; ) {
if (item.StartsWith("<", StringComparison.InvariantCultureIgnoreCase)) {