/* BASIC2026 portable N-up and booklet imposition.  Source pages remain vector
 * PDF Form XObjects. */
#define B2026_PDF_IMP_MAX_SLOTS 16
typedef struct {
  int columns, rows, slots, auto_rotate, allow_upscale, border, column_order,
      right_binding;
  double sheet_w, sheet_h, margin, gap;
} B2026PdfImposeSettings;
typedef struct {
  int input_pages, output_pages, blank_slots, slots_per_sheet, verified;
} B2026PdfImposeStats;

static void b2026_pdf_imp_pair_number(const char *t, const char *jp,
                                      const char *en, double v) {
  if (t == NULL)
    return;
  hash_set_number(t, jp, v);
  hash_set_number(t, en, v);
}
static void b2026_pdf_imp_pair_string(const char *t, const char *jp,
                                      const char *en, const char *v) {
  if (t == NULL)
    return;
  hash_set_string(t, jp, v != NULL ? v : "");
  hash_set_string(t, en, v != NULL ? v : "");
}
static void b2026_pdf_imp_report(const char *t, const char *path, int ok,
                                 const char *mode,
                                 const B2026PdfImposeStats *s) {
  HashVariable *h;
  if (t == NULL || *t == '\0')
    return;
  h = ensure_local_hash(get_hash_frame(t), t);
  clear_hash_entries(h);
  b2026_pdf_imp_pair_number(t, "成功", "ok", ok ? 1.0 : 0.0);
  b2026_pdf_imp_pair_string(t, "保存先", "path",
                            ok && path != NULL ? path : "");
  b2026_pdf_imp_pair_string(t, "方式", "mode", mode != NULL ? mode : "");
  b2026_pdf_imp_pair_number(t, "入力ページ数", "inputPages",
                            s != NULL ? (double)s->input_pages : 0.0);
  b2026_pdf_imp_pair_number(t, "出力ページ数", "outputPages",
                            s != NULL ? (double)s->output_pages : 0.0);
  b2026_pdf_imp_pair_number(t, "1面ページ数", "pagesPerSheet",
                            s != NULL ? (double)s->slots_per_sheet : 0.0);
  b2026_pdf_imp_pair_number(t, "空白枠数", "blankSlots",
                            s != NULL ? (double)s->blank_slots : 0.0);
  b2026_pdf_imp_pair_string(t, "検証結果", "verification",
                            ok && s != NULL && s->verified ? "PASS" : "FAIL");
}

