function createPackerProcessor(isWorker) {
    var FILL_GAPS = 1;
    var HORIZONTAL = 2;
    var ALIGN_RIGHT = 4;
    var ALIGN_BOTTOM = 8;
    var ROUNDING = 16;

    var EPS = 0.001;
    var MIN_SLOT_SIZE = 0.5;

    // Rounds number first to three decimal precision and then floors the result
    // to two decimal precision.
    // Math.floor(Math.round(number * 1000) / 10) / 100
    function roundNumber(number) {
      return ((((number * 1000 + 0.5) << 0) / 10) << 0) / 100;
    }

    /**
     * @class
     */
    function PackerProcessor() {
      this.currentRects = [];
      this.nextRects = [];
      this.rectTarget = {};
      this.rectStore = [];
      this.slotSizes = [];
      this.rectId = 0;
      this.slotIndex = -1;
      this.slotData = { left: 0, top: 0, width: 0, height: 0 };
      this.sortRectsLeftTop = this.sortRectsLeftTop.bind(this);
      this.sortRectsTopLeft = this.sortRectsTopLeft.bind(this);
    }

    /**
     * Takes a layout object as an argument and computes positions (slots) for the
     * layout items. Also computes the final width and height of the layout. The
     * provided layout object's slots array is mutated as well as the width and
     * height properties.
     *
     * @param {Object} layout
     * @param {Number} layout.width
     *   - The start (current) width of the layout in pixels.
     * @param {Number} layout.height
     *   - The start (current) height of the layout in pixels.
     * @param {(Item[]|Number[])} layout.items
     *   - List of Muuri.Item instances or a list of item dimensions
     *     (e.g [ item1Width, item1Height, item2Width, item2Height, ... ]).
     * @param {(Array|Float32Array)} layout.slots
     *   - An Array/Float32Array instance which's length should equal to
     *     the amount of items times two. The position (width and height) of each
     *     item will be written into this array.
     * @param {Number} settings
     *   - The layout's settings as bitmasks.
     * @returns {Object}
     */
    PackerProcessor.prototype.computeLayout = function (layout, settings) {
      var items = layout.items;
      var slots = layout.slots;
      var fillGaps = !!(settings & FILL_GAPS);
      var horizontal = !!(settings & HORIZONTAL);
      var alignRight = !!(settings & ALIGN_RIGHT);
      var alignBottom = !!(settings & ALIGN_BOTTOM);
      var rounding = !!(settings & ROUNDING);
      var isPreProcessed = typeof items[0] === 'number';
      var i, bump, item, slotWidth, slotHeight, slot;

      // No need to go further if items do not exist.
      if (!items.length) return layout;

      // Compute slots for the items.
      bump = isPreProcessed ? 2 : 1;
      for (i = 0; i < items.length; i += bump) {
        // If items are pre-processed it means that items array contains only
        // the raw dimensions of the items. Otherwise we assume it is an array
        // of normal Muuri items.
        if (isPreProcessed) {
          slotWidth = items[i];
          slotHeight = items[i + 1];
        } else {
          item = items[i];
          slotWidth = item._width + item._marginLeft + item._marginRight;
          slotHeight = item._height + item._marginTop + item._marginBottom;
        }

        // If rounding is enabled let's round the item's width and height to
        // make the layout algorithm a bit more stable. This has a performance
        // cost so don't use this if not necessary.
        if (rounding) {
          slotWidth = roundNumber(slotWidth);
          slotHeight = roundNumber(slotHeight);
        }

        // Get slot data.
        slot = this.computeNextSlot(layout, slotWidth, slotHeight, fillGaps, horizontal);

        // Update layout width/height.
        if (horizontal) {
          if (slot.left + slot.width > layout.width) {
            layout.width = slot.left + slot.width;
          }
        } else {
          if (slot.top + slot.height > layout.height) {
            layout.height = slot.top + slot.height;
          }
        }

        // Add item slot data to layout slots.
        slots[++this.slotIndex] = slot.left;
        slots[++this.slotIndex] = slot.top;

        // Store the size too (for later usage) if needed.
        if (alignRight || alignBottom) {
          this.slotSizes.push(slot.width, slot.height);
        }
      }

      // If the alignment is set to right we need to adjust the results.
      if (alignRight) {
        for (i = 0; i < slots.length; i += 2) {
          slots[i] = layout.width - (slots[i] + this.slotSizes[i]);
        }
      }

      // If the alignment is set to bottom we need to adjust the results.
      if (alignBottom) {
        for (i = 1; i < slots.length; i += 2) {
          slots[i] = layout.height - (slots[i] + this.slotSizes[i]);
        }
      }

      // Reset stuff.
      this.slotSizes.length = 0;
      this.currentRects.length = 0;
      this.nextRects.length = 0;
      this.rectStore.length = 0;
      this.rectId = 0;
      this.slotIndex = -1;

      return layout;
    };

    /**
     * Calculate next slot in the layout. Returns a slot object with position and
     * dimensions data. The returned object is reused between calls.
     *
     * @param {Object} layout
     * @param {Number} slotWidth
     * @param {Number} slotHeight
     * @param {Boolean} fillGaps
     * @param {Boolean} horizontal
     * @returns {Object}
     */
    PackerProcessor.prototype.computeNextSlot = function (
      layout,
      slotWidth,
      slotHeight,
      fillGaps,
      horizontal
    ) {
      var slot = this.slotData;
      var currentRects = this.currentRects;
      var nextRects = this.nextRects;
      var ignoreCurrentRects = false;
      var rect;
      var rectId;
      var shards;
      var i;
      var j;

      // Reset new slots.
      nextRects.length = 0;

      // Set item slot initial data.
      slot.left = null;
      slot.top = null;
      slot.width = slotWidth;
      slot.height = slotHeight;

      // Try to find position for the slot from the existing free spaces in the
      // layout.
      for (i = 0; i < currentRects.length; i++) {
        rectId = currentRects[i];
        if (!rectId) continue;
        rect = this.getRect(rectId);
        if (slot.width <= rect.width + EPS && slot.height <= rect.height + EPS) {
          slot.left = rect.left;
          slot.top = rect.top;
          break;
        }
      }

      // If no position was found for the slot let's position the slot to
      // the bottom left (in vertical mode) or top right (in horizontal mode) of
      // the layout.
      if (slot.left === null) {
        if (horizontal) {
          slot.left = layout.width;
          slot.top = 0;
        } else {
          slot.left = 0;
          slot.top = layout.height;
        }

        // If gaps don't need filling let's throw away all the current free spaces
        // (currentRects).
        if (!fillGaps) {
          ignoreCurrentRects = true;
        }
      }

      // In vertical mode, if the slot's bottom overlaps the layout's bottom.
      if (!horizontal && slot.top + slot.height > layout.height + EPS) {
        // If slot is not aligned to the left edge, create a new free space to the
        // left of the slot.
        if (slot.left > MIN_SLOT_SIZE) {
          nextRects.push(this.addRect(0, layout.height, slot.left, Infinity));
        }

        // If slot is not aligned to the right edge, create a new free space to
        // the right of the slot.
        if (slot.left + slot.width < layout.width - MIN_SLOT_SIZE) {
          nextRects.push(
            this.addRect(
              slot.left + slot.width,
              layout.height,
              layout.width - slot.left - slot.width,
              Infinity
            )
          );
        }

        // Update layout height.
        layout.height = slot.top + slot.height;
      }

      // In horizontal mode, if the slot's right overlaps the layout's right edge.
      if (horizontal && slot.left + slot.width > layout.width + EPS) {
        // If slot is not aligned to the top, create a new free space above the
        // slot.
        if (slot.top > MIN_SLOT_SIZE) {
          nextRects.push(this.addRect(layout.width, 0, Infinity, slot.top));
        }

        // If slot is not aligned to the bottom, create a new free space below
        // the slot.
        if (slot.top + slot.height < layout.height - MIN_SLOT_SIZE) {
          nextRects.push(
            this.addRect(
              layout.width,
              slot.top + slot.height,
              Infinity,
              layout.height - slot.top - slot.height
            )
          );
        }

        // Update layout width.
        layout.width = slot.left + slot.width;
      }

      // Clean up the current free spaces making sure none of them overlap with
      // the slot. Split all overlapping free spaces into smaller shards that do
      // not overlap with the slot.
      if (!ignoreCurrentRects) {
        if (fillGaps) i = 0;
        for (; i < currentRects.length; i++) {
          rectId = currentRects[i];
          if (!rectId) continue;
          rect = this.getRect(rectId);
          shards = this.splitRect(rect, slot);
          for (j = 0; j < shards.length; j++) {
            rectId = shards[j];
            rect = this.getRect(rectId);
            // Make sure that the free space is within the boundaries of the
            // layout. This routine is critical to the algorithm as it makes sure
            // that there are no leftover spaces with infinite height/width.
            // It's also essential that we don't compare values absolutely to each
            // other but leave a little headroom (EPSILON) to get rid of false
            // positives.
            if (
              horizontal ? rect.left + EPS < layout.width - EPS : rect.top + EPS < layout.height - EPS
            ) {
              nextRects.push(rectId);
            }
          }
        }
      }

      // Sanitize and sort all the new free spaces that will be used in the next
      // iteration. This procedure is critical to make the bin-packing algorithm
      // work. The free spaces have to be in correct order in the beginning of the
      // next iteration.
      if (nextRects.length > 1) {
        this.purgeRects(nextRects).sort(horizontal ? this.sortRectsLeftTop : this.sortRectsTopLeft);
      }

      // Finally we need to make sure that `this.currentRects` points to
      // `nextRects` array as that is used in the next iteration's beginning when
      // we try to find a space for the next slot.
      this.currentRects = nextRects;
      this.nextRects = currentRects;

      return slot;
    };

    /**
     * Add a new rectangle to the rectangle store. Returns the id of the new
     * rectangle.
     *
     * @param {Number} left
     * @param {Number} top
     * @param {Number} width
     * @param {Number} height
     * @returns {Number}
     */
    PackerProcessor.prototype.addRect = function (left, top, width, height) {
      var rectId = ++this.rectId;
      this.rectStore[rectId] = left || 0;
      this.rectStore[++this.rectId] = top || 0;
      this.rectStore[++this.rectId] = width || 0;
      this.rectStore[++this.rectId] = height || 0;
      return rectId;
    };

    /**
     * Get rectangle data from the rectangle store by id. Optionally you can
     * provide a target object where the rectangle data will be written in. By
     * default an internal object is reused as a target object.
     *
     * @param {Number} id
     * @param {Object} [target]
     * @returns {Object}
     */
    PackerProcessor.prototype.getRect = function (id, target) {
      if (!target) target = this.rectTarget;
      target.left = this.rectStore[id] || 0;
      target.top = this.rectStore[++id] || 0;
      target.width = this.rectStore[++id] || 0;
      target.height = this.rectStore[++id] || 0;
      return target;
    };

    /**
     * Punch a hole into a rectangle and return the shards (1-4).
     *
     * @param {Object} rect
     * @param {Object} hole
     * @returns {Number[]}
     */
    PackerProcessor.prototype.splitRect = (function () {
      var shards = [];
      var width = 0;
      var height = 0;
      return function (rect, hole) {
        // Reset old shards.
        shards.length = 0;

        // If the slot does not overlap with the hole add slot to the return data
        // as is. Note that in this case we are eager to keep the slot as is if
        // possible so we use the EPSILON in favour of that logic.
        if (
          rect.left + rect.width <= hole.left + EPS ||
          hole.left + hole.width <= rect.left + EPS ||
          rect.top + rect.height <= hole.top + EPS ||
          hole.top + hole.height <= rect.top + EPS
        ) {
          shards.push(this.addRect(rect.left, rect.top, rect.width, rect.height));
          return shards;
        }

        // Left split.
        width = hole.left - rect.left;
        if (width >= MIN_SLOT_SIZE) {
          shards.push(this.addRect(rect.left, rect.top, width, rect.height));
        }

        // Right split.
        width = rect.left + rect.width - (hole.left + hole.width);
        if (width >= MIN_SLOT_SIZE) {
          shards.push(this.addRect(hole.left + hole.width, rect.top, width, rect.height));
        }

        // Top split.
        height = hole.top - rect.top;
        if (height >= MIN_SLOT_SIZE) {
          shards.push(this.addRect(rect.left, rect.top, rect.width, height));
        }

        // Bottom split.
        height = rect.top + rect.height - (hole.top + hole.height);
        if (height >= MIN_SLOT_SIZE) {
          shards.push(this.addRect(rect.left, hole.top + hole.height, rect.width, height));
        }

        return shards;
      };
    })();

    /**
     * Check if a rectangle is fully within another rectangle.
     *
     * @param {Object} a
     * @param {Object} b
     * @returns {Boolean}
     */
    PackerProcessor.prototype.isRectAWithinRectB = function (a, b) {
      return (
        a.left + EPS >= b.left &&
        a.top + EPS >= b.top &&
        a.left + a.width - EPS <= b.left + b.width &&
        a.top + a.height - EPS <= b.top + b.height
      );
    };

    /**
     * Loops through an array of rectangle ids and resets all that are fully
     * within another rectangle in the array. Resetting in this case means that
     * the rectangle id value is replaced with zero.
     *
     * @param {Number[]} rectIds
     * @returns {Number[]}
     */
    PackerProcessor.prototype.purgeRects = (function () {
      var rectA = {};
      var rectB = {};
      return function (rectIds) {
        var i = rectIds.length;
        var j;

        while (i--) {
          j = rectIds.length;
          if (!rectIds[i]) continue;
          this.getRect(rectIds[i], rectA);
          while (j--) {
            if (!rectIds[j] || i === j) continue;
            this.getRect(rectIds[j], rectB);
            if (this.isRectAWithinRectB(rectA, rectB)) {
              rectIds[i] = 0;
              break;
            }
          }
        }

        return rectIds;
      };
    })();

    /**
     * Sort rectangles with top-left gravity.
     *
     * @param {Number} aId
     * @param {Number} bId
     * @returns {Number}
     */
    PackerProcessor.prototype.sortRectsTopLeft = (function () {
      var rectA = {};
      var rectB = {};
      return function (aId, bId) {
        this.getRect(aId, rectA);
        this.getRect(bId, rectB);

        return rectA.top < rectB.top && rectA.top + EPS < rectB.top
          ? -1
          : rectA.top > rectB.top && rectA.top - EPS > rectB.top
          ? 1
          : rectA.left < rectB.left && rectA.left + EPS < rectB.left
          ? -1
          : rectA.left > rectB.left && rectA.left - EPS > rectB.left
          ? 1
          : 0;
      };
    })();

    /**
     * Sort rectangles with left-top gravity.
     *
     * @param {Number} aId
     * @param {Number} bId
     * @returns {Number}
     */
    PackerProcessor.prototype.sortRectsLeftTop = (function () {
      var rectA = {};
      var rectB = {};
      return function (aId, bId) {
        this.getRect(aId, rectA);
        this.getRect(bId, rectB);
        return rectA.left < rectB.left && rectA.left + EPS < rectB.left
          ? -1
          : rectA.left > rectB.left && rectA.left - EPS < rectB.left
          ? 1
          : rectA.top < rectB.top && rectA.top + EPS < rectB.top
          ? -1
          : rectA.top > rectB.top && rectA.top - EPS > rectB.top
          ? 1
          : 0;
      };
    })();

    if (isWorker) {
      var PACKET_INDEX_WIDTH = 1;
      var PACKET_INDEX_HEIGHT = 2;
      var PACKET_INDEX_OPTIONS = 3;
      var PACKET_HEADER_SLOTS = 4;
      var processor = new PackerProcessor();

      self.onmessage = function (msg) {
        var data = new Float32Array(msg.data);
        var items = data.subarray(PACKET_HEADER_SLOTS, data.length);
        var slots = new Float32Array(items.length);
        var settings = data[PACKET_INDEX_OPTIONS];
        var layout = {
          items: items,
          slots: slots,
          width: data[PACKET_INDEX_WIDTH],
          height: data[PACKET_INDEX_HEIGHT],
        };

        // Compute the layout (width / height / slots).
        processor.computeLayout(layout, settings);

        // Copy layout data to the return data.
        data[PACKET_INDEX_WIDTH] = layout.width;
        data[PACKET_INDEX_HEIGHT] = layout.height;
        data.set(layout.slots, PACKET_HEADER_SLOTS);

        // Send layout back to the main thread.
        postMessage(data.buffer, [data.buffer]);
      };
    }

    return PackerProcessor;
  }
Destroy item instance.

@private
@param {Boolean} [removeElement=false]