summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsPageSequenceFrame.cpp
blob: cd3c2cd6a5236e69b96d85a3f55322361955a960 (plain)
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
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsPageSequenceFrame.h"

#include "mozilla/intl/AppDateTimeFormat.h"
#include "mozilla/Logging.h"
#include "mozilla/PresShell.h"
#include "mozilla/PrintedSheetFrame.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/StaticPresData.h"

#include "nsCOMPtr.h"
#include "nsDeviceContext.h"
#include "nsPresContext.h"
#include "gfxContext.h"
#include "nsGkAtoms.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "nsIPrintSettings.h"
#include "nsPageFrame.h"
#include "nsSubDocumentFrame.h"
#include "nsRegion.h"
#include "nsCSSFrameConstructor.h"
#include "nsContentUtils.h"
#include "nsDisplayList.h"
#include "nsHTMLCanvasFrame.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsServiceManagerUtils.h"
#include <algorithm>
#include <limits>

using namespace mozilla;
using namespace mozilla::dom;

mozilla::LazyLogModule gLayoutPrintingLog("printing-layout");

#define PR_PL(_p1) MOZ_LOG(gLayoutPrintingLog, mozilla::LogLevel::Debug, _p1)

nsPageSequenceFrame* NS_NewPageSequenceFrame(PresShell* aPresShell,
                                             ComputedStyle* aStyle) {
  return new (aPresShell)
      nsPageSequenceFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsPageSequenceFrame)

static const nsPagesPerSheetInfo kSupportedPagesPerSheet[] = {
    /* Members are: {mNumPages, mLargerNumTracks} */
    // clang-format off
    {1, 1},
    {2, 2},
    {4, 2},
    {6, 3},
    {9, 3},
    {16, 4},
    // clang-format on
};

inline void SanityCheckPagesPerSheetInfo() {
#ifdef DEBUG
  // Sanity-checks:
  MOZ_ASSERT(ArrayLength(kSupportedPagesPerSheet) > 0,
             "Should have at least one pages-per-sheet option.");
  MOZ_ASSERT(kSupportedPagesPerSheet[0].mNumPages == 1,
             "The 0th index is reserved for default 1-page-per-sheet entry");

  uint16_t prevInfoPPS = 0;
  for (const auto& info : kSupportedPagesPerSheet) {
    MOZ_ASSERT(info.mNumPages > prevInfoPPS,
               "page count field should be positive & monotonically increase");
    MOZ_ASSERT(info.mLargerNumTracks > 0,
               "page grid has to have a positive number of tracks");
    MOZ_ASSERT(info.mNumPages % info.mLargerNumTracks == 0,
               "page count field should be evenly divisible by "
               "the given track-count");
    prevInfoPPS = info.mNumPages;
  }
#endif
}

const nsPagesPerSheetInfo& nsPagesPerSheetInfo::LookupInfo(int32_t aPPS) {
  SanityCheckPagesPerSheetInfo();

  // Walk the array, looking for a match:
  for (const auto& info : kSupportedPagesPerSheet) {
    if (aPPS == info.mNumPages) {
      return info;
    }
  }

  NS_WARNING("Unsupported pages-per-sheet value");
  // If no match was found, return the first entry (for 1 page per sheet).
  return kSupportedPagesPerSheet[0];
}

const nsPagesPerSheetInfo* nsSharedPageData::PagesPerSheetInfo() {
  if (mPagesPerSheetInfo) {
    return mPagesPerSheetInfo;
  }

  int32_t pagesPerSheet;
  if (!mPrintSettings ||
      NS_FAILED(mPrintSettings->GetNumPagesPerSheet(&pagesPerSheet))) {
    // If we can't read the value from print settings, just fall back to 1.
    pagesPerSheet = 1;
  }

  mPagesPerSheetInfo = &nsPagesPerSheetInfo::LookupInfo(pagesPerSheet);
  return mPagesPerSheetInfo;
}

