1
using System;
2
3
using System.Text;
4
5
using System.Collections;
6
7
using System.Collections.Generic;
8
9
using System.Drawing;
10
11
using System.Drawing.Printing;
12
13
using System.Data;
14
15
using System.Windows.Forms;
16
17
namespace EtaocnCS
18
{
19
public class DataGridViewPrinter
20
{
21
private DataGridView TheDataGridView; // The DataGridView Control which will be printed
22
23
private PrintDocument ThePrintDocument; // The PrintDocument to be used for printing
24
25
private bool IsCenterOnPage; // Determine if the report will be printed in the Top-Center of the page
26
27
private bool IsWithTitle; // Determine if the page contain title text
28
29
private string TheTitleText; // The title text to be printed in each page (if IsWithTitle is set to true)
30
31
private Font TheTitleFont; // The font to be used with the title text (if IsWithTitle is set to true)
32
33
private Color TheTitleColor; // The color to be used with the title text (if IsWithTitle is set to true)
34
35
private bool IsWithPaging; // Determine if paging is used
36
37
static int CurrentRow; // A static parameter that keep track on which Row (in the DataGridView control) that should be printed
38
39
static int PageNumber;
40
41
private int PageWidth;
42
43
private int PageHeight;
44
45
private int LeftMargin;
46
47
private int TopMargin;
48
49
private int RightMargin;
50
51
private int BottomMargin;
52
53
private float CurrentY; // A parameter that keep track on the y coordinate of the page, so the next object to be printed will start from this y coordinate
54
55
private float RowHeaderHeight;
56
57
private List<float> RowsHeight;
58
59
private List<float> ColumnsWidth;
60
61
private float TheDataGridViewWidth;
62
63
64
65
// Maintain a generic list to hold start/stop points for the column printing
66
67
// This will be used for wrapping in situations where the DataGridView will not fit on a single page
68
69
private List<int[]> mColumnPoints;
70
71
private List<float> mColumnPointsWidth;
72
73
private int mColumnPoint;
74
75
76
77
// The class constructor
78
79
public DataGridViewPrinter(DataGridView aDataGridView, PrintDocument aPrintDocument, bool CenterOnPage, bool WithTitle, string aTitleText, Font aTitleFont, Color aTitleColor, bool WithPaging)
80
{
81
82
TheDataGridView = aDataGridView;
83
84
ThePrintDocument = aPrintDocument;
85
86
IsCenterOnPage = CenterOnPage;
87
88
IsWithTitle = WithTitle;
89
90
TheTitleText = aTitleText;
91
92
TheTitleFont = aTitleFont;
93
94
TheTitleColor = aTitleColor;
95
96
IsWithPaging = WithPaging;
97
98
PageNumber = 0;
99
100
RowsHeight = new List<float>();
101
102
ColumnsWidth = new List<float>();
103
104
mColumnPoints = new List<int[]>();
105
106
mColumnPointsWidth = new List<float>();
107
108
// Claculating the PageWidth and the PageHeight
109
110
if (!ThePrintDocument.DefaultPageSettings.Landscape)
111
{
112
113
PageWidth = ThePrintDocument.DefaultPageSettings.PaperSize.Width;
114
115
PageHeight = ThePrintDocument.DefaultPageSettings.PaperSize.Height;
116
117
}
118
119
else
120
{
121
122
PageHeight = ThePrintDocument.DefaultPageSettings.PaperSize.Width;
123
124
PageWidth = ThePrintDocument.DefaultPageSettings.PaperSize.Height;
125
126
}
127
128
// Claculating the page margins
129
130
LeftMargin = ThePrintDocument.DefaultPageSettings.Margins.Left;
131
132
TopMargin = ThePrintDocument.DefaultPageSettings.Margins.Top;
133
134
RightMargin = ThePrintDocument.DefaultPageSettings.Margins.Right;
135
136
BottomMargin = ThePrintDocument.DefaultPageSettings.Margins.Bottom;
137
138
// First, the current row to be printed is the first row in the DataGridView control
139
140
CurrentRow = 0;
141
142
}
143
144
// The function that calculate the height of each row (including the header row), the width of each column (according to the longest text in all its cells including the header cell), and the whole DataGridView width
145
146
private void Calculate(Graphics g)
147
{
148
149
if (PageNumber == 0) // Just calculate once
150
{
151
152
SizeF tmpSize = new SizeF();
153
154
Font tmpFont;
155
156
float tmpWidth;
157
158
TheDataGridViewWidth = 0;
159
160
for (int i = 0; i < TheDataGridView.Columns.Count; i++)
161
{
162
163
tmpFont = TheDataGridView.ColumnHeadersDefaultCellStyle.Font;
164
165
if (tmpFont == null) // If there is no special HeaderFont style, then use the default DataGridView font style
166
167
tmpFont = TheDataGridView.DefaultCellStyle.Font;
168
169
tmpSize = g.MeasureString(TheDataGridView.Columns[i].HeaderText, tmpFont);
170
171
tmpWidth = tmpSize.Width;
172
173
RowHeaderHeight = tmpSize.Height;
174
175
for (int j = 0; j < TheDataGridView.Rows.Count; j++)
176
{
177
178
tmpFont = TheDataGridView.Rows[j].DefaultCellStyle.Font;
179
180
if (tmpFont == null) // If the there is no special font style of the CurrentRow, then use the default one associated with the DataGridView control
181
182
tmpFont = TheDataGridView.DefaultCellStyle.Font;
183
184
tmpSize = g.MeasureString("Anything", tmpFont);
185
186
RowsHeight.Add(tmpSize.Height);
187
188
tmpSize = g.MeasureString(TheDataGridView.Rows[j].Cells[i].EditedFormattedValue.ToString(), tmpFont);
189
190
if (tmpSize.Width > tmpWidth)
191
192
tmpWidth = tmpSize.Width;
193
194
}
195
196
if (TheDataGridView.Columns[i].Visible)
197
198
TheDataGridViewWidth += tmpWidth;
199
200
ColumnsWidth.Add(tmpWidth);
201
202
}
203
204
// Define the start/stop column points based on the page width and the DataGridView Width
205
206
// We will use this to determine the columns which are drawn on each page and how wrapping will be handled
207
208
// By default, the wrapping will occurr such that the maximum number of columns for a page will be determine
209
210
int k;
211
212
int mStartPoint = 0;
213
214
for (k = 0; k < TheDataGridView.Columns.Count; k++)
215
216
if (TheDataGridView.Columns[k].Visible)
217
{
218
219
mStartPoint = k;
220
221
break;
222
223
}
224
225
int mEndPoint = TheDataGridView.Columns.Count;
226
227
for (k = TheDataGridView.Columns.Count - 1; k >= 0; k--)
228
229
if (TheDataGridView.Columns[k].Visible)
230
{
231
232
mEndPoint = k + 1;
233
234
break;
235
236
}
237
238
float mTempWidth = TheDataGridViewWidth;
239
240
float mTempPrintArea = (float)PageWidth - (float)LeftMargin - (float)RightMargin;
241
242
243
244
// We only care about handling where the total datagridview width is bigger then the print area
245
246
if (TheDataGridViewWidth > mTempPrintArea)
247
{
248
249
mTempWidth = 0.0F;
250
251
for (k = 0; k < TheDataGridView.Columns.Count; k++)
252
{
253
254
if (TheDataGridView.Columns[k].Visible)
255
{
256
257
mTempWidth += ColumnsWidth[k];
258
259
// If the width is bigger than the page area, then define a new column print range
260
261
if (mTempWidth > mTempPrintArea)
262
{
263
264
mTempWidth -= ColumnsWidth[k];
265
266
mColumnPoints.Add(new int[] { mStartPoint, mEndPoint });
267
268
mColumnPointsWidth.Add(mTempWidth);
269
270
mStartPoint = k;
271
272
mTempWidth = ColumnsWidth[k];
273
274
}
275
276
}
277
278
// Our end point is actually one index above the current index
279
280
mEndPoint = k + 1;
281
282
}
283
284
}
285
286
// Add the last set of columns
287
288
mColumnPoints.Add(new int[] { mStartPoint, mEndPoint });
289
290
mColumnPointsWidth.Add(mTempWidth);
291
292
mColumnPoint = 0;
293
294
}
295
296
}
297
298
// The funtion that print the title, page number, and the header row
299
300
private void DrawHeader(Graphics g)
301
{
302
303
CurrentY = (float)TopMargin;
304
305
// Printing the page number (if isWithPaging is set to true)
306
307
if (IsWithPaging)
308
{
309
310
PageNumber = PageNumber+1;
311
312
string PageString = "Page " + PageNumber.ToString();
313
314
StringFormat PageStringFormat = new StringFormat();
315
316
PageStringFormat.Trimming = StringTrimming.Word;
317
318
PageStringFormat.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.LineLimit | StringFormatFlags.NoClip;
319
320
PageStringFormat.Alignment = StringAlignment.Far;
321
322
Font PageStringFont = new Font("Tahoma", 8, FontStyle.Regular, GraphicsUnit.Point);
323
324
RectangleF PageStringRectangle = new RectangleF((float)LeftMargin, CurrentY, (float)PageWidth - (float)RightMargin - (float)LeftMargin, g.MeasureString(PageString, PageStringFont).Height);
325
326
g.DrawString(PageString, PageStringFont, new SolidBrush(Color.Black), PageStringRectangle, PageStringFormat);
327
328
CurrentY += g.MeasureString(PageString, PageStringFont).Height;
329
330
}
331
332
// Printing the title (if IsWithTitle is set to true)
333
334
if (IsWithTitle)
335
{
336
337
StringFormat TitleFormat = new StringFormat();
338
339
TitleFormat.Trimming = StringTrimming.Word;
340
341
TitleFormat.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.LineLimit | StringFormatFlags.NoClip;
342
343
if (IsCenterOnPage)
344
345
TitleFormat.Alignment = StringAlignment.Center;
346
347
else
348
349
TitleFormat.Alignment = StringAlignment.Near;
350
351
RectangleF TitleRectangle = new RectangleF((float)LeftMargin, CurrentY, (float)PageWidth - (float)RightMargin - (float)LeftMargin, g.MeasureString(TheTitleText, TheTitleFont).Height);
352
353
g.DrawString(TheTitleText, TheTitleFont, new SolidBrush(TheTitleColor), TitleRectangle, TitleFormat);
354
355
CurrentY += g.MeasureString(TheTitleText, TheTitleFont).Height;
356
357
}
358
359
// Calculating the starting x coordinate that the printing process will start from
360
361
float CurrentX = (float)LeftMargin;
362
363
if (IsCenterOnPage)
364
365
CurrentX += (((float)PageWidth - (float)RightMargin - (float)LeftMargin) - mColumnPointsWidth[mColumnPoint]) / 2.0F;
366
367
// Setting the HeaderFore style
368
369
Color HeaderForeColor = TheDataGridView.ColumnHeadersDefaultCellStyle.ForeColor;
370
371
if (HeaderForeColor.IsEmpty) // If there is no special HeaderFore style, then use the default DataGridView style
372
373
HeaderForeColor = TheDataGridView.DefaultCellStyle.ForeColor;
374
375
SolidBrush HeaderForeBrush = new SolidBrush(HeaderForeColor);
376
377
// Setting the HeaderBack style
378
379
Color HeaderBackColor = TheDataGridView.ColumnHeadersDefaultCellStyle.BackColor;
380
381
if (HeaderBackColor.IsEmpty) // If there is no special HeaderBack style, then use the default DataGridView style
382
383
HeaderBackColor = TheDataGridView.DefaultCellStyle.BackColor;
384
385
SolidBrush HeaderBackBrush = new SolidBrush(HeaderBackColor);
386
387
// Setting the LinePen that will be used to draw lines and rectangles (derived from the GridColor property of the DataGridView control)
388
389
Pen TheLinePen = new Pen(TheDataGridView.GridColor, 1);
390
391
// Setting the HeaderFont style
392
393
Font HeaderFont = TheDataGridView.ColumnHeadersDefaultCellStyle.Font;
394
395
if (HeaderFont == null) // If there is no special HeaderFont style, then use the default DataGridView font style
396
397
HeaderFont = TheDataGridView.DefaultCellStyle.Font;
398
399
// Calculating and drawing the HeaderBounds
400
401
RectangleF HeaderBounds = new RectangleF(CurrentX, CurrentY, mColumnPointsWidth[mColumnPoint], RowHeaderHeight);
402
403
g.FillRectangle(HeaderBackBrush, HeaderBounds);
404
405
// Setting the format that will be used to print each cell of the header row
406
407
StringFormat CellFormat = new StringFormat();
408
409
CellFormat.Trimming = StringTrimming.Word;
410
411
CellFormat.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.LineLimit | StringFormatFlags.NoClip;
412
413
// Printing each visible cell of the header row
414
415
RectangleF CellBounds;
416
417
float ColumnWidth;
418
419
for (int i = (int)mColumnPoints[mColumnPoint].GetValue(0); i < (int)mColumnPoints[mColumnPoint].GetValue(1); i++)
420
{
421
422
if (!TheDataGridView.Columns[i].Visible) continue; // If the column is not visible then ignore this iteration
423
424
ColumnWidth = ColumnsWidth[i];
425
426
// Check the CurrentCell alignment and apply it to the CellFormat
427
428
if (TheDataGridView.ColumnHeadersDefaultCellStyle.Alignment.ToString().Contains("Right"))
429
430
CellFormat.Alignment = StringAlignment.Far;
431
432
else if (TheDataGridView.ColumnHeadersDefaultCellStyle.Alignment.ToString().Contains("Center"))
433
434
CellFormat.Alignment = StringAlignment.Center;
435
436
else
437
438
CellFormat.Alignment = StringAlignment.Near;
439
440
CellBounds = new RectangleF(CurrentX, CurrentY, ColumnWidth, RowHeaderHeight);
441
442
// Printing the cell text
443
444
g.DrawString(TheDataGridView.Columns[i].HeaderText, HeaderFont, HeaderForeBrush, CellBounds, CellFormat);
445
446
// Drawing the cell bounds
447
448
if (TheDataGridView.RowHeadersBorderStyle != DataGridViewHeaderBorderStyle.None) // Draw the cell border only if the HeaderBorderStyle is not None
449
450
g.DrawRectangle(TheLinePen, CurrentX, CurrentY, ColumnWidth, RowHeaderHeight);
451
452
CurrentX += ColumnWidth;
453
454
}
455
456
CurrentY += RowHeaderHeight;
457
458
}
459
460
// The function that print a bunch of rows that fit in one page
461
462
// When it returns true, meaning that there are more rows still not printed, so another PagePrint action is required
463
464
// When it returns false, meaning that all rows are printed (the CureentRow parameter reaches the last row of the DataGridView control) and no further PagePrint action is required
465
466
private bool DrawRows(Graphics g)
467
{
468
469
// Setting the LinePen that will be used to draw lines and rectangles (derived from the GridColor property of the DataGridView control)
470
471
Pen TheLinePen = new Pen(TheDataGridView.GridColor, 1);
472
473
// The style paramters that will be used to print each cell
474
475
Font RowFont;
476
477
Color RowForeColor;
478
479
Color RowBackColor;
480
481
SolidBrush RowForeBrush;
482
483
SolidBrush RowBackBrush;
484
485
SolidBrush RowAlternatingBackBrush;
486
487
// Setting the format that will be used to print each cell
488
489
StringFormat CellFormat = new StringFormat();
490
491
CellFormat.Trimming = StringTrimming.Word;
492
493
CellFormat.FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.LineLimit;
494
495
// Printing each visible cell
496
497
RectangleF RowBounds;
498
499
float CurrentX;
500
501
float ColumnWidth;
502
503
while (CurrentRow < TheDataGridView.Rows.Count)
504
{
505
506
if (TheDataGridView.Rows[CurrentRow].Visible) // Print the cells of the CurrentRow only if that row is visible
507
{
508
509
// Setting the row font style
510
511
RowFont = TheDataGridView.Rows[CurrentRow].DefaultCellStyle.Font;
512
513
if (RowFont == null) // If the there is no special font style of the CurrentRow, then use the default one associated with the DataGridView control
514
515
RowFont = TheDataGridView.DefaultCellStyle.Font;
516
517
// Setting the RowFore style
518
519
RowForeColor = TheDataGridView.Rows[CurrentRow].DefaultCellStyle.ForeColor;
520
521
if (RowForeColor.IsEmpty) // If the there is no special RowFore style of the CurrentRow, then use the default one associated with the DataGridView control
522
523
RowForeColor = TheDataGridView.DefaultCellStyle.ForeColor;
524
525
RowForeBrush = new SolidBrush(RowForeColor);
526
527
// Setting the RowBack (for even rows) and the RowAlternatingBack (for odd rows) styles
528
529
RowBackColor = TheDataGridView.Rows[CurrentRow].DefaultCellStyle.BackColor;
530
531
if (RowBackColor.IsEmpty) // If the there is no special RowBack style of the CurrentRow, then use the default one associated with the DataGridView control
532
{
533
534
RowBackBrush = new SolidBrush(TheDataGridView.DefaultCellStyle.BackColor);
535
536
RowAlternatingBackBrush = new SolidBrush(TheDataGridView.AlternatingRowsDefaultCellStyle.BackColor);
537
538
}
539
540
else // If the there is a special RowBack style of the CurrentRow, then use it for both the RowBack and the RowAlternatingBack styles
541
{
542
543
RowBackBrush = new SolidBrush(RowBackColor);
544
545
RowAlternatingBackBrush = new SolidBrush(RowBackColor);
546
547
}
548
549
// Calculating the starting x coordinate that the printing process will start from
550
551
CurrentX = (float)LeftMargin;
552
553
if (IsCenterOnPage)
554
555
CurrentX += (((float)PageWidth - (float)RightMargin - (float)LeftMargin) - mColumnPointsWidth[mColumnPoint]) / 2.0F;
556
557
// Calculating the entire CurrentRow bounds
558
559
RowBounds = new RectangleF(CurrentX, CurrentY, mColumnPointsWidth[mColumnPoint], RowsHeight[CurrentRow]);
560
561
// Filling the back of the CurrentRow
562
563
if (CurrentRow % 2 == 0)
564
565
g.FillRectangle(RowBackBrush, RowBounds);
566
567
else
568
569
g.FillRectangle(RowAlternatingBackBrush, RowBounds);
570
571
// Printing each visible cell of the CurrentRow
572
573
for (int CurrentCell = (int)mColumnPoints[mColumnPoint].GetValue(0); CurrentCell < (int)mColumnPoints[mColumnPoint].GetValue(1); CurrentCell++)
574
{
575
576
if (!TheDataGridView.Columns[CurrentCell].Visible) continue; // If the cell is belong to invisible column, then ignore this iteration
577
578
// Check the CurrentCell alignment and apply it to the CellFormat
579
580
if (TheDataGridView.Columns[CurrentCell].DefaultCellStyle.Alignment.ToString().Contains("Right"))
581
582
CellFormat.Alignment = StringAlignment.Far;
583
584
else if (TheDataGridView.Columns[CurrentCell].DefaultCellStyle.Alignment.ToString().Contains("Center"))
585
586
CellFormat.Alignment = StringAlignment.Center;
587
588
else
589
590
CellFormat.Alignment = StringAlignment.Near;
591
592
593
594
ColumnWidth = ColumnsWidth[CurrentCell];
595
596
RectangleF CellBounds = new RectangleF(CurrentX, CurrentY, ColumnWidth, RowsHeight[CurrentRow]);
597
598
// Printing the cell text
599
600
g.DrawString(TheDataGridView.Rows[CurrentRow].Cells[CurrentCell].EditedFormattedValue.ToString(), RowFont, RowForeBrush, CellBounds, CellFormat);
601
602
603
604
// Drawing the cell bounds
605
606
if (TheDataGridView.CellBorderStyle != DataGridViewCellBorderStyle.None) // Draw the cell border only if the CellBorderStyle is not None
607
608
g.DrawRectangle(TheLinePen, CurrentX, CurrentY, ColumnWidth, RowsHeight[CurrentRow]);
609
610
CurrentX += ColumnWidth;
611
612
}
613
614
CurrentY += RowsHeight[CurrentRow];
615
616
// Checking if the CurrentY is exceeds the page boundries
617
618
// If so then exit the function and returning true meaning another PagePrint action is required
619
620
if ((int)CurrentY > (PageHeight - TopMargin - BottomMargin))
621
{
622
623
CurrentRow++;
624
625
return true;
626
627
}
628
629
}
630
631
CurrentRow++;
632
633
}
634
635
CurrentRow = 0;
636
637
mColumnPoint++; // Continue to print the next group of columns
638
639
if (mColumnPoint == mColumnPoints.Count) // Which means all columns are printed
640
{
641
642
mColumnPoint = 0;
643
644
return false;
645
646
}
647
648
else
649
650
return true;
651
652
}
653
654
// The method that calls all other functions
655
656
public bool DrawDataGridView(Graphics g)
657
{
658
659
try
660
{
661
662
Calculate(g);
663
664
665
DrawHeader(g);
666
IsWithTitle = false;
667
bool bContinue = DrawRows(g);
668
669
return bContinue;
670
671
}
672
673
catch (Exception ex)
674
{
675
676
MessageBox.Show("Operation failed: " + ex.Message.ToString(), Application.ProductName + " - Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
677
678
return false;
679
680
}
681
682
}
683
684
685
}
686
}
687
688
689
690
691

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
