// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "printing/printed_document.h" #include #include "app/gfx/font.h" #include "app/gfx/text_elider.h" #include "app/win_util.h" #include "base/file_util.h" #include "base/message_loop.h" #include "base/singleton.h" #include "base/string_util.h" #include "base/time.h" #include "printing/page_number.h" #include "printing/page_overlays.h" #include "printing/printed_pages_source.h" #include "printing/printed_page.h" #include "printing/units.h" #include "skia/ext/platform_device.h" using base::Time; namespace { struct PrintDebugDumpPath { PrintDebugDumpPath() : enabled(false) { } bool enabled; std::wstring debug_dump_path; }; Singleton g_debug_dump_info; } // namespace 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, NativeMetafile* metafile, 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 page( new PrintedPage(page_number + 1, metafile, 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); } } DebugDump(*page); } bool PrintedDocument::GetPage(int page_number, scoped_refptr* page) { 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; } } return false; } void PrintedDocument::RenderPrintedPage(const PrintedPage& page, HDC context) const { #ifndef NDEBUG { // 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); skia::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( immutable_.settings_.page_setup_pixels().content_area().x()); xform.eDy = static_cast( immutable_.settings_.page_setup_pixels().content_area().y()); xform.eM11 = static_cast(1. / mutable_.shrink_factor); xform.eM22 = static_cast(1. / mutable_.shrink_factor); BOOL res = ModifyWorldTransform(context, &xform, MWT_LEFTMULTIPLY); DCHECK_NE(res, 0); if (!page.native_metafile()->SafePlayback(context)) { NOTREACHED(); } res = RestoreDC(context, saved_state); DCHECK_NE(res, 0); } // Print the header and footer. int base_font_size = gfx::Font().height(); int new_font_size = ConvertUnit(10, immutable_.settings_.desired_dpi, immutable_.settings_.dpi()); DCHECK_GT(new_font_size, base_font_size); gfx::Font font(gfx::Font().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 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->native_metafile()) return false; } return true; } void PrintedDocument::DisconnectSource() { AutoLock lock(lock_); mutable_.source_ = NULL; } size_t PrintedDocument::MemoryUsage() const { std::vector> 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]->native_metafile()->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); } } 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 gfx::Font& 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()) { if (line == PageOverlays::kUrl) { output = gfx::ElideUrl(url(), font, bounding.width(), std::wstring()); } else { 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(output.size())); int res = RestoreDC(context, saved_state); DCHECK_NE(res, 0); } void PrintedDocument::DebugDump(const PrintedPage& page) { if (!g_debug_dump_info->enabled) return; std::wstring filename; filename += date(); filename += L"_"; filename += time(); filename += L"_"; filename += name(); filename += L"_"; filename += StringPrintf(L"%02d", page.page_number()); filename += L"_.emf"; file_util::ReplaceIllegalCharacters(&filename, '_'); std::wstring path(g_debug_dump_info->debug_dump_path); file_util::AppendToPath(&path, filename); page.native_metafile()->SaveTo(path); } void PrintedDocument::set_debug_dump_path(const std::wstring& debug_dump_path) { g_debug_dump_info->enabled = !debug_dump_path.empty(); g_debug_dump_info->debug_dump_path = debug_dump_path; } const std::wstring& PrintedDocument::debug_dump_path() { return g_debug_dump_info->debug_dump_path; } 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