nsPageSequenceFrame::nsPageSequenceFrame(ComputedStyle* aStyle,
                                         nsPresContext* aPresContext)
    : nsContainerFrame(aStyle, aPresContext, kClassID),
      mMaxSheetSize(mWritingMode),
      mScrollportSize(mWritingMode),
      mCalledBeginPage(false),
      mCurrentCanvasListSetup(false) {
  mPageData = MakeUnique<nsSharedPageData>();
  mPageData->mHeadFootFont =
      *PresContext()
           ->Document()
           ->GetFontPrefsForLang(aStyle->StyleFont()->mLanguage)
           ->GetDefaultFont(StyleGenericFontFamily::Serif);
  mPageData->mHeadFootFont.size =
      Length::FromPixels(CSSPixel::FromPoints(10.0f));
  mPageData->mPrintSettings = aPresContext->GetPrintSettings();
  MOZ_RELEASE_ASSERT(mPageData->mPrintSettings, "How?");

  // Doing this here so we only have to go get these formats once
  SetPageNumberFormat("pagenumber", "%1$d", true);
  SetPageNumberFormat("pageofpages", "%1$d of %2$d", false);
}

nsPageSequenceFrame::~nsPageSequenceFrame() { ResetPrintCanvasList(); }

NS_QUERYFRAME_HEAD(nsPageSequenceFrame)
  NS_QUERYFRAME_ENTRY(nsPageSequenceFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)

//----------------------------------------------------------------------

float nsPageSequenceFrame::GetPrintPreviewScale() const {
  nsPresContext* pc = PresContext();
  float scale = pc->GetPrintPreviewScaleForSequenceFrameOrScrollbars();

  WritingMode wm = GetWritingMode();
  if (pc->IsScreen() && MOZ_LIKELY(mScrollportSize.ISize(wm) > 0 &&
                                   mScrollportSize.BSize(wm) > 0)) {
    // For print preview, scale down as-needed to ensure that each of our
    // sheets will fit in the the scrollport.

    // Check if the current scale is sufficient for our sheets to fit in inline
    // axis (and if not, reduce the scale so that it will fit).
    nscoord scaledISize = NSToCoordCeil(mMaxSheetSize.ISize(wm) * scale);
    if (scaledISize > mScrollportSize.ISize(wm)) {
      scale *= float(mScrollportSize.ISize(wm)) / float(scaledISize);
    }

    // Further reduce the scale (if needed) to be sure each sheet will fit in
    // block axis, too.
    // NOTE: in general, a scrollport's BSize *could* be unconstrained,
    // i.e. sized to its contents. If that happens, then shrinking the contents
    // to fit the scrollport is not a meaningful operation in this axis, so we
    // skip over this.  But we can be pretty sure that the print-preview UI
    // will have given the scrollport a fixed size; hence the MOZ_LIKELY here.
    if (MOZ_LIKELY(mScrollportSize.BSize(wm) != NS_UNCONSTRAINEDSIZE)) {
      nscoord scaledBSize = NSToCoordCeil(mMaxSheetSize.BSize(wm) * scale);
      if (scaledBSize > mScrollportSize.BSize(wm)) {
        scale *= float(mScrollportSize.BSize(wm)) / float(scaledBSize);
      }
    }
  }
  return scale;
}

void nsPageSequenceFrame::PopulateReflowOutput(
    ReflowOutput& aReflowOutput, const ReflowInput& aReflowInput) {
  // Aim to fill the whole available space, not only so we can act as a
  // background in print preview but also handle overflow in child page frames
  // correctly.
  // Use availableISize so we don't cause a needless horizontal scrollbar.
  float scale = GetPrintPreviewScale();

  WritingMode wm = aReflowInput.GetWritingMode();
  nscoord iSize = wm.IsVertical() ? mSize.Height() : mSize.Width();
  nscoord bSize = wm.IsVertical() ? mSize.Width() : mSize.Height();

  nscoord availableISize = aReflowInput.AvailableISize();
  nscoord computedBSize = aReflowInput.ComputedBSize();
  if (MOZ_UNLIKELY(computedBSize == NS_UNCONSTRAINEDSIZE)) {
    // We have unconstrained BSize, which should only happen if someone calls
    // SizeToContent() on our window (which we don't expect to happen for
    // actual user flows, but is possible for fuzzers to trigger). We just nerf
    // the ReflowInput's contributions to the std::max() expressions below,
    // which does indeed make us "size to content", via letting std::max()
    // choose the scaled iSize/bSize expressions.
    availableISize = computedBSize = 0;
  }
  aReflowOutput.ISize(wm) =
      std::max(NSToCoordFloor(iSize * scale), availableISize);
  aReflowOutput.BSize(wm) =
      std::max(NSToCoordFloor(bSize * scale), computedBSize);
  aReflowOutput.SetOverflowAreasToDesiredBounds();
}