static int b2026_pdf_imp_bool(HashVariable *h, const char *jp, const char *en,
                              int fallback, int *out) {
  HashEntry *e = b2026_pdf_tf_entry(h, jp, en);
  if (e == NULL) {
    *out = fallback;
    return 1;
  }
  if (e->value.type != TYPE_NUMBER || !isfinite(e->value.val.number))
    return 0;
  *out = e->value.val.number != 0.0;
  return 1;
}
static int b2026_pdf_imp_int(HashVariable *h, const char *jp, const char *en,
                             int fallback, int lo, int hi, int *out) {
  double v = 0.0;
  int p = 0;
  if (!b2026_pdf_tf_number(h, jp, en, &v, &p))
    return 0;
  if (!p) {
    *out = fallback;
    return 1;
  }
  if (floor(v) != v || v < lo || v > hi)
    return 0;
  *out = (int)v;
  return 1;
}
static int b2026_pdf_imp_settings(HashVariable *h, int booklet,
                                  B2026PdfImposeSettings *s) {
  const char *paper, *orientation, *order, *binding, *unit;
  double factor = 72.0 / 25.4, w = 0.0, hh = 0.0, m = 10.0, g = 5.0;
  int pw = 0, ph = 0, pm = 0, pg = 0;
  memset(s, 0, sizeof(*s));
  s->columns = booklet ? 2 : 2;
  s->rows = 1;
  if (!booklet &&
      (!b2026_pdf_imp_int(h, "列", "columns", 2, 1, 16, &s->columns) ||
       !b2026_pdf_imp_int(h, "行", "rows", 1, 1, 16, &s->rows)))
    return 0;
  s->slots = s->columns * s->rows;
  if (s->slots < 1 || s->slots > B2026_PDF_IMP_MAX_SLOTS)
    return 0;
  if (!b2026_pdf_imp_bool(h, "自動回転", "autoRotate", 1, &s->auto_rotate) ||
      !b2026_pdf_imp_bool(h, "拡大許可", "allowUpscale", 1,
                          &s->allow_upscale) ||
      !b2026_pdf_imp_bool(h, "枠線", "border", 0, &s->border))
    return 0;
  unit = b2026_pdf_tf_text(h, "単位", "unit");
  if (unit != NULL) {
    if (b2026_strcasecmp(unit, "PT") == 0)
      factor = 1.0;
    else if (b2026_strcasecmp(unit, "MM") != 0)
      return 0;
  }
  if (!b2026_pdf_tf_number(h, "用紙横幅", "paperWidth", &w, &pw) ||
      !b2026_pdf_tf_number(h, "用紙縦幅", "paperHeight", &hh, &ph) ||
      !b2026_pdf_tf_number(h, "余白", "margin", &m, &pm) ||
      !b2026_pdf_tf_number(h, "間隔", "gap", &g, &pg))
    return 0;
  if (!pm)
    m = 10.0;
  if (!pg)
    g = 5.0;
  if (m < 0.0 || g < 0.0)
    return 0;
  paper = b2026_pdf_tf_text(h, "用紙", "paper");
  if (pw || ph) {
    if (!(pw && ph) || w <= 0.0 || hh <= 0.0)
      return 0;
    s->sheet_w = w * factor;
    s->sheet_h = hh * factor;
  } else if (paper != NULL && (b2026_strcasecmp(paper, "A3") == 0)) {
    s->sheet_w = 297.0 * factor;
    s->sheet_h = 420.0 * factor;
  } else if (paper != NULL && (b2026_strcasecmp(paper, "LETTER") == 0 ||
                               strcmp(paper, "レター") == 0)) {
    s->sheet_w = 215.9 * factor;
    s->sheet_h = 279.4 * factor;
  } else {
    s->sheet_w = 210.0 * factor;
    s->sheet_h = 297.0 * factor;
  }
  orientation = b2026_pdf_tf_text(h, "方向", "orientation");
  if (orientation == NULL || b2026_strcasecmp(orientation, "AUTO") == 0 ||
      strcmp(orientation, "自動") == 0) {
    if (booklet || s->columns > s->rows) {
      double z = s->sheet_w;
      s->sheet_w = s->sheet_h;
      s->sheet_h = z;
    }
  } else if (b2026_strcasecmp(orientation, "LANDSCAPE") == 0 ||
             strcmp(orientation, "横") == 0 ||
             strcmp(orientation, "横向き") == 0) {
    if (s->sheet_w < s->sheet_h) {
      double z = s->sheet_w;
      s->sheet_w = s->sheet_h;
      s->sheet_h = z;
    }
  } else if (b2026_strcasecmp(orientation, "PORTRAIT") == 0 ||
             strcmp(orientation, "縦") == 0 ||
             strcmp(orientation, "縦向き") == 0) {
    if (s->sheet_w > s->sheet_h) {
      double z = s->sheet_w;
      s->sheet_w = s->sheet_h;
      s->sheet_h = z;
    }
  } else
    return 0;
  s->margin = m * factor;
  s->gap = g * factor;
  if (s->sheet_w <= 2.0 * s->margin + (s->columns - 1) * s->gap ||
      s->sheet_h <= 2.0 * s->margin + (s->rows - 1) * s->gap)
    return 0;
  order = b2026_pdf_tf_text(h, "配置順", "order");
  if (order != NULL &&
      (b2026_strcasecmp(order, "COLUMN") == 0 || strcmp(order, "列") == 0 ||
       strcmp(order, "列優先") == 0))
    s->column_order = 1;
  else if (order != NULL && b2026_strcasecmp(order, "ROW") != 0 &&
           strcmp(order, "行") != 0 && strcmp(order, "行優先") != 0)
    return 0;
  binding = b2026_pdf_tf_text(h, "綴じ方向", "binding");
  if (binding != NULL &&
      (b2026_strcasecmp(binding, "RIGHT") == 0 || strcmp(binding, "右") == 0 ||
       strcmp(binding, "右綴じ") == 0))
    s->right_binding = 1;
  else if (binding != NULL && b2026_strcasecmp(binding, "LEFT") != 0 &&
           strcmp(binding, "左") != 0 && strcmp(binding, "左綴じ") != 0)
    return 0;
  return 1;
}

