blob: b80faa7d4e147857b1e39f3ae91960557128755d [file] [log] [blame]
// Copyright 2013 The Flutter 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 "impeller/typographer/rectangle_packer.h"
#include <algorithm>
#include <vector>
namespace impeller {
// Pack rectangles and track the current silhouette
// Based, in part, on Jukka Jylanki's work at http://clb.demon.fi
// and ported from Skia's implementation
// https://github.com/google/skia/blob/b5de4b8ae95c877a9ecfad5eab0765bc22550301/src/gpu/RectanizerSkyline.cpp
class SkylineRectanglePacker final : public RectanglePacker {
public:
SkylineRectanglePacker(int w, int h) : RectanglePacker(w, h) {
this->reset();
}
~SkylineRectanglePacker() final {}
void reset() final {
area_so_far_ = 0;
skyline_.clear();
skyline_.push_back(SkylineSegment{0, 0, this->width()});
}
bool addRect(int w, int h, IPoint16* loc) final;
float percentFull() const final {
return area_so_far_ / ((float)this->width() * this->height());
}
private:
struct SkylineSegment {
int x_;
int y_;
int width_;
};
std::vector<SkylineSegment> skyline_;
int32_t area_so_far_;
// Can a width x height rectangle fit in the free space represented by
// the skyline segments >= 'skylineIndex'? If so, return true and fill in
// 'y' with the y-location at which it fits (the x location is pulled from
// 'skylineIndex's segment.
bool rectangleFits(int skylineIndex, int width, int height, int* y) const;
// Update the skyline structure to include a width x height rect located
// at x,y.
void addSkylineLevel(int skylineIndex, int x, int y, int width, int height);
};
bool SkylineRectanglePacker::addRect(int width, int height, IPoint16* loc) {
if ((unsigned)width > (unsigned)this->width() ||
(unsigned)height > (unsigned)this->height()) {
return false;
}
// find position for new rectangle
int bestWidth = this->width() + 1;
int bestX = 0;
int bestY = this->height() + 1;
int bestIndex = -1;
for (int i = 0; i < (int)skyline_.size(); ++i) {
int y;
if (this->rectangleFits(i, width, height, &y)) {
// minimize y position first, then width of skyline
if (y < bestY || (y == bestY && skyline_[i].width_ < bestWidth)) {
bestIndex = i;
bestWidth = skyline_[i].width_;
bestX = skyline_[i].x_;
bestY = y;
}
}
}
// add rectangle to skyline
if (-1 != bestIndex) {
this->addSkylineLevel(bestIndex, bestX, bestY, width, height);
loc->x_ = bestX;
loc->y_ = bestY;
area_so_far_ += width * height;
return true;
}
loc->x_ = 0;
loc->y_ = 0;
return false;
}
bool SkylineRectanglePacker::rectangleFits(int skylineIndex,
int width,
int height,
int* ypos) const {
int x = skyline_[skylineIndex].x_;
if (x + width > this->width()) {
return false;
}
int widthLeft = width;
int i = skylineIndex;
int y = skyline_[skylineIndex].y_;
while (widthLeft > 0 && i < (int)skyline_.size()) {
y = std::max(y, skyline_[i].y_);
if (y + height > this->height()) {
return false;
}
widthLeft -= skyline_[i].width_;
++i;
}
*ypos = y;
return true;
}
void SkylineRectanglePacker::addSkylineLevel(int skylineIndex,
int x,
int y,
int width,
int height) {
SkylineSegment newSegment;
newSegment.x_ = x;
newSegment.y_ = y + height;
newSegment.width_ = width;
skyline_.insert(std::next(skyline_.begin(), skylineIndex), newSegment);
FML_DCHECK(newSegment.x_ + newSegment.width_ <= this->width());
FML_DCHECK(newSegment.y_ <= this->height());
// delete width of the new skyline segment from following ones
for (int i = skylineIndex + 1; i < (int)skyline_.size(); ++i) {
// The new segment subsumes all or part of skyline_[i]
FML_DCHECK(skyline_[i - 1].x_ <= skyline_[i].x_);
if (skyline_[i].x_ < skyline_[i - 1].x_ + skyline_[i - 1].width_) {
int shrink = skyline_[i - 1].x_ + skyline_[i - 1].width_ - skyline_[i].x_;
skyline_[i].x_ += shrink;
skyline_[i].width_ -= shrink;
if (skyline_[i].width_ <= 0) {
// fully consumed, remove item at index i
skyline_.erase(std::next(skyline_.begin(), i));
--i;
} else {
// only partially consumed
break;
}
} else {
break;
}
}
// merge skyline_s
for (int i = 0; i < ((int)skyline_.size()) - 1; ++i) {
if (skyline_[i].y_ == skyline_[i + 1].y_) {
skyline_[i].width_ += skyline_[i + 1].width_;
skyline_.erase(std::next(skyline_.begin(), i));
--i;
}
}
}
RectanglePacker* RectanglePacker::Factory(int width, int height) {
return new SkylineRectanglePacker(width, height);
}
} // namespace impeller