// Helper function to compute the offset needed to center a child
// page-frame's margin-box inside our content-box.
nscoord nsPageSequenceFrame::ComputeCenteringMargin(
    nscoord aContainerContentBoxWidth, nscoord aChildPaddingBoxWidth,
    const nsMargin& aChildPhysicalMargin) {
  // We'll be centering our child's margin-box, so get the size of that:
  nscoord childMarginBoxWidth =
      aChildPaddingBoxWidth + aChildPhysicalMargin.LeftRight();

  // When rendered, our child's rect will actually be scaled up by the
  // print-preview scale factor, via ComputePageSequenceTransform().
  // We really want to center *that scaled-up rendering* inside of
  // aContainerContentBoxWidth.  So, we scale up its margin-box here...
  float scale = GetPrintPreviewScale();
  nscoord scaledChildMarginBoxWidth =
      NSToCoordRound(childMarginBoxWidth * scale);

  // ...and see we how much space is left over, when we subtract that scaled-up
  // size from the container width:
  nscoord scaledExtraSpace =
      aContainerContentBoxWidth - scaledChildMarginBoxWidth;

  if (scaledExtraSpace <= 0) {
    // (Don't bother centering if there's zero/negative space.)
    return 0;
  }

  // To center the child, we want to give it an additional left-margin of half
  // of the extra space.  And then, we have to scale that space back down, so
  // that it'll produce the correct scaled-up amount when we render (because
  // rendering will scale it back up):
  return NSToCoordRound(scaledExtraSpace * 0.5 / scale);
}

uint32_t nsPageSequenceFrame::GetPagesInFirstSheet() const {
  nsIFrame* firstSheet = mFrames.FirstChild();
  if (!firstSheet) {
    return 0;
  }

  MOZ_DIAGNOSTIC_ASSERT(firstSheet->IsPrintedSheetFrame());
  return static_cast<PrintedSheetFrame*>(firstSheet)->GetNumPages();
}

/*
 * Note: we largely position/size out our children (page frames) using
 * \*physical\* x/y/width/height values, because the print preview UI is always
 * arranged in the same orientation, regardless of writing mode.
 */