static int b2026_pdf_imp_push_index(int **a, int *n, int *cap, int v) {
  if (*n == *cap) {
    int nc = *cap ? *cap * 2 : 8;
    int *p = (int *)realloc(*a, (size_t)nc * sizeof(*p));
    if (p == NULL)
      return 0;
    *a = p;
    *cap = nc;
  }
  (*a)[(*n)++] = v;
  return 1;
}
static int b2026_pdf_imp_content_refs(B2026PdfDocument *d,
                                      const unsigned char *v, size_t n,
                                      int depth, int **out, int *count,
                                      int *cap) {
  size_t p = 0U;
  int num, gen, oi;
  if (depth > 16)
    return 0;
  p = b2026_pdf_tf_space(v, n, p);
  if (p >= n)
    return 1;
  if (b2026_pdf_tf_ref(v + p, n - p, &num, &gen) &&
      (oi = b2026_pdf_object_lookup(d, num, gen)) >= 0) {
    if (b2026_pdf_memfind(d->objects[oi].body, d->objects[oi].body_length,
                          "stream", 6U) != NULL)
      return b2026_pdf_imp_push_index(out, count, cap, oi);
    return b2026_pdf_imp_content_refs(d, d->objects[oi].body,
                                      d->objects[oi].body_length, depth + 1,
                                      out, count, cap);
  }
  if (v[p] == '[') {
    ++p;
    while ((p = b2026_pdf_tf_space(v, n, p)) < n && v[p] != ']') {
      size_t e = b2026_pdf_tf_value_end(v, n, p, 0);
      if (e <= p || !b2026_pdf_imp_content_refs(d, v + p, e - p, depth + 1, out,
                                                count, cap))
        return 0;
      p = e;
    }
    return 1;
  }
  if (b2026_pdf_memfind(v, n, "stream", 6U) != NULL)
    return 0;
  return 1;
}
static int b2026_pdf_imp_clone_content(B2026PdfDocument *d, int oi,
                                       const double box[4],
                                       const char *resources) {
  B2026PdfObject *o = &d->objects[oi];
  B2026PdfKeyRange r;
  B2026PdfTextBuffer b;
  char add[256];
  int number, index;
  memset(&b, 0, sizeof(b));
  if (!b2026_pdf_tf_find_key(o->body, o->body_length, "/__B2026_NEVER__", &r))
    return -1;
  snprintf(add, sizeof(add),
           " /Type /XObject /Subtype /Form /FormType 1 /BBox [%.17g %.17g "
           "%.17g %.17g] /Resources ",
           box[0], box[1], box[2], box[3]);
  if (!b2026_pdf_text_append_bytes(&b, (char *)o->body, r.dict_end) ||
      !b2026_pdf_text_append_bytes(&b, add, strlen(add)) ||
      !b2026_pdf_text_append_bytes(&b, resources, strlen(resources)) ||
      !b2026_pdf_text_append_char(&b, ' ') ||
      !b2026_pdf_text_append_bytes(&b, (char *)o->body + r.dict_end,
                                   o->body_length - r.dict_end))
    goto fail;
  number = b2026_pdf_tf_next_old(d);
  if (!b2026_pdf_add_object(d, number, 0, (unsigned char *)b.data, b.used))
    goto fail;
  free(b.data);
  index = b2026_pdf_object_lookup(d, number, 0);
  return index;
fail:
  free(b.data);
  return -1;
}
static int b2026_pdf_imp_page_form(B2026PdfDocument *d, int page, double *dw,
                                   double *dh) {
  B2026PdfObject *p = &d->objects[page];
  B2026PdfKeyRange r;
  const unsigned char *rv;
  size_t rn;
  char *resources = NULL;
  int *refs = NULL, count = 0, cap = 0, i, wrapper = -1, rot;
  double box[4];
  B2026PdfTextBuffer content, xobj, res;
  char line[256], bbox[128];
  memset(&content, 0, sizeof(content));
  memset(&xobj, 0, sizeof(xobj));
  memset(&res, 0, sizeof(res));
  if (!b2026_pdf_page_box(d, page, "/CropBox", box) &&
      !b2026_pdf_page_box(d, page, "/MediaBox", box))
    goto fail;
  if (b2026_pdf_tf_inherited_value(d, page, "/Resources", &rv, &rn))
    resources = b2026_pdf_tf_value_copy(d, rv, rn, 1);
  else
    resources = b2026_strdup_simple("<< >>");
  if (resources == NULL)
    goto fail;
  if (!b2026_pdf_tf_find_key(p->body, p->body_length, "/Contents", &r))
    goto fail;
  if (r.found && !b2026_pdf_imp_content_refs(d, p->body + r.value_start,
                                             r.value_end - r.value_start, 0,
                                             &refs, &count, &cap))
    goto fail;
  if (!b2026_pdf_text_append_bytes(&xobj, "<<", 2U))
    goto fail;
  for (i = 0; i < count; ++i) {
    int ci = b2026_pdf_imp_clone_content(d, refs[i], box, resources);
    if (ci < 0)
      goto fail;
    snprintf(line, sizeof(line), " /C%d %d 0 R", i, d->objects[ci].old_number);
    if (!b2026_pdf_text_append_bytes(&xobj, line, strlen(line)))
      goto fail;
  }
  if (!b2026_pdf_text_append_bytes(&xobj, " >>", 3U))
    goto fail;
  rot =
      ((b2026_pdf_page_inherited_integer(d, page, "/Rotate", 0) % 360) + 360) %
      360;
  if (rot == 90 || rot == 270) {
    *dw = box[3] - box[1];
    *dh = box[2] - box[0];
  } else {
    *dw = box[2] - box[0];
    *dh = box[3] - box[1];
  }
  if (*dw <= 0.0 || *dh <= 0.0)
    goto fail;
  if (rot == 90)
    snprintf(line, sizeof(line), "q 0 1 -1 0 %.17g %.17g cm", box[3], -box[0]);
  else if (rot == 180)
    snprintf(line, sizeof(line), "q -1 0 0 -1 %.17g %.17g cm", box[2], box[3]);
  else if (rot == 270)
    snprintf(line, sizeof(line), "q 0 -1 1 0 %.17g %.17g cm", -box[1], box[2]);
  else
    snprintf(line, sizeof(line), "q 1 0 0 1 %.17g %.17g cm", -box[0], -box[1]);
  if (!b2026_pdf_text_append_bytes(&content, line, strlen(line)))
    goto fail;
  for (i = 0; i < count; ++i) {
    snprintf(line, sizeof(line), " /C%d Do", i);
    if (!b2026_pdf_text_append_bytes(&content, line, strlen(line)))
      goto fail;
  }
  if (!b2026_pdf_text_append_bytes(&content, " Q", 2U))
    goto fail;
  wrapper =
      b2026_pdf_tf_add_stream(d, content.data != NULL ? content.data : "");
  if (wrapper < 0)
    goto fail;
  snprintf(bbox, sizeof(bbox), "[0 0 %.17g %.17g]", *dw, *dh);
  if (!b2026_pdf_tf_set_key(d, wrapper, "/Type", "/XObject", 0) ||
      !b2026_pdf_tf_set_key(d, wrapper, "/Subtype", "/Form", 0) ||
      !b2026_pdf_tf_set_key(d, wrapper, "/FormType", "1", 0)) {
    wrapper = -1;
    goto fail;
  }
  if (!b2026_pdf_tf_set_key(d, wrapper, "/BBox", bbox, 0)) {
    wrapper = -1;
    goto fail;
  }
  if (!b2026_pdf_text_append_bytes(&res, "<< /XObject ",
                                   strlen("<< /XObject ")) ||
      !b2026_pdf_text_append_bytes(&res, xobj.data, xobj.used) ||
      !b2026_pdf_text_append_bytes(&res, " >>", 3U) ||
      !b2026_pdf_tf_set_key(d, wrapper, "/Resources", res.data, 0)) {
    wrapper = -1;
    goto fail;
  }
  free(resources);
  free(refs);
  free(content.data);
  free(xobj.data);
  free(res.data);
  return wrapper;
fail:
  free(resources);
  free(refs);
  free(content.data);
  free(xobj.data);
  free(res.data);
  return -1;
}

