diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/printing | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/printing')
34 files changed, 6435 insertions, 0 deletions
diff --git a/chrome/browser/printing/page_number.cc b/chrome/browser/printing/page_number.cc new file mode 100644 index 0000000..041ddaf --- /dev/null +++ b/chrome/browser/printing/page_number.cc @@ -0,0 +1,104 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/page_number.h" + +#include "base/logging.h" +#include "chrome/browser/printing/print_settings.h" + +namespace printing { + +PageNumber::PageNumber(const PrintSettings& settings, int document_page_count) { + Init(settings, document_page_count); +} + +PageNumber::PageNumber() + : ranges_(NULL), + page_number_(-1), + page_range_index_(-1), + document_page_count_(0) { +} + +void PageNumber::operator=(const PageNumber& other) { + ranges_ = other.ranges_; + page_number_ = other.page_number_; + page_range_index_ = other.page_range_index_; + document_page_count_ = other.document_page_count_; +} + +void PageNumber::Init(const PrintSettings& settings, int document_page_count) { + DCHECK(document_page_count); + ranges_ = settings.ranges.empty() ? NULL : &settings.ranges; + document_page_count_ = document_page_count; + if (ranges_) { + page_range_index_ = 0; + page_number_ = (*ranges_)[0].from; + } else { + if (document_page_count) { + page_number_ = 0; + } else { + page_number_ = -1; + } + page_range_index_ = -1; + } +} + +int PageNumber::operator++() { + if (!ranges_) { + // Switch to next page. + if (++page_number_ == document_page_count_) { + // Finished. + *this = npos(); + } + } else { + // Switch to next page. + ++page_number_; + // Page ranges are inclusive. + if (page_number_ > (*ranges_)[page_range_index_].to) { + if (++page_range_index_ == ranges_->size()) { + // Finished. + *this = npos(); + } else { + page_number_ = (*ranges_)[page_range_index_].from; + } + } + } + return ToInt(); +} + +bool PageNumber::operator==(const PageNumber& other) const { + return page_number_ == other.page_number_ && + page_range_index_ == other.page_range_index_; +} +bool PageNumber::operator!=(const PageNumber& other) const { + return page_number_ != other.page_number_ || + page_range_index_ != other.page_range_index_; +} + +} // namespace printing diff --git a/chrome/browser/printing/page_number.h b/chrome/browser/printing/page_number.h new file mode 100644 index 0000000..398de42 --- /dev/null +++ b/chrome/browser/printing/page_number.h @@ -0,0 +1,100 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PAGE_NUMBER_H__ +#define CHROME_BROWSER_PRINTING_PAGE_NUMBER_H__ + +#ifdef _DEBUG +#include <ostream> +#endif + +#include "chrome/browser/printing/page_range.h" + +namespace printing { + +class PrintSettings; + +// Represents a page series following the array of page ranges defined in a +// PrintSettings. +class PageNumber { + public: + // Initializes the page to the first page in the settings's range or 0. + PageNumber(const PrintSettings& settings, int document_page_count); + + PageNumber(); + + void operator=(const PageNumber& other); + + // Initializes the page to the first page in the setting's range or 0. It + // initialize to npos if the range is empty and document_page_count is 0. + void Init(const PrintSettings& settings, int document_page_count); + + // Converts to a page numbers. + int ToInt() const { + return page_number_; + } + + // Calculates the next page in the serie. + int operator++(); + + // Returns an instance that represents the end of a serie. + static const PageNumber npos() { + return PageNumber(); + } + + // Equality operator. Only the current page number is verified so that + // "page != PageNumber::npos()" works. + bool operator==(const PageNumber& other) const; + bool operator!=(const PageNumber& other) const; + + private: + // The page range to follow. + const PageRanges* ranges_; + + // The next page to be printed. -1 when not printing. + int page_number_; + + // The next page to be printed. -1 when not used. Valid only if + // document()->settings().range.empty() is false. + int page_range_index_; + + // Number of expected pages in the document. Used when ranges_ is NULL. + int document_page_count_; +}; + +// Debug output support. +template<class E, class T> +inline std::basic_ostream<E,T>& operator<<(std::basic_ostream<E,T>& ss, + const PageNumber& page) { + return ss << page.ToInt(); +} + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PAGE_NUMBER_H__ diff --git a/chrome/browser/printing/page_overlays.cc b/chrome/browser/printing/page_overlays.cc new file mode 100644 index 0000000..6f9d8c3 --- /dev/null +++ b/chrome/browser/printing/page_overlays.cc @@ -0,0 +1,182 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/page_overlays.h" + +#include "base/string_util.h" +#include "chrome/browser/printing/printed_document.h" +#include "chrome/browser/printing/printed_page.h" +#include "chrome/common/gfx/url_elider.h" + +namespace { + +// Replaces a subpart of a string by other value, and returns the position right +// after the new value. +size_t ReplaceKey(std::wstring* string, + size_t offset, + size_t old_string_len, + const std::wstring& new_string) { + string->replace(offset, old_string_len, new_string); + return offset + new_string.size(); +} + +} // namespace + +namespace printing { + +const wchar_t* const PageOverlays::kTitle = L"{title}"; +const wchar_t* const PageOverlays::kTime = L"{time}"; +const wchar_t* const PageOverlays::kDate = L"{date}"; +const wchar_t* const PageOverlays::kPage = L"{page}"; +const wchar_t* const PageOverlays::kPageCount = L"{pagecount}"; +const wchar_t* const PageOverlays::kPageOnTotal = L"{pageontotal}"; +const wchar_t* const PageOverlays::kUrl = L"{url}"; + +PageOverlays::PageOverlays() + : top_left(kDate), + top_center(kTitle), + top_right(), + bottom_left(kUrl), + bottom_center(), + bottom_right(kPageOnTotal) { +} + +bool PageOverlays::Equals(const PageOverlays& rhs) const { + return top_left == rhs.top_left && + top_center == rhs.top_center && + top_right == rhs.top_right && + bottom_left == rhs.bottom_left && + bottom_center == rhs.bottom_center && + bottom_right == rhs.bottom_right; +} + +const std::wstring& PageOverlays::GetOverlay(HorizontalPosition x, + VerticalPosition y) const { + switch (x) { + case LEFT: + switch (y) { + case TOP: + return top_left; + case BOTTOM: + return bottom_left; + } + break; + case CENTER: + switch (y) { + case TOP: + return top_center; + case BOTTOM: + return bottom_center; + } + break; + case RIGHT: + switch (y) { + case TOP: + return top_right; + case BOTTOM: + return bottom_right; + } + break; + } + NOTREACHED(); + return EmptyWString(); +} + +//static +std::wstring PageOverlays::ReplaceVariables(const std::wstring& input, + const PrintedDocument& document, + const PrintedPage& page) { + std::wstring output(input); + for (size_t offset = output.find(L'{', 0); + offset != std::wstring::npos; + offset = output.find(L'{', offset)) { + + if (0 == output.compare(offset, + wcslen(kTitle), + kTitle)) { + offset = ReplaceKey(&output, + offset, + wcslen(kTitle), + document.name()); + } else if (0 == output.compare(offset, + wcslen(kTime), + kTime)) { + offset = ReplaceKey(&output, + offset, + wcslen(kTime), + document.time()); + } else if (0 == output.compare(offset, + wcslen(kDate), + kDate)) { + offset = ReplaceKey(&output, + offset, + wcslen(kDate), + document.date()); + } else if (0 == output.compare(offset, + wcslen(kPage), + kPage)) { + offset = ReplaceKey(&output, + offset, + wcslen(kPage), + IntToWString(page.page_number())); + } else if (0 == output.compare(offset, + wcslen(kPageCount), + kPageCount)) { + offset = ReplaceKey(&output, + offset, + wcslen(kPageCount), + IntToWString(document.page_count())); + } else if (0 == output.compare(offset, + wcslen(kPageOnTotal), + kPageOnTotal)) { + std::wstring replacement; + replacement = IntToWString(page.page_number()); + replacement += L"/"; + replacement += IntToWString(document.page_count()); + offset = ReplaceKey(&output, + offset, + wcslen(kPageOnTotal), + replacement); + } else if (0 == output.compare(offset, + wcslen(kUrl), + kUrl)) { + // TODO(maruel): http://b/1126373 gfx::ElideUrl(document.url(), ...) + offset = ReplaceKey(&output, + offset, + wcslen(kUrl), + UTF8ToWide(document.url().spec())); + } else { + // There is just a { in the string. + ++offset; + } + } + return output; +} + +} // namespace printing diff --git a/chrome/browser/printing/page_overlays.h b/chrome/browser/printing/page_overlays.h new file mode 100644 index 0000000..905b917 --- /dev/null +++ b/chrome/browser/printing/page_overlays.h @@ -0,0 +1,101 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PAGE_OVERLAYS_H__ +#define CHROME_BROWSER_PRINTING_PAGE_OVERLAYS_H__ + +#include <string> + +namespace printing { + +class PrintedDocument; +class PrintedPage; + +// Page's overlays, i.e. headers and footers. Contains the strings that will be +// printed in the overlays, with actual values as variables. The variables are +// replaced by their actual values with ReplaceVariables(). +class PageOverlays { + public: + // Position of the header/footer. + enum HorizontalPosition { + LEFT, + CENTER, + RIGHT, + }; + + // Position of the header/footer. + enum VerticalPosition { + TOP, + BOTTOM, + }; + + PageOverlays(); + + // Equality operator. + bool Equals(const PageOverlays& rhs) const; + + // Returns the string of an overlay according to its x,y position. + const std::wstring& GetOverlay(HorizontalPosition x, + VerticalPosition y) const; + + // Replaces the variables in |input| with their actual values according to the + // properties of the current printed document and the current printed page. + static std::wstring ReplaceVariables(const std::wstring& input, + const PrintedDocument& document, + const PrintedPage& page); + + // Keys that are dynamically replaced in the header and footer by their actual + // values. + // Web page's title. + static const wchar_t* const kTitle; + // Print job's start time. + static const wchar_t* const kTime; + // Print job's start date. + static const wchar_t* const kDate; + // Printed page's number. + static const wchar_t* const kPage; + // Print job's total page count. + static const wchar_t* const kPageCount; + // Printed page's number on total page count. + static const wchar_t* const kPageOnTotal; + // Web page's displayed url. + static const wchar_t* const kUrl; + + // Actual headers and footers. + std::wstring top_left; + std::wstring top_center; + std::wstring top_right; + std::wstring bottom_left; + std::wstring bottom_center; + std::wstring bottom_right; +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PAGE_OVERLAYS_H__ diff --git a/chrome/browser/printing/page_range.cc b/chrome/browser/printing/page_range.cc new file mode 100644 index 0000000..f967877 --- /dev/null +++ b/chrome/browser/printing/page_range.cc @@ -0,0 +1,48 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/page_range.h" + +#include "chrome/common/stl_util-inl.h" + +namespace printing { + +std::vector<int> PageRange::GetPages(const PageRanges& ranges) { + std::set<int> pages; + for (unsigned i = 0; i < ranges.size(); ++i) { + const PageRange& range = ranges[i]; + // Ranges are inclusive. + for (int i = range.from; i <= range.to; ++i) { + pages.insert(i); + } + } + return SetToVector(pages); +} + +} // namespace printing diff --git a/chrome/browser/printing/page_range.h b/chrome/browser/printing/page_range.h new file mode 100644 index 0000000..59d808a --- /dev/null +++ b/chrome/browser/printing/page_range.h @@ -0,0 +1,56 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PAGE_RANGE_H__ +#define CHROME_BROWSER_PRINTING_PAGE_RANGE_H__ + +#include <vector> + +namespace printing { + +struct PageRange; + +typedef std::vector<PageRange> PageRanges; + +// Print range is inclusive. To select one page, set from == to. +struct PageRange { + int from; + int to; + + bool operator==(const PageRange& rhs) const { + return from == rhs.from && to == rhs.to; + } + + // Retrieves the sorted list of unique pages in the page ranges. + static std::vector<int> GetPages(const PageRanges& ranges); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PAGE_RANGE_H__ diff --git a/chrome/browser/printing/page_setup.cc b/chrome/browser/printing/page_setup.cc new file mode 100644 index 0000000..6d6c42a --- /dev/null +++ b/chrome/browser/printing/page_setup.cc @@ -0,0 +1,151 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/page_setup.h" + +#include "base/logging.h" + +namespace printing { + +PageMargins::PageMargins() + : header(0), + footer(0), + left(0), + right(0), + top(0), + bottom(0) { +} + +void PageMargins::Clear() { + header = 0; + footer = 0; + left = 0; + right = 0; + top = 0; + bottom = 0; +} + +bool PageMargins::Equals(const PageMargins& rhs) const { + return header == rhs.header && + footer == rhs.footer && + left == rhs.left && + top == rhs.top && + right == rhs.right && + bottom == rhs.bottom; +} + +PageSetup::PageSetup() : text_height_(0) { +} + +void PageSetup::Clear() { + physical_size_.SetSize(0, 0); + printable_area_.SetRect(0, 0, 0, 0); + overlay_area_.SetRect(0, 0, 0, 0); + content_area_.SetRect(0, 0, 0, 0); + effective_margins_.Clear(); + text_height_ = 0; +} + +bool PageSetup::Equals(const PageSetup& rhs) const { + return physical_size_ == rhs.physical_size_ && + printable_area_ == rhs.printable_area_ && + overlay_area_ == rhs.overlay_area_ && + content_area_ == rhs.content_area_ && + effective_margins_.Equals(rhs.effective_margins_) && + requested_margins_.Equals(rhs.requested_margins_) && + text_height_ == rhs.text_height_; +} + +void PageSetup::Init(const gfx::Size& physical_size, + const gfx::Rect& printable_area, + int text_height) { + DCHECK_LE(printable_area.right(), physical_size.width()); + // I've seen this assert triggers on Canon GP160PF PCL 5e. + // 28092 vs. 27940 @ 600 dpi = ~.25 inch. + DCHECK_LE(printable_area.bottom(), physical_size.height()); + DCHECK_GE(printable_area.x(), 0); + DCHECK_GE(printable_area.y(), 0); + DCHECK_GE(text_height, 0); + physical_size_ = physical_size; + printable_area_ = printable_area; + text_height_ = text_height; + + // Calculate the effective margins. The tricky part. + effective_margins_.header = std::max(requested_margins_.header, + printable_area_.y()); + effective_margins_.footer = std::max(requested_margins_.footer, + physical_size.height() - + printable_area_.bottom()); + effective_margins_.left = std::max(requested_margins_.left, + printable_area_.x()); + effective_margins_.top = std::max(std::max(requested_margins_.top, + printable_area_.y()), + effective_margins_.header + text_height); + effective_margins_.right = std::max(requested_margins_.right, + physical_size.width() - + printable_area_.right()); + effective_margins_.bottom = std::max(std::max(requested_margins_.bottom, + physical_size.height() - + printable_area_.bottom()), + effective_margins_.footer + text_height); + + // Calculate the overlay area. If the margins are excessive, the overlay_area + // size will be (0, 0). + overlay_area_.set_x(effective_margins_.left); + overlay_area_.set_y(effective_margins_.header); + overlay_area_.set_width(std::max(0, + physical_size.width() - + effective_margins_.right - + overlay_area_.x())); + overlay_area_.set_height(std::max(0, + physical_size.height() - + effective_margins_.footer - + overlay_area_.y())); + + // Calculate the content area. If the margins are excessive, the content_area + // size will be (0, 0). + content_area_.set_x(effective_margins_.left); + content_area_.set_y(effective_margins_.top); + content_area_.set_width(std::max(0, + physical_size.width() - + effective_margins_.right - + content_area_.x())); + content_area_.set_height(std::max(0, + physical_size.height() - + effective_margins_.bottom - + content_area_.y())); +} + +void PageSetup::SetRequestedMargins(const PageMargins& requested_margins) { + requested_margins_ = requested_margins; + if (physical_size_.width() && physical_size_.height()) + Init(physical_size_, printable_area_, text_height_); +} + +} // namespace printing diff --git a/chrome/browser/printing/page_setup.h b/chrome/browser/printing/page_setup.h new file mode 100644 index 0000000..4902843 --- /dev/null +++ b/chrome/browser/printing/page_setup.h @@ -0,0 +1,107 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PAGE_SETUP_H__ +#define CHROME_BROWSER_PRINTING_PAGE_SETUP_H__ + +#include "base/gfx/rect.h" + +namespace printing { + +// Margins for a page setup. +class PageMargins { + public: + PageMargins(); + + void Clear(); + + // Equality operator. + bool Equals(const PageMargins& rhs) const; + + // Vertical space for the overlay from the top of the sheet. + int header; + // Vertical space for the overlay from the bottom of the sheet. + int footer; + // Margin on each side of the sheet. + int left; + int top; + int right; + int bottom; +}; + +// Settings that define the size and printable areas of a page. Unit is +// unspecified. +class PageSetup { + public: + PageSetup(); + + void Clear(); + + // Equality operator. + bool Equals(const PageSetup& rhs) const; + + void Init(const gfx::Size& physical_size, const gfx::Rect& printable_area, + int text_height); + + void SetRequestedMargins(const PageMargins& requested_margins); + + const gfx::Size& physical_size() const { return physical_size_; } + const gfx::Rect& overlay_area() const { return overlay_area_; } + const gfx::Rect& content_area() const { return content_area_; } + const PageMargins& effective_margins() const { + return effective_margins_; + } + + private: + // Physical size of the page, including non-printable margins. + gfx::Size physical_size_; + + // The printable area as specified by the printer driver. We can't get + // larger than this. + gfx::Rect printable_area_; + + // The printable area for headers and footers. + gfx::Rect overlay_area_; + + // The printable area as selected by the user's margins. + gfx::Rect content_area_; + + // Effective margins. + PageMargins effective_margins_; + + // Requested margins. + PageMargins requested_margins_; + + // Space that must be kept free for the overlays. + int text_height_; +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PAGE_SETUP_H__ diff --git a/chrome/browser/printing/page_setup_unittest.cc b/chrome/browser/printing/page_setup_unittest.cc new file mode 100644 index 0000000..7128aed --- /dev/null +++ b/chrome/browser/printing/page_setup_unittest.cc @@ -0,0 +1,171 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/page_setup.h" + +#include <stdlib.h> +#include <time.h> + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PageSetupTest, Random) { + time_t seed = time(NULL); + int kMax = 10; + srand(static_cast<unsigned>(seed)); + + // Margins. + printing::PageMargins margins; + margins.header = rand() % kMax; + margins.footer = rand() % kMax; + margins.left = rand() % kMax; + margins.top = rand() % kMax; + margins.right = rand() % kMax; + margins.bottom = rand() % kMax; + int kTextHeight = rand() % kMax; + + // Page description. + gfx::Size page_size(100 + rand() % kMax, 200 + rand() % kMax); + gfx::Rect printable_area(rand() % kMax, rand() % kMax, 0, 0); + printable_area.set_width(page_size.width() - (rand() % kMax) - + printable_area.x()); + printable_area.set_height(page_size.height() - (rand() % kMax) - + printable_area.y()); + + // Make the calculations. + printing::PageSetup setup; + setup.SetRequestedMargins(margins); + setup.Init(page_size, printable_area, kTextHeight); + + // Calculate the effective margins. + printing::PageMargins effective_margins; + effective_margins.header = std::max(margins.header, printable_area.y()); + effective_margins.left = std::max(margins.left, printable_area.x()); + effective_margins.top = std::max(margins.top, + effective_margins.header + kTextHeight); + effective_margins.footer = std::max(margins.footer, + page_size.height() - + printable_area.bottom()); + effective_margins.right = std::max(margins.right, + page_size.width() - + printable_area.right()); + effective_margins.bottom = std::max(margins.bottom, + effective_margins.footer + kTextHeight); + + // Calculate the overlay area. + gfx::Rect overlay_area(effective_margins.left, effective_margins.header, + page_size.width() - effective_margins.right - + effective_margins.left, + page_size.height() - effective_margins.footer - + effective_margins.header); + + // Calculate the content area. + gfx::Rect content_area(overlay_area.x(), + effective_margins.top, + overlay_area.width(), + page_size.height() - effective_margins.bottom - + effective_margins.top); + + // Test values. + EXPECT_EQ(page_size, setup.physical_size()) << seed << " " << page_size << + " " << printable_area << " " << kTextHeight; + EXPECT_EQ(overlay_area, setup.overlay_area()) << seed << " " << page_size << + " " << printable_area << " " << kTextHeight; + EXPECT_EQ(content_area, setup.content_area()) << seed << " " << page_size << + " " << printable_area << " " << kTextHeight; + + EXPECT_EQ(effective_margins.header, setup.effective_margins().header) << + seed << " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.footer, setup.effective_margins().footer) << + seed << " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.left, setup.effective_margins().left) << seed << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.top, setup.effective_margins().top) << seed << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.right, setup.effective_margins().right) << seed << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.bottom, setup.effective_margins().bottom) << + seed << " " << page_size << " " << printable_area << " " << kTextHeight; +} + +TEST(PageSetupTest, HardCoded) { + // Margins. + printing::PageMargins margins; + margins.header = 2; + margins.footer = 2; + margins.left = 4; + margins.top = 4; + margins.right = 4; + margins.bottom = 4; + int kTextHeight = 3; + + // Page description. + gfx::Size page_size(100, 100); + gfx::Rect printable_area(3, 3, 94, 94); + + // Make the calculations. + printing::PageSetup setup; + setup.SetRequestedMargins(margins); + setup.Init(page_size, printable_area, kTextHeight); + + // Calculate the effective margins. + printing::PageMargins effective_margins; + effective_margins.header = 3; + effective_margins.left = 4; + effective_margins.top = 6; + effective_margins.footer = 3; + effective_margins.right = 4; + effective_margins.bottom = 6; + + // Calculate the overlay area. + gfx::Rect overlay_area(4, 3, 92, 94); + + // Calculate the content area. + gfx::Rect content_area(4, 6, 92, 88); + + // Test values. + EXPECT_EQ(page_size, setup.physical_size()) << " " << page_size << + " " << printable_area << " " << kTextHeight; + EXPECT_EQ(overlay_area, setup.overlay_area()) << " " << page_size << + " " << printable_area << " " << kTextHeight; + EXPECT_EQ(content_area, setup.content_area()) << " " << page_size << + " " << printable_area << " " << kTextHeight; + + EXPECT_EQ(effective_margins.header, setup.effective_margins().header) << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.footer, setup.effective_margins().footer) << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.left, setup.effective_margins().left) << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.top, setup.effective_margins().top) << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.right, setup.effective_margins().right) << + " " << page_size << " " << printable_area << " " << kTextHeight; + EXPECT_EQ(effective_margins.bottom, setup.effective_margins().bottom) << + " " << page_size << " " << printable_area << " " << kTextHeight; +} diff --git a/chrome/browser/printing/print_job.cc b/chrome/browser/printing/print_job.cc new file mode 100644 index 0000000..54713cf --- /dev/null +++ b/chrome/browser/printing/print_job.cc @@ -0,0 +1,454 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/print_job.h" + +#include "base/message_loop.h" +#include "chrome/browser/printing/print_job_worker.h" +#include "chrome/browser/printing/printed_document.h" +#include "chrome/browser/printing/printed_page.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + +namespace printing { + +PrintJob::PrintJob(PrintedPagesSource* source) + : ui_message_loop_(MessageLoop::current()), + worker_(new PrintJobWorker(this)), + source_(source), + is_job_pending_(false), + is_print_dialog_box_shown_(false), + is_blocking_(false), + is_canceling_(false) { +} + +PrintJob::PrintJob() + : ui_message_loop_(MessageLoop::current()), + worker_(), + source_(NULL), + settings_(), + is_job_pending_(false), + is_print_dialog_box_shown_(false), + is_blocking_(false), + is_canceling_(false) { +} + +PrintJob::~PrintJob() { + // The job should be finished (or at least canceled) when it is destroyed. + DCHECK(!is_job_pending_); + DCHECK(!is_print_dialog_box_shown_); + DCHECK(!is_blocking_); + DCHECK(!is_canceling_); + DCHECK(worker_->message_loop() == NULL); + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); +} + +void PrintJob::Initialize(PrintJobWorkerOwner* job, + PrintedPagesSource* source) { + DCHECK(!source_); + DCHECK(!worker_.get()); + DCHECK(!is_job_pending_); + DCHECK(!is_print_dialog_box_shown_); + DCHECK(!is_blocking_); + DCHECK(!is_canceling_); + DCHECK(!document_.get()); + source_ = source; + worker_.reset(job->DetachWorker(this)); + settings_ = job->settings(); + + UpdatePrintedDocument(new PrintedDocument(settings_, source_, job->cookie())); + + // Don't forget to register to our own messages. + NotificationService::current()->AddObserver( + this, NOTIFY_PRINT_JOB_EVENT, Source<PrintJob>(this)); +} + +void PrintJob::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + switch (type) { + case NOTIFY_PRINTED_DOCUMENT_UPDATED: { + DCHECK(Source<PrintedDocument>(source).ptr() == + document_.get()); + + // This notification may happens even if no job is started (i.e. print + // preview) + if (is_job_pending_ == true && + Source<PrintedDocument>(source).ptr() == document_.get() && + Details<PrintedPage>(details).ptr() != NULL) { + // Are we waiting for a page to print? The worker will know. + worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), &PrintJobWorker::OnNewPage)); + } + break; + } + case NOTIFY_PRINT_JOB_EVENT: { + OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr()); + break; + } + default: { + break; + } + } +} + +void PrintJob::GetSettingsDone(const PrintSettings& new_settings, + PrintingContext::Result result) { + DCHECK(!is_job_pending_); + DCHECK(!is_blocking_); + + if (!source_ || result == PrintingContext::FAILED) { + // The source is gone, there's nothing to do. + Cancel(); + return; + } + + // Only create a new PrintedDocument if the settings have changed or if + // there was no printed document. + if (!document_.get() || !new_settings.Equals(settings_)) { + UpdatePrintedDocument(new PrintedDocument(new_settings, source_, + PrintSettings::NewCookie())); + } + + JobEventDetails::Type type; + if (is_print_dialog_box_shown_) { + type = (result == PrintingContext::OK) ? + JobEventDetails::USER_INIT_DONE : + JobEventDetails::USER_INIT_CANCELED; + // Dialog box is not shown anymore. + is_print_dialog_box_shown_ = false; + } else { + DCHECK_EQ(result, PrintingContext::OK); + type = JobEventDetails::DEFAULT_INIT_DONE; + } + scoped_refptr<JobEventDetails> details( + new JobEventDetails(type, document_.get(), NULL)); + NotificationService::current()->Notify( + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(this), + Details<JobEventDetails>(details.get())); +} + +PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) { + NOTREACHED(); + return NULL; +} + +int PrintJob::cookie() const { + if (!document_.get()) + // Always use an invalid cookie in this case. + return 0; + return document_->cookie(); +} + +void PrintJob::GetSettings(GetSettingsAskParam ask_user_for_settings, + HWND parent_window) { + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + DCHECK(!is_job_pending_); + DCHECK(!is_print_dialog_box_shown_); + DCHECK(!is_blocking_); + // Is not reentrant. + if (is_job_pending_ || is_blocking_) + return; + + // Lazy create the worker thread. There is one worker thread per print job. + if (!worker_->message_loop()) { + if (!worker_->Start()) + return; + + // Don't re-register if we were already registered. + NotificationService::current()->AddObserver( + this, NOTIFY_PRINT_JOB_EVENT, Source<PrintJob>(this)); + } + + int page_count = 0; + if (document_.get()) { + page_count = document_->page_count(); + } + + // Real work is done in PrintJobWorker::Init(). + is_print_dialog_box_shown_ = ask_user_for_settings == ASK_USER; + worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), &PrintJobWorker::GetSettings, is_print_dialog_box_shown_, + parent_window, page_count)); +} + +void PrintJob::StartPrinting() { + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + DCHECK(worker_->message_loop()); + DCHECK(!is_job_pending_); + DCHECK(!is_print_dialog_box_shown_); + DCHECK(!is_blocking_); + if (!worker_->message_loop() || is_job_pending_ || is_blocking_) + return; + + // Real work is done in PrintJobWorker::StartPrinting(). + worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), &PrintJobWorker::StartPrinting, document_)); + // Set the flag right now. + is_job_pending_ = true; + + // Tell everyone! + scoped_refptr<JobEventDetails> details( + new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL)); + NotificationService::current()->Notify( + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(this), + Details<JobEventDetails>(details.get())); +} + +void PrintJob::Stop() { + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + + // Be sure to live long enough. + scoped_refptr<PrintJob> handle(this); + + MessageLoop* worker_loop = worker_->message_loop(); + if (worker_loop) { + if (is_print_dialog_box_shown_) { + // Make sure there is no dialog box. + worker_loop->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), &PrintJobWorker::DismissDialog)); + is_print_dialog_box_shown_ = false; + } + // It will wait infinitely for the worker thread to quit. + worker_->NonBlockingStop(); + is_job_pending_ = false; + NotificationService::current()->RemoveObserver( + this, NOTIFY_PRINT_JOB_EVENT, Source<PrintJob>(this)); + } + // Flush the cached document. + UpdatePrintedDocument(NULL); + + if (is_blocking_) { + // Make sure we don't get stuck in an inner message loop. + MessageLoop::current()->Quit(); + is_blocking_ = false; + } +} + +void PrintJob::Cancel() { + if (is_canceling_) + return; + is_canceling_ = true; + + // Be sure to live long enough. + scoped_refptr<PrintJob> handle(this); + + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + MessageLoop* worker_loop = worker_.get() ? worker_->message_loop() : NULL; + if (worker_loop) { + // Call this right now so it renders the context invalid. Do not use + // InvokeLater since it would take too much time. + worker_->Cancel(); + } + // Make sure a Cancel() is broadcast. + scoped_refptr<JobEventDetails> details( + new JobEventDetails(JobEventDetails::FAILED, NULL, NULL)); + NotificationService::current()->Notify( + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(this), + Details<JobEventDetails>(details.get())); + Stop(); + is_canceling_ = false; +} + +bool PrintJob::RequestMissingPages() { + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + DCHECK(!is_print_dialog_box_shown_); + DCHECK(!is_blocking_); + if (!is_job_pending_ || is_print_dialog_box_shown_ || is_blocking_) + return false; + + MessageLoop* worker_loop = worker_.get() ? worker_->message_loop() : NULL; + if (!worker_loop) + return false; + + worker_loop->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), &PrintJobWorker::RequestMissingPages)); + return true; +} + +bool PrintJob::FlushJob(int timeout_ms) { + if (!RequestMissingPages()) + return false; + + // Make sure the object outlive this message loop. + scoped_refptr<PrintJob> handle(this); + + MessageLoop::QuitTask timeout_task; + scoped_ptr<Timer> timeout; + if (timeout_ms) { + timeout.reset(MessageLoop::current()->timer_manager()->StartTimer( + timeout_ms, + &timeout_task, + false)); + } + + is_blocking_ = true; + // Stop() will eventually be called, which will get out of the inner message + // loop. But, don't take it for granted and set a timer in case something goes + // wrong. + + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + // Restore task state. + MessageLoop::current()->SetNestableTasksAllowed(old_state); + + if (timeout.get()) { + MessageLoop::current()->timer_manager()->StopTimer(timeout.get()); + } + return true; +} + +void PrintJob::DisconnectSource() { + source_ = NULL; + if (document_.get()) + document_->DisconnectSource(); +} + +bool PrintJob::is_job_pending() const { + return is_job_pending_; +} + +bool PrintJob::is_print_dialog_box_shown() const { + return is_print_dialog_box_shown_; +} + +PrintedDocument* PrintJob::document() const { + return document_.get(); +} + +void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { + if (document_.get() == new_document) + return; + // Unregisters. + if (document_.get()) { + NotificationService::current()-> + RemoveObserver(this, + NOTIFY_PRINTED_DOCUMENT_UPDATED, + Source<PrintedDocument>(document_.get())); + } + document_ = new_document; + + // Registers. + if (document_.get()) { + NotificationService::current()-> + AddObserver(this, + NOTIFY_PRINTED_DOCUMENT_UPDATED, + Source<PrintedDocument>(document_.get())); + settings_ = document_->settings(); + } + + if (worker_.get() && worker_->message_loop()) { + DCHECK(!is_job_pending_); + // Sync the document with the worker. + worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), &PrintJobWorker::OnDocumentChanged, document_)); + } +} + +void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::FAILED: { + settings_.Clear(); + // Update internal state. + is_print_dialog_box_shown_ = false; + // No need to cancel since the worker already canceled itself. + Stop(); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: { + DCHECK_EQ(event_details.document(), document_.get()); + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: + case JobEventDetails::JOB_DONE: + case JobEventDetails::ALL_PAGES_REQUESTED: { + // Don't care. + break; + } + case JobEventDetails::DOC_DONE: { + // This will call Stop() and broadcast a JOB_DONE message. + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &PrintJob::OnDocumentDone)); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintJob::OnDocumentDone() { + // Be sure to live long enough. The instance could be destroyed by the + // JOB_DONE broadcast. + scoped_refptr<PrintJob> handle(this); + + // Stop the worker thread. + Stop(); + + scoped_refptr<JobEventDetails> details( + new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL)); + NotificationService::current()->Notify( + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(this), + Details<JobEventDetails>(details.get())); +} + +// Takes settings_ ownership and will be deleted in the receiving thread. +JobEventDetails::JobEventDetails(Type type, + PrintedDocument* document, + PrintedPage* page) + : document_(document), + page_(page), + type_(type) { +} + +JobEventDetails::~JobEventDetails() { +} + +PrintedDocument* JobEventDetails::document() const { + return document_; +} + +PrintedPage* JobEventDetails::page() const { + return page_; +} + +} // namespace printing diff --git a/chrome/browser/printing/print_job.h b/chrome/browser/printing/print_job.h new file mode 100644 index 0000000..6f0accd --- /dev/null +++ b/chrome/browser/printing/print_job.h @@ -0,0 +1,249 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_JOB_H__ +#define CHROME_BROWSER_PRINTING_PRINT_JOB_H__ + +#include "base/ref_counted.h" +#include "chrome/browser/printing/print_job_worker_owner.h" +#include "chrome/common/notification_service.h" + +class ChromeFont; +class GURL; +class Thread; + +namespace printing { + +// See definition below. +class JobEventDetails; + +class PrintedDocument; +class PrintedPage; +class PrintedPagesSource; +class PrintJobWorker; +class PrinterQuery; + +// Manages the print work for a specific document. Talks to the printer through +// PrintingContext though PrintJob::Worker. Hides access to PrintingContext in a +// worker thread so the caller never blocks. PrintJob will send notifications on +// any state change. While printing, the PrintJobManager instance keeps a +// reference to the job to be sure it is kept alive. All the code in this class +// runs in the UI thread. +class PrintJob : public base::RefCountedThreadSafe<PrintJob>, + public NotificationObserver, + public PrintJobWorkerOwner { + public: + // GetSettings() UI parameter. + enum GetSettingsAskParam { + DEFAULTS, + ASK_USER, + }; + + // Create a standalone PrintJob. When initializing with this constructor, + // Initialize() must not be called. + PrintJob(PrintedPagesSource* source); + // Create a empty PrintJob. When initializing with this constructor, + // post-constructor initialization must be done with Initialize(). + PrintJob(); + virtual ~PrintJob(); + + void Initialize(PrintJobWorkerOwner* job, PrintedPagesSource* source); + + // NotificationObserver + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // PrintJobWorkerOwner + virtual void AddRef() { + return base::RefCountedThreadSafe<PrintJob>::AddRef(); + } + virtual void Release() { + return base::RefCountedThreadSafe<PrintJob>::Release(); + } + virtual void GetSettingsDone(const PrintSettings& new_settings, + PrintingContext::Result result); + virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner); + virtual MessageLoop* message_loop() { return ui_message_loop_; } + virtual const PrintSettings& settings() const { return settings_; } + virtual int cookie() const; + + // Initializes the printing context. This can be done synchronously or not. It + // is fine to call this function multiple times to reinitialize the settings. + // |parent_window| parameter will be the owner of the print setting dialog + // box. It is unused when |ask_for_user_settings| is DEFAULTS. No-op if a + // print job is active. + void GetSettings(GetSettingsAskParam ask_user_for_settings, + HWND parent_window); + + // Starts the actual printing. Signals the worker that it should begin to + // spool as soon as data is available. + void StartPrinting(); + + // Waits for the worker thread to finish its queued tasks and disconnects the + // delegate object. The PrintJobManager will remove it reference. This may + // have the side-effect of destroying the object if the caller doesn't have a + // handle to the object. + void Stop(); + + // Cancels printing job and stops the worker thread. Takes effect immediately. + void Cancel(); + + // Requests all the missing pages in the PrintedDocument. Returns true if at + // least one page has been requested. Returns false if there was not enough + // information to request the missing pages, i.e. + // document()->document_page_count() is not initialized or no page + // has been requested. + bool RequestMissingPages(); + + // Synchronously wait for the job to finish. It is mainly useful when the + // process is about to be shut down and we're waiting for the spooler to eat + // our data. + bool FlushJob(int timeout_ms); + + // Disconnects the PrintedPage source (PrintedPagesSource). It is done when + // the source is being destroyed. + void DisconnectSource(); + + // Returns true if the print job is pending, i.e. between a StartPrinting() + // and the end of the spooling. + bool is_job_pending() const; + + // Returns true if the Print... dialog box is currently displayed. + bool is_print_dialog_box_shown() const; + + // Access the current printed document. Warning: may be NULL. + PrintedDocument* document() const; + + + private: + // Updates document_ to a new instance. + void UpdatePrintedDocument(PrintedDocument* new_document); + + // Processes a NOTIFY_PRINT_JOB_EVENT notification. + void OnNotifyPrintJobEvent(const JobEventDetails& event_details); + + // Releases the worker thread by calling Stop(), then broadcasts a JOB_DONE + // notification. + void OnDocumentDone(); + + // Main message loop reference. Used to send notifications in the right + // thread. + MessageLoop* const ui_message_loop_; + + // Source that generates the PrintedPage's (i.e. a WebContents). It will be + // set back to NULL if the source is deleted before this object. + PrintedPagesSource* source_; + + // All the UI is done in a worker thread because many Win32 print functions + // are blocking and enters a message loop without your consent. There is one + // worker thread per print job. + scoped_ptr<PrintJobWorker> worker_; + + // Cache of the print context settings for access in the UI thread. + PrintSettings settings_; + + // The printed document. + scoped_refptr<PrintedDocument> document_; + + // Is the worker thread printing. + bool is_job_pending_; + + // Is the Print... dialog box currently shown. + bool is_print_dialog_box_shown_; + + // Is GetSettings() blocking to act like a synchronous function? + bool is_blocking_; + + // Is Canceling? If so, try to not cause recursion if on FAILED notification, + // the notified calls Cancel() again. + bool is_canceling_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintJob); +}; + +// Details for a NOTIFY_PRINT_JOB_EVENT notification. The members may be NULL. +class JobEventDetails : public base::RefCountedThreadSafe<JobEventDetails> { + public: + // Event type. + enum Type { + // Print... dialog box has been closed with OK button. + USER_INIT_DONE, + + // Print... dialog box has been closed with CANCEL button. + USER_INIT_CANCELED, + + // An automated initialization has been done, e.g. Init(false, NULL). + DEFAULT_INIT_DONE, + + // A new document started printing. + NEW_DOC, + + // A new page started printing. + NEW_PAGE, + + // A page is done printing. + PAGE_DONE, + + // A document is done printing. The worker thread is still alive. Warning: + // not a good moment to release the handle to PrintJob. + DOC_DONE, + + // The worker thread is finished. A good moment to release the handle to + // PrintJob. + JOB_DONE, + + // All missing pages have been requested. + ALL_PAGES_REQUESTED, + + // An error occured. Printing is canceled. + FAILED, + }; + + JobEventDetails(Type type, PrintedDocument* document, PrintedPage* page); + ~JobEventDetails(); + + // Getters. + PrintedDocument* document() const; + PrintedPage* page() const; + Type type() const { + return type_; + } + + private: + scoped_refptr<PrintedDocument> document_; + scoped_refptr<PrintedPage> page_; + const Type type_; + + DISALLOW_EVIL_CONSTRUCTORS(JobEventDetails); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINT_JOB_H__ diff --git a/chrome/browser/printing/print_job_manager.cc b/chrome/browser/printing/print_job_manager.cc new file mode 100644 index 0000000..794af34 --- /dev/null +++ b/chrome/browser/printing/print_job_manager.cc @@ -0,0 +1,220 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/print_job_manager.h" + +#include "base/file_util.h" +#include "base/string_util.h" +#include "chrome/browser/printing/print_job.h" +#include "chrome/browser/printing/printer_query.h" +#include "chrome/browser/printing/printed_document.h" +#include "chrome/browser/printing/printed_page.h" +#include "chrome/common/gfx/emf.h" + +namespace printing { + +PrintJobManager::PrintJobManager() + : debug_dump_path_() { + NotificationService::current()->AddObserver( + this, + NOTIFY_PRINT_JOB_EVENT, + NotificationService::AllSources()); + NotificationService::current()->AddObserver( + this, + NOTIFY_PRINTED_DOCUMENT_UPDATED, + NotificationService::AllSources()); +} + +PrintJobManager::~PrintJobManager() { + // When this object is destroyed, the shared NotificationService instance is + // already destroyed. + AutoLock lock(lock_); + queued_queries_.clear(); + NotificationService::current()->RemoveObserver( + this, + NOTIFY_PRINT_JOB_EVENT, + NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, + NOTIFY_PRINTED_DOCUMENT_UPDATED, + NotificationService::AllSources()); +} + +void PrintJobManager::OnQuit() { + // Common case, no print job pending. + if (current_jobs_.size() == 0) + return; + { + // Don't take a chance and copy the array since it can be modified in transit. + PrintJobs current_jobs(current_jobs_); + // Wait for every jobs to finish. + for (size_t i = 0; i < current_jobs.size(); ++i) { + PrintJob* job = current_jobs[i]; + if (!job) + continue; + // Wait for 120 seconds for the print job to be spooled. + job->FlushJob(120000); + job->Stop(); + } + } + current_jobs_.clear(); + NotificationService::current()->RemoveObserver( + this, + NOTIFY_PRINT_JOB_EVENT, + NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, + NOTIFY_PRINTED_DOCUMENT_UPDATED, + NotificationService::AllSources()); + DCHECK_EQ(current_jobs_.size(), 0); +} + +void PrintJobManager::QueuePrinterQuery(PrinterQuery* job) { + AutoLock lock(lock_); + DCHECK(job); + queued_queries_.push_back(job); + DCHECK(job->is_valid()); +} + +void PrintJobManager::PopPrinterQuery(int document_cookie, + scoped_refptr<PrinterQuery>* job) { + AutoLock lock(lock_); + for (PrinterQueries::iterator itr = queued_queries_.begin(); + itr != queued_queries_.end(); + ++itr) { + PrinterQuery* current_query = *itr; + if (current_query->cookie() == document_cookie && + !current_query->is_callback_pending()) { + *job = current_query; + queued_queries_.erase(itr); + DCHECK(current_query->is_valid()); + return; + } + } +} + + +void PrintJobManager::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFY_PRINT_JOB_EVENT: { + OnPrintJobEvent(Source<PrintJob>(source).ptr(), + *Details<JobEventDetails>(details).ptr()); + break; + } + case NOTIFY_PRINTED_DOCUMENT_UPDATED: { + PrintedPage* printed_page = Details<PrintedPage>(details).ptr(); + if (printed_page) + OnPrintedDocumentUpdated(*Source<PrintedDocument>(source).ptr(), + *printed_page); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintJobManager::OnPrintJobEvent( + PrintJob* print_job, + const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::NEW_DOC: { + DCHECK(current_jobs_.end() == std::find(current_jobs_.begin(), + current_jobs_.end(), + print_job)); + // Causes a AddRef(). + current_jobs_.push_back(print_job); + break; + } + case JobEventDetails::JOB_DONE: { + PrintJobs::iterator itr = std::find(current_jobs_.begin(), + current_jobs_.end(), + print_job); + DCHECK(current_jobs_.end() != itr); + current_jobs_.erase(itr); + DCHECK(current_jobs_.end() == std::find(current_jobs_.begin(), + current_jobs_.end(), + print_job)); + break; + } + case JobEventDetails::FAILED: { + PrintJobs::iterator itr = std::find(current_jobs_.begin(), + current_jobs_.end(), + print_job); + // A failed job may have never started. + if (current_jobs_.end() != itr) { + current_jobs_.erase(itr); + DCHECK(current_jobs_.end() == + std::find(current_jobs_.begin(), + current_jobs_.end(), + print_job)); + } + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: + case JobEventDetails::DOC_DONE: + case JobEventDetails::ALL_PAGES_REQUESTED: { + // Don't care. + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintJobManager::OnPrintedDocumentUpdated(const PrintedDocument& document, + const PrintedPage& page) { + if (debug_dump_path_.empty()) + return; + + std::wstring filename; + filename += document.date(); + filename += L"_"; + filename += document.time(); + filename += L"_"; + filename += document.name(); + filename += L"_"; + filename += StringPrintf(L"%02d", page.page_number()); + filename += L"_.emf"; + file_util::ReplaceIllegalCharacters(&filename, '_'); + std::wstring path(debug_dump_path_); + file_util::AppendToPath(&path, filename); + page.emf()->SaveTo(path); +} + +} // namespace printing diff --git a/chrome/browser/printing/print_job_manager.h b/chrome/browser/printing/print_job_manager.h new file mode 100644 index 0000000..ee95367 --- /dev/null +++ b/chrome/browser/printing/print_job_manager.h @@ -0,0 +1,111 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_JOB_MANAGER_H__ +#define CHROME_BROWSER_PRINTING_PRINT_JOB_MANAGER_H__ + +#include "base/lock.h" +#include "base/ref_counted.h" +#include "chrome/common/notification_service.h" + +namespace printing { + +class JobEventDetails; +class PrintedDocument; +class PrintJob; +class PrintedPage; +class PrinterQuery; + +class PrintJobManager : public NotificationObserver { + public: + PrintJobManager(); + ~PrintJobManager(); + + // On browser quit, we should wait to have the print job finished. + void OnQuit(); + + // Queues a semi-initialized worker thread. Can be called from any thread. + // Current use case is queuing from the I/O thread. + // TODO(maruel): Have them vanish after a timeout (~5 minutes?) + void QueuePrinterQuery(PrinterQuery* job); + + // Pops a queued PrintJobWorkerOwner object that was previously queued. Can be + // called from any thread. Current use case is poping from the browser thread. + void PopPrinterQuery(int document_cookie, scoped_refptr<PrinterQuery>* job); + + // NotificationObserver + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Sets a path where to dump EMF data files. This enables debug behavior where + // every rendered pages are dumped as-is. By default the path is empty, which + // disables the dumping. + // TODO(maruel): Remove me once printing is awesome. + void set_debug_dump_path(const std::wstring& debug_dump_path) { + debug_dump_path_ = debug_dump_path; + } + + const std::wstring& debug_dump_path() const { + return debug_dump_path_; + } + + private: + typedef std::vector<scoped_refptr<PrintJob> > PrintJobs; + typedef std::vector<scoped_refptr<PrinterQuery> > PrinterQueries; + + // Processes a NOTIFY_PRINT_JOB_EVENT notification. + void OnPrintJobEvent(PrintJob* print_job, + const JobEventDetails& event_details); + + // Processes a NOTIFY_PRINTED_DOCUMENT_UPDATED notification. When + // debug_dump_path_ is not empty, it is processed to detect newly rendered + // pages and to dump their EMF buffer. + void OnPrintedDocumentUpdated(const PrintedDocument& document, + const PrintedPage& page); + + // Used to serialize access to queued_workers_. + Lock lock_; + + PrinterQueries queued_queries_; + + // Current print jobs that are active. + PrintJobs current_jobs_; + + // Path where debug dump of EMF buffer are saved. Empty by default. When + // empty, EMF dumping is disabled. + // TODO(maruel): Remove me once printing is awesome. + std::wstring debug_dump_path_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintJobManager); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINT_JOB_MANAGER_H__ diff --git a/chrome/browser/printing/print_job_worker.cc b/chrome/browser/printing/print_job_worker.cc new file mode 100644 index 0000000..5a26ef91 --- /dev/null +++ b/chrome/browser/printing/print_job_worker.cc @@ -0,0 +1,325 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/print_job_worker.h" + +#include "base/message_loop.h" +#include "chrome/browser/printing/print_job.h" +#include "chrome/browser/printing/printed_document.h" +#include "chrome/browser/printing/printed_page.h" +#include "chrome/common/gfx/emf.h" + +namespace printing { + +class PrintJobWorker::NotificationTask : public Task { + public: + NotificationTask() : print_job_(NULL), details_(NULL) { + } + ~NotificationTask() { + } + + // Initializes the object. This object can't be initialized in the constructor + // since it is not created directly. + void Init(PrintJobWorkerOwner* print_job, + JobEventDetails::Type detail_type, + PrintedDocument* document, + PrintedPage* page) { + DCHECK(!print_job_); + DCHECK(!details_); + print_job_ = print_job; + details_ = new JobEventDetails(detail_type, document, page); + } + + virtual void Run() { + // Send the notification in the right thread. + NotificationService::current()->Notify( + NOTIFY_PRINT_JOB_EVENT, + // We know that is is a PrintJob object in this circumstance. + Source<PrintJob>(static_cast<PrintJob*>(print_job_.get())), + Details<JobEventDetails>(details_)); + } + + // The job which originates this notification. + scoped_refptr<PrintJobWorkerOwner> print_job_; + scoped_refptr<JobEventDetails> details_; + NotificationType type_; +}; + + +PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner) + : Thread("Printing_Worker"), + owner_(owner) { + // The object is created in the UI thread. + DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); +} + +PrintJobWorker::~PrintJobWorker() { + // The object is deleted in the UI thread. + DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); +} + +void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) { + DCHECK(page_number_ == PageNumber::npos()); + owner_ = new_owner; +} + +void PrintJobWorker::GetSettings(bool ask_user_for_settings, + HWND parent_window, + int document_page_count) { + DCHECK_EQ(message_loop(), MessageLoop::current()); + DCHECK_EQ(page_number_, PageNumber::npos()); + + // Recursive task processing is needed for the dialog in case it needs to be + // destroyed by a task. + MessageLoop::current()->SetNestableTasksAllowed(true); + + PrintingContext::Result result; + if (ask_user_for_settings) { + result = printing_context_.AskUserForSettings(parent_window, + document_page_count); + } else { + result = printing_context_.UseDefaultSettings(); + } + + // Most PrintingContext functions may start a message loop and process + // message recursively, so disable recursive task processing. + MessageLoop::current()->SetNestableTasksAllowed(false); + + // We can't use OnFailure() here since owner_ may not support notifications. + + // PrintJob will create the new PrintedDocument. + owner_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + owner_, + &PrintJobWorkerOwner::GetSettingsDone, + printing_context_.settings(), + result)); +} + +void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { + DCHECK_EQ(message_loop(), MessageLoop::current()); + DCHECK_EQ(page_number_, PageNumber::npos()); + DCHECK_EQ(document_, new_document); + DCHECK(document_.get()); + DCHECK(new_document->settings().Equals(printing_context_.settings())); + DCHECK(printing_context_.context()); + if (!document_.get() || page_number_ != PageNumber::npos() || + document_ != new_document) { + return; + } + + PrintingContext::Result result = + printing_context_.NewDocument(document_->name()); + if (result != PrintingContext::OK) { + OnFailure(); + return; + } + + // Try to print already cached data. It may already have been generated for + // the print preview. + OnNewPage(); + // Don't touch this anymore since the instance could be destroyed. It happens + // if all the pages are printed a one sweep and the client doesn't have a + // handle to us anymore. There's a timing issue involved between the worker + // thread and the UI thread. Take no chance. +} + +void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) { + DCHECK_EQ(message_loop(), MessageLoop::current()); + DCHECK_EQ(page_number_, PageNumber::npos()); + DCHECK(!new_document || + new_document->settings().Equals(printing_context_.settings())); + DCHECK(printing_context_.context()); + if (page_number_ != PageNumber::npos()) + return; + + document_ = new_document; +} + +void PrintJobWorker::OnNewPage() { + DCHECK_EQ(message_loop(), MessageLoop::current()); + if (!document_.get()) { + // Spurious message. + return; + } + DCHECK(printing_context_.context()); + if (!printing_context_.context()) + return; + + if (page_number_ == PageNumber::npos()) { + // Find first page to print. + int page_count = document_->page_count(); + if (!page_count) { + // We still don't know how many pages the document contains. We can't + // start to print the document yet since the header/footer may refer to + // the document's page count. + return; + } + // We have enough information to initialize page_number_. + page_number_.Init(document_->settings(), page_count); + } + DCHECK_NE(page_number_, PageNumber::npos()); + + for (;;) { + // Is the page available? + scoped_refptr<PrintedPage> page; + if (!document_->GetPage(page_number_.ToInt(), &page)) { + // The page is implictly requested. + break; + } + // The page is there, print it. + SpoolPage(*page); + ++page_number_; + if (page_number_ == PageNumber::npos()) { + OnDocumentDone(); + // Don't touch this anymore since the instance could be destroyed. + break; + } + } +} + +void PrintJobWorker::Cancel() { + // This is the only function that can be called from any thread. + printing_context_.Cancel(); + // Cannot touch any member variable since we don't know in which thread + // context we run. +} + +void PrintJobWorker::DismissDialog() { + printing_context_.DismissDialog(); +} + +void PrintJobWorker::RequestMissingPages() { + DCHECK_EQ(message_loop(), MessageLoop::current()); + // It may arrive out of order. Don't mind about it. + if (page_number_ != PageNumber::npos()) { + // We are printing. + document_->RequestMissingPages(); + } + NotificationTask* task = new NotificationTask(); + task->Init(owner_, + JobEventDetails::ALL_PAGES_REQUESTED, + document_.get(), + NULL); + owner_->message_loop()->PostTask(FROM_HERE, task); +} + +void PrintJobWorker::OnDocumentDone() { + DCHECK_EQ(message_loop(), MessageLoop::current()); + DCHECK_EQ(page_number_, PageNumber::npos()); + DCHECK(document_.get()); + DCHECK(printing_context_.context()); + + if (printing_context_.DocumentDone() != PrintingContext::OK) { + OnFailure(); + return; + } + + // Tell everyone! + NotificationTask* task = new NotificationTask(); + task->Init(owner_, + JobEventDetails::DOC_DONE, + document_.get(), + NULL); + owner_->message_loop()->PostTask(FROM_HERE, task); + + // Makes sure the variables are reinitialized. + document_ = NULL; +} + +void PrintJobWorker::SpoolPage(PrintedPage& page) { + DCHECK_EQ(message_loop(), MessageLoop::current()); + DCHECK_NE(page_number_, PageNumber::npos()); + DCHECK(printing_context_.context()); + // Signal everyone that the page is about to be printed. + NotificationTask* task = new NotificationTask(); + task->Init(owner_, + JobEventDetails::NEW_PAGE, + document_.get(), + &page); + owner_->message_loop()->PostTask(FROM_HERE, task); + + // Preprocess. + if (printing_context_.NewPage() != PrintingContext::OK) { + OnFailure(); + return; + } + + // Actual printing. + document_->RenderPrintedPage(page, printing_context_.context()); + + // Postprocess. + if (printing_context_.PageDone() != PrintingContext::OK) { + OnFailure(); + return; + } + + // Signal everyone that the page is printed. + task = new NotificationTask(); + task->Init(owner_, + JobEventDetails::PAGE_DONE, + document_.get(), + &page); + owner_->message_loop()->PostTask(FROM_HERE, task); +} + +void PrintJobWorker::OnFailure() { + DCHECK_EQ(message_loop(), MessageLoop::current()); + + // We may loose our last reference by broadcasting the FAILED event. + scoped_refptr<PrintJobWorkerOwner> handle(owner_); + + NotificationTask* task = new NotificationTask(); + task->Init(owner_, + JobEventDetails::FAILED, + document_.get(), + NULL); + owner_->message_loop()->PostTask(FROM_HERE, task); + Cancel(); + + // Makes sure the variables are reinitialized. + document_ = NULL; + page_number_ = PageNumber::npos(); +} + +} // namespace printing + +RunnableMethodTraits<printing::PrintJobWorker>::RunnableMethodTraits() { +} + +void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee( + printing::PrintJobWorker* obj) { + DCHECK(!owner_.get()); + owner_ = obj->owner_; +} + +void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee( + printing::PrintJobWorker* obj) { + DCHECK_EQ(owner_, obj->owner_); + owner_ = NULL; +} diff --git a/chrome/browser/printing/print_job_worker.h b/chrome/browser/printing/print_job_worker.h new file mode 100644 index 0000000..c7064f7 --- /dev/null +++ b/chrome/browser/printing/print_job_worker.h @@ -0,0 +1,133 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_JOB_WORKER_H__ +#define CHROME_BROWSER_PRINTING_PRINT_JOB_WORKER_H__ + +#include "base/task.h" +#include "base/thread.h" +#include "chrome/browser/printing/page_number.h" +#include "chrome/browser/printing/win_printing_context.h" +#include "googleurl/src/gurl.h" + +namespace printing { + +class PrintedDocument; +class PrintedPage; +class PrintJob; +class PrintJobWorkerOwner; + +// Worker thread code. All this code, except for the constructor, is executed in +// the worker thread. It manages the PrintingContext, which can be blocking +// and/or run a message loop. This is the object that generates most +// NOTIFY_PRINT_JOB_EVENT notifications, but they are generated through a +// NotificationTask task to be executed from the right thread, the UI thread. +// PrintJob always outlives its worker instance. +class PrintJobWorker : public Thread { + public: + PrintJobWorker(PrintJobWorkerOwner* owner); + ~PrintJobWorker(); + + void SetNewOwner(PrintJobWorkerOwner* new_owner); + + // Initializes the print settings. If |ask_user_for_settings| is true, a + // Print... dialog box will be shown to ask the user his preference. + void GetSettings(bool ask_user_for_settings, + HWND parent_window, + int document_page_count); + + // Starts the printing loop. Every pages are printed as soon as the data is + // available. Makes sure the new_document is the right one. + void StartPrinting(PrintedDocument* new_document); + + // Updates the printed document. + void OnDocumentChanged(PrintedDocument* new_document); + + // Unqueues waiting pages. Called when PrintJob receives a + // NOTIFY_PRINTED_DOCUMENT_UPDATED notification. It's time to look again if + // the next page can be printed. + void OnNewPage(); + + // This is the only function that can be called in a thread. + void Cancel(); + + // Cancels the Print... dialog box if shown, noop otherwise. + void DismissDialog(); + + // Requests the missing pages in rendered_document_. Sends back a + // ALL_PAGES_REQUESTED notification once done. + void RequestMissingPages(); + + private: + // The shared NotificationService service can only be accessed from the UI + // thread, so this class encloses the necessary information to send the + // notification from the right thread. Most NOTIFY_PRINT_JOB_EVENT + // notifications are sent this way, except USER_INIT_DONE, USER_INIT_CANCELED + // and DEFAULT_INIT_DONE. These three are sent through PrintJob::InitDone(). + class NotificationTask; + friend struct RunnableMethodTraits<PrintJobWorker>; + + // Renders a page in the printer. + void SpoolPage(PrintedPage& page); + + // Closes the job since spooling is done. + void OnDocumentDone(); + + // Discards the current document, the current page and cancels the printing + // context. + void OnFailure(); + + // Information about the printer setting. + PrintingContext printing_context_; + + // The printed document. Only has read-only access. + scoped_refptr<PrintedDocument> document_; + + // The print job owning this worker thread. It is guaranteed to outlive this + // object. + PrintJobWorkerOwner* owner_; + + // Current page number to print. + PageNumber page_number_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintJobWorker); +}; + +} // namespace printing + +template <> +struct RunnableMethodTraits<printing::PrintJobWorker> { + RunnableMethodTraits(); + void RetainCallee(printing::PrintJobWorker* obj); + void ReleaseCallee(printing::PrintJobWorker* obj); + private: + scoped_refptr<printing::PrintJobWorkerOwner> owner_; +}; + +#endif // CHROME_BROWSER_PRINTING_PRINT_JOB_WORKER_H__ diff --git a/chrome/browser/printing/print_job_worker_owner.h b/chrome/browser/printing/print_job_worker_owner.h new file mode 100644 index 0000000..c74b819 --- /dev/null +++ b/chrome/browser/printing/print_job_worker_owner.h @@ -0,0 +1,70 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_JOB_WORKER_OWNER_H__ +#define CHROME_BROWSER_PRINTING_PRINT_JOB_WORKER_OWNER_H__ + +#include "chrome/browser/printing/win_printing_context.h" + +class MessageLoop; + +namespace printing { + +class PrintJobWorker; +class PrintSettings; + +class PrintJobWorkerOwner { + public: + virtual ~PrintJobWorkerOwner() { + } + virtual void AddRef() = 0; + virtual void Release() = 0; + + // Finishes the initialization began by PrintJobWorker::Init(). Creates a + // new PrintedDocument if necessary. Solely meant to be called by + // PrintJobWorker. + virtual void GetSettingsDone(const PrintSettings& new_settings, + PrintingContext::Result result) = 0; + + // Detach the PrintJobWorker associated to this object. + virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) = 0; + + // Retrieves the message loop that is expected to process GetSettingsDone. + virtual MessageLoop* message_loop() = 0; + + // Access the current settings. + virtual const PrintSettings& settings() const = 0; + + // Cookie uniquely identifying the PrintedDocument and/or loaded settings. + virtual int cookie() const = 0; +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINT_JOB_WORKER_OWNER_H__ diff --git a/chrome/browser/printing/print_settings.cc b/chrome/browser/printing/print_settings.cc new file mode 100644 index 0000000..308e306 --- /dev/null +++ b/chrome/browser/printing/print_settings.cc @@ -0,0 +1,195 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/print_settings.h" + +#include "base/logging.h" +#include "chrome/browser/printing/units.h" +#include "chrome/common/render_messages.h" + +namespace printing { + +int PrintSettings::s_cookie_; + +PrintSettings::PrintSettings() + : min_shrink(1.25), + max_shrink(2.0), + desired_dpi(72), + dpi_(0), + landscape_(false) { +} + +void PrintSettings::Clear() { + ranges.clear(); + min_shrink = 1.25; + max_shrink = 2.; + desired_dpi = 72; + printer_name_.clear(); + device_name_.clear(); + page_setup_cmm_.Clear(); + page_setup_pixels_.Clear(); + dpi_ = 0; + landscape_ = false; +} + +#ifdef WIN32 +void PrintSettings::Init(HDC hdc, + const DEVMODE& dev_mode, + const PageRanges& new_ranges, + const std::wstring& new_device_name) { + DCHECK(hdc); + printer_name_ = dev_mode.dmDeviceName; + device_name_ = new_device_name; + ranges = new_ranges; + landscape_ = dev_mode.dmOrientation == DMORIENT_LANDSCAPE; + + int old_dpi = dpi_; + dpi_ = GetDeviceCaps(hdc, LOGPIXELSX); + // No printer device is known to advertise different dpi in X and Y axis; even + // the fax device using the 200x100 dpi setting. It's ought to break so many + // applications that it's not even needed to care about. WebKit doesn't + // support different dpi settings in X and Y axis. + DCHECK_EQ(dpi_, GetDeviceCaps(hdc, LOGPIXELSY)); + + DCHECK_EQ(GetDeviceCaps(hdc, SCALINGFACTORX), 0); + DCHECK_EQ(GetDeviceCaps(hdc, SCALINGFACTORY), 0); + + // Initialize page_setup_pixels_. + gfx::Size physical_size_pixels(GetDeviceCaps(hdc, PHYSICALWIDTH), + GetDeviceCaps(hdc, PHYSICALHEIGHT)); + gfx::Rect printable_area_pixels(GetDeviceCaps(hdc, PHYSICALOFFSETX), + GetDeviceCaps(hdc, PHYSICALOFFSETY), + GetDeviceCaps(hdc, HORZRES), + GetDeviceCaps(hdc, VERTRES)); + // Hard-code text_height = 0.5cm = ~1/5 of inch + page_setup_pixels_.Init(physical_size_pixels, printable_area_pixels, + ConvertUnit(500, kHundrethsMMPerInch, dpi_)); + + // Initialize page_setup_cmm_. + // In theory, we should be using HORZSIZE and VERTSIZE but their value is + // so wrong it's useless. So read the values in dpi unit and convert them back + // in 0.01 mm. + gfx::Size physical_size_cmm( + ConvertUnit(physical_size_pixels.width(), dpi_, kHundrethsMMPerInch), + ConvertUnit(physical_size_pixels.height(), dpi_, kHundrethsMMPerInch)); + gfx::Rect printable_area_cmm( + ConvertUnit(printable_area_pixels.x(), dpi_, kHundrethsMMPerInch), + ConvertUnit(printable_area_pixels.y(), dpi_, kHundrethsMMPerInch), + ConvertUnit(printable_area_pixels.width(), dpi_, kHundrethsMMPerInch), + ConvertUnit(printable_area_pixels.bottom(), dpi_, kHundrethsMMPerInch)); + + static const int kRoundingTolerance = 5; + // Some printers may advertise a slightly larger printable area than the + // physical area. This is mostly due to integer calculation and rounding. + if (physical_size_cmm.height() > printable_area_cmm.bottom() && + physical_size_cmm.height() <= (printable_area_cmm.bottom() + + kRoundingTolerance)) { + physical_size_cmm.set_height(printable_area_cmm.bottom()); + } + if (physical_size_cmm.width() > printable_area_cmm.right() && + physical_size_cmm.width() <= (printable_area_cmm.right() + + kRoundingTolerance)) { + physical_size_cmm.set_width(printable_area_cmm.right()); + } + page_setup_cmm_.Init(physical_size_cmm, printable_area_cmm, 500); +} +#endif + +void PrintSettings::UpdateMarginsMetric(const PageMargins& new_margins) { + // Apply the new margins in 0.01 mm unit. + page_setup_cmm_.SetRequestedMargins(new_margins); + + // Converts the margins in dpi unit and apply those too. + PageMargins pixels_margins; + pixels_margins.header = ConvertUnit(new_margins.header, kHundrethsMMPerInch, + dpi_); + pixels_margins.footer = ConvertUnit(new_margins.footer, kHundrethsMMPerInch, + dpi_); + pixels_margins.left = ConvertUnit(new_margins.left, kHundrethsMMPerInch, + dpi_); + pixels_margins.top = ConvertUnit(new_margins.top, kHundrethsMMPerInch, dpi_); + pixels_margins.right = ConvertUnit(new_margins.right, kHundrethsMMPerInch, + dpi_); + pixels_margins.bottom = ConvertUnit(new_margins.bottom, kHundrethsMMPerInch, + dpi_); + page_setup_pixels_.SetRequestedMargins(pixels_margins); +} + +void PrintSettings::UpdateMarginsMilliInch(const PageMargins& new_margins) { + // Convert margins from thousandth inches to cmm (0.01mm). + PageMargins cmm_margins; + cmm_margins.header = + ConvertMilliInchToHundredThousanthMeter(new_margins.header); + cmm_margins.footer = + ConvertMilliInchToHundredThousanthMeter(new_margins.footer); + cmm_margins.left = ConvertMilliInchToHundredThousanthMeter(new_margins.left); + cmm_margins.top = ConvertMilliInchToHundredThousanthMeter(new_margins.top); + cmm_margins.right = + ConvertMilliInchToHundredThousanthMeter(new_margins.right); + cmm_margins.bottom = + ConvertMilliInchToHundredThousanthMeter(new_margins.bottom); + UpdateMarginsMetric(cmm_margins); +} + +void PrintSettings::RenderParams(ViewMsg_Print_Params* params) const { + DCHECK(params); + params->printable_size.SetSize(page_setup_pixels_.content_area().width(), + page_setup_pixels_.content_area().height()); + params->dpi = dpi_; + // Currently hardcoded at 1.25. See PrintSettings' constructor. + params->min_shrink = min_shrink; + // Currently hardcoded at 2.0. See PrintSettings' constructor. + params->max_shrink = max_shrink; + // Currently hardcoded at 72dpi. See PrintSettings' constructor. + params->desired_dpi = desired_dpi; + // Always use an invalid cookie. + params->document_cookie = 0; +} + +bool PrintSettings::Equals(const PrintSettings& rhs) const { + // Do not test the display device name (printer_name_) for equality since it + // may sometimes be chopped off at 30 chars. As long as device_name is the + // same, that's fine. + return ranges == rhs.ranges && + min_shrink == rhs.min_shrink && + max_shrink == rhs.max_shrink && + desired_dpi == rhs.desired_dpi && + overlays.Equals(rhs.overlays) && + device_name_ == rhs.device_name_ && + page_setup_pixels_.Equals(rhs.page_setup_pixels_) && + page_setup_cmm_.Equals(rhs.page_setup_cmm_) && + dpi_ == rhs.dpi_ && + landscape_ == rhs.landscape_; +} + +int PrintSettings::NewCookie() { + return base::AtomicIncrement(&s_cookie_); +} + +} // namespace printing diff --git a/chrome/browser/printing/print_settings.h b/chrome/browser/printing/print_settings.h new file mode 100644 index 0000000..4e9416a --- /dev/null +++ b/chrome/browser/printing/print_settings.h @@ -0,0 +1,142 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_SETTINGS_H__ +#define CHROME_BROWSER_PRINTING_PRINT_SETTINGS_H__ + +#include "base/gfx/rect.h" +#include "chrome/browser/printing/page_overlays.h" +#include "chrome/browser/printing/page_range.h" +#include "chrome/browser/printing/page_setup.h" + +struct ViewMsg_Print_Params; +typedef struct HDC__* HDC; +typedef struct _devicemodeW DEVMODE; + +namespace printing { + +// OS-independent print settings. +class PrintSettings { + public: + PrintSettings(); + + // Reinitialize the settings to the default values. + void Clear(); + +#ifdef WIN32 + // Reads the settings from the selected device context. Calculates derived + // values like printable_area_. + void Init(HDC hdc, + const DEVMODE& dev_mode, + const PageRanges& new_ranges, + const std::wstring& new_device_name); +#endif + + // Sets margins in 0.01 millimeter unit. + void UpdateMarginsMetric(const PageMargins& new_margins); + + // Sets margins in thousandth of inch. + void UpdateMarginsMilliInch(const PageMargins& new_margins); + + // Initializes the print parameters that needs to be sent to the renderer + // process. + void RenderParams(ViewMsg_Print_Params* params) const; + + // Equality operator. + // NOTE: printer_name is NOT tested for equality since it doesn't affect the + // output. + bool Equals(const PrintSettings& rhs) const; + + const std::wstring& printer_name() const { return printer_name_; } + void set_device_name(const std::wstring& device_name) { + device_name_ = device_name; + } + const std::wstring& device_name() const { return device_name_; } + int dpi() const { return dpi_; } + const PageSetup& page_setup_cmm() const { return page_setup_cmm_; } + const PageSetup& page_setup_pixels() const { return page_setup_pixels_; } + + // Multipage printing. Each PageRange describes a from-to page combinaison. + // This permits printing some selected pages only. + PageRanges ranges; + + // By imaging to a width a little wider than the available pixels, thin pages + // will be scaled down a little, matching the way they print in IE and Camino. + // This lets them use fewer sheets than they would otherwise, which is + // presumably why other browsers do this. Wide pages will be scaled down more + // than this. + double min_shrink; + + // This number determines how small we are willing to reduce the page content + // in order to accommodate the widest line. If the page would have to be + // reduced smaller to make the widest line fit, we just clip instead (this + // behavior matches MacIE and Mozilla, at least) + double max_shrink; + + // Desired visible dots per inch rendering for output. Printing should be + // scaled to ScreenDpi/dpix*desired_dpi. + int desired_dpi; + + // The various overlays (headers and footers). + PageOverlays overlays; + + // Generates a new cookie to uniquely identify a PrintedDocument. + static int NewCookie(); + + private: + ////////////////////////////////////////////////////////////////////////////// + // Settings that can't be changed without side-effects. + + // Printer name as shown to the user. + std::wstring printer_name_; + + // Printer device name as opened by the OS. + std::wstring device_name_; + + // Page setup in centimillimeter (0.01 mm) units. + PageSetup page_setup_cmm_; + + // Page setup in pixel units, dpi adjusted. + PageSetup page_setup_pixels_; + + // Printer's device effective dots per inch in both axis. + int dpi_; + + // Is the orientation landscape or portrait. + bool landscape_; + + // Cookie generator. It is used to initialize PrintedDocument with its + // associated PrintSettings, to be sure that each generated PrintedPage is + // correctly associated with its corresponding PrintedDocument. + static int s_cookie_; +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINT_SETTINGS_H__ diff --git a/chrome/browser/printing/print_view_manager.cc b/chrome/browser/printing/print_view_manager.cc new file mode 100644 index 0000000..9c299b1 --- /dev/null +++ b/chrome/browser/printing/print_view_manager.cc @@ -0,0 +1,578 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/print_view_manager.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/printing/print_job.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/printed_document.h" +#include "chrome/browser/printing/printer_query.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/web_contents.h" +#include "chrome/common/gfx/emf.h" +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +namespace printing { + +PrintViewManager::PrintViewManager(WebContents& owner) + : owner_(owner), + waiting_to_print_(false), + inside_inner_message_loop_(false), + waiting_to_show_print_dialog_(false) { + memset(&print_params_, 0, sizeof(print_params_)); +} + +void PrintViewManager::Destroy() { + DisconnectFromCurrentPrintJob(); +} + +void PrintViewManager::Stop() { + // Cancel the current job, wait for the worker to finish. + TerminatePrintJob(true); +} + +void PrintViewManager::ShowPrintDialog() { + if (!CreateNewPrintJob(NULL)) + return; + + // Retrieve default settings. PrintJob will send back a + // NOTIFY_PRINT_JOB_EVENT with either INIT_DONE, INIT_CANCELED or FAILED. + // On failure, simply back off. Otherwise, request the number of pages to + // the renderer. Wait for its response (DidGetPrintedPagesCount), which will + // give the value used to initialize the Print... dialog. PrintJob will send + // back (again) a NOTIFY_PRINT_JOB_EVENT with either INIT_DONE, INIT_CANCELED + // or FAILED. The result is to call PrintNowInternal or to back off. + waiting_to_show_print_dialog_ = true; + print_job_->GetSettings(PrintJob::DEFAULTS, NULL); +} + +bool PrintViewManager::PrintNow() { + if (!CreateNewPrintJob(NULL)) + return false; + + // Retrieve default settings. PrintJob will send back a NOTIFY_PRINT_JOB_EVENT + // with either DEFAULT_INIT_DONE or FAILED. On failure, simply back off. + // Otherwise, call PrintNowInternal() again to start the print job. + waiting_to_print_ = true; + print_job_->GetSettings(PrintJob::DEFAULTS, NULL); + return true; +} + +bool PrintViewManager::OnRendererGone(RenderViewHost* render_view_host) { + if (!print_job_.get()) + return true; + + if (render_view_host != owner_.render_view_host()) + return false; + + scoped_refptr<PrintedDocument> document(print_job_->document()); + if (document) { + // If IsComplete() returns false, the document isn't completely renderered. + // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, + // the print job may finish without problem. + TerminatePrintJob(!document->IsComplete()); + } + return true; +} + +void PrintViewManager::DidGetPrintedPagesCount(int cookie, int number_pages) { + DCHECK_GT(cookie, 0); + if (!OpportunisticallyCreatePrintJob(cookie)) + return; + + PrintedDocument* document = print_job_->document(); + if (!document || cookie != document->cookie()) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return; + } + + // Time to inform our print job. Make sure it is for the right document. + if (!document->page_count()) { + document->set_page_count(number_pages); + if (waiting_to_show_print_dialog_) { + // Ask for user settings. There's a timing issue since we may not have + // received the INIT_DONE notification yet. If so, the dialog will be + // shown in Observe() since the page count arrived before the settings. + print_job_->GetSettings(PrintJob::ASK_USER, + ::GetParent(owner_.GetContainerHWND())); + waiting_to_show_print_dialog_ = false; + } + } +} + +void PrintViewManager::DidPrintPage( + const ViewHostMsg_DidPrintPage_Params& params) { + DCHECK(!inside_inner_message_loop_); + if (!OpportunisticallyCreatePrintJob(params.document_cookie)) + return; + + PrintedDocument* document = print_job_->document(); + if (!document || params.document_cookie != document->cookie()) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return; + } + + // http://msdn2.microsoft.com/en-us/library/ms535522.aspx + // Windows 2000/XP: When a page in a spooled file exceeds approximately 350 + // MB, it can fail to print and not send an error message. + if (params.data_size && params.data_size >= 350*1024*1024) { + NOTREACHED() << "size:" << params.data_size; + TerminatePrintJob(true); + owner_.Stop(); + return; + } + + SharedMemory shared_buf(params.emf_data_handle, true); + if (!shared_buf.Map(params.data_size)) { + NOTREACHED() << "couldn't map"; + owner_.Stop(); + return; + } + + gfx::Emf* emf = new gfx::Emf; + if (!emf->CreateFromData(shared_buf.memory(), params.data_size)) { + NOTREACHED() << "Invalid EMF header"; + delete emf; + owner_.Stop(); + return; + } + + // Update the rendered document. It will send notifications to the listener. + document->SetPage(params.page_number, emf, params.actual_shrink); + ShouldQuitFromInnerMessageLoop(); +} + +void PrintViewManager::RenderOnePrintedPage(PrintedDocument* document, + int page_number) { + // Currently a no-op. Rationale: printing is now completely synchronous and is + // handled by PrintAllPages. The reason is that PrintPreview is not used + // anymore and to make sure to not corrupt the screen, the whole generation is + // done synchronously. To make this work completely asynchronously, a + // duplicate copy of RenderView must be made to have an "innert" web page. + // Once this object is created, we'll have all the leasure to do whatever we + // want. +} + +std::wstring PrintViewManager::RenderSourceName() { + std::wstring name(owner_.GetTitle()); + if (name.empty()) + name = l10n_util::GetString(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); + return name; +} + +GURL PrintViewManager::RenderSourceUrl() { + NavigationEntry* entry = owner_.controller()->GetActiveEntry(); + if (entry) + return entry->GetDisplayURL(); + else + return GURL(); +} + +void PrintViewManager::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFY_PRINT_JOB_EVENT: { + OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr()); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintViewManager::OnNotifyPrintJobEvent( + const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::FAILED: { + // TODO(maruel): bug 1123882 Show some kind of notification. + TerminatePrintJob(true); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: { + OnNotifyPrintJobInitEvent(event_details); + break; + } + case JobEventDetails::ALL_PAGES_REQUESTED: { + ShouldQuitFromInnerMessageLoop(); + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: { + // Don't care about the actual printing process. + break; + } + case JobEventDetails::DOC_DONE: { + waiting_to_print_ = false; + break; + } + case JobEventDetails::JOB_DONE: { + // Printing is done, we don't need it anymore. + // print_job_->is_job_pending() may still be true, depending on the order + // of object registration. + ReleasePrintJob(); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintViewManager::OnNotifyPrintJobInitEvent( + const JobEventDetails& event_details) { + ViewMsg_Print_Params old_print_params(print_params_); + + // Backup the print settings relevant to the renderer. + DCHECK_EQ(print_job_->document(), event_details.document()); + event_details.document()->settings().RenderParams(&print_params_); + print_params_.document_cookie = event_details.document()->cookie(); + DCHECK_GT(print_params_.document_cookie, 0); + + // If settings changed + DCHECK(owner_.render_view_host()); + // Equals() doesn't compare the cookie value. + if (owner_.render_view_host() && + owner_.render_view_host()->IsRenderViewLive() && + (!old_print_params.Equals(print_params_) || + !event_details.document()->page_count())) { + // This will generate a DidGetPrintedPagesCount() callback. + if (!owner_.render_view_host()->GetPrintedPagesCount(print_params_)) { + NOTREACHED(); + if (inside_inner_message_loop_) { + MessageLoop::current()->Quit(); + return; + } + } + } + + // Continue even if owner_.render_view_host() is dead because we may already + // have buffered all the necessary pages. + switch (event_details.type()) { + case JobEventDetails::USER_INIT_DONE: { + // The user clicked the "Print" button in the Print... dialog. + // Time to print. + DCHECK_EQ(waiting_to_print_, false); + DCHECK_EQ(waiting_to_show_print_dialog_, false); + waiting_to_print_ = true; + PrintNowInternal(); + break; + } + case JobEventDetails::USER_INIT_CANCELED: { + DCHECK(!waiting_to_show_print_dialog_); + // The print dialog box has been dismissed (Cancel button or the X). + TerminatePrintJob(false); + break; + } + case JobEventDetails::DEFAULT_INIT_DONE: { + // Init(false) returned. + if (waiting_to_print_) { + // PrintNow() is pending. + DCHECK_EQ(waiting_to_show_print_dialog_, false); + PrintNowInternal(); + } else if (waiting_to_show_print_dialog_ && + event_details.document()->page_count()) { + // Time to ask the user for the print settings. + print_job_->GetSettings(PrintJob::ASK_USER, + ::GetParent(owner_.GetContainerHWND())); + waiting_to_show_print_dialog_ = false; + } else { + // event_details.document()->page_count() is false. This simply means + // that the renderer was slower to calculate the number of pages than + // the print_job_ to initialize the default settings. If so, the dialog + // will be shown in DidGetPrintedPagesCount() since the settings arrived + // before the page count. + DCHECK_EQ(waiting_to_show_print_dialog_, true); + } + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +bool PrintViewManager::RenderAllMissingPagesNow() { + if (waiting_to_show_print_dialog_) { + // TODO(maruel): http://b/1186708 This happens in one of these case: + // - javascript:window.print();window.close(); which closes the window very + // fast. + // - The worker thread is hung, like the network printer failed, the network + // print server failed or the network cable is disconnected. + // In the first case we want to wait, but not on the second case. + + if (!RunInnerMessageLoop()) { + // This function is always called from DisconnectFromCurrentPrintJob() + // so we know that the job will be stopped/canceled in any case. + return false; + } + } + + if (!print_job_.get() || !print_job_->is_job_pending()) { + DCHECK_EQ(waiting_to_print_, false); + return false; + } + + // We can't print if there is no renderer. + if (!owner_.render_view_host() || + !owner_.render_view_host()->IsRenderViewLive()) { + waiting_to_print_ = false; + return false; + } + + // Is the document already complete? + if (print_job_->document() && print_job_->document()->IsComplete()) { + waiting_to_print_ = false; + return true; + } + + // WebContents is either dying or a second consecutive request to print + // happened before the first had time to finish. We need to render all the + // pages in an hurry if a print_job_ is still pending. No need to wait for it + // to actually spool the pages, only to have the renderer generate them. Run + // a message loop until we get our signal that the print job is satisfied. + // PrintJob will send a ALL_PAGES_REQUESTED after having received all the + // pages it needs. MessageLoop::current()->Quit() will be called as soon as + // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED + // or in DidPrintPage(). The check is done in + // ShouldQuitFromInnerMessageLoop(). + // BLOCKS until all the pages are received. (Need to enable recursive task) + if (!RunInnerMessageLoop()) { + // This function is always called from DisconnectFromCurrentPrintJob() so we + // know that the job will be stopped/canceled in any case. + return false; + } + return true; +} + +void PrintViewManager::ShouldQuitFromInnerMessageLoop() { + // Look at the reason. + DCHECK(print_job_->document()); + if (print_job_->document() && + print_job_->document()->IsComplete() && + inside_inner_message_loop_) { + // We are in a message loop created by RenderAllMissingPagesNow. Quit from + // it. + MessageLoop::current()->Quit(); + inside_inner_message_loop_ = false; + waiting_to_print_ = false; + } +} + +bool PrintViewManager::CreateNewPrintJob(PrintJobWorkerOwner* job) { + DCHECK(!inside_inner_message_loop_); + if (waiting_to_print_ || waiting_to_show_print_dialog_) { + // We can't help; we are waiting for a print job initialization. The user is + // button bashing. The only thing we could do is to batch up the requests. + return false; + } + + // Disconnect the current print_job_. + DisconnectFromCurrentPrintJob(); + + // We can't print if there is no renderer. + if (!owner_.render_view_host() || + !owner_.render_view_host()->IsRenderViewLive()) { + return false; + } + + // Ask the renderer to generate the print preview, create the print preview + // view and switch to it, initialize the printer and show the print dialog. + DCHECK(!print_job_.get()); + if (job) { + print_job_ = new PrintJob(); + print_job_->Initialize(job, this); + } else { + print_job_ = new PrintJob(this); + } + NotificationService::current()-> + AddObserver(this, + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(print_job_.get())); + return true; +} + +void PrintViewManager::DisconnectFromCurrentPrintJob() { + // Make sure all the necessary rendered page are done. Don't bother with the + // return value. + bool result = RenderAllMissingPagesNow(); + + // Verify that assertion. + if (print_job_.get() && + print_job_->document() && + !print_job_->document()->IsComplete()) { + DCHECK(!result); + // That failed. + TerminatePrintJob(true); + } else { + // DO NOT wait for the job to finish. + ReleasePrintJob(); + } +} + +void PrintViewManager::TerminatePrintJob(bool cancel) { + if (!print_job_.get()) + return; + + if (cancel) { + // We don't need the EMF data anymore because the printing is canceled. + print_job_->Cancel(); + waiting_to_print_ = false; + waiting_to_show_print_dialog_ = false; + inside_inner_message_loop_ = false; + } else { + DCHECK(!inside_inner_message_loop_); + DCHECK(!waiting_to_show_print_dialog_); + DCHECK(!print_job_->document() || print_job_->document()->IsComplete() || + !waiting_to_print_); + + // WebContents is either dying or navigating elsewhere. We need to render + // all the pages in an hurry if a print job is still pending. This does the + // trick since it runs a blocking message loop: + print_job_->Stop(); + } + ReleasePrintJob(); +} + +void PrintViewManager::ReleasePrintJob() { + DCHECK_EQ(waiting_to_print_, false); + if (!print_job_.get()) + return; + NotificationService::current()->RemoveObserver( + this, + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(print_job_.get())); + + print_job_->DisconnectSource(); + // Don't close the worker thread. + print_job_ = NULL; + memset(&print_params_, 0, sizeof(print_params_)); +} + +void PrintViewManager::PrintNowInternal() { + DCHECK(waiting_to_print_); + + // Settings are already loaded. Go ahead. This will set + // print_job_->is_job_pending() to true. + print_job_->StartPrinting(); + + if (!print_job_->document() || + !print_job_->document()->IsComplete()) { + ViewMsg_PrintPages_Params params; + params.params = print_params_; + params.pages = PageRange::GetPages(print_job_->settings().ranges); + owner_.render_view_host()->PrintPages(params); + } +} + +bool PrintViewManager::RunInnerMessageLoop() { + // This value may actually be too low: + // + // - If we're looping because of printer settings initializaton, the premise + // here is that some poor users have their print server away on a VPN over + // dialup. In this situation, the simple fact of opening the printer can be + // dead slow. On the other side, we don't want to die infinitely for a real + // network error. Give the printer 60 seconds to comply. + // + // - If we're looping because of renderer page generation, the renderer could + // be cpu bound, the page overly complex/large or the system just + // memory-bound. + static const int kPrinterSettingsTimeout = 60000; + MessageLoop::QuitTask timeout_task; + Timer* timeout = MessageLoop::current()->timer_manager()->StartTimer( + kPrinterSettingsTimeout, + &timeout_task, + false); + inside_inner_message_loop_ = true; + // Need to enable recursive task. + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + // Restore task state. + MessageLoop::current()->SetNestableTasksAllowed(old_state); + + bool success = true; + if (inside_inner_message_loop_) { + // Ok we timed out. That's sad. + inside_inner_message_loop_ = false; + success = false; + } + + if (timeout) { + MessageLoop::current()->timer_manager()->StopTimer(timeout); + delete timeout; + timeout = NULL; + } + return success; +} + +bool PrintViewManager::OpportunisticallyCreatePrintJob(int cookie) { + if (print_job_.get()) + return true; + + if (!cookie) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return false; + } + + // The job was initiated by a script. Time to get the corresponding worker + // thread. + scoped_refptr<PrinterQuery> queued_query; + g_browser_process->print_job_manager()->PopPrinterQuery(cookie, + &queued_query); + DCHECK(queued_query.get()); + if (!queued_query.get()) + return false; + + if (!CreateNewPrintJob(queued_query.get())) { + // Don't kill anything. + return false; + } + + // Settings are already loaded. Go ahead. This will set + // print_job_->is_job_pending() to true. + print_job_->StartPrinting(); + return true; +} + +} // namespace printing diff --git a/chrome/browser/printing/print_view_manager.h b/chrome/browser/printing/print_view_manager.h new file mode 100644 index 0000000..b0a154e --- /dev/null +++ b/chrome/browser/printing/print_view_manager.h @@ -0,0 +1,192 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_H__ +#define CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_H__ + +#include "chrome/browser/printing/printed_pages_source.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" + +class RenderViewHost; +class WebContents; + +namespace printing { + +class JobEventDetails; +class PrintJob; +class PrintJobWorkerOwner; + +// Manages the print commands in relation to a WebContents. WebContents +// delegates a few printing related commands to this instance. +class PrintViewManager : public NotificationObserver, + public PrintedPagesSource { + public: + PrintViewManager(WebContents& owner); + + // Destroys the "Print..." dialog, makes sure the pages are finished rendering + // and release the print job. + void Destroy(); + + // Cancels the print job. + void Stop(); + + // Shows the "Print..." dialog if none is shown and if no rendering is + // pending. This is done asynchronously. + void ShowPrintDialog(); + + // Initiates a print job immediately. This is done asynchronously. Returns + // false if printing is impossible at the moment. + bool PrintNow(); + + // Terminates or cancels the print job if one was pending, depending on the + // current state. Returns false if the renderer was not valuable. + bool OnRendererGone(RenderViewHost* render_view_host); + + // Received a notification from the renderer that the number of printed page + // has just been calculated.. + void DidGetPrintedPagesCount(int cookie, int number_pages); + + // Received a notification from the renderer that a printed page page is + // finished renderering. + void DidPrintPage(const ViewHostMsg_DidPrintPage_Params& params); + + // PrintedPagesSource implementation. + virtual void RenderOnePrintedPage(PrintedDocument* document, int page_number); + virtual std::wstring RenderSourceName(); + virtual GURL RenderSourceUrl(); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + // Processes a NOTIFY_PRINT_JOB_EVENT notification. + void OnNotifyPrintJobEvent(const JobEventDetails& event_details); + + // Processes a xxx_INIT_xxx type of NOTIFY_PRINT_JOB_EVENT notification. + void OnNotifyPrintJobInitEvent(const JobEventDetails& event_details); + + // Requests the RenderView to render all the missing pages for the print job. + // Noop if no print job is pending. Returns true if at least one page has been + // requested to the renderer. + bool RenderAllMissingPagesNow(); + + // Quits the current message loop if these conditions hold true: a document is + // loaded and is complete and waiting_for_pages_to_be_rendered_ is true. This + // function is called in DidPrintPage() or on ALL_PAGES_REQUESTED + // notification. The inner message loop is created was created by + // RenderAllMissingPagesNow(). + void ShouldQuitFromInnerMessageLoop(); + + // Creates a new empty print job. It has no settings loaded. If there is + // currently a print job, safely disconnect from it. Returns false if it is + // impossible to safely disconnect from the current print job or it is + // impossible to create a new print job. + bool CreateNewPrintJob(PrintJobWorkerOwner* job); + + // Makes sure the current print_job_ has all its data before continuing, and + // disconnect from it. + void DisconnectFromCurrentPrintJob(); + + // Terminates the print job. Noop if no print job has been created. If + // |cancel| is true, cancel it instead of waiting for the job to finish. Will + // call ReleasePrintJob(). + void TerminatePrintJob(bool cancel); + + // Releases print_job_. Correctly deregisters from notifications. Noop if + // no print job has been created. + void ReleasePrintJob(); + + // Prints the document. Starts the actual print job. Requests asynchronously + // the renderered pages from the renderer. Is called once the printing context + // is initialized, on a DEFAULT_INIT_DONE notification when waiting_to_print_ + // is true. + void PrintNowInternal(); + + // Runs an inner message loop. It will set inside_inner_message_loop_ to true + // while the blocking inner message loop is running. This is useful in cases + // where the RenderView is about to be destroyed while a printing job isn't + // finished. + bool RunInnerMessageLoop(); + + // In the case of Scripted Printing, where the renderer is controlling the + // control flow, print_job_ is initialized whenever possible. No-op is + // print_job_ is initialized. + bool OpportunisticallyCreatePrintJob(int cookie); + + // Cache the last print settings requested to the renderer. + ViewMsg_Print_Params print_params_; + + // Manages the low-level talk to the printer. + scoped_refptr<PrintJob> print_job_; + + // Waiting for print_job_ initialization to be completed to start printing. + // Specifically the DEFAULT_INIT_DONE notification. Set when PrintNow() is + // called. + bool waiting_to_print_; + + // Running an inner message loop inside RenderAllMissingPagesNow(). This means + // we are _blocking_ until all the necessary pages have been rendered or the + // print settings are being loaded. + bool inside_inner_message_loop_; + + // The object is waiting for some information to call print_job_->Init(true). + // It is either a DEFAULT_INIT_DONE notification or the + // DidGetPrintedPagesCount() callback. + // Showing the Print... dialog box is a multi-step operation: + // - print_job_->Init(false) to get the default settings. Set + // waiting_to_show_print_dialog_ = true. + // - on DEFAULT_INIT_DONE, gathers new settings. + // - did settings() or document() change since the last intialization? + // - Call SwitchCssToPrintMediaType() + // - On DidGetPrintedPagesCount() call, if + // waiting_to_show_print_dialog_ = true + // - calls print_job_->Init(true). + // - waiting_to_show_print_dialog_ = false. + // - DONE. + // - else (It may happens when redisplaying the dialog box with settings that + // haven't changed) + // - if waiting_to_show_print_dialog_ = true && page_count is initialized. + // - calls print_job_->Init(true). + // - waiting_to_show_print_dialog_ = false. + bool waiting_to_show_print_dialog_; + + // PrintViewManager is created as an extension of WebContent specialized for + // printing-related behavior. Still, access to the renderer is needed so a + // back reference is kept the the "parent object". + WebContents& owner_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintViewManager); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_H__ diff --git a/chrome/browser/printing/printed_document.cc b/chrome/browser/printing/printed_document.cc new file mode 100644 index 0000000..fb4d13a --- /dev/null +++ b/chrome/browser/printing/printed_document.cc @@ -0,0 +1,401 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/printed_document.h" + +#include <set> + +#include "base/gfx/platform_device.h" +#include "base/message_loop.h" +#include "base/time.h" +#include "chrome/browser/printing/page_number.h" +#include "chrome/browser/printing/page_overlays.h" +#include "chrome/browser/printing/printed_pages_source.h" +#include "chrome/browser/printing/printed_page.h" +#include "chrome/browser/printing/units.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/gfx/emf.h" +#include "chrome/common/gfx/url_elider.h" +#include "chrome/common/time_format.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/win_util.h" + +namespace printing { + +PrintedDocument::PrintedDocument(const PrintSettings& settings, + PrintedPagesSource* source, + int cookie) + : mutable_(source), + immutable_(settings, source, cookie) { + + // Records the expected page count if a range is setup. + if (!settings.ranges.empty()) { + // If there is a range, set the number of page + for (unsigned i = 0; i < settings.ranges.size(); ++i) { + const PageRange& range = settings.ranges[i]; + mutable_.expected_page_count_ += range.to - range.from + 1; + } + } +} + +PrintedDocument::~PrintedDocument() { +} + +void PrintedDocument::SetPage(int page_number, gfx::Emf* emf, double shrink) { + // Notice the page_number + 1, the reason is that this is the value that will + // be shown. Users dislike 0-based counting. + scoped_refptr<PrintedPage> page( + new PrintedPage(page_number + 1, + emf, immutable_.settings_.page_setup_pixels().physical_size())); + { + AutoLock lock(lock_); + mutable_.pages_[page_number] = page; + if (mutable_.shrink_factor == 0) { + mutable_.shrink_factor = shrink; + } else { + DCHECK_EQ(mutable_.shrink_factor, shrink); + } + } + NotificationService::current()->Notify( + NOTIFY_PRINTED_DOCUMENT_UPDATED, + Source<PrintedDocument>(this), + Details<PrintedPage>(page)); +} + +bool PrintedDocument::GetPage(int page_number, + scoped_refptr<PrintedPage>* page) { + bool request = false; + { + AutoLock lock(lock_); + PrintedPages::const_iterator itr = mutable_.pages_.find(page_number); + if (itr != mutable_.pages_.end()) { + if (itr->second.get()) { + *page = itr->second; + return true; + } + request = false; + } else { + request = true; + // Force the creation to not repeatedly request the same page. + mutable_.pages_[page_number]; + } + } + if (request) { + PrintPage_ThreadJump(page_number); + } + return false; +} + +void PrintedDocument::RenderPrintedPage(const PrintedPage& page, + HDC context) const { +#ifdef _DEBUG + { + // Make sure the page is from our list. + AutoLock lock(lock_); + DCHECK(&page == mutable_.pages_.find(page.page_number() - 1)->second.get()); + } +#endif + + // Save the state to make sure the context this function call does not modify + // the device context. + int saved_state = SaveDC(context); + DCHECK_NE(saved_state, 0); + gfx::PlatformDevice::InitializeDC(context); + { + // Save the state (again) to apply the necessary world transformation. + int saved_state = SaveDC(context); + DCHECK_NE(saved_state, 0); + + // Setup the matrix to translate and scale to the right place. Take in + // account the actual shrinking factor. + XFORM xform = { 0 }; + xform.eDx = static_cast<float>( + immutable_.settings_.page_setup_pixels().content_area().x()); + xform.eDy = static_cast<float>( + immutable_.settings_.page_setup_pixels().content_area().y()); + xform.eM11 = static_cast<float>(1. / mutable_.shrink_factor); + xform.eM22 = static_cast<float>(1. / mutable_.shrink_factor); + BOOL res = ModifyWorldTransform(context, &xform, MWT_LEFTMULTIPLY); + DCHECK_NE(res, 0); + + if (!page.emf()->SafePlayback(context)) { + NOTREACHED(); + } + + res = RestoreDC(context, saved_state); + DCHECK_NE(res, 0); + } + + // Print the header and footer. + int base_font_size = ChromeFont().height(); + int new_font_size = ConvertUnit(10, 72, immutable_.settings_.dpi()); + DCHECK_GT(new_font_size, base_font_size); + ChromeFont font(ChromeFont().DeriveFont(new_font_size - base_font_size)); + HGDIOBJ old_font = SelectObject(context, font.hfont()); + DCHECK(old_font != NULL); + // We don't want a white square around the text ever if overflowing. + SetBkMode(context, TRANSPARENT); + PrintHeaderFooter(context, page, PageOverlays::LEFT, PageOverlays::TOP, + font); + PrintHeaderFooter(context, page, PageOverlays::CENTER, PageOverlays::TOP, + font); + PrintHeaderFooter(context, page, PageOverlays::RIGHT, PageOverlays::TOP, + font); + PrintHeaderFooter(context, page, PageOverlays::LEFT, PageOverlays::BOTTOM, + font); + PrintHeaderFooter(context, page, PageOverlays::CENTER, PageOverlays::BOTTOM, + font); + PrintHeaderFooter(context, page, PageOverlays::RIGHT, PageOverlays::BOTTOM, + font); + int res = RestoreDC(context, saved_state); + DCHECK_NE(res, 0); +} + +bool PrintedDocument::RenderPrintedPageNumber(int page_number, HDC context) { + scoped_refptr<PrintedPage> page; + if (!GetPage(page_number, &page)) + return false; + RenderPrintedPage(*page.get(), context); + return true; +} + +bool PrintedDocument::IsComplete() const { + AutoLock lock(lock_); + if (!mutable_.page_count_) + return false; + PageNumber page(immutable_.settings_, mutable_.page_count_); + if (page == PageNumber::npos()) + return false; + for (; page != PageNumber::npos(); ++page) { + PrintedPages::const_iterator itr = mutable_.pages_.find(page.ToInt()); + if (itr == mutable_.pages_.end() || !itr->second.get() || + !itr->second->emf()) + return false; + } + return true; +} + +bool PrintedDocument::RequestMissingPages() { + typedef std::set<int> PageNumbers; + PageNumbers missing_pages; + { + AutoLock lock(lock_); + PageNumber page(immutable_.settings_, mutable_.page_count_); + if (page == PageNumber::npos()) + return false; + for (; page != PageNumber::npos(); ++page) { + PrintedPage* printed_page = mutable_.pages_[page.ToInt()].get(); + if (!printed_page || !printed_page->emf()) + missing_pages.insert(page.ToInt()); + } + } + if (!missing_pages.size()) + return true; + PageNumbers::const_iterator end = missing_pages.end(); + for (PageNumbers::const_iterator itr = missing_pages.begin(); + itr != end; + ++itr) { + int page_number = *itr; + PrintPage_ThreadJump(page_number); + } + return true; +} + +void PrintedDocument::DisconnectSource() { + AutoLock lock(lock_); + mutable_.source_ = NULL; +} + +size_t PrintedDocument::MemoryUsage() const { + std::vector<scoped_refptr<PrintedPage>> pages_copy; + { + AutoLock lock(lock_); + pages_copy.reserve(mutable_.pages_.size()); + PrintedPages::const_iterator end = mutable_.pages_.end(); + for (PrintedPages::const_iterator itr = mutable_.pages_.begin(); + itr != end; ++itr) { + if (itr->second.get()) { + pages_copy.push_back(itr->second); + } + } + } + size_t total = 0; + for (size_t i = 0; i < pages_copy.size(); ++i) { + total += pages_copy[i]->emf()->GetDataSize(); + } + return total; +} + +void PrintedDocument::set_page_count(int max_page) { + { + AutoLock lock(lock_); + DCHECK_EQ(0, mutable_.page_count_); + mutable_.page_count_ = max_page; + if (immutable_.settings_.ranges.empty()) { + mutable_.expected_page_count_ = max_page; + } else { + // If there is a range, don't bother since expected_page_count_ is already + // initialized. + DCHECK_NE(mutable_.expected_page_count_, 0); + } + } + NotificationService::current()->Notify( + NOTIFY_PRINTED_DOCUMENT_UPDATED, + Source<PrintedDocument>(this), + NotificationService::NoDetails()); +} + +int PrintedDocument::page_count() const { + AutoLock lock(lock_); + return mutable_.page_count_; +} + +int PrintedDocument::expected_page_count() const { + AutoLock lock(lock_); + return mutable_.expected_page_count_; +} + +void PrintedDocument::PrintHeaderFooter(HDC context, + const PrintedPage& page, + PageOverlays::HorizontalPosition x, + PageOverlays::VerticalPosition y, + const ChromeFont& font) const { + const PrintSettings& settings = immutable_.settings_; + const std::wstring& line = settings.overlays.GetOverlay(x, y); + if (line.empty()) { + return; + } + std::wstring output(PageOverlays::ReplaceVariables(line, *this, page)); + if (output.empty()) { + // May happens if document name or url is empty. + return; + } + const gfx::Size string_size(font.GetStringWidth(output), font.height()); + gfx::Rect bounding; + bounding.set_height(string_size.height()); + const gfx::Rect& overlay_area(settings.page_setup_pixels().overlay_area()); + // Hard code .25 cm interstice between overlays. Make sure that some space is + // kept between each headers. + const int interstice = ConvertUnit(250, kHundrethsMMPerInch, settings.dpi()); + const int max_width = overlay_area.width() / 3 - interstice; + const int actual_width = std::min(string_size.width(), max_width); + switch (x) { + case PageOverlays::LEFT: + bounding.set_x(overlay_area.x()); + bounding.set_width(max_width); + break; + case PageOverlays::CENTER: + bounding.set_x(overlay_area.x() + + (overlay_area.width() - actual_width) / 2); + bounding.set_width(actual_width); + break; + case PageOverlays::RIGHT: + bounding.set_x(overlay_area.right() - actual_width); + bounding.set_width(actual_width); + break; + } + + DCHECK_LE(bounding.right(), overlay_area.right()); + + switch (y) { + case PageOverlays::BOTTOM: + bounding.set_y(overlay_area.bottom() - string_size.height()); + break; + case PageOverlays::TOP: + bounding.set_y(overlay_area.y()); + break; + } + + if (string_size.width() > bounding.width()) + output = gfx::ElideText(output, font, bounding.width()); + + // Save the state (again) for the clipping region. + int saved_state = SaveDC(context); + DCHECK_NE(saved_state, 0); + + int result = IntersectClipRect(context, bounding.x(), bounding.y(), + bounding.right() + 1, bounding.bottom() + 1); + DCHECK(result == SIMPLEREGION || result == COMPLEXREGION); + TextOut(context, + bounding.x(), bounding.y(), + output.c_str(), + static_cast<int>(output.size())); + int res = RestoreDC(context, saved_state); + DCHECK_NE(res, 0); +} + +void PrintedDocument::PrintPage_ThreadJump(int page_number) { + if (MessageLoop::current() != immutable_.source_message_loop_) { + immutable_.source_message_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &PrintedDocument::PrintPage_ThreadJump, page_number)); + } else { + PrintedPagesSource* source = NULL; + { + AutoLock lock(lock_); + source = mutable_.source_; + } + if (source) { + // Don't render with the lock held. + source->RenderOnePrintedPage(this, page_number); + } else { + // Printing has probably been canceled already. + } + } +} + +PrintedDocument::Mutable::Mutable(PrintedPagesSource* source) + : source_(source), + expected_page_count_(0), + page_count_(0), + shrink_factor(0) { +} + +PrintedDocument::Immutable::Immutable(const PrintSettings& settings, + PrintedPagesSource* source, + int cookie) + : settings_(settings), + source_message_loop_(MessageLoop::current()), + name_(source->RenderSourceName()), + url_(source->RenderSourceUrl()), + cookie_(cookie) { + // Setup the document's date. +#ifdef WIN32 + // On Windows, use the native time formatting for printing. + SYSTEMTIME systemtime; + GetLocalTime(&systemtime); + date_ = win_util::FormatSystemDate(systemtime, std::wstring()); + time_ = win_util::FormatSystemTime(systemtime, std::wstring()); +#else + Time now = Time::Now(); + date_ = TimeFormat::ShortDateNumeric(now); + time_ = TimeFormat::TimeOfDay(now); +#endif // WIN32 +} + +} // namespace printing diff --git a/chrome/browser/printing/printed_document.h b/chrome/browser/printing/printed_document.h new file mode 100644 index 0000000..43ed635 --- /dev/null +++ b/chrome/browser/printing/printed_document.h @@ -0,0 +1,220 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINTED_DOCUMENT_H__ +#define CHROME_BROWSER_PRINTING_PRINTED_DOCUMENT_H__ + +#include <map> + +#include "base/lock.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/printing/print_settings.h" +#include "googleurl/src/gurl.h" + +class ChromeFont; +class MessageLoop; + +namespace gfx { +class Emf; +} + +namespace printing { + +class PrintedPage; +class PrintedPagesSource; + +// A collection of rendered pages. The settings are immuable. If the print +// settings are changed, a new PrintedDocument must be created. +// Warning: May be accessed from many threads at the same time. Only one thread +// will have write access. Sensible functions are protected by a lock. +// Warning: Once a page is loaded, it cannot be replaced. Pages may be discarded +// under low memory conditions. +class PrintedDocument : public base::RefCountedThreadSafe<PrintedDocument> { + public: + // The cookie shall be unique and has a specific relationship with its + // originating source and settings. + PrintedDocument(const PrintSettings& settings, + PrintedPagesSource* source, + int cookie); + ~PrintedDocument(); + + // Sets a page's data. 0-based. Takes emf ownership. + // Note: locks for a short amount of time. + void SetPage(int page_number, gfx::Emf* emf, double shrink); + + // Retrieves a page. If the page is not available right now, it + // requests to have this page be rendered and returns false. + // Note: locks for a short amount of time. + bool GetPage(int page_number, scoped_refptr<PrintedPage>* page); + + // Draws the page in the context. + // Note: locks for a short amount of time in debug only. + void RenderPrintedPage(const PrintedPage& page, HDC context) const; + + // Draws the page in the context. If the page is not available right now, it + // requests to have this page be rendered and returns false. + // Note: locks for a short amount of time. + bool RenderPrintedPageNumber(int page_number, HDC context); + + // Returns true if all the necessary pages for the settings are already + // rendered. + // Note: locks while parsing the whole tree. + bool IsComplete() const; + + // Requests all the missing pages. Returns true if at least one page has been + // requested. Returns false if there was not enough information to request the + // missing pages, i.e. document_page_count_ is not initialized or no page has + // been requested. + // Note: locks while parsing the whole tree. + bool RequestMissingPages(); + + // Disconnects the PrintedPage source (PrintedPagesSource). It is done when + // the source is being destroyed. + void DisconnectSource(); + + // Retrieves the current memory usage of the renderer pages. + // Note: locks for a short amount of time. + size_t MemoryUsage() const; + + // Sets the number of pages in the document to be rendered. Can only be set + // once. + // Note: locks for a short amount of time. + void set_page_count(int max_page); + + // Number of pages in the document. Used for headers/footers. + // Note: locks for a short amount of time. + int page_count() const; + + // Returns the number of expected pages to be rendered. It is a non-linear + // series if settings().ranges is not empty. It is the same value as + // document_page_count() otherwise. + // Note: locks for a short amount of time. + int expected_page_count() const; + + // Getters. All these items are immuable hence thread-safe. + const PrintSettings& settings() const { return immutable_.settings_; } + const std::wstring& name() const { + return immutable_.name_; + } + const GURL& url() const { return immutable_.url_; } + const std::wstring& date() const { return immutable_.date_; } + const std::wstring& time() const { return immutable_.time_; } + const int cookie() const { return immutable_.cookie_; } + + private: + // Array of EMF data for each print previewed page. + typedef std::map<int, scoped_refptr<PrintedPage>> PrintedPages; + + // Contains all the mutable stuff. All this stuff MUST be accessed with the + // lock held. + struct Mutable { + Mutable(PrintedPagesSource* source); + + // Source that generates the PrintedPage's (i.e. a WebContents). It will be + // set back to NULL if the source is deleted before this object. + PrintedPagesSource* source_; + + // Contains the pages' representation. This is a collection of PrintedPage. + // Warning: Lock must be held when accessing this member. + PrintedPages pages_; + + // Number of expected pages to be rendered. + // Warning: Lock must be held when accessing this member. + int expected_page_count_; + + // The total number of pages in the document. + int page_count_; + + // Shrink done in comparison to desired_dpi. + double shrink_factor; + }; + + // Contains all the immutable stuff. All this stuff can be accessed without + // any lock held. This is because it can't be changed after the object's + // construction. + struct Immutable { + Immutable(const PrintSettings& settings, PrintedPagesSource* source, + int cookie); + + // Print settings used to generate this document. Immuable. + PrintSettings settings_; + + // Native thread for the render source. + MessageLoop* source_message_loop_; + + // Document name. Immuable. + std::wstring name_; + + // URL that generated this document. Immuable. + GURL url_; + + // The date on which this job started. Immuable. + std::wstring date_; + + // The time at which this job started. Immuable. + std::wstring time_; + + // Cookie to uniquely identify this document. It is used to make sure that a + // PrintedPage is correctly belonging to the PrintedDocument. Since + // PrintedPage generation is completely asynchronous, it could be easy to + // mess up and send the page to the wrong document. It can be viewed as a + // simpler hash of PrintSettings since a new document is made each time the + // print settings change. + int cookie_; + }; + + // Prints the headers and footers for one page in the specified context + // according to the current settings. + void PrintHeaderFooter(HDC context, + const PrintedPage& page, + PageOverlays::HorizontalPosition x, + PageOverlays::VerticalPosition y, + const ChromeFont& font) const; + + // Calls the render source to render a page. Makes sure to execute the call in + // the right thread context. + void PrintPage_ThreadJump(int page_number); + + // All writable data member access must be guarded by this lock. Needs to be + // mutable since it can be acquired from const member functions. + mutable Lock lock_; + + // All the mutable members. + Mutable mutable_; + + // All the immutable members. + const Immutable immutable_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintedDocument); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINTED_DOCUMENT_H__ diff --git a/chrome/browser/printing/printed_page.cc b/chrome/browser/printing/printed_page.cc new file mode 100644 index 0000000..588d973 --- /dev/null +++ b/chrome/browser/printing/printed_page.cc @@ -0,0 +1,51 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/printed_page.h" + +#include "chrome/common/gfx/emf.h" + +namespace printing { + +PrintedPage::PrintedPage(int page_number, + gfx::Emf* emf, + const gfx::Size& page_size) + : page_number_(page_number), + emf_(emf), + page_size_(page_size) { +} + +PrintedPage::~PrintedPage() { +} + +const gfx::Emf* PrintedPage::emf() const { + return emf_.get(); +} + +} // namespace printing diff --git a/chrome/browser/printing/printed_page.h b/chrome/browser/printing/printed_page.h new file mode 100644 index 0000000..b15a452 --- /dev/null +++ b/chrome/browser/printing/printed_page.h @@ -0,0 +1,78 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINTED_PAGE_H__ +#define CHROME_BROWSER_PRINTING_PRINTED_PAGE_H__ + +#include "base/gfx/rect.h" +#include "base/gfx/size.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" + +namespace gfx { +class Emf; +} + +namespace printing { + +// Contains the data to reproduce a printed page, either on screen or on +// paper. Once created, this object is immuable. It has no reference to the +// PrintedDocument containing this page. +// Note: May be accessed from many threads at the same time. This is an non +// issue since this object is immuable. The reason is that a page may be printed +// and be displayed at the same time. +class PrintedPage : public base::RefCountedThreadSafe<PrintedPage> { + public: + PrintedPage(int page_number, + gfx::Emf* emf, + const gfx::Size& page_size); + ~PrintedPage(); + + // Getters + int page_number() const { return page_number_; } + const gfx::Emf* emf() const; + const gfx::Size& page_size() const { return page_size_; } + + private: + // Page number inside the printed document. + const int page_number_; + + // Actual paint data. + const scoped_ptr<gfx::Emf> emf_; + + // The physical page size. To support multiple page formats inside on print + // job. + const gfx::Size page_size_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintedPage); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINTED_PAGE_H__ diff --git a/chrome/browser/printing/printed_pages_source.h b/chrome/browser/printing/printed_pages_source.h new file mode 100644 index 0000000..6012519 --- /dev/null +++ b/chrome/browser/printing/printed_pages_source.h @@ -0,0 +1,59 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINTED_PAGES_SOURCE_H__ +#define CHROME_BROWSER_PRINTING_PRINTED_PAGES_SOURCE_H__ + +#include <string> + +class GURL; +class MessageLoop; + +namespace printing { + +class PrintedDocument; + +// Source of printed pages. +class PrintedPagesSource { + public: + // Renders a printed page. It is not necessary to be synchronous. It must call + // document->SetPage() once the source is done rendering the requested page. + virtual void RenderOnePrintedPage(PrintedDocument* document, + int page_number) = 0; + + // Returns the document title. + virtual std::wstring RenderSourceName() = 0; + + // Returns the URL's source of the document if applicable. + virtual GURL RenderSourceUrl() = 0; +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINTED_PAGES_SOURCE_H__ diff --git a/chrome/browser/printing/printer_query.cc b/chrome/browser/printing/printer_query.cc new file mode 100644 index 0000000..0e440b7 --- /dev/null +++ b/chrome/browser/printing/printer_query.cc @@ -0,0 +1,141 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/printer_query.h" + +#include "base/message_loop.h" +#include "chrome/browser/printing/print_job_worker.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + +namespace printing { + +PrinterQuery::PrinterQuery() + : ui_message_loop_(MessageLoop::current()), + worker_(new PrintJobWorker(this)), + is_print_dialog_box_shown_(false), + last_status_(PrintingContext::FAILED), + cookie_(PrintSettings::NewCookie()) { +} + +PrinterQuery::~PrinterQuery() { + // The job should be finished (or at least canceled) when it is destroyed. + DCHECK(!is_print_dialog_box_shown_); + // If this fires, it is that this pending printer context has leaked. + DCHECK(!worker_.get()); + if (callback_.get()) { + // Be sure to cancel it. + callback_->Cancel(); + } + // It may get deleted in a different thread that the one that created it. + // That's fine so don't DCHECK_EQ(ui_message_loop_, MessageLoop::current()); +} + +void PrinterQuery::GetSettingsDone(const PrintSettings& new_settings, + PrintingContext::Result result) { + is_print_dialog_box_shown_ = false; + last_status_ = result; + if (result != PrintingContext::FAILED) { + settings_ = new_settings; + cookie_ = PrintSettings::NewCookie(); + } else { + // Failure. + cookie_ = 0; + } + if (callback_.get()) { + // This may cause reentrancy like to call StopWorker(). + callback_->Run(); + callback_.reset(NULL); + } +} + +PrintJobWorker* PrinterQuery::DetachWorker(PrintJobWorkerOwner* new_owner) { + DCHECK(!callback_.get()); + DCHECK(worker_.get()); + if (!worker_.get()) + return NULL; + worker_->SetNewOwner(new_owner); + return worker_.release(); +} + +void PrinterQuery::GetSettings(GetSettingsAskParam ask_user_for_settings, + HWND parent_window, + int expected_page_count, + CancelableTask* callback) { + DCHECK_EQ(ui_message_loop_, MessageLoop::current()); + DCHECK(!is_print_dialog_box_shown_); + DCHECK(!callback_.get()); + DCHECK(worker_.get()); + if (!worker_.get()) + return; + // Lazy create the worker thread. There is one worker thread per print job. + if (!worker_->message_loop()) { + if (!worker_->Start()) { + if (callback) { + callback->Cancel(); + delete callback; + } + NOTREACHED(); + return; + } + } + + callback_.reset(callback); + // Real work is done in PrintJobWorker::Init(). + is_print_dialog_box_shown_ = ask_user_for_settings == ASK_USER; + worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + worker_.get(), + &PrintJobWorker::GetSettings, + is_print_dialog_box_shown_, + parent_window, + expected_page_count)); +} + +void PrinterQuery::StopWorker() { + if (worker_.get()) { + worker_->Stop(); + worker_.reset(); + } +} + +bool PrinterQuery::is_print_dialog_box_shown() const { + return is_print_dialog_box_shown_; +} + +bool PrinterQuery::is_callback_pending() const { + return callback_.get() != NULL; +} + +bool PrinterQuery::is_valid() const { + return worker_.get() != NULL; +} + +} // namespace printing diff --git a/chrome/browser/printing/printer_query.h b/chrome/browser/printing/printer_query.h new file mode 100644 index 0000000..9b1e50b --- /dev/null +++ b/chrome/browser/printing/printer_query.h @@ -0,0 +1,127 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINTER_QUERY_H__ +#define CHROME_BROWSER_PRINTING_PRINTER_QUERY_H__ + +#include "base/ref_counted.h" +#include "chrome/browser/printing/print_job_worker_owner.h" + +class CancelableTask; +class MessageLoop; +class Thread; + +namespace printing { + +class PrintJobWorker; + +// Query the printer for settings. +class PrinterQuery : public base::RefCountedThreadSafe<PrinterQuery>, + public PrintJobWorkerOwner { + public: + // GetSettings() UI parameter. + enum GetSettingsAskParam { + DEFAULTS, + ASK_USER, + }; + + PrinterQuery(); + virtual ~PrinterQuery(); + + // PrintJobWorkerOwner + virtual void AddRef() { + return base::RefCountedThreadSafe<PrinterQuery>::AddRef(); + } + virtual void Release() { + return base::RefCountedThreadSafe<PrinterQuery>::Release(); + } + virtual void GetSettingsDone(const PrintSettings& new_settings, + PrintingContext::Result result); + virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner); + virtual MessageLoop* message_loop() { + return ui_message_loop_; + } + virtual const PrintSettings& settings() const { return settings_; } + + virtual int cookie() const { return cookie_; } + + // Initializes the printing context. It is fine to call this function multiple + // times to reinitialize the settings. |parent_window| parameter will be the + // owner of the print setting dialog box. It is unused when + // |ask_for_user_settings| is DEFAULTS. + void GetSettings(GetSettingsAskParam ask_user_for_settings, + HWND parent_window, + int expected_page_count, + CancelableTask* callback); + + // Stops the worker thread since the client is done with this object. + void StopWorker(); + + // Returns true if the Print... dialog box is currently displayed. + bool is_print_dialog_box_shown() const; + + // Returns true if a GetSettings() call is pending completion. + bool is_callback_pending() const; + + PrintingContext::Result last_status() const { return last_status_; } + + // Returns if a worker thread is still associated to this instance. + bool is_valid() const; + + private: + // Main message loop reference. Used to send notifications in the right + // thread. + MessageLoop* const ui_message_loop_; + + // All the UI is done in a worker thread because many Win32 print functions + // are blocking and enters a message loop without your consent. There is one + // worker thread per print job. + scoped_ptr<PrintJobWorker> worker_; + + // Cache of the print context settings for access in the UI thread. + PrintSettings settings_; + + // Is the Print... dialog box currently shown. + bool is_print_dialog_box_shown_; + + // Cookie that make this instance unique. + int cookie_; + + // Results from the last GetSettingsDone() callback. + PrintingContext::Result last_status_; + + // Task waiting to be executed. + scoped_ptr<CancelableTask> callback_; + + DISALLOW_EVIL_CONSTRUCTORS(PrinterQuery); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINTER_QUERY_H__ diff --git a/chrome/browser/printing/printing_layout_uitest.cc b/chrome/browser/printing/printing_layout_uitest.cc new file mode 100644 index 0000000..9a6d04c --- /dev/null +++ b/chrome/browser/printing/printing_layout_uitest.cc @@ -0,0 +1,643 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/command_line.h" +#include "base/gfx/bitmap_header.h" +#include "base/gfx/platform_device.h" +#include "base/gfx/png_decoder.h" +#include "base/gfx/png_encoder.h" +#include "base/time.h" +#include "base/win_util.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/gfx/emf.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/automation/window_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "chrome/browser/printing/printing_test.h" +#include "net/url_request/url_request_unittest.h" + +namespace { + +const wchar_t* const kGenerateSwitch = L"print-layout-generate"; +const wchar_t kDocRoot[] = L"chrome/test/data"; + +// Lightweight raw-bitmap management. The image, once initialized, is immuable. +// It is mainly used for comparison. +class Image { + public: + // Creates the image from the given filename on disk. + Image(const std::wstring& filename) : ignore_alpha_(true) { + std::string data; + file_util::ReadFileToString(filename, &data); + EXPECT_TRUE(data.size()); + std::wstring ext = file_util::GetFileExtensionFromPath(filename); + if (LowerCaseEqualsASCII(ext, "png")) { + LoadPng(data); + } else if (LowerCaseEqualsASCII(ext, "emf")) { + LoadEmf(data); + } else { + EXPECT_TRUE(false); + } + } + + const gfx::Size& size() const { + return size_; + } + + // Used to create the initial test files. + void SaveToPng(const std::wstring& filename) { + ASSERT_FALSE(data_.empty()); + std::vector<unsigned char> compressed; + ASSERT_TRUE(PNGEncoder::Encode(&*data_.begin(), + PNGEncoder::FORMAT_BGRA, + size_.width(), + size_.height(), + row_length_, + true, + &compressed)); + ASSERT_TRUE(compressed.size()); + FILE* f; + ASSERT_EQ(_wfopen_s(&f, filename.c_str(), L"wbS"), 0); + ASSERT_EQ(fwrite(&*compressed.begin(), 1, compressed.size(), f), + compressed.size()); + fclose(f); + } + + double PercentageDifferent(const Image& rhs) const { + if (size_.width() == 0 || size_.height() == 0 || + rhs.size_.width() == 0 || rhs.size_.height() == 0) + return 100.; + + int width = std::min(size_.width(), rhs.size_.width()); + int height = std::min(size_.height(), rhs.size_.height()); + // Compute pixels different in the overlap + int pixels_different = 0; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32 lhs_pixel = pixel_at(x, y); + uint32 rhs_pixel = rhs.pixel_at(x, y); + if (lhs_pixel != rhs_pixel) + ++pixels_different; + } + + // Look for extra right lhs pixels. They should be white. + for (int x = width; x < size_.width(); ++x) { + uint32 lhs_pixel = pixel_at(x, y); + if (lhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + + // Look for extra right rhs pixels. They should be white. + for (int x = width; x < rhs.size_.width(); ++x) { + uint32 rhs_pixel = rhs.pixel_at(x, y); + if (rhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + } + + // Look for extra bottom lhs pixels. They should be white. + for (int y = height; y < size_.height(); ++y) { + for (int x = 0; x < size_.width(); ++x) { + uint32 lhs_pixel = pixel_at(x, y); + if (lhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + } + + // Look for extra bottom rhs pixels. They should be white. + for (int y = height; y < rhs.size_.height(); ++y) { + for (int x = 0; x < rhs.size_.width(); ++x) { + uint32 rhs_pixel = rhs.pixel_at(x, y); + if (rhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + } + + // Like the WebKit ImageDiff tool, we define percentage different in terms + // of the size of the 'actual' bitmap. + double total_pixels = static_cast<double>(size_.width()) * + static_cast<double>(height); + return static_cast<double>(pixels_different) / total_pixels * 100.; + } + + // Returns the 0x0RGB or 0xARGB value of the pixel at the given location, + // depending on ignore_alpha_. + uint32 Color(uint32 color) const { + if (ignore_alpha_) + return color & 0xFFFFFF; // Strip out A. + else + return color; + } + + uint32 pixel_at(int x, int y) const { + EXPECT_TRUE(x >= 0 && x < size_.width()); + EXPECT_TRUE(y >= 0 && y < size_.height()); + const uint32* data = reinterpret_cast<const uint32*>(&*data_.begin()); + const uint32* data_row = data + y * row_length_ / sizeof(uint32); + return Color(data_row[x]); + } + + private: + void LoadPng(const std::string& compressed) { + int w; + int h; + EXPECT_TRUE(PNGDecoder::Decode( + reinterpret_cast<const unsigned char*>(compressed.c_str()), + compressed.size(), PNGDecoder::FORMAT_BGRA, &data_, &w, &h)); + size_.SetSize(w, h); + row_length_ = size_.width() * sizeof(uint32); + } + + void LoadEmf(const std::string& data) { + ASSERT_FALSE(data.empty()); + gfx::Emf emf; + emf.CreateFromData(data.data(), data.size()); + gfx::Rect rect(emf.GetBounds()); + // Create a temporary HDC and bitmap to retrieve the renderered data. + HDC hdc = CreateCompatibleDC(NULL); + BITMAPV4HEADER hdr; + EXPECT_FALSE(rect.x()); + EXPECT_FALSE(rect.y()); + EXPECT_NE(rect.width(), 0); + EXPECT_NE(rect.height(), 0); + size_ = rect.size(); + gfx::CreateBitmapV4Header(rect.width(), rect.height(), &hdr); + void* bits; + HBITMAP bitmap = CreateDIBSection(hdc, + reinterpret_cast<BITMAPINFO*>(&hdr), 0, + &bits, NULL, 0); + EXPECT_TRUE(bitmap); + EXPECT_TRUE(SelectObject(hdc, bitmap)); + gfx::PlatformDevice::InitializeDC(hdc); + EXPECT_TRUE(emf.Playback(hdc, NULL)); + row_length_ = size_.width() * sizeof(uint32); + size_t bytes = row_length_ * size_.height(); + ASSERT_TRUE(bytes); + data_.resize(bytes); + memcpy(&*data_.begin(), bits, bytes); + DeleteDC(hdc); + DeleteObject(bitmap); + } + + // Pixel dimensions of the image. + gfx::Size size_; + + // Length of a line in bytes. + int row_length_; + + // Actual bitmap data in arrays of RGBAs (so when loaded as uint32, it's + // 0xABGR). + std::vector<unsigned char> data_; + + // Flag to signal if the comparison functions should ignore the alpha channel. + const bool ignore_alpha_; + + DISALLOW_EVIL_CONSTRUCTORS(Image); +}; + +class PrintingLayoutTest : public PrintingTest<UITest> { + public: + PrintingLayoutTest() { + emf_path_ = browser_directory_; + file_util::AppendToPath(&emf_path_, L"emf_dumps"); + std::wstring arg(L" --debug-print=\""); + arg += emf_path_; + arg += L"\""; + launch_arguments_.append(arg); + show_window_ = true; + } + + virtual void SetUp() { + // Make sure there is no left overs. + CleanupDumpDirectory(); + UITest::SetUp(); + } + + virtual void TearDown() { + UITest::TearDown(); + file_util::Delete(emf_path_, true); + } + + protected: + void PrintNowTab() { + scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + if (!tab_proxy.get()) + return; + + ASSERT_TRUE(tab_proxy->PrintNow()); + } + + // Finds the dump for the last print job and compares it to the data named + // |verification_name|. Compares the saved printed job pixels with the test + // data pixels and returns the percentage of different pixels; 0 for success, + // ]0, 100] for failure. + double CompareWithResult(const std::wstring& verification_name) { + std::wstring test_result(ScanFiles(verification_name)); + if (test_result.empty()) { + // 100% different, the print job buffer is not there. + return 100.; + } + + std::wstring verification_file(test_data_directory_); + file_util::AppendToPath(&verification_file, L"printing"); + file_util::AppendToPath(&verification_file, verification_name); + std::wstring emf(verification_file + L".emf"); + std::wstring png(verification_file + L".png"); + + // Looks for Cleartype override. + if (file_util::PathExists(verification_file + L"_cleartype.png") && + IsClearTypeEnabled()) { + png = verification_file + L"_cleartype.png"; + } + + if (GenerateFiles()) { + // Copy the .emf and generate an .png. + file_util::CopyFile(test_result, emf); + Image emf_content(emf); + emf_content.SaveToPng(png); + // Saving is always fine. + return 0; + } else { + // File compare between test and result. + Image emf_content(emf); + Image test_content(test_result); + Image png_content(png); + double diff_emf = emf_content.PercentageDifferent(test_content); + + EXPECT_EQ(0., diff_emf) << verification_name << + L" original size:" << emf_content.size() << + L" result size:" << test_content.size(); + if (diff_emf) { + // Backup the result emf file. + file_util::CopyFile(test_result, verification_file + L"_failed.emf"); + } + + // This verification is only to know that the EMF rendering stays + // immutable. + double diff_png = emf_content.PercentageDifferent(png_content); + EXPECT_EQ(0., diff_png) << verification_name << + L" original size:" << emf_content.size() << + L" result size:" << test_content.size(); + if (diff_png) { + // Backup the rendered emf file to detect the rendering difference. + emf_content.SaveToPng(verification_file + L"_rendering.png"); + } + return std::max(diff_png, diff_emf); + } + } + + // Makes sure the directory exists and is empty. + void CleanupDumpDirectory() { + // Tries to delete the dumping directory for around 10 seconds. + for (int i = 0; i < 100 && file_util::PathExists(emf_path()); ++i) { + // It's fine fail sometimes because of opened left over .PRN file. + // Explanation: + // When calling PrintNowTab(), it makes sure the page is rendered and + // sent to the spooler. It does *not* wait for the spooler to flush the + // job. It is completely unnecessary to wait for that. So the printer + // may write the file too late. Since the printer holds an exclusive + // access to the file, it can't be deleted until the printer is done. + if (file_util::Delete(emf_path(), true)) { + break; + } + Sleep(100); + } + file_util::CreateDirectory(emf_path()); + } + + // Returns if Clear Type is currently enabled. + static bool IsClearTypeEnabled() { + BOOL ct_enabled = 0; + if (SystemParametersInfo(SPI_GETCLEARTYPE, 0, &ct_enabled, 0) && ct_enabled) + return true; + UINT smoothing = 0; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing, 0) && + smoothing == FE_FONTSMOOTHINGCLEARTYPE) + return true; + return false; + } + + private: + // Verifies that there is one .emf and one .prn file in the dump directory. + // Returns the path of the .emf file and deletes the .prn file. + std::wstring ScanFiles(const std::wstring& verification_name) { + // Try to 10 seconds. + std::wstring emf_file; + std::wstring prn_file; + bool found_emf = false; + bool found_prn = false; + for (int i = 0; i < 100; ++i) { + file_util::FileEnumerator enumerator(emf_path(), false, + file_util::FileEnumerator::FILES); + emf_file.clear(); + prn_file.clear(); + found_emf = false; + found_prn = false; + std::wstring file; + while (!(file = enumerator.Next()).empty()) { + std::wstring ext = file_util::GetFileExtensionFromPath(file); + if (!_wcsicmp(ext.c_str(), L"emf")) { + EXPECT_FALSE(found_emf) << "Found a leftover .EMF file: \"" << + emf_file << "\" and \"" << file << "\" when looking for \"" << + verification_name << "\""; + found_emf = true; + emf_file = file; + continue; + } + if (!_wcsicmp(ext.c_str(), L"prn")) { + EXPECT_FALSE(found_prn) << "Found a leftover .PRN file: \"" << + prn_file << "\" and \"" << file << "\" when looking for \"" << + verification_name << "\""; + prn_file = file; + found_prn = true; + file_util::Delete(file, false); + continue; + } + EXPECT_TRUE(false); + } + if (found_emf && found_prn) + break; + Sleep(100); + } + EXPECT_TRUE(found_emf) << ".PRN file is: " << prn_file; + EXPECT_TRUE(found_prn) << ".EMF file is: " << emf_file; + return emf_file; + } + + static bool GenerateFiles() { + return CommandLine().HasSwitch(kGenerateSwitch); + } + + const std::wstring& emf_path() const { return emf_path_; } + + std::wstring emf_path_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintingLayoutTest); +}; + +// Tests that don't need UI access. +class PrintingLayoutTestHidden : public PrintingLayoutTest { + public: + PrintingLayoutTestHidden() { + show_window_ = false; + } +}; + +class PrintingLayoutTextTest : public PrintingLayoutTest { + typedef PrintingLayoutTest Parent; + public: + // Returns if the test is disabled. + // TODO(maruel): http://b/1157665 Until the issue is fixed, disable the test + // if ClearType is enabled. + static bool IsTestCaseDisabled() { + return Parent::IsTestCaseDisabled() || IsClearTypeEnabled(); + } +}; + +// Dismiss the first dialog box child of owner_window by "executing" the +// default button. +class DismissTheWindow : public Task { + public: + DismissTheWindow(DWORD owner_process) + : owner_process_(owner_process), + dialog_was_found_(false), + dialog_window_(NULL), + other_thread_(MessageLoop::current()), + timer_(NULL), + start_time_(Time::Now()) { + } + virtual void Run() { + // A bit twisted code that runs in 2 passes or more. First it tries to find + // a dialog box, if it finds it, it will execute the default action. If it + // still works, it will loop again but then it will try to *not* find the + // window. Once this is right, it will stop the timer and unlock the + // other_thread_ message loop. + if (!timer_) + return; + + if (!dialog_window_) { + HWND dialog_window = NULL; + for (;;) { + dialog_window = FindWindowEx(NULL, + dialog_window, + MAKEINTATOM(32770), + NULL); + if (!dialog_window) + break; + + // In some corner case, the Print... dialog box may not have any owner. + // Trap that case. Too bad if the user has a dialog opened. + if (Time::Now() - start_time_ > TimeDelta::FromSeconds(3)) + break; + + DWORD process_id = 0; + GetWindowThreadProcessId(dialog_window, &process_id); + if (process_id == owner_process_) + break; + } + if (dialog_window) { + LRESULT res = SendMessage(dialog_window, DM_GETDEFID, 0, 0); + if (!res) + return; + EXPECT_EQ(DC_HASDEFID, HIWORD(res)); + WORD print_button_id = LOWORD(res); + res = SendMessage( + dialog_window, + WM_COMMAND, + print_button_id, + reinterpret_cast<LPARAM>(GetDlgItem(dialog_window, print_button_id))); + // Try again. + if (res) + return; + + // Ok it succeeded. + dialog_window_ = dialog_window; + dialog_was_found_ = true; + return; + } + if (!dialog_was_found_) + return; + } + + // Now verify that it indeed closed itself. + if (!IsWindow(dialog_window_)) { + MessageLoop::current()->timer_manager()->StopTimer(timer_); + timer_ = NULL; + // Unlock the other thread. + other_thread_->Quit(); + } else { + // Maybe it's time to try to click it again. Restart from the begining. + dialog_window_ = NULL; + } + } + void SetTimer(Timer* timer) { + timer_ = timer; + } + private: + DWORD owner_process_; + bool dialog_was_found_; + HWND dialog_window_; + MessageLoop* other_thread_; + Timer* timer_; + Time start_time_; +}; + +} // namespace + +TEST_F(PrintingLayoutTextTest, Complex) { + if (IsTestCaseDisabled()) + return; + + // Print a document, check its output. + TestServer server(kDocRoot); + NavigateToURL(server.TestServerPage("files/printing/test1.html")); + PrintNowTab(); + EXPECT_EQ(0., CompareWithResult(L"test1")); +} + +struct TestPool { + const wchar_t* source; + const wchar_t* result; +}; + +const TestPool kTestPool[] = { + // ImagesB&W + L"files/printing/test2.html", L"test2", + // ImagesTransparent + L"files/printing/test3.html", L"test3", + // ImageColor + L"files/printing/test4.html", L"test4", + // TODO(maruel): http://b/1171450 Transparent overlays are drawn opaque + // L"files/printing/test5.html", L"test5", +}; + +TEST_F(PrintingLayoutTestHidden, ManyTimes) { + if (IsTestCaseDisabled()) + return; + + TestServer server(kDocRoot); + ASSERT_GT(arraysize(kTestPool), 0u); + for (int i = 0; i < arraysize(kTestPool); ++i) { + if (i) + CleanupDumpDirectory(); + const TestPool& test = kTestPool[i % arraysize(kTestPool)]; + NavigateToURL(server.TestServerPageW(test.source)); + PrintNowTab(); + EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; + CleanupDumpDirectory(); + PrintNowTab(); + EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; + CleanupDumpDirectory(); + PrintNowTab(); + EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; + CleanupDumpDirectory(); + PrintNowTab(); + EXPECT_EQ(0., CompareWithResult(test.result)) << test.result; + } +} + +// Prints a popup and immediately closes it. +TEST_F(PrintingLayoutTest, DISABLED_Delayed) { + if (IsTestCaseDisabled()) + return; + + TestServer server(kDocRoot); + + { + scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + bool is_timeout = true; + GURL url = server.TestServerPage("files/printing/popup_delayed_print.htm"); + EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab_proxy->NavigateToURL(url)); + + + scoped_ptr<Thread> worker(new Thread("PrintingLayoutTest_worker")); + DismissTheWindow dismiss_task(process_util::GetProcId(process())); + // We need to start the thread to be able to set the timer. + worker->Start(); + scoped_ptr<Timer> timer(worker->message_loop()->timer_manager()->StartTimer( + 250, + &dismiss_task, + true)); + dismiss_task.SetTimer(timer.get()); + MessageLoop::current()->Run(); + + worker->Stop(); + + // Force a navigation elsewhere to verify that it's fine with it. + url = server.TestServerPage("files/printing/test1.html"); + EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab_proxy->NavigateToURL(url)); + } + CloseBrowserAndServer(); + + EXPECT_EQ(0., CompareWithResult(L"popup_delayed_print")) + << L"popup_delayed_print"; +} + +// Prints a popup and immediately closes it. +TEST_F(PrintingLayoutTest, DISABLED_IFrame) { + if (IsTestCaseDisabled()) + return; + + TestServer server(kDocRoot); + + { + scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + GURL url = server.TestServerPage("files/printing/iframe.htm"); + EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab_proxy->NavigateToURL(url)); + + scoped_ptr<Thread> worker(new Thread("PrintingLayoutTest_worker")); + DismissTheWindow dismiss_task(process_util::GetProcId(process())); + // We need to start the thread to be able to set the timer. + worker->Start(); + scoped_ptr<Timer> timer(worker->message_loop()->timer_manager()->StartTimer( + 250, + &dismiss_task, + true)); + dismiss_task.SetTimer(timer.get()); + MessageLoop::current()->Run(); + + worker->Stop(); + + // Force a navigation elsewhere to verify that it's fine with it. + url = server.TestServerPage("files/printing/test1.html"); + EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab_proxy->NavigateToURL(url)); + } + CloseBrowserAndServer(); + + EXPECT_EQ(0., CompareWithResult(L"iframe")) + << L"iframe"; +} diff --git a/chrome/browser/printing/printing_test.h b/chrome/browser/printing/printing_test.h new file mode 100644 index 0000000..c2344dd --- /dev/null +++ b/chrome/browser/printing/printing_test.h @@ -0,0 +1,61 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_PRINTING_TEST_H__ +#define CHROME_BROWSER_PRINTING_PRINTING_TEST_H__ + +#include <windows.h> +#include <winspool.h> + +// Disable the whole test case when executing on a computer that has no printer +// installed. +// Note: Parent should be testing::Test or UITest. +template<typename Parent> +class PrintingTest : public Parent { + public: + static bool IsTestCaseDisabled() { + return GetDefaultPrinter().empty(); + } + static std::wstring GetDefaultPrinter() { + wchar_t printer_name[MAX_PATH]; + DWORD size = arraysize(printer_name); + BOOL result = ::GetDefaultPrinter(printer_name, &size); + if (result == 0) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + printf("There is no printer installed, printing can't be tested!\n"); + return std::wstring(); + } + printf("INTERNAL PRINTER ERROR!\n"); + return std::wstring(); + } + return printer_name; + } +}; + +#endif // CHROME_BROWSER_PRINTING_PRINTING_TEST_H__ diff --git a/chrome/browser/printing/units.cc b/chrome/browser/printing/units.cc new file mode 100644 index 0000000..881b819 --- /dev/null +++ b/chrome/browser/printing/units.cc @@ -0,0 +1,67 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/units.h" + +#include "base/logging.h" + +namespace printing { + +int ConvertUnit(int value, int old_unit, int new_unit) { + DCHECK_GT(new_unit, 0); + DCHECK_GT(old_unit, 0); + // With integer arithmetic, to divide a value with correct rounding, you need + // to add half of the divisor value to the dividend value. You need to do the + // reverse with negative number. + if (value >= 0) { + return ((value * new_unit) + (old_unit / 2)) / old_unit; + } else { + return ((value * new_unit) - (old_unit / 2)) / old_unit; + } +} + +double ConvertUnitDouble(double value, double old_unit, double new_unit) { + DCHECK_GT(new_unit, 0); + DCHECK_GT(old_unit, 0); + return value * new_unit / old_unit; +} + +int ConvertMilliInchToHundredThousanthMeter(int milli_inch) { + // 1" == 25.4 mm + // 1" == 25400 um + // 0.001" == 25.4 um + // 0.001" == 2.54 cmm + return ConvertUnit(milli_inch, 100, 254); +} + +int ConvertHundredThousanthMeterToMilliInch(int cmm) { + return ConvertUnit(cmm, 254, 100); +} + +} // namespace printing diff --git a/chrome/browser/printing/units.h b/chrome/browser/printing/units.h new file mode 100644 index 0000000..1501509 --- /dev/null +++ b/chrome/browser/printing/units.h @@ -0,0 +1,52 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_UNITS_H__ +#define CHROME_BROWSER_PRINTING_UNITS_H__ + +namespace printing { + +// Length of a thousanth of inches in 0.01mm unit. +const int kHundrethsMMPerInch = 2540; + +// Converts from one unit system to another using integer arithmetics. +int ConvertUnit(int value, int old_unit, int new_unit); + +// Converts from one unit system to another using doubles. +double ConvertUnitDouble(double value, double old_unit, double new_unit); + +// Converts from 0.001 inch unit to 0.00001 meter. +int ConvertMilliInchToHundredThousanthMeter(int milli_inch); + +// Converts from 0.00001 meter unit to 0.001 inch. +int ConvertHundredThousanthMeterToMilliInch(int cmm); + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_UNITS_H__ diff --git a/chrome/browser/printing/win_printing_context.cc b/chrome/browser/printing/win_printing_context.cc new file mode 100644 index 0000000..81315f3 --- /dev/null +++ b/chrome/browser/printing/win_printing_context.cc @@ -0,0 +1,629 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/win_printing_context.h" + +#include <winspool.h> + +#include "base/file_util.h" +#include "base/gfx/platform_device.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/common/gfx/emf.h" +#include "chrome/common/time_format.h" + +namespace { + +// Retrieves the content of a GetPrinter call. +void GetPrinterHelper(HANDLE printer, int level, scoped_array<uint8>* buffer) { + DWORD buf_size = 0; + GetPrinter(printer, level, NULL, 0, &buf_size); + if (buf_size) { + buffer->reset(new uint8[buf_size]); + memset(buffer->get(), 0, buf_size); + if (!GetPrinter(printer, level, buffer->get(), buf_size, &buf_size)) { + buffer->reset(); + } + } +} + +} // namespace + +namespace printing { + +class PrintingContext::CallbackHandler + : public IPrintDialogCallback, + public IObjectWithSite { + public: + CallbackHandler(PrintingContext& owner, HWND owner_hwnd) + : owner_(owner), + owner_hwnd_(owner_hwnd), + services_(NULL) { + } + + ~CallbackHandler() { + if (services_) + services_->Release(); + } + + IUnknown* ToIUnknown() { + return static_cast<IUnknown*>(static_cast<IPrintDialogCallback*>(this)); + } + + // IUnknown + virtual HRESULT WINAPI QueryInterface(REFIID riid, void**object) { + if (riid == IID_IUnknown) { + *object = ToIUnknown(); + } else if (riid == IID_IPrintDialogCallback) { + *object = static_cast<IPrintDialogCallback*>(this); + } else if (riid == IID_IObjectWithSite) { + *object = static_cast<IObjectWithSite*>(this); + } else { + return E_NOINTERFACE; + } + return S_OK; + } + + // No real ref counting. + virtual ULONG WINAPI AddRef() { + return 1; + } + virtual ULONG WINAPI Release() { + return 1; + } + + // IPrintDialogCallback methods + virtual HRESULT WINAPI InitDone() { + return S_OK; + } + + virtual HRESULT WINAPI SelectionChange() { + if (services_) { + // TODO(maruel): Get the devmode for the new printer with + // services_->GetCurrentDevMode(&devmode, &size), send that information + // back to our client and continue. The client needs to recalculate the + // number of rendered pages and send back this information here. + } + return S_OK; + } + + virtual HRESULT WINAPI HandleMessage(HWND dialog, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + // Cheap way to retrieve the window handle. + if (!owner_.dialog_box_) { + // The handle we receive is the one of the groupbox in the General tab. We + // need to get the grand-father to get the dialog box handle. + owner_.dialog_box_ = GetAncestor(dialog, GA_ROOT); + // Trick to enable the owner window. This can cause issues with navigation + // events so it may have to be disabled if we don't fix the side-effects. + EnableWindow(owner_hwnd_, TRUE); + } + return S_FALSE; + } + + virtual HRESULT WINAPI SetSite(IUnknown* site) { + if (!site) { + DCHECK(services_); + services_->Release(); + services_ = NULL; + // The dialog box is destroying, PrintJob::Worker don't need the handle + // anymore. + owner_.dialog_box_ = NULL; + } else { + DCHECK(services_ == NULL); + HRESULT hr = site->QueryInterface(IID_IPrintDialogServices, + reinterpret_cast<void**>(&services_)); + DCHECK(SUCCEEDED(hr)); + } + return S_OK; + } + + virtual HRESULT WINAPI GetSite(REFIID riid, void** site) { + return E_NOTIMPL; + } + + private: + PrintingContext& owner_; + HWND owner_hwnd_; + IPrintDialogServices* services_; + + DISALLOW_EVIL_CONSTRUCTORS(CallbackHandler); +}; + +PrintingContext::PrintingContext() + : hdc_(NULL), +#ifdef _DEBUG + page_number_(-1), +#endif + dialog_box_(NULL), + dialog_box_dismissed_(false), + abort_printing_(false), + in_print_job_(false) { +} + +PrintingContext::~PrintingContext() { + ResetSettings(); +} + +PrintingContext::Result PrintingContext::AskUserForSettings(HWND window, + int max_pages) { + DCHECK(window); + DCHECK(!in_print_job_); + dialog_box_dismissed_ = false; + // Show the OS-dependent dialog box. + // If the user press + // - OK, the settings are reset and reinitialized with the new settings. OK is + // returned. + // - Apply then Cancel, the settings are reset and reinitialized with the new + // settings. CANCEL is returned. + // - Cancel, the settings are not changed, the previous setting, if it was + // initialized before, are kept. CANCEL is returned. + // On failure, the settings are reset and FAILED is returned. + PRINTDLGEX dialog_options = { sizeof(PRINTDLGEX) }; + dialog_options.hwndOwner = window; + // Disables the Current Page and Selection radio buttons since WebKit can't + // print a part of the webpage and we don't know which page is the current + // one. + // TODO(maruel): Reuse the previously loaded settings! + dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE | + PD_NOSELECTION | PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE; + PRINTPAGERANGE ranges[32]; + dialog_options.nStartPage = START_PAGE_GENERAL; + if (max_pages) { + // Default initialize to print all the pages. + memset(ranges, 0, sizeof(ranges)); + ranges[0].nFromPage = 1; + ranges[0].nToPage = max_pages; + dialog_options.nPageRanges = 1; + dialog_options.nMaxPageRanges = arraysize(ranges); + dialog_options.nMaxPage = max_pages; + dialog_options.lpPageRanges = ranges; + } else { + // No need to bother, we don't know how many pages are available. + dialog_options.Flags |= PD_NOPAGENUMS; + } + + { + CallbackHandler handler(*this, window); + dialog_options.lpCallback = handler.ToIUnknown(); + if (PrintDlgEx(&dialog_options) != S_OK) { + ResetSettings(); + return FAILED; + } + } + // TODO(maruel): Support PD_PRINTTOFILE. + return ParseDialogResultEx(dialog_options); +} + +PrintingContext::Result PrintingContext::UseDefaultSettings() { + DCHECK(!in_print_job_); + + PRINTDLG dialog_options = { sizeof(PRINTDLG) }; + dialog_options.Flags = PD_RETURNDC | PD_RETURNDEFAULT; + if (PrintDlg(&dialog_options) == 0) { + ResetSettings(); + return FAILED; + } + return ParseDialogResult(dialog_options); +} + +PrintingContext::Result PrintingContext::InitWithSettings( + const PrintSettings& settings) { + DCHECK(!in_print_job_); + settings_ = settings; + // TODO(maruel): settings_->ToDEVMODE() + HANDLE printer; + if (!OpenPrinter(const_cast<wchar_t*>(settings_.device_name().c_str()), + &printer, + NULL)) + return FAILED; + + Result status = OK; + + if (!GetPrinterSettings(printer, settings_.device_name())) + status = FAILED; + + // Close the printer after retrieving the context. + ClosePrinter(printer); + + if (status != OK) + ResetSettings(); + return status; +} + +void PrintingContext::ResetSettings() { + if (hdc_ != NULL) { + DeleteDC(hdc_); + hdc_ = NULL; + } + settings_.Clear(); + in_print_job_ = false; + +#ifdef _DEBUG + page_number_ = -1; +#endif +} + +PrintingContext::Result PrintingContext::NewDocument( + const std::wstring& document_name) { + DCHECK(!in_print_job_); + if (!hdc_) + return OnErrror(); + + // Set the flag used by the AbortPrintJob dialog procedure. + abort_printing_ = false; + + in_print_job_ = true; + + // Register the application's AbortProc function with GDI. + if (SP_ERROR == SetAbortProc(hdc_, &AbortProc)) + return OnErrror(); + + DOCINFO di = { sizeof(DOCINFO) }; + di.lpszDocName = document_name.c_str(); + + // Is there a debug dump directory specified? If so, force to print to a file. + std::wstring debug_dump_path; + if (!g_browser_process || !g_browser_process->print_job_manager()) { + // Happens only inside a unit test. + debug_dump_path = L"."; + } else { + debug_dump_path = g_browser_process->print_job_manager()->debug_dump_path(); + } + + if (!debug_dump_path.empty()) { + // Create a filename. + std::wstring filename; + Time now(Time::Now()); + filename = TimeFormat::ShortDateNumeric(now); + filename += L"_"; + filename += TimeFormat::TimeOfDay(now); + filename += L"_"; + filename += document_name; + filename += L"_"; + filename += L"buffer.prn"; + file_util::ReplaceIllegalCharacters(&filename, '_'); + file_util::AppendToPath(&debug_dump_path, filename); + di.lpszOutput = debug_dump_path.c_str(); + } + + DCHECK_EQ(MessageLoop::current()->NestableTasksAllowed(), false); + // Begin a print job by calling the StartDoc function. + // NOTE: StartDoc() starts a message loop. That causes a lot of problems with + // IPC. Make sure recursive task processing is disabled. + if (StartDoc(hdc_, &di) <= 0) + return OnErrror(); + +#ifdef _DEBUG + page_number_ = 0; +#endif + return OK; +} + +PrintingContext::Result PrintingContext::NewPage() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Inform the driver that the application is about to begin sending data. + if (StartPage(hdc_) <= 0) + return OnErrror(); + +#ifdef _DEBUG + ++page_number_; +#endif + + return OK; +} + +PrintingContext::Result PrintingContext::PageDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + if (EndPage(hdc_) <= 0) + return OnErrror(); + return OK; +} + +PrintingContext::Result PrintingContext::DocumentDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Inform the driver that document has ended. + if (EndDoc(hdc_) <= 0) + return OnErrror(); + + ResetSettings(); + return OK; +} + +void PrintingContext::Cancel() { + abort_printing_ = true; + in_print_job_ = false; + if (hdc_) + CancelDC(hdc_); + DismissDialog(); +} + +void PrintingContext::DismissDialog() { + if (dialog_box_) { + DestroyWindow(dialog_box_); + dialog_box_dismissed_ = true; + } +} + +PrintingContext::Result PrintingContext::OnErrror() { + // This will close hdc_ and clear settings_. + ResetSettings(); + return abort_printing_ ? CANCEL : FAILED; +} + +// static +BOOL PrintingContext::AbortProc(HDC hdc, int nCode) { + if (nCode) { + // TODO(maruel): Need a way to find the right instance to set. Should + // leverage PrintJobManager here? + // abort_printing_ = true; + } + return true; +} + +bool PrintingContext::InitializeSettings(const DEVMODE& dev_mode, + const std::wstring& new_device_name, + const PRINTPAGERANGE* ranges, + int number_ranges) { + gfx::PlatformDevice::InitializeDC(hdc_); + DCHECK(GetDeviceCaps(hdc_, CLIPCAPS)); + DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB); + DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64); + // Some printers don't advertise these. + // DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_SCALING); + // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_CONST_ALPHA); + // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_PIXEL_ALPHA); + + // StretchDIBits() support is needed for printing. + if (!(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB) || + !(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64)) { + NOTREACHED(); + ResetSettings(); + return false; + } + + DCHECK(!in_print_job_); + DCHECK(hdc_); + // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector. + PageRanges ranges_vector; + ranges_vector.reserve(number_ranges); + for (int i = 0; i < number_ranges; ++i) { + PageRange range; + // Transfert from 1-based to 0-based. + range.from = ranges[i].nFromPage - 1; + range.to = ranges[i].nToPage - 1; + ranges_vector.push_back(range); + } + settings_.Init(hdc_, dev_mode, ranges_vector, new_device_name); + PageMargins margins; + margins.header = 500; + margins.footer = 500; + margins.left = 500; + margins.top = 500; + margins.right = 500; + margins.bottom = 500; + settings_.UpdateMarginsMilliInch(margins); + return true; +} + +bool PrintingContext::GetPrinterSettings(HANDLE printer, + const std::wstring& device_name) { + DCHECK(!in_print_job_); + scoped_array<uint8> buffer; + + // A PRINTER_INFO_9 structure specifying the per-user default printer + // settings. + GetPrinterHelper(printer, 9, &buffer); + if (buffer.get()) { + PRINTER_INFO_9* info_9 = reinterpret_cast<PRINTER_INFO_9*>(buffer.get()); + if (info_9->pDevMode != NULL) { + if (!AllocateContext(device_name, info_9->pDevMode)) { + ResetSettings(); + return false; + } + return InitializeSettings(*info_9->pDevMode, device_name, NULL, 0); + } + buffer.reset(); + } + + // A PRINTER_INFO_8 structure specifying the global default printer settings. + GetPrinterHelper(printer, 8, &buffer); + if (buffer.get()) { + PRINTER_INFO_8* info_8 = reinterpret_cast<PRINTER_INFO_8*>(buffer.get()); + if (info_8->pDevMode != NULL) { + if (!AllocateContext(device_name, info_8->pDevMode)) { + ResetSettings(); + return false; + } + return InitializeSettings(*info_8->pDevMode, device_name, NULL, 0); + } + buffer.reset(); + } + + // A PRINTER_INFO_2 structure specifying the driver's default printer + // settings. + GetPrinterHelper(printer, 2, &buffer); + if (buffer.get()) { + PRINTER_INFO_2* info_2 = reinterpret_cast<PRINTER_INFO_2*>(buffer.get()); + if (info_2->pDevMode != NULL) { + if (!AllocateContext(device_name, info_2->pDevMode)) { + ResetSettings(); + return false; + } + return InitializeSettings(*info_2->pDevMode, device_name, NULL, 0); + } + buffer.reset(); + } + // Failed to retrieve the printer settings. + ResetSettings(); + return false; +} + +bool PrintingContext::AllocateContext(const std::wstring& printer_name, + const DEVMODE* dev_mode) { + hdc_ = CreateDC(L"WINSPOOL", printer_name.c_str(), NULL, dev_mode); + DCHECK(hdc_); + return hdc_ != NULL; +} + +PrintingContext::Result PrintingContext::ParseDialogResultEx( + const PRINTDLGEX& dialog_options) { + // If the user clicked OK or Apply then Cancel, but not only Cancel. + if (dialog_options.dwResultAction != PD_RESULT_CANCEL) { + // Start fresh. + ResetSettings(); + + DEVMODE* dev_mode = NULL; + if (dialog_options.hDevMode) { + dev_mode = + reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode)); + DCHECK(dev_mode); + } + + std::wstring device_name; + if (dialog_options.hDevNames) { + DEVNAMES* dev_names = + reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames)); + DCHECK(dev_names); + if (dev_names) { + device_name = + reinterpret_cast<const wchar_t*>( + reinterpret_cast<const wchar_t*>(dev_names) + + dev_names->wDeviceOffset); + GlobalUnlock(dialog_options.hDevNames); + } + } + + bool success = false; + if (dev_mode && !device_name.empty()) { + hdc_ = dialog_options.hDC; + if (dialog_options.Flags & PD_PAGENUMS) { + success = InitializeSettings(*dev_mode, + device_name, + dialog_options.lpPageRanges, + dialog_options.nPageRanges); + } else { + success = InitializeSettings(*dev_mode, device_name, NULL, 0); + } + } + + if (!success && dialog_options.hDC) { + DeleteDC(dialog_options.hDC); + hdc_ = NULL; + } + + if (dev_mode) { + GlobalUnlock(dialog_options.hDevMode); + } + } else { + if (dialog_options.hDC) { + DeleteDC(dialog_options.hDC); + } + } + + if (dialog_options.hDevMode != NULL) + GlobalFree(dialog_options.hDevMode); + if (dialog_options.hDevNames != NULL) + GlobalFree(dialog_options.hDevNames); + + switch (dialog_options.dwResultAction) { + case PD_RESULT_PRINT: + return hdc_ ? OK : FAILED; + case PD_RESULT_APPLY: + return hdc_ ? CANCEL : FAILED; + case PD_RESULT_CANCEL: + return CANCEL; + default: + return FAILED; + } +} + +PrintingContext::Result PrintingContext::ParseDialogResult( + const PRINTDLG& dialog_options) { + // If the user clicked OK or Apply then Cancel, but not only Cancel. + // Start fresh. + ResetSettings(); + + DEVMODE* dev_mode = NULL; + if (dialog_options.hDevMode) { + dev_mode = + reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode)); + DCHECK(dev_mode); + } + + std::wstring device_name; + if (dialog_options.hDevNames) { + DEVNAMES* dev_names = + reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames)); + DCHECK(dev_names); + if (dev_names) { + device_name = + reinterpret_cast<const wchar_t*>( + reinterpret_cast<const wchar_t*>(dev_names) + + dev_names->wDeviceOffset); + GlobalUnlock(dialog_options.hDevNames); + } + } + + bool success = false; + if (dev_mode && !device_name.empty()) { + hdc_ = dialog_options.hDC; + success = InitializeSettings(*dev_mode, device_name, NULL, 0); + } + + if (!success && dialog_options.hDC) { + DeleteDC(dialog_options.hDC); + hdc_ = NULL; + } + + if (dev_mode) { + GlobalUnlock(dialog_options.hDevMode); + } + + if (dialog_options.hDevMode != NULL) + GlobalFree(dialog_options.hDevMode); + if (dialog_options.hDevNames != NULL) + GlobalFree(dialog_options.hDevNames); + + return hdc_ ? OK : FAILED; +} + +} // namespace printing diff --git a/chrome/browser/printing/win_printing_context.h b/chrome/browser/printing/win_printing_context.h new file mode 100644 index 0000000..63b8bcb --- /dev/null +++ b/chrome/browser/printing/win_printing_context.h @@ -0,0 +1,163 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_BROWSER_PRINTING_WIN_PRINTING_CONTEXT_H__ +#define CHROME_BROWSER_PRINTING_WIN_PRINTING_CONTEXT_H__ + +#include <ocidl.h> +#include <commdlg.h> +#include <string> + +#include "base/basictypes.h" +#include "chrome/browser/printing/print_settings.h" + +namespace printing { + +// Describe the user selected printing context for Windows. This includes the +// OS-dependent UI to ask the user about the print settings. This class directly +// talk to the printer and manages the document and pages breaks. +class PrintingContext { + public: + // Tri-state result for user behavior-dependent functions. + enum Result { + OK, + CANCEL, + FAILED, + }; + + PrintingContext(); + ~PrintingContext(); + + // Asks the user what printer and format should be used to print. Updates the + // context with the select device settings. + Result AskUserForSettings(HWND window, int max_pages); + + // Selects the user's default printer and format. Updates the context with the + // default device settings. + Result UseDefaultSettings(); + + // Initializes with predefined settings. + Result InitWithSettings(const PrintSettings& settings); + + // Reinitializes the settings to uninitialized for object reuse. + void ResetSettings(); + + // Does platform specific setup of the printer before the printing. Signal the + // printer that a document is about to be spooled. + // Warning: This function enters a message loop. That may cause side effects + // like IPC message processing! Some printers have side-effects on this call + // like virtual printers that ask the user for the path of the saved document; + // for example a PDF printer. + Result NewDocument(const std::wstring& document_name); + + // Starts a new page. + Result NewPage(); + + // Closes the printed page. + Result PageDone(); + + // Closes the printing job. After this call the object is ready to start a new + // document. + Result DocumentDone(); + + // Cancels printing. Can be used in a multithreaded context. Takes effect + // immediately. + void Cancel(); + + // Dismiss the Print... dialog box if shown. + void DismissDialog(); + + HDC context() { + return hdc_; + } + + const PrintSettings& settings() const { + return settings_; + } + + private: + // Class that manages the PrintDlgEx() callbacks. This is meant to be a + // temporary object used during the Print... dialog display. + class CallbackHandler; + + // Does bookeeping when an error occurs. + PrintingContext::Result OnErrror(); + + // Used in response to the user cancelling the printing. + static BOOL CALLBACK AbortProc(HDC hdc, int nCode); + + // Reads the settings from the selected device context. Updates settings_ and + // its margins. + bool InitializeSettings(const DEVMODE& dev_mode, + const std::wstring& new_device_name, + const PRINTPAGERANGE* ranges, + int number_ranges); + + // Retrieves the printer's default low-level settings. hdc_ is allocated with + // this call. + bool GetPrinterSettings(HANDLE printer, + const std::wstring& device_name); + + // Allocates the HDC for a specific DEVMODE. + bool AllocateContext(const std::wstring& printer_name, + const DEVMODE* dev_mode); + + // Parses the result of a PRINTDLGEX result. + Result ParseDialogResultEx(const PRINTDLGEX& dialog_options); + Result ParseDialogResult(const PRINTDLG& dialog_options); + + // The selected printer context. + HDC hdc_; + + // Complete print context settings. + PrintSettings settings_; + +#ifdef _DEBUG + // Current page number in the print job. + int page_number_; +#endif + + // The dialog box for the time it is shown. + volatile HWND dialog_box_; + + // The dialog box has been dismissed. + volatile bool dialog_box_dismissed_; + + // Is a print job being done. + volatile bool in_print_job_; + + // Did the user cancel the print job. + volatile bool abort_printing_; + + DISALLOW_EVIL_CONSTRUCTORS(PrintingContext); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_WIN_PRINTING_CONTEXT_H__
\ No newline at end of file diff --git a/chrome/browser/printing/win_printing_context_unittest.cc b/chrome/browser/printing/win_printing_context_unittest.cc new file mode 100644 index 0000000..a324425 --- /dev/null +++ b/chrome/browser/printing/win_printing_context_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/win_printing_context.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "chrome/browser/printing/printing_test.h" +#include "chrome/browser/printing/print_settings.h" + +// This test is automatically disabled if no printer is available. +class PrintingContextTest : public PrintingTest<testing::Test> { +}; + +TEST_F(PrintingContextTest, Base) { + printing::PrintSettings settings; + + settings.set_device_name(GetDefaultPrinter()); + // Initialize it. + printing::PrintingContext context; + EXPECT_EQ(context.InitWithSettings(settings), printing::PrintingContext::OK); + + ; + // The print may lie to use and may not support world transformation. + // Verify right now. + XFORM random_matrix = { 1, 0.1f, 0, 1.5f, 0, 1 }; + EXPECT_TRUE(SetWorldTransform(context.context(), &random_matrix)); + EXPECT_TRUE(ModifyWorldTransform(context.context(), NULL, MWT_IDENTITY)); +} |