private static @Nullable Pair<Pointer, Integer> _packItems(
    @NotNull List<TBItemScrubber.ItemData> items,
    int visibleItems, boolean withImages, boolean withText
  ) {
    if (items.isEmpty()) {
      return null;
    }

    long ptr = 0;
    try {
      // 1. calculate size
      int byteCount = 2; // first 2 bytes contains count of items
      int c = 0;
      for (TBItemScrubber.ItemData id : items) {
        if (c++ >= visibleItems) {
          byteCount += 6;
          continue;
        }
        final int textSize = 2 + (withText && id.getTextBytes() != null && id.getTextBytes().length > 0 ? id.getTextBytes().length + 1 : 0);
        byteCount += textSize;

        if (withImages
            && id.darkIcon == null
            && id.getIcon() != null
            && !(id.getIcon() instanceof EmptyIcon)
            && id.getIcon().getIconWidth() > 0
            && id.getIcon().getIconHeight() > 0
        ) {
          id.darkIcon = ReadAction.nonBlocking(() -> IconLoader.getDarkIcon(id.getIcon(), true)).executeSynchronously();
          if (id.darkIcon != null && (id.darkIcon.getIconWidth() <= 0 || id.darkIcon.getIconHeight() <= 0)) {
            LOG.debug("Can't obtain dark icon for scrubber item '%s', use default icon", id.getText());
            id.darkIcon = id.getIcon();
          }
        }

        if (withImages && id.darkIcon != null) {
          id.fMulX = getIconScaleForTouchbar(id.darkIcon);
          id.scaledWidth = Math.round(id.darkIcon.getIconWidth() * id.fMulX);
          id.scaledHeight = Math.round(id.darkIcon.getIconHeight() * id.fMulX);
          final int sizeInBytes = id.scaledWidth * id.scaledHeight * 4;
          final int totalSize = sizeInBytes + 4;
          byteCount += totalSize;
        }
        else {
          byteCount += 4;
        }
      }

      // 2. write items
      final Pointer result = new Pointer(ptr = Native.malloc(byteCount));
      result.setShort(0, (short)items.size());
      int offset = 2;
      c = 0;
      for (TBItemScrubber.ItemData id : items) {
        if (c++ >= visibleItems) {
          result.setShort(offset, (short)0);
          result.setShort(offset + 2, (short)0);
          result.setShort(offset + 4, (short)0);
          offset += 6;
          continue;
        }

        final byte[] txtBytes = withText ? id.getTextBytes() : null;
        if (txtBytes != null && txtBytes.length > 0) {
          result.setShort(offset, (short)txtBytes.length);
          offset += 2;
          result.write(offset, txtBytes, 0, txtBytes.length);
          offset += txtBytes.length;
          result.setByte(offset, (byte)0);
          offset += 1;
        }
        else {
          result.setShort(offset, (short)0);
          offset += 2;
        }

        if (withImages && id.darkIcon != null) {
          offset += _writeIconRaster(id.darkIcon, id.fMulX, result, offset, byteCount);
        }
        else {
          final boolean hasIcon = id.getIcon() != null &&
                                  !(id.getIcon() instanceof EmptyIcon) &&
                                  id.getIcon().getIconWidth() > 0 &&
                                  id.getIcon().getIconHeight() > 0;
          result.setShort(offset, hasIcon ? (short)1 : (short)0);
          result.setShort(offset + 2, (short)0);
          offset += 4;
        }
      }

      return Pair.create(result, byteCount);
    }
    catch (Throwable e) {
      if (ptr != 0) {
        Native.free(ptr);
      }
      LOG.debug(e);
      return null;
    }
  }
private static @Nullable Pair<Pointer, Integer> _packItems(
    @NotNull List<TBItemScrubber.ItemData> items,
    int visibleItems, boolean withImages, boolean withText
  )