static int b2026_pdf_imp_generated_page(B2026PdfDocument *d,
                                        B2026PdfPageList *pages,
                                        const B2026PdfImposeSettings *s,
                                        const int *map, const int *forms,
                                        const double *fw, const double *fh,
                                        B2026PdfImposeStats *st) {
  B2026PdfTextBuffer content, xobj, page_body;
  char line[512];
  int slot, stream, page;
  double cw = (s->sheet_w - 2.0 * s->margin - (s->columns - 1) * s->gap) /
              s->columns,
         ch = (s->sheet_h - 2.0 * s->margin - (s->rows - 1) * s->gap) / s->rows;
  memset(&content, 0, sizeof(content));
  memset(&xobj, 0, sizeof(xobj));
  memset(&page_body, 0, sizeof(page_body));
  if (!b2026_pdf_text_append_bytes(&xobj, "<<", 2U))
    goto fail;
  for (slot = 0; slot < s->slots; ++slot) {
    int src = map[slot], col, row, rotate = 0;
    if (s->column_order) {
      col = slot / s->rows;
      row = slot % s->rows;
    } else {
      row = slot / s->columns;
      col = slot % s->columns;
    }
    if (src < 0) {
      ++st->blank_slots;
      continue;
    }
    if (s->right_binding && s->slots == 2)
      col = 1 - col;
    {
      double x = s->margin + col * (cw + s->gap),
             y = s->margin + (s->rows - 1 - row) * (ch + s->gap),
             a = fmin(cw / fw[src], ch / fh[src]),
             ar = fmin(cw / fh[src], ch / fw[src]), scale, tx, ty;
      if (s->auto_rotate && ar > a) {
        rotate = 1;
        scale = ar;
      } else
        scale = a;
      if (!s->allow_upscale && scale > 1.0)
        scale = 1.0;
      if (rotate) {
        double rw = fh[src] * scale, rh = fw[src] * scale;
        tx = x + (cw + rw) * 0.5;
        ty = y + (ch - rh) * 0.5;
        snprintf(line, sizeof(line),
                 "q 0 %.17g %.17g 0 %.17g %.17g cm /P%d Do Q\n", scale, -scale,
                 tx, ty + rh, slot);
      } else {
        double rw = fw[src] * scale, rh = fh[src] * scale;
        tx = x + (cw - rw) * 0.5;
        ty = y + (ch - rh) * 0.5;
        snprintf(line, sizeof(line),
                 "q %.17g 0 0 %.17g %.17g %.17g cm /P%d Do Q\n", scale, scale,
                 tx, ty, slot);
      }
      if (!b2026_pdf_text_append_bytes(&content, line, strlen(line)))
        goto fail;
      if (s->border) {
        snprintf(line, sizeof(line),
                 "q 0 0 0 RG 0.5 w %.17g %.17g %.17g %.17g re S Q\n", x, y, cw,
                 ch);
        if (!b2026_pdf_text_append_bytes(&content, line, strlen(line)))
          goto fail;
      }
      snprintf(line, sizeof(line), " /P%d %d 0 R", slot,
               d->objects[forms[src]].old_number);
      if (!b2026_pdf_text_append_bytes(&xobj, line, strlen(line)))
        goto fail;
    }
  }
  if (!b2026_pdf_text_append_bytes(&xobj, " >>", 3U))
    goto fail;
  stream = b2026_pdf_tf_add_stream(d, content.data != NULL ? content.data : "");
  if (stream < 0)
    goto fail;
  snprintf(line, sizeof(line),
           "<< /Type /Page /Parent 0 0 R /MediaBox [0 0 %.17g %.17g] /CropBox "
           "[0 0 %.17g %.17g] /Resources << /XObject ",
           s->sheet_w, s->sheet_h, s->sheet_w, s->sheet_h);
  if (!b2026_pdf_text_append_bytes(&page_body, line, strlen(line)) ||
      !b2026_pdf_text_append_bytes(&page_body, xobj.data, strlen(xobj.data)))
    goto fail;
  snprintf(line, sizeof(line), " >> /Contents %d 0 R >>",
           d->objects[stream].old_number);
  if (!b2026_pdf_text_append_bytes(&page_body, line, strlen(line)))
    goto fail;
  page = b2026_pdf_tf_add_body(d, page_body.data);
  if (page < 0 || !b2026_pdf_pages_push(pages, d, page, -1))
    goto fail;
  free(content.data);
  free(xobj.data);
  free(page_body.data);
  return 1;
fail:
  free(content.data);
  free(xobj.data);
  free(page_body.data);
  return 0;
}