void nsPageSequenceFrame::Reflow(nsPresContext* aPresContext,
                                 ReflowOutput& aReflowOutput,
                                 const ReflowInput& aReflowInput,
                                 nsReflowStatus& aStatus) {
  MarkInReflow();
  MOZ_ASSERT(aPresContext->IsRootPaginatedDocument(),
             "A Page Sequence is only for real pages");
  DO_GLOBAL_REFLOW_COUNT("nsPageSequenceFrame");
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  NS_FRAME_TRACE_REFLOW_IN("nsPageSequenceFrame::Reflow");

  auto CenterPages = [&] {
    for (nsIFrame* child : mFrames) {
      nsMargin pageCSSMargin = child->GetUsedMargin();
      nscoord centeringMargin =
          ComputeCenteringMargin(aReflowInput.ComputedWidth(),
                                 child->GetRect().Width(), pageCSSMargin);
      nscoord newX = pageCSSMargin.left + centeringMargin;

      // Adjust the child's x-position:
      child->MovePositionBy(nsPoint(newX - child->GetNormalPosition().x, 0));
    }
  };

  if (aPresContext->IsScreen()) {
    // When we're displayed on-screen, the computed size that we're given is
    // the size of our scrollport. We need to save this for use in
    // GetPrintPreviewScale.
    // (NOTE: It's possible but unlikely that we have an unconstrained BSize
    // here, if we're being sized to content. GetPrintPreviewScale() checks
    // for and handles this, when making use of this member-var.)
    mScrollportSize = aReflowInput.ComputedSize();
  }

  // Don't do incremental reflow until we've taught tables how to do
  // it right in paginated mode.
  if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    // Return our desired size
    PopulateReflowOutput(aReflowOutput, aReflowInput);
    FinishAndStoreOverflow(&aReflowOutput);

    if (GetSize() != aReflowOutput.PhysicalSize()) {
      CenterPages();
    }
    return;
  }

  nsIntMargin unwriteableTwips =
      mPageData->mPrintSettings->GetUnwriteableMarginInTwips();

  nsIntMargin edgeTwips = mPageData->mPrintSettings->GetEdgeInTwips();

  // sanity check the values. three inches are sometimes needed
  int32_t threeInches = NS_INCHES_TO_INT_TWIPS(3.0);
  edgeTwips.EnsureAtMost(
      nsIntMargin(threeInches, threeInches, threeInches, threeInches));
  edgeTwips.EnsureAtLeast(unwriteableTwips);

  mPageData->mEdgePaperMargin = nsPresContext::CSSTwipsToAppUnits(edgeTwips);

  // Get the custom page-range state:
  mPageData->mPrintSettings->GetPageRanges(mPageData->mPageRanges);

  // We use the CSS "margin" property on the -moz-printed-sheet pseudoelement
  // to determine the space between each printed sheet in print preview.
  // Keep a running y-offset for each printed sheet.
  nscoord y = 0;

  // These represent the maximum sheet size across all our sheets (in each
  // axis), inflated a bit to account for the -moz-printed-sheet 'margin'.
  nscoord maxInflatedSheetWidth = 0;
  nscoord maxInflatedSheetHeight = 0;

  // Tile the sheets vertically
  for (nsIFrame* kidFrame : mFrames) {
    // Set the shared data into the page frame before reflow
    MOZ_ASSERT(kidFrame->IsPrintedSheetFrame(),
               "we're only expecting PrintedSheetFrame as children");
    auto* sheet = static_cast<PrintedSheetFrame*>(kidFrame);
    sheet->SetSharedPageData(mPageData.get());

    // If we want to reliably access the nsPageFrame before reflowing the sheet
    // frame, we need to call this:
    sheet->ClaimPageFrameFromPrevInFlow();

    const nsSize sheetSize = sheet->ComputeSheetSize(aPresContext);

    // Reflow the sheet
    ReflowInput kidReflowInput(
        aPresContext, aReflowInput, kidFrame,
        LogicalSize(kidFrame->GetWritingMode(), sheetSize));
    kidReflowInput.mBreakType = ReflowInput::BreakType::Page;

    ReflowOutput kidReflowOutput(kidReflowInput);
    nsReflowStatus status;

    kidReflowInput.SetComputedISize(kidReflowInput.AvailableISize());
    // kidReflowInput.SetComputedHeight(kidReflowInput.AvailableHeight());
    PR_PL(("AV ISize: %d   BSize: %d\n", kidReflowInput.AvailableISize(),
           kidReflowInput.AvailableBSize()));

    nsMargin pageCSSMargin = kidReflowInput.ComputedPhysicalMargin();
    y += pageCSSMargin.top;

    nscoord x = pageCSSMargin.left;

    // Place and size the sheet.
    ReflowChild(kidFrame, aPresContext, kidReflowOutput, kidReflowInput, x, y,
                ReflowChildFlags::Default, status);

    FinishReflowChild(kidFrame, aPresContext, kidReflowOutput, &kidReflowInput,
                      x, y, ReflowChildFlags::Default);
    MOZ_ASSERT(kidFrame->GetSize() == sheetSize,
               "PrintedSheetFrame::ComputeSheetSize() gave the wrong size!");
    y += kidReflowOutput.Height();
    y += pageCSSMargin.bottom;

    maxInflatedSheetWidth =
        std::max(maxInflatedSheetWidth,
                 kidReflowOutput.Width() + pageCSSMargin.LeftRight());
    maxInflatedSheetHeight =
        std::max(maxInflatedSheetHeight,
                 kidReflowOutput.Height() + pageCSSMargin.TopBottom());

    // Is the sheet complete?
    nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();

    if (status.IsFullyComplete()) {
      NS_ASSERTION(!kidNextInFlow, "bad child flow list");
    } else if (!kidNextInFlow) {
      // The sheet isn't complete and it doesn't have a next-in-flow, so
      // create a continuing sheet.
      nsIFrame* continuingSheet =
          PresShell()->FrameConstructor()->CreateContinuingFrame(kidFrame,
                                                                 this);

      // Add it to our child list
      mFrames.InsertFrame(nullptr, kidFrame, continuingSheet);
    }
  }

  nsAutoString formattedDateString;
  PRTime now = PR_Now();
  mozilla::intl::DateTimeFormat::StyleBag style;
  style.date = Some(mozilla::intl::DateTimeFormat::Style::Short);
  style.time = Some(mozilla::intl::DateTimeFormat::Style::Short);
  if (NS_SUCCEEDED(mozilla::intl::AppDateTimeFormat::Format(
          style, now, formattedDateString))) {
    SetDateTimeStr(formattedDateString);
  }

  // cache the size so we can set the desired size for the other reflows that
  // happen.  Since we're tiling our sheets vertically: in the x axis, we are
  // as wide as our widest sheet (inflated via "margin"); and in the y axis,
  // we're as tall as the sum of our sheets' inflated heights, which the 'y'
  // variable is conveniently storing at this point.
  mSize = nsSize(maxInflatedSheetWidth, y);

  if (aPresContext->IsScreen()) {
    // Also cache the maximum size of all our sheets, to use together with the
    // scrollport size (available as our computed size, and captured higher up
    // in this function), so that we can scale to ensure that every sheet will
    // fit in the scrollport.
    WritingMode wm = aReflowInput.GetWritingMode();
    mMaxSheetSize =
        LogicalSize(wm, nsSize(maxInflatedSheetWidth, maxInflatedSheetHeight));
  }

  // Return our desired size
  // Adjust the reflow size by PrintPreviewScale so the scrollbars end up the
  // correct size
  PopulateReflowOutput(aReflowOutput, aReflowInput);

  FinishAndStoreOverflow(&aReflowOutput);

  // Now center our pages.
  CenterPages();

  NS_FRAME_TRACE_REFLOW_OUT("nsPageSequenceFrame::Reflow", aStatus);
}

