15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ppapi/utility/graphics/paint_manager.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ppapi/c/pp_errors.h"
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ppapi/cpp/instance.h"
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ppapi/cpp/logging.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ppapi/cpp/module.h"
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace pp {
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)PaintManager::PaintManager()
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : instance_(NULL),
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      client_(NULL),
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      is_always_opaque_(false),
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      callback_factory_(NULL),
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      manual_callback_pending_(false),
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      flush_pending_(false),
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      has_pending_resize_(false) {
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Set the callback object outside of the initializer list to avoid a
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // compiler warning about using "this" in an initializer list.
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  callback_factory_.Initialize(this);
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)PaintManager::PaintManager(Instance* instance,
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           Client* client,
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           bool is_always_opaque)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : instance_(instance),
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      client_(client),
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      is_always_opaque_(is_always_opaque),
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      callback_factory_(NULL),
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      manual_callback_pending_(false),
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      flush_pending_(false),
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      has_pending_resize_(false) {
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Set the callback object outside of the initializer list to avoid a
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // compiler warning about using "this" in an initializer list.
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  callback_factory_.Initialize(this);
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // You can not use a NULL client pointer.
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(client);
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)PaintManager::~PaintManager() {
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::Initialize(Instance* instance,
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              Client* client,
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              bool is_always_opaque) {
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(!instance_ && !client_);  // Can't initialize twice.
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  instance_ = instance;
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  client_ = client;
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_always_opaque_ = is_always_opaque;
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::SetSize(const Size& new_size) {
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (GetEffectiveSize() == new_size)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  has_pending_resize_ = true;
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pending_size_ = new_size;
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Invalidate();
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::Invalidate() {
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // You must call SetSize before using.
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(!graphics_.is_null() || has_pending_resize_);
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EnsureCallbackPending();
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  aggregator_.InvalidateRect(Rect(GetEffectiveSize()));
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::InvalidateRect(const Rect& rect) {
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // You must call SetSize before using.
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(!graphics_.is_null() || has_pending_resize_);
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Clip the rect to the device area.
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Rect clipped_rect = rect.Intersect(Rect(GetEffectiveSize()));
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (clipped_rect.IsEmpty())
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;  // Nothing to do.
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EnsureCallbackPending();
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  aggregator_.InvalidateRect(clipped_rect);
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::ScrollRect(const Rect& clip_rect, const Point& amount) {
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // You must call SetSize before using.
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(!graphics_.is_null() || has_pending_resize_);
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EnsureCallbackPending();
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  aggregator_.ScrollRect(clip_rect, amount);
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Size PaintManager::GetEffectiveSize() const {
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return has_pending_resize_ ? pending_size_ : graphics_.size();
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::EnsureCallbackPending() {
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The best way for us to do the next update is to get a notification that
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // a previous one has completed. So if we're already waiting for one, we
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // don't have to do anything differently now.
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (flush_pending_)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If no flush is pending, we need to do a manual call to get back to the
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // main thread. We may have one already pending, or we may need to schedule.
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (manual_callback_pending_)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Module::Get()->core()->CallOnMainThread(
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      0,
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      callback_factory_.NewCallback(&PaintManager::OnManualCallbackComplete),
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      0);
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  manual_callback_pending_ = true;
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::DoPaint() {
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(aggregator_.HasPendingUpdate());
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Make a copy of the pending update and clear the pending update flag before
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // actually painting. A plugin might cause invalidates in its Paint code, and
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // we want those to go to the *next* paint.
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PaintAggregator::PaintUpdate update = aggregator_.GetPendingUpdate();
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  aggregator_.ClearPendingUpdate();
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Apply any pending resize. Setting the graphics to this class must happen
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // before asking the plugin to paint in case it requests the Graphics2D during
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // painting. However, the bind must not happen until afterward since we don't
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // want to have an unpainted device bound. The needs_binding flag tells us
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // whether to do this later.
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool needs_binding = false;
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (has_pending_resize_) {
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graphics_ = Graphics2D(instance_, pending_size_, is_always_opaque_);
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    needs_binding = true;
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Since we're binding a new one, all of the callbacks have been canceled.
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    manual_callback_pending_ = false;
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    flush_pending_ = false;
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    callback_factory_.CancelAll();
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // This must be cleared before calling into the plugin since it may do
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // additional invalidation or sizing operations.
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    has_pending_resize_ = false;
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pending_size_ = Size();
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Apply any scroll before asking the client to paint.
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (update.has_scroll)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graphics_.Scroll(update.scroll_rect, update.scroll_delta);
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (client_->OnPaint(graphics_, update.paint_rects, update.paint_bounds)) {
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Something was painted, schedule a flush.
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int32_t result = graphics_.Flush(
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        callback_factory_.NewOptionalCallback(&PaintManager::OnFlushComplete));
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // If you trigger this assertion, then your plugin has called Flush()
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // manually. When using the PaintManager, you should not call Flush, it
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // will handle that for you because it needs to know when it can do the
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // next paint by implementing the flush callback.
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    //
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Another possible cause of this assertion is re-using devices. If you
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // use one device, swap it with another, then swap it back, we won't know
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // that we've already scheduled a Flush on the first device. It's best to
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // not re-use devices in this way.
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PP_DCHECK(result != PP_ERROR_INPROGRESS);
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (result == PP_OK_COMPLETIONPENDING) {
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      flush_pending_ = true;
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      PP_DCHECK(result == PP_OK);  // Catch all other errors in debug mode.
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (needs_binding)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    instance_->BindGraphics(graphics_);
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::OnFlushComplete(int32_t result) {
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(flush_pending_);
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  flush_pending_ = false;
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Theoretically this shouldn't fail unless we've made an error, but don't
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // want to call into the client code to do more painting if something bad
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // did happen.
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (result != PP_OK)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If more paints were enqueued while we were waiting for the flush to
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // complete, execute them now.
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (aggregator_.HasPendingUpdate())
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DoPaint();
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void PaintManager::OnManualCallbackComplete(int32_t) {
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PP_DCHECK(manual_callback_pending_);
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  manual_callback_pending_ = false;
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Just because we have a manual callback doesn't mean there are actually any
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // invalid regions. Even though we only schedule this callback when something
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // is pending, a Flush callback could have come in before this callback was
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // executed and that could have cleared the queue.
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (aggregator_.HasPendingUpdate() && !flush_pending_)
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DoPaint();
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace pp
210