static int b2026_pdf_imp_verify(const char *path, int pages, double w,
                                double h) {
  B2026PdfDocument d;
  int rc, i;
  memset(&d, 0, sizeof(d));
  rc = b2026_pdf_load(&d, path, 0);
  if (rc <= 0 || d.page_count != pages)
    goto fail;
  for (i = 0; i < d.page_count; ++i) {
    double b[4];
    if (!b2026_pdf_page_box(&d, d.page_objects[i], "/MediaBox", b) ||
        fabs((b[2] - b[0]) - w) > 0.05 || fabs((b[3] - b[1]) - h) > 0.05)
      goto fail;
  }
  b2026_pdf_document_free(&d);
  return 1;
fail:
  b2026_pdf_document_free(&d);
  return 0;
}

static const char *b2026_pdf_impose(const char *target, const char *operation,
                                    int argc, const B2026PdfArg *args) {
  int booklet = strcmp(operation, "BOOKLET") == 0, rc, i, side, total_padded,
      sheets;
  const char *source, *result;
  char *output = NULL, *stage = NULL;
  HashVariable *h = NULL;
  B2026PdfDocument doc;
  B2026PdfPageList pages;
  B2026PdfImposeSettings set;
  B2026PdfImposeStats stats;
  int *forms = NULL;
  double *fw = NULL, *fh = NULL;
  memset(&doc, 0, sizeof(doc));
  memset(&pages, 0, sizeof(pages));
  memset(&stats, 0, sizeof(stats));
  if ((argc != 2 && argc != 3) || args[0].kind != 1 || args[0].text == NULL ||
      args[1].kind != 1 || args[1].text == NULL ||
      (argc == 3 && !b2026_pdf_tf_hash(&args[2], &h))) {
    b2026_pdf_imp_report(target, "", 0, operation, &stats);
    return b2026_pdf_fail(
        4440, "",
        "NUP/BOOKLET requires output$, source$ [, settings{}] / "
        "Nアップ・冊子配置の引数が正しくありません");
  }
  source = args[1].text;
  output = b2026_pdf_output_path(args[0].text);
  if (output == NULL)
    goto memory_fail;
  if (!b2026_pdf_imp_settings(h, booklet, &set)) {
    b2026_pdf_fail(
        4441, source,
        "invalid imposition settings / 面付け設定が正しくありません");
    goto fail;
  }
  rc = b2026_pdf_load(&doc, source, 4);
  if (rc <= 0) {
    b2026_pdf_fail(4442, source, b2026_pdf_load_error(rc));
    goto fail;
  }
  if (b2026_pdf_memfind(doc.bytes, doc.length, "/ByteRange", 10U) != NULL) {
    b2026_pdf_fail(4443, source,
                   "digitally signed PDF cannot be imposed / "
                   "電子署名付きPDFは面付けできません");
    goto fail;
  }
  stats.input_pages = doc.page_count;
  stats.slots_per_sheet = set.slots;
  forms = (int *)malloc((size_t)doc.page_count * sizeof(*forms));
  fw = (double *)malloc((size_t)doc.page_count * sizeof(*fw));
  fh = (double *)malloc((size_t)doc.page_count * sizeof(*fh));
  if (forms == NULL || fw == NULL || fh == NULL)
    goto memory_fail;
  for (i = 0; i < doc.page_count; ++i) {
    forms[i] =
        b2026_pdf_imp_page_form(&doc, doc.page_objects[i], &fw[i], &fh[i]);
    if (forms[i] < 0) {
      b2026_pdf_fail(
          4444, source,
          "page content streams could not be converted to vector forms / "
          "ページ内容をベクター面付け用フォームへ変換できません");
      goto fail;
    }
  }
  if (booklet) {
    int map[2];
    total_padded = ((doc.page_count + 3) / 4) * 4;
    sheets = total_padded / 4;
    set.slots = 2;
    set.columns = 2;
    set.rows = 1;
    stats.slots_per_sheet = 2;
    for (i = 0; i < sheets; ++i) {
      int a = total_padded - 2 * i, b = 1 + 2 * i, c = 2 + 2 * i,
          e = total_padded - 1 - 2 * i;
      map[0] = a <= doc.page_count ? a - 1 : -1;
      map[1] = b <= doc.page_count ? b - 1 : -1;
      if (!b2026_pdf_imp_generated_page(&doc, &pages, &set, map, forms, fw, fh,
                                        &stats))
        goto memory_fail;
      map[0] = c <= doc.page_count ? c - 1 : -1;
      map[1] = e <= doc.page_count ? e - 1 : -1;
      if (!b2026_pdf_imp_generated_page(&doc, &pages, &set, map, forms, fw, fh,
                                        &stats))
        goto memory_fail;
    }
  } else {
    int *map = (int *)malloc((size_t)set.slots * sizeof(*map));
    if (map == NULL)
      goto memory_fail;
    for (side = 0; side < (doc.page_count + set.slots - 1) / set.slots;
         ++side) {
      for (i = 0; i < set.slots; ++i) {
        int p = side * set.slots + i;
        map[i] = p < doc.page_count ? p : -1;
      }
      if (!b2026_pdf_imp_generated_page(&doc, &pages, &set, map, forms, fw, fh,
                                        &stats)) {
        free(map);
        goto memory_fail;
      }
    }
    free(map);
  }
  stats.output_pages = pages.count;
  stage = b2026_atomic_temp_path(output);
  if (stage == NULL ||
      !b2026_pdf_write_output(stage, &doc, 1, &pages, 0, 1, 0)) {
    b2026_pdf_fail(4445, output,
                   "imposed PDF could not be staged or exceeded 2 GiB / "
                   "面付けPDFを準備できないか2GiB上限を超えました");
    goto fail;
  }
  if (!b2026_pdf_imp_verify(stage, pages.count, set.sheet_w, set.sheet_h)) {
    b2026_pdf_fail(4446, output,
                   "post-write imposition verification failed / "
                   "面付け後の検証に失敗しました");
    goto fail;
  }
  stats.verified = 1;
  if (!b2026_atomic_commit_file(stage, output)) {
    b2026_pdf_fail(4447, output,
                   "verified PDF could not be committed / "
                   "検証済みPDFを保存先へ反映できません");
    goto fail;
  }
  free(stage);
  stage = NULL;
  b2026_pdf_imp_report(target, output, 1, operation, &stats);
  result = runtime_temp_string_copy(output);
  free(output);
  free(forms);
  free(fw);
  free(fh);
  free(pages.items);
  b2026_pdf_document_free(&doc);
  return result;
memory_fail:
  b2026_pdf_fail(
      4448, source,
      "out of memory while imposing PDF / PDF面付け中にメモリ不足になりました");
fail:
  if (stage != NULL) {
    b2026_atomic_discard_file(stage);
    free(stage);
  }
  b2026_pdf_imp_report(target, "", 0, operation, &stats);
  free(output);
  free(forms);
  free(fw);
  free(fh);
  free(pages.items);
  b2026_pdf_document_free(&doc);
  return runtime_temp_string_copy("");
}