//----------------------------------------------------------------------

#ifdef DEBUG_FRAME_DUMP
nsresult nsPageSequenceFrame::GetFrameName(nsAString& aResult) const {
  return MakeFrameName(u"PageSequence"_ns, aResult);
}
#endif

// Helper Function
void nsPageSequenceFrame::SetPageNumberFormat(const char* aPropName,
                                              const char* aDefPropVal,
                                              bool aPageNumOnly) {
  // Doing this here so we only have to go get these formats once
  nsAutoString pageNumberFormat;
  // Now go get the Localized Page Formating String
  nsresult rv = nsContentUtils::GetLocalizedString(
      nsContentUtils::ePRINTING_PROPERTIES, aPropName, pageNumberFormat);
  if (NS_FAILED(rv)) {  // back stop formatting
    pageNumberFormat.AssignASCII(aDefPropVal);
  }

  SetPageNumberFormat(pageNumberFormat, aPageNumOnly);
}

nsresult nsPageSequenceFrame::StartPrint(nsPresContext* aPresContext,
                                         nsIPrintSettings* aPrintSettings,
                                         const nsAString& aDocTitle,
                                         const nsAString& aDocURL) {
  NS_ENSURE_ARG_POINTER(aPresContext);
  NS_ENSURE_ARG_POINTER(aPrintSettings);

  if (!mPageData->mPrintSettings) {
    mPageData->mPrintSettings = aPrintSettings;
  }

  if (!aDocTitle.IsEmpty()) {
    mPageData->mDocTitle = aDocTitle;
  }
  if (!aDocURL.IsEmpty()) {
    mPageData->mDocURL = aDocURL;
  }

  // Begin printing of the document
  mCurrentSheetIdx = 0;
  return NS_OK;
}

static void GetPrintCanvasElementsInFrame(
    nsIFrame* aFrame, nsTArray<RefPtr<HTMLCanvasElement>>* aArr) {
  if (!aFrame) {
    return;
  }
  for (const auto& childList : aFrame->ChildLists()) {
    for (nsIFrame* child : childList.mList) {
      // Check if child is a nsHTMLCanvasFrame.
      nsHTMLCanvasFrame* canvasFrame = do_QueryFrame(child);

      // If there is a canvasFrame, try to get actual canvas element.
      if (canvasFrame) {
        HTMLCanvasElement* canvas =
            HTMLCanvasElement::FromNodeOrNull(canvasFrame->GetContent());
        if (canvas && canvas->GetMozPrintCallback()) {
          aArr->AppendElement(canvas);
          continue;
        }
      }

      if (!child->PrincipalChildList().FirstChild()) {
        nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(child);
        if (subdocumentFrame) {
          // Descend into the subdocument
          nsIFrame* root = subdocumentFrame->GetSubdocumentRootFrame();
          child = root;
        }
      }
      // The current child is not a nsHTMLCanvasFrame OR it is but there is
      // no HTMLCanvasElement on it. Check if children of `child` might
      // contain a HTMLCanvasElement.
      GetPrintCanvasElementsInFrame(child, aArr);
    }
  }
}

// Note: this isn't quite a full tree traversal, since we exclude any
// nsPageFame children that have the NS_PAGE_SKIPPED_BY_CUSTOM_RANGE state-bit.
static void GetPrintCanvasElementsInSheet(
    PrintedSheetFrame* aSheetFrame, nsTArray<RefPtr<HTMLCanvasElement>>* aArr) {
  MOZ_ASSERT(aSheetFrame, "Caller should've null-checked for us already");
  for (nsIFrame* child : aSheetFrame->PrincipalChildList()) {
    // Exclude any pages that are technically children but are skipped by a
    // custom range; they're not meant to be printed, so we don't want to
    // waste time rendering their canvas descendants.
    MOZ_ASSERT(child->IsPageFrame(),
               "PrintedSheetFrame's children must all be nsPageFrames");
    auto* pageFrame = static_cast<nsPageFrame*>(child);
    if (!pageFrame->HasAnyStateBits(NS_PAGE_SKIPPED_BY_CUSTOM_RANGE)) {
      GetPrintCanvasElementsInFrame(pageFrame, aArr);
    }
  }
}

PrintedSheetFrame* nsPageSequenceFrame::GetCurrentSheetFrame() {
  uint32_t i = 0;
  for (nsIFrame* child : mFrames) {
    MOZ_ASSERT(child->IsPrintedSheetFrame(),
               "Our children must all be PrintedSheetFrame");
    if (i == mCurrentSheetIdx) {
      return static_cast<PrintedSheetFrame*>(child);
    }
    ++i;
  }
  return nullptr;
}

nsresult nsPageSequenceFrame::PrePrintNextSheet(nsITimerCallback* aCallback,
                                                bool* aDone) {
  PrintedSheetFrame* currentSheet = GetCurrentSheetFrame();
  if (!currentSheet) {
    *aDone = true;
    return NS_ERROR_FAILURE;
  }

  if (!PresContext()->IsRootPaginatedDocument()) {
    // XXXdholbert I don't think this clause is ever actually visited in
    // practice... Maybe we should warn & return a failure code?  There used to
    // be a comment here explaining why we don't need to proceed past this
    // point for print preview, but in fact, this function isn't even called for
    // print preview.
    *aDone = true;
    return NS_OK;
  }

  // If the canvasList is null, then generate it and start the render
  // process for all the canvas.
  if (!mCurrentCanvasListSetup) {
    mCurrentCanvasListSetup = true;
    GetPrintCanvasElementsInSheet(currentSheet, &mCurrentCanvasList);

    if (!mCurrentCanvasList.IsEmpty()) {
      nsresult rv = NS_OK;

      // Begin printing of the document
      nsDeviceContext* dc = PresContext()->DeviceContext();
      PR_PL(("\n"));
      PR_PL(("***************** BeginPage *****************\n"));
      const gfx::IntSize sizeInPoints =
          currentSheet->GetPrintTargetSizeInPoints(
              dc->AppUnitsPerPhysicalInch());
      rv = dc->BeginPage(sizeInPoints);
      NS_ENSURE_SUCCESS(rv, rv);

      mCalledBeginPage = true;

      UniquePtr<gfxContext> renderingContext = dc->CreateRenderingContext();
      NS_ENSURE_TRUE(renderingContext, NS_ERROR_OUT_OF_MEMORY);

      DrawTarget* drawTarget = renderingContext->GetDrawTarget();
      if (NS_WARN_IF(!drawTarget)) {
        return NS_ERROR_FAILURE;
      }

      for (HTMLCanvasElement* canvas : Reversed(mCurrentCanvasList)) {
        nsIntSize size = canvas->GetSize();

        RefPtr<DrawTarget> canvasTarget =
            drawTarget->CreateSimilarDrawTarget(size, drawTarget->GetFormat());
        if (!canvasTarget) {
          continue;
        }

        nsICanvasRenderingContextInternal* ctx = canvas->GetCurrentContext();
        if (!ctx) {
          continue;
        }

        // Initialize the context with the new DrawTarget.
        ctx->InitializeWithDrawTarget(nullptr, WrapNotNull(canvasTarget));

        // Start the rendering process.
        // Note: Other than drawing to our CanvasRenderingContext2D, the
        // callback cannot access or mutate our static clone document.  It is
        // evaluated in its original context (the window of the original
        // document) of course, and our canvas has a strong ref to the
        // original HTMLCanvasElement (in mOriginalCanvas) so that if the
        // callback calls GetCanvas() on our CanvasRenderingContext2D (passed
        // to it via a MozCanvasPrintState argument) it will be given the
        // original 'canvas' element.
        AutoWeakFrame weakFrame = this;
        canvas->DispatchPrintCallback(aCallback);
        NS_ENSURE_STATE(weakFrame.IsAlive());
      }
    }
  }
  uint32_t doneCounter = 0;
  for (HTMLCanvasElement* canvas : mCurrentCanvasList) {
    if (canvas->IsPrintCallbackDone()) {
      doneCounter++;
    }
  }
  // If all canvas have finished rendering, return true, otherwise false.
  *aDone = doneCounter == mCurrentCanvasList.Length();

  return NS_OK;
}

void nsPageSequenceFrame::ResetPrintCanvasList() {
  for (int32_t i = mCurrentCanvasList.Length() - 1; i >= 0; i--) {
    HTMLCanvasElement* canvas = mCurrentCanvasList[i];
    canvas->ResetPrintCallback();
  }

  mCurrentCanvasList.Clear();
  mCurrentCanvasListSetup = false;
}

nsresult nsPageSequenceFrame::PrintNextSheet() {
  // Note: When print al the pages or a page range the printed page shows the
  // actual page number, when printing selection it prints the page number
  // starting with the first page of the selection. For example if the user has
  // a selection that starts on page 2 and ends on page 3, the page numbers when
  // print are 1 and then two (which is different than printing a page range,
  // where the page numbers would have been 2 and then 3)

  PrintedSheetFrame* currentSheetFrame = GetCurrentSheetFrame();
  if (!currentSheetFrame) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = NS_OK;

  nsDeviceContext* dc = PresContext()->DeviceContext();

  if (PresContext()->IsRootPaginatedDocument()) {
    if (!mCalledBeginPage) {
      // We must make sure BeginPage() has been called since some printing
      // backends can't give us a valid rendering context for a [physical]
      // page otherwise.
      PR_PL(("\n"));
      PR_PL(("***************** BeginPage *****************\n"));
      const gfx::IntSize sizeInPoints =
          currentSheetFrame->GetPrintTargetSizeInPoints(
              dc->AppUnitsPerPhysicalInch());
      rv = dc->BeginPage(sizeInPoints);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  PR_PL(("SeqFr::PrintNextSheet -> %p SheetIdx: %d", currentSheetFrame,
         mCurrentSheetIdx));

  // CreateRenderingContext can fail
  UniquePtr<gfxContext> gCtx = dc->CreateRenderingContext();
  NS_ENSURE_TRUE(gCtx, NS_ERROR_OUT_OF_MEMORY);

  nsRect drawingRect(nsPoint(0, 0), currentSheetFrame->GetSize());
  nsRegion drawingRegion(drawingRect);
  nsLayoutUtils::PaintFrame(gCtx.get(), currentSheetFrame, drawingRegion,
                            NS_RGBA(0, 0, 0, 0),
                            nsDisplayListBuilderMode::PaintForPrinting,
                            nsLayoutUtils::PaintFrameFlags::SyncDecodeImages);
  return rv;
}

nsresult nsPageSequenceFrame::DoPageEnd() {
  nsresult rv = NS_OK;
  if (PresContext()->IsRootPaginatedDocument()) {
    PR_PL(("***************** End Page (DoPageEnd) *****************\n"));
    rv = PresContext()->DeviceContext()->EndPage();
    // Fall through to clean up resources/state below even if EndPage failed.
  }

  ResetPrintCanvasList();
  mCalledBeginPage = false;

  mCurrentSheetIdx++;

  return rv;
}

static gfx::Matrix4x4 ComputePageSequenceTransform(const nsIFrame* aFrame,
                                                   float aAppUnitsPerPixel) {
  MOZ_ASSERT(aFrame->IsPageSequenceFrame());
  float scale =
      static_cast<const nsPageSequenceFrame*>(aFrame)->GetPrintPreviewScale();
  return gfx::Matrix4x4::Scaling(scale, scale, 1);
}

nsIFrame::ComputeTransformFunction nsPageSequenceFrame::GetTransformGetter()
    const {
  return ComputePageSequenceTransform;
}

void nsPageSequenceFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                           const nsDisplayListSet& aLists) {
  aBuilder->SetDisablePartialUpdates(true);
  DisplayBorderBackgroundOutline(aBuilder, aLists);

  nsDisplayList content(aBuilder);

  {
    // Clear clip state while we construct the children of the
    // nsDisplayTransform, since they'll be in a different coordinate system.
    DisplayListClipState::AutoSaveRestore clipState(aBuilder);
    clipState.Clear();

    nsIFrame* child = PrincipalChildList().FirstChild();
    nsRect visible = aBuilder->GetVisibleRect();
    visible.ScaleInverseRoundOut(GetPrintPreviewScale());

    while (child) {
      if (child->InkOverflowRectRelativeToParent().Intersects(visible)) {
        nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
            aBuilder, child, visible - child->GetPosition(),
            visible - child->GetPosition());
        child->BuildDisplayListForStackingContext(aBuilder, &content);
        aBuilder->ResetMarkedFramesForDisplayList(this);
      }
      child = child->GetNextSibling();
    }
  }

  content.AppendNewToTop<nsDisplayTransform>(
      aBuilder, this, &content, content.GetBuildingRect(),
      nsDisplayTransform::WithTransformGetter);

  aLists.Content()->AppendToTop(&content);
}

//------------------------------------------------------------------------------
void nsPageSequenceFrame::SetPageNumberFormat(const nsAString& aFormatStr,
                                              bool aForPageNumOnly) {
  NS_ASSERTION(mPageData != nullptr, "mPageData string cannot be null!");

  if (aForPageNumOnly) {
    mPageData->mPageNumFormat = aFormatStr;
  } else {
    mPageData->mPageNumAndTotalsFormat = aFormatStr;
  }
}

//------------------------------------------------------------------------------
void nsPageSequenceFrame::SetDateTimeStr(const nsAString& aDateTimeStr) {
  NS_ASSERTION(mPageData != nullptr, "mPageData string cannot be null!");

  mPageData->mDateTimeStr = aDateTimeStr;
}