/* BASIC2026 portable PDF page editor.
 * The editor rebuilds a conventional cross-reference table and preserves
 * page content streams byte-for-byte.  It intentionally has no dependency
 * on an OS PDF framework or an external command. */
typedef struct {
    int kind;                 /* 0 number, 1 text, 2 whole string array */
    const char *text;
    double number;
    const char *array_name;
    const char *hash_name;
} B2026PdfArg;

typedef struct {
    int old_number;
    int old_generation;
    int new_number;
    unsigned char *body;
    size_t body_length;
    int is_page;
    int selected;
    int rotation_override;
    int page_target_number;
    int included;
} B2026PdfObject;

typedef struct {
    char *path;
    unsigned char *bytes;
    size_t length;
    B2026PdfObject *objects;
    int object_count;
    int object_capacity;
    int *page_objects;
    int page_count;
    int page_capacity;
    int version_major;
    int version_minor;
    int info_object;
    int metadata_object;
    int root_object;
} B2026PdfDocument;

typedef struct {
    B2026PdfDocument *doc;
    int object_index;         /* -1 for a generated blank page */
    int rotation_override;
    double media[4];
    int has_media;
    double crop[4];
    int has_crop;
    int output_number;
} B2026PdfPage;

typedef struct {
    B2026PdfPage *items;
    int count;
    int capacity;
} B2026PdfPageList;

static const unsigned long long B2026_PDF_EDIT_MAX_BYTES = UINT64_C(2147483648);

static const unsigned char *b2026_pdf_memfind(const unsigned char *p, size_t n,
                                               const char *needle, size_t needle_len) {
    size_t i;
    if (needle_len == 0U) return p;
    if (n < needle_len) return NULL;
    for (i = 0; i <= n - needle_len; ++i)
        if (p[i] == (unsigned char)needle[0] && memcmp(p + i, needle, needle_len) == 0) return p + i;
    return NULL;
}

static int b2026_pdf_is_delim(unsigned char c) {
    return c == 0 || isspace(c) || c == '/' || c == '<' || c == '>' || c == '[' || c == ']' ||
           c == '(' || c == ')' || c == '%';
}

static int b2026_pdf_hex_value(unsigned char c) {
    if (c >= '0' && c <= '9') return (int)(c - '0');
    if (c >= 'A' && c <= 'F') return (int)(c - 'A') + 10;
    if (c >= 'a' && c <= 'f') return (int)(c - 'a') + 10;
    return -1;
}

/* Compare a PDF dictionary name without depending on cosmetic whitespace.
 * PDF names delimit one another, so both `/Type /Catalog` and
 * `/Type/Catalog` are valid. Comments and #xx name escapes are accepted. */
static int b2026_pdf_dict_name_is(const unsigned char *body, size_t n,
                                  const char *key, const char *expected) {
    const unsigned char *q = body;
    size_t key_length = strlen(key), expected_length = strlen(expected);
    while ((q = b2026_pdf_memfind(q, n - (size_t)(q - body), key, key_length)) != NULL) {
        size_t key_pos = (size_t)(q - body), p = key_pos + key_length, matched = 0U;
        int mismatch = 0;
        if (p < n && !b2026_pdf_is_delim(body[p])) { ++q; continue; }
        for (;;) {
            while (p < n && isspace(body[p])) ++p;
            if (p >= n || body[p] != '%') break;
            while (p < n && body[p] != '\r' && body[p] != '\n') ++p;
        }
        if (p >= n || body[p] != '/') { ++q; continue; }
        ++p;
        while (p < n && !b2026_pdf_is_delim(body[p])) {
            int high;
            unsigned char decoded = body[p++];
            if (decoded == '#' && p + 1U < n &&
                (high = b2026_pdf_hex_value(body[p])) >= 0) {
                int low = b2026_pdf_hex_value(body[p + 1U]);
                if (low >= 0) { decoded = (unsigned char)((high << 4) | low); p += 2U; }
            }
            if (matched >= expected_length || decoded != (unsigned char)expected[matched]) mismatch = 1;
            ++matched;
        }
        if (!mismatch && matched == expected_length) return 1;
        ++q;
    }
    return 0;
}

static void b2026_pdf_log_warning(const char *message) {
    char line[4096];
    snprintf(line, sizeof(line), "warning: command=PDF file=%s line=%d message=%s",
             current_source_path != NULL ? current_source_path : "", current_line,
             message != NULL ? message : "PDF warning");
    b2026_try_append_log_line("__PDF_ERROR.log", line);
}

static const char *b2026_pdf_fail(int code, const char *path, const char *detail) {
    char message[4096];
    snprintf(message, sizeof(message),
             "PDF operation failed: path=%s: %s",
             path != NULL ? path : "", detail != NULL ? detail : "unknown error / 不明なエラー");
    basic_set_error_logged(code, message, "PDF", current_source_path, current_line, "PDF");
    b2026_try_append_log_line("__PDF_ERROR.log", message);
    return runtime_temp_string_copy("");
}

static double b2026_pdf_fail_number(int code, const char *path, const char *detail) {
    (void)b2026_pdf_fail(code, path, detail);
    return -1.0;
}

static char *b2026_pdf_output_path(const char *path) {
    const char *slash, *backslash, *dot; size_t n; char *r;
    if (path == NULL || *path == '\0') return NULL;
    slash = strrchr(path, '/'); backslash = strrchr(path, '\\');
    if (backslash != NULL && (slash == NULL || backslash > slash)) slash = backslash;
    dot = strrchr(path, '.');
    if (dot != NULL && (slash == NULL || dot > slash)) return b2026_strdup_simple(path);
    n = strlen(path); r = (char *)malloc(n + 5U); if (r == NULL) return NULL;
    memcpy(r, path, n); memcpy(r + n, ".pdf", 5U); return r;
}

static int b2026_pdf_read_file(const char *path, unsigned char **out, size_t *out_len) {
    FILE *fp; long end; unsigned char *data; size_t got = 0U;
    *out = NULL; *out_len = 0U;
    b2026_io_hard_guard_begin("PDF", path, "READ");
    fp = b2026_fopen(path, "rb"); if (fp == NULL) { b2026_io_hard_guard_end(); return 0; }
    if (fseek(fp, 0, SEEK_END) != 0 || (end = ftell(fp)) < 0 ||
        (unsigned long long)end >= B2026_PDF_EDIT_MAX_BYTES || fseek(fp, 0, SEEK_SET) != 0) {
        b2026_io_hard_guard_end(); fclose(fp); return 0;
    }
    data = (unsigned char *)malloc((size_t)end + 1U); if (data == NULL) {
        b2026_io_hard_guard_end(); fclose(fp); return 0;
    }
    while (got < (size_t)end) {
        size_t request = (size_t)end - got, amount;
        if (request > 65536U) request = 65536U;
        if (!b2026_io_wait_stream(fp, 0, "PDF", path)) {
            basic_set_io_timeout_error("PDF", path, "READ", got > 0U ? "PARTIAL" : "NOT_APPLIED", g_io_last_wait_elapsed);
            b2026_io_hard_guard_end(); free(data); fclose(fp); return 0;
        }
        amount = fread(data + got, 1U, request, fp);
        if (amount == 0U) { b2026_io_hard_guard_end(); free(data); fclose(fp); return 0; }
        got += amount;
        b2026_io_hard_guard_progress();
    }
    b2026_io_hard_guard_end();
    if (got != (size_t)end || ferror(fp)) { free(data); fclose(fp); return 0; }
    fclose(fp); data[got] = 0; *out = data; *out_len = got; return 1;
}

static void b2026_pdf_document_free(B2026PdfDocument *doc) {
    int i; if (doc == NULL) return;
    for (i = 0; i < doc->object_count; ++i) free(doc->objects[i].body);
    free(doc->objects); free(doc->page_objects); free(doc->bytes); free(doc->path);
    memset(doc, 0, sizeof(*doc));
}

static int b2026_pdf_add_object(B2026PdfDocument *doc, int number, int generation,
                                const unsigned char *body, size_t body_length) {
    int i; B2026PdfObject *o;
    for (i = 0; i < doc->object_count; ++i) {
        if (doc->objects[i].old_number == number && doc->objects[i].old_generation == generation) {
            free(doc->objects[i].body); doc->objects[i].body = NULL;
            doc->objects[i].body_length = 0U; o = &doc->objects[i]; goto copy_body;
        }
    }
    if (doc->object_count == doc->object_capacity) {
        int cap = doc->object_capacity == 0 ? 32 : doc->object_capacity * 2;
        B2026PdfObject *p = (B2026PdfObject *)realloc(doc->objects, (size_t)cap * sizeof(*p));
        if (p == NULL) return 0; doc->objects = p; doc->object_capacity = cap;
    }
    o = &doc->objects[doc->object_count++]; memset(o, 0, sizeof(*o));
copy_body:
    o->old_number = number; o->old_generation = generation;
    o->body = (unsigned char *)malloc(body_length + 1U); if (o->body == NULL) return 0;
    memcpy(o->body, body, body_length); o->body[body_length] = 0; o->body_length = body_length;
    o->rotation_override = -1; o->included = 1; return 1;
}

static int b2026_pdf_scan_objects(B2026PdfDocument *doc) {
    size_t i = 0U;
    while (i < doc->length) {
        size_t start = i, p, body_start; unsigned long a = 0UL, g = 0UL;
        const unsigned char *end, *stream, *endstream;
        if (!isdigit(doc->bytes[i]) || (i > 0 && !isspace(doc->bytes[i - 1]))) { ++i; continue; }
        p = i; while (p < doc->length && isdigit(doc->bytes[p])) { a = a * 10UL + (unsigned long)(doc->bytes[p] - '0'); ++p; }
        if (p >= doc->length || !isspace(doc->bytes[p])) { i = start + 1U; continue; }
        while (p < doc->length && isspace(doc->bytes[p])) ++p;
        if (p >= doc->length || !isdigit(doc->bytes[p])) { i = start + 1U; continue; }
        while (p < doc->length && isdigit(doc->bytes[p])) { g = g * 10UL + (unsigned long)(doc->bytes[p] - '0'); ++p; }
        while (p < doc->length && isspace(doc->bytes[p])) ++p;
        if (p + 3U > doc->length || memcmp(doc->bytes + p, "obj", 3U) != 0 ||
            (p + 3U < doc->length && !isspace(doc->bytes[p + 3U]))) { i = start + 1U; continue; }
        body_start = p + 3U; while (body_start < doc->length && (doc->bytes[body_start] == '\r' || doc->bytes[body_start] == '\n')) ++body_start;
        end = b2026_pdf_memfind(doc->bytes + body_start, doc->length - body_start, "endobj", 6U);
        if (end == NULL) return 0;
        stream = b2026_pdf_memfind(doc->bytes + body_start, (size_t)(end - (doc->bytes + body_start)), "stream", 6U);
        if (stream != NULL) {
            endstream = b2026_pdf_memfind(stream + 6U, doc->length - (size_t)(stream + 6U - doc->bytes), "endstream", 9U);
            if (endstream == NULL) return 0;
            end = b2026_pdf_memfind(endstream + 9U, doc->length - (size_t)(endstream + 9U - doc->bytes), "endobj", 6U);
            if (end == NULL) return 0;
        }
        if (a > INT_MAX || g > INT_MAX || !b2026_pdf_add_object(doc, (int)a, (int)g,
                doc->bytes + body_start, (size_t)(end - (doc->bytes + body_start)))) return 0;
        i = (size_t)(end - doc->bytes) + 6U;
    }
    return doc->object_count > 0;
}

static int b2026_pdf_page_append(B2026PdfDocument *doc, int object_index) {
    int *p;
    if (doc->page_count == doc->page_capacity) {
        int cap = doc->page_capacity == 0 ? 16 : doc->page_capacity * 2;
        p = (int *)realloc(doc->page_objects, (size_t)cap * sizeof(*p));
        if (p == NULL) return 0; doc->page_objects = p; doc->page_capacity = cap;
    }
    doc->page_objects[doc->page_count++] = object_index; doc->objects[object_index].is_page = 1; return 1;
}

static int b2026_pdf_object_lookup(B2026PdfDocument *doc, int number, int generation);

static int b2026_pdf_named_ref(const unsigned char *body, size_t n, const char *name,
                               int *number, int *generation) {
    const unsigned char *q = body; size_t name_len = strlen(name);
    while ((q = b2026_pdf_memfind(q, n - (size_t)(q - body), name, name_len)) != NULL) {
        size_t p = (size_t)(q - body) + name_len; unsigned long a = 0UL, g = 0UL;
        if (p < n && !b2026_pdf_is_delim(body[p])) { ++q; continue; }
        while (p < n && isspace(body[p])) ++p; if (p >= n || !isdigit(body[p])) { ++q; continue; }
        while (p < n && isdigit(body[p])) { a = a * 10UL + (unsigned long)(body[p] - '0'); ++p; }
        while (p < n && isspace(body[p])) ++p; if (p >= n || !isdigit(body[p])) { ++q; continue; }
        while (p < n && isdigit(body[p])) { g = g * 10UL + (unsigned long)(body[p] - '0'); ++p; }
        while (p < n && isspace(body[p])) ++p;
        if (p < n && body[p] == 'R' && a <= INT_MAX && g <= INT_MAX) { *number = (int)a; *generation = (int)g; return 1; }
        ++q;
    }
    return 0;
}

static int b2026_pdf_last_named_ref(const unsigned char *body, size_t n, const char *name,
                                    int *number, int *generation) {
    const unsigned char *q = body; size_t name_length = strlen(name); int found = 0;
    while ((q = b2026_pdf_memfind(q, n - (size_t)(q - body), name, name_length)) != NULL) {
        int a, g; size_t remaining = n - (size_t)(q - body);
        if (b2026_pdf_named_ref(q, remaining, name, &a, &g)) {
            *number = a; *generation = g; found = 1;
        }
        ++q;
    }
    return found;
}

static int b2026_pdf_collect_page_tree(B2026PdfDocument *doc, int object_index,
                                       unsigned char *visited, int depth) {
    B2026PdfObject *o; const unsigned char *kids; size_t p, end;
    if (object_index < 0 || object_index >= doc->object_count || depth > 128 || visited[object_index]) return 0;
    visited[object_index] = 1; o = &doc->objects[object_index];
    if (b2026_pdf_dict_name_is(o->body, o->body_length, "/Type", "Page"))
        return b2026_pdf_page_append(doc, object_index);
    kids = b2026_pdf_memfind(o->body, o->body_length, "/Kids", 5U); if (kids == NULL) return 0;
    p = (size_t)(kids - o->body) + 5U; while (p < o->body_length && o->body[p] != '[') ++p;
    if (p >= o->body_length) return 0; end = p + 1U; { int level = 1; while (end < o->body_length && level > 0) { if (o->body[end] == '[') ++level; else if (o->body[end] == ']') --level; ++end; } }
    while (++p < end) {
        unsigned long a = 0UL, g = 0UL; size_t save = p; int child;
        while (p < end && isspace(o->body[p])) ++p; if (p >= end || !isdigit(o->body[p])) continue;
        while (p < end && isdigit(o->body[p])) { a = a * 10UL + (unsigned long)(o->body[p] - '0'); ++p; }
        while (p < end && isspace(o->body[p])) ++p; if (p >= end || !isdigit(o->body[p])) { p = save; continue; }
        while (p < end && isdigit(o->body[p])) { g = g * 10UL + (unsigned long)(o->body[p] - '0'); ++p; }
        while (p < end && isspace(o->body[p])) ++p;
        if (p < end && o->body[p] == 'R' && a <= INT_MAX && g <= INT_MAX &&
            (child = b2026_pdf_object_lookup(doc, (int)a, (int)g)) >= 0)
            (void)b2026_pdf_collect_page_tree(doc, child, visited, depth + 1);
    }
    return doc->page_count > 0;
}

static int b2026_pdf_dict_integer(const unsigned char *body, size_t n, const char *name, int *value) {
    const unsigned char *q = b2026_pdf_memfind(body, n, name, strlen(name)); size_t p; long v; char *end;
    if (q == NULL) return 0; p = (size_t)(q - body) + strlen(name);
    while (p < n && isspace(body[p])) ++p; if (p >= n) return 0;
    errno = 0; v = strtol((const char *)body + p, &end, 10);
    if (end == (char *)body + p || errno == ERANGE || v < INT_MIN || v > INT_MAX) return 0;
    *value = (int)v; return 1;
}

static int b2026_pdf_expand_object_streams(B2026PdfDocument *doc) {
    int oi, initial_count = doc->object_count;
    for (oi = 0; oi < initial_count; ++oi) {
        B2026PdfObject *o = &doc->objects[oi]; const unsigned char *stream, *endstream, *payload;
        int count, first, decoded_length = 0, i; size_t payload_length; char *decoded; long *numbers, *offsets;
        if (!b2026_pdf_dict_name_is(o->body, o->body_length, "/Type", "ObjStm")) continue;
        if (!b2026_pdf_dict_integer(o->body, o->body_length, "/N", &count) ||
            !b2026_pdf_dict_integer(o->body, o->body_length, "/First", &first) || count < 0 || count > 1000000) return 0;
        stream = b2026_pdf_memfind(o->body, o->body_length, "stream", 6U);
        if (stream == NULL) return 0; payload = stream + 6U;
        if (payload < o->body + o->body_length && *payload == '\r') ++payload;
        if (payload < o->body + o->body_length && *payload == '\n') ++payload;
        endstream = b2026_pdf_memfind(payload, o->body_length - (size_t)(payload - o->body), "endstream", 9U);
        if (endstream == NULL) return 0; payload_length = (size_t)(endstream - payload);
        while (payload_length > 0U && (payload[payload_length - 1U] == '\r' || payload[payload_length - 1U] == '\n')) --payload_length;
        if (payload_length > INT_MAX) return 0;
        if (b2026_pdf_memfind(o->body, (size_t)(stream - o->body), "/FlateDecode", 12U) != NULL) {
            decoded = stbi_zlib_decode_malloc((const char *)payload, (int)payload_length, &decoded_length);
        } else if (b2026_pdf_memfind(o->body, (size_t)(stream - o->body), "/Filter", 7U) == NULL) {
            decoded = (char *)malloc(payload_length + 1U);
            if (decoded != NULL) { memcpy(decoded, payload, payload_length); decoded[payload_length] = '\0'; decoded_length = (int)payload_length; }
        } else return 0;
        if (decoded == NULL || first < 0 || first > decoded_length) { free(decoded); return 0; }
        numbers = (long *)malloc((size_t)count * sizeof(*numbers)); offsets = (long *)malloc((size_t)count * sizeof(*offsets));
        if ((count > 0 && numbers == NULL) || (count > 0 && offsets == NULL)) { free(numbers); free(offsets); free(decoded); return 0; }
        { char *p = decoded, *limit = decoded + first, *e;
          for (i = 0; i < count; ++i) {
              while (p < limit && isspace((unsigned char)*p)) ++p; numbers[i] = strtol(p, &e, 10); if (e == p || e > limit) { free(numbers); free(offsets); free(decoded); return 0; } p = e;
              while (p < limit && isspace((unsigned char)*p)) ++p; offsets[i] = strtol(p, &e, 10); if (e == p || e > limit) { free(numbers); free(offsets); free(decoded); return 0; } p = e;
          }
        }
        for (i = 0; i < count; ++i) {
            long start = (long)first + offsets[i]; long finish = i + 1 < count ? (long)first + offsets[i + 1] : decoded_length;
            if (numbers[i] <= 0 || numbers[i] > INT_MAX || start < first || finish < start || finish > decoded_length ||
                !b2026_pdf_add_object(doc, (int)numbers[i], 0, (const unsigned char *)decoded + start, (size_t)(finish - start))) {
                free(numbers); free(offsets); free(decoded); return 0;
            }
        }
        free(numbers); free(offsets); free(decoded);
    }
    return 1;
}

static int b2026_pdf_load(B2026PdfDocument *doc, const char *path, int edit_mode) {
    int i, root = -1, root_number = 0, root_generation = 0, pages_number = 0, pages_generation = 0, info_number = 0, info_generation = 0; unsigned char *visited;
    const unsigned char *header;
    memset(doc, 0, sizeof(*doc)); doc->version_major = 1; doc->version_minor = 4;
    doc->info_object = -1; doc->metadata_object = -1; doc->root_object = -1;
    doc->path = b2026_strdup_simple(path != NULL ? path : ""); if (doc->path == NULL) return 0;
    if (!b2026_pdf_read_file(path, &doc->bytes, &doc->length) || doc->length < 8U) return 0;
    header = b2026_pdf_memfind(doc->bytes, doc->length < 1024U ? doc->length : 1024U, "%PDF-", 5U);
    if (header == NULL && !(edit_mode == 2 && b2026_pdf_memfind(doc->bytes, doc->length, " obj", 4U) != NULL)) return 0;
    if (header != NULL && (size_t)(header - doc->bytes) + 8U <= doc->length) {
        if (header[5] >= '0' && header[5] <= '9') doc->version_major = header[5] - '0';
        if (header[7] >= '0' && header[7] <= '9') doc->version_minor = header[7] - '0';
    }
    if (b2026_pdf_memfind(doc->bytes, doc->length, "/Encrypt", 8U) != NULL) return -2;
    if (edit_mode && b2026_pdf_memfind(doc->bytes, doc->length, "/ByteRange", 10U) != NULL) return -3;
    if (edit_mode && edit_mode != 3 && edit_mode != 4 &&
        b2026_pdf_memfind(doc->bytes, doc->length, "/AcroForm", 9U) != NULL) return -4;
    if (edit_mode && edit_mode != 4 && (b2026_pdf_memfind(doc->bytes, doc->length, "/EmbeddedFiles", 14U) != NULL ||
        b2026_pdf_memfind(doc->bytes, doc->length, "/JavaScript", 11U) != NULL ||
        b2026_pdf_memfind(doc->bytes, doc->length, "/JS", 3U) != NULL)) return -6;
    if (!b2026_pdf_scan_objects(doc) || !b2026_pdf_expand_object_streams(doc)) return 0;
    if (b2026_pdf_last_named_ref(doc->bytes, doc->length, "/Root", &root_number, &root_generation))
        root = b2026_pdf_object_lookup(doc, root_number, root_generation);
    if (b2026_pdf_last_named_ref(doc->bytes, doc->length, "/Info", &info_number, &info_generation))
        doc->info_object = b2026_pdf_object_lookup(doc, info_number, info_generation);
    if (root < 0) for (i = 0; i < doc->object_count; ++i)
        if (b2026_pdf_dict_name_is(doc->objects[i].body, doc->objects[i].body_length, "/Type", "Catalog")) root = i;
    doc->root_object = root;
    if (root >= 0) {
        int metadata_number, metadata_generation;
        if (b2026_pdf_named_ref(doc->objects[root].body, doc->objects[root].body_length,
                "/Metadata", &metadata_number, &metadata_generation))
            doc->metadata_object = b2026_pdf_object_lookup(doc, metadata_number, metadata_generation);
    }
    visited = (unsigned char *)calloc((size_t)doc->object_count, 1U); if (visited == NULL) return 0;
    if (root >= 0 && b2026_pdf_named_ref(doc->objects[root].body, doc->objects[root].body_length,
            "/Pages", &pages_number, &pages_generation)) {
        int pages_index = b2026_pdf_object_lookup(doc, pages_number, pages_generation);
        if (pages_index >= 0) (void)b2026_pdf_collect_page_tree(doc, pages_index, visited, 0);
    }
    free(visited);
    if (doc->page_count == 0 && b2026_pdf_memfind(doc->bytes, doc->length, "/ObjStm", 7U) != NULL) return -5;
    return doc->page_count > 0 ? 1 : 0;
}

static int b2026_pdf_object_lookup(B2026PdfDocument *doc, int number, int generation) {
    int i, fallback = -1;
    for (i = 0; i < doc->object_count; ++i) {
        if (doc->objects[i].old_number == number) {
            fallback = i; if (doc->objects[i].old_generation == generation) return i;
        }
    }
    return fallback;
}

static int b2026_pdf_pages_push(B2026PdfPageList *list, B2026PdfDocument *doc,
                                int object_index, int rotation) {
    B2026PdfPage *p;
    if (list->count == list->capacity) {
        int cap = list->capacity == 0 ? 16 : list->capacity * 2;
        p = (B2026PdfPage *)realloc(list->items, (size_t)cap * sizeof(*p));
        if (p == NULL) return 0; list->items = p; list->capacity = cap;
    }
    p = &list->items[list->count++]; memset(p, 0, sizeof(*p));
    p->doc = doc; p->object_index = object_index; p->rotation_override = rotation;
    p->media[0] = 0.0; p->media[1] = 0.0; p->media[2] = 595.0; p->media[3] = 842.0;
    p->has_media = object_index < 0; return 1;
}

static const char *b2026_pdf_arg_text(const B2026PdfArg *arg) {
    return arg != NULL && arg->kind == 1 && arg->text != NULL ? arg->text : NULL;
}

static int b2026_pdf_arg_integer(const B2026PdfArg *arg, int *value) {
    double n; char *end = NULL;
    if (arg->kind == 0) n = arg->number;
    else if (arg->kind == 1 && arg->text != NULL) { n = strtod(arg->text, &end); if (end == arg->text || *end != '\0') return 0; }
    else return 0;
    if (!isfinite(n) || floor(n) != n || n < (double)INT_MIN || n > (double)INT_MAX) return 0;
    *value = (int)n; return 1;
}

static int b2026_pdf_clamp_page(int value, int count, int *adjusted) {
    if (value < 1) { *adjusted = 1; return 1; }
    if (value > count) { *adjusted = count; return 1; }
    *adjusted = value; return 0;
}

static int b2026_pdf_parse_page_spec(const B2026PdfArg *arg, int page_count,
                                     unsigned char **selected_out) {
    unsigned char *selected; const char *s; int adjusted_any = 0, selected_count = 0, scan;
    selected = (unsigned char *)calloc((size_t)page_count, 1U); if (selected == NULL) return 0;
    if (arg->kind == 0) {
        int n, c; if (!b2026_pdf_arg_integer(arg, &n)) { free(selected); return 0; }
        adjusted_any |= b2026_pdf_clamp_page(n, page_count, &c); selected[c - 1] = 1;
    } else if (arg->kind == 1 && arg->text != NULL) {
        s = arg->text;
        if (b2026_strcasecmp(s, "ALL") == 0 || strcmp(s, "すべて") == 0 || strcmp(s, "全て") == 0) {
            memset(selected, 1, (size_t)page_count);
        } else {
            while (*s != '\0') {
                char *end; long a, b; int ca, cb, i;
                while (isspace((unsigned char)*s) || *s == ',') ++s;
                if (*s == '\0') break;
                errno = 0; a = strtol(s, &end, 10); if (end == s || errno == ERANGE) { free(selected); return 0; }
                s = end; while (isspace((unsigned char)*s)) ++s; b = a;
                if (*s == '-') { ++s; while (isspace((unsigned char)*s)) ++s; errno = 0; b = strtol(s, &end, 10); if (end == s || errno == ERANGE) { free(selected); return 0; } s = end; }
                if (a < INT_MIN || a > INT_MAX || b < INT_MIN || b > INT_MAX) { free(selected); return 0; }
                adjusted_any |= b2026_pdf_clamp_page((int)a, page_count, &ca);
                adjusted_any |= b2026_pdf_clamp_page((int)b, page_count, &cb);
                if (ca > cb) { int t = ca; ca = cb; cb = t; adjusted_any = 1; }
                for (i = ca; i <= cb; ++i) selected[i - 1] = 1;
                while (isspace((unsigned char)*s)) ++s;
                if (*s != '\0' && *s != ',') { free(selected); return 0; }
            }
        }
    } else { free(selected); return 0; }
    for (scan = 0; scan < page_count; ++scan) if (selected[scan]) ++selected_count;
    if (selected_count == 0) { free(selected); return 0; }
    if (adjusted_any) b2026_pdf_log_warning("ページ範囲を有効な範囲へ調整しました。 / Page range was adjusted.");
    *selected_out = selected; return 1;
}

static int b2026_pdf_compose_push(B2026PdfPageList *pages, B2026PdfDocument *doc,
                                  int page, int *count) {
    if(*count>=1000000)return 0;
    if(!b2026_pdf_pages_push(pages,doc,doc->page_objects[page-1],-1))return 0;
    ++*count;return 1;
}

static int b2026_pdf_compose_page_spec(const B2026PdfArg *arg, B2026PdfDocument *doc,
                                       B2026PdfPageList *pages) {
    int adjusted_any=0,count=0;
    if(arg->kind==0){int n,c;if(!b2026_pdf_arg_integer(arg,&n))return 0;adjusted_any|=b2026_pdf_clamp_page(n,doc->page_count,&c);if(!b2026_pdf_compose_push(pages,doc,c,&count))return 0;}
    else if(arg->kind==1&&arg->text!=NULL){const char *s=arg->text;
        while(*s!='\0'){
            char *end;long a,b;int ca,cb,i,step;size_t word=0U;
            while(isspace((unsigned char)*s)||*s==',')++s;if(*s=='\0')break;
            if(b2026_strncasecmp(s,"ALL",3U)==0&&(s[3]=='\0'||s[3]==','||isspace((unsigned char)s[3])))word=3U;
            else if(strncmp(s,"すべて",12U)==0&&(s[12]=='\0'||s[12]==','||isspace((unsigned char)s[12])))word=12U;
            else if(strncmp(s,"全て",6U)==0&&(s[6]=='\0'||s[6]==','||isspace((unsigned char)s[6])))word=6U;
            if(word>0U){s+=word;for(i=1;i<=doc->page_count;++i)if(!b2026_pdf_compose_push(pages,doc,i,&count))return 0;}
            else{
                errno=0;a=strtol(s,&end,10);if(end==s||errno==ERANGE)return 0;s=end;while(isspace((unsigned char)*s))++s;b=a;
                if(*s=='-'){++s;while(isspace((unsigned char)*s))++s;errno=0;b=strtol(s,&end,10);if(end==s||errno==ERANGE)return 0;s=end;}
                if(a<INT_MIN||a>INT_MAX||b<INT_MIN||b>INT_MAX)return 0;
                adjusted_any|=b2026_pdf_clamp_page((int)a,doc->page_count,&ca);adjusted_any|=b2026_pdf_clamp_page((int)b,doc->page_count,&cb);
                step=ca<=cb?1:-1;for(i=ca;;i+=step){if(!b2026_pdf_compose_push(pages,doc,i,&count))return 0;if(i==cb)break;}
            }
            while(isspace((unsigned char)*s))++s;if(*s!='\0'&&*s!=',')return 0;
        }
    }else return 0;
    if(count==0)return 0;if(adjusted_any)b2026_pdf_log_warning("ページ範囲を有効な範囲へ調整しました。 / Page range was adjusted.");return 1;
}

static int b2026_pdf_rotation(const B2026PdfArg *arg, int *rotation) {
    int n;
    if (arg->kind == 1 && arg->text != NULL) {
        if (strcmp(arg->text, "左") == 0 || b2026_strcasecmp(arg->text, "LEFT") == 0 || b2026_strcasecmp(arg->text, "LEFT90") == 0) { *rotation = 270; return 1; }
        if (strcmp(arg->text, "右") == 0 || b2026_strcasecmp(arg->text, "RIGHT") == 0 || b2026_strcasecmp(arg->text, "RIGHT90") == 0) { *rotation = 90; return 1; }
    }
    if (!b2026_pdf_arg_integer(arg, &n) || (n != -90 && n != 90 && n != 180)) return 0;
    *rotation = (n % 360 + 360) % 360; return 1;
}

static int b2026_pdf_page_inherited_integer(B2026PdfDocument *doc, int object_index,
                                            const char *key, int default_value) {
    int depth;
    for (depth = 0; depth < 128 && object_index >= 0; ++depth) {
        B2026PdfObject *o = &doc->objects[object_index]; int value, pn, pg;
        if (b2026_pdf_dict_integer(o->body, o->body_length, key, &value)) return value;
        if (!b2026_pdf_named_ref(o->body, o->body_length, "/Parent", &pn, &pg)) break;
        object_index = b2026_pdf_object_lookup(doc, pn, pg);
    }
    return default_value;
}

static int b2026_pdf_page_box(B2026PdfDocument *doc, int object_index,
                              const char *key, double box[4]) {
    int depth;
    for (depth = 0; depth < 128 && object_index >= 0; ++depth) {
        B2026PdfObject *o = &doc->objects[object_index]; const unsigned char *q;
        int pn, pg; size_t p; char *end; int i, ref_number, ref_generation, ref_index;
        size_t key_length = strlen(key), offset = 0U;
        q = NULL;
        while (offset < o->body_length &&
               (q = b2026_pdf_memfind(o->body + offset, o->body_length - offset, key, key_length)) != NULL) {
            size_t qi = (size_t)(q - o->body);
            if (qi + key_length >= o->body_length || b2026_pdf_is_delim(o->body[qi + key_length])) break;
            offset = qi + 1U; q = NULL;
        }
        if (q != NULL) {
            p = (size_t)(q - o->body) + key_length;
            while (p < o->body_length && isspace(o->body[p])) ++p;
            if (p < o->body_length && o->body[p] == '[') {
                ++p; for (i = 0; i < 4; ++i) { while (p < o->body_length && isspace(o->body[p])) ++p; errno = 0; box[i] = strtod((const char *)o->body + p, &end); if (end == (char *)o->body + p || errno == ERANGE || !isfinite(box[i])) break; p = (size_t)(end - (char *)o->body); }
                if (i == 4) return 1;
            } else if (b2026_pdf_named_ref(q, o->body_length - (size_t)(q - o->body), key,
                                          &ref_number, &ref_generation) &&
                       (ref_index = b2026_pdf_object_lookup(doc, ref_number, ref_generation)) >= 0) {
                B2026PdfObject *r = &doc->objects[ref_index];
                p = 0U; while (p < r->body_length && isspace(r->body[p])) ++p;
                if (p < r->body_length && r->body[p] == '[') {
                    ++p; for (i = 0; i < 4; ++i) { while (p < r->body_length && isspace(r->body[p])) ++p; errno = 0; box[i] = strtod((const char *)r->body + p, &end); if (end == (char *)r->body + p || errno == ERANGE || !isfinite(box[i])) break; p = (size_t)(end - (char *)r->body); }
                    if (i == 4) return 1;
                }
            }
        }
        if (!b2026_pdf_named_ref(o->body, o->body_length, "/Parent", &pn, &pg)) break;
        object_index = b2026_pdf_object_lookup(doc, pn, pg);
    }
    return 0;
}

static int b2026_pdf_page_direct_box(B2026PdfDocument *doc, int object_index,
                                     const char *key, double box[4]) {
    B2026PdfObject *o; const unsigned char *q; size_t key_length, offset = 0U, p;
    char *end; int i, ref_number, ref_generation, ref_index;
    if (object_index < 0 || object_index >= doc->object_count) return 0;
    o = &doc->objects[object_index]; key_length = strlen(key); q = NULL;
    while (offset < o->body_length &&
           (q = b2026_pdf_memfind(o->body + offset, o->body_length - offset, key, key_length)) != NULL) {
        size_t qi = (size_t)(q - o->body);
        if (qi + key_length >= o->body_length || b2026_pdf_is_delim(o->body[qi + key_length])) break;
        offset = qi + 1U; q = NULL;
    }
    if (q == NULL) return 0;
    p = (size_t)(q - o->body) + key_length;
    while (p < o->body_length && isspace(o->body[p])) ++p;
    if (p < o->body_length && o->body[p] == '[') {
        ++p;
        for (i = 0; i < 4; ++i) {
            while (p < o->body_length && isspace(o->body[p])) ++p;
            errno = 0; box[i] = strtod((const char *)o->body + p, &end);
            if (end == (char *)o->body + p || errno == ERANGE || !isfinite(box[i])) return -1;
            p = (size_t)(end - (char *)o->body);
        }
        return 1;
    }
    if (!b2026_pdf_named_ref(q, o->body_length - (size_t)(q - o->body), key,
                             &ref_number, &ref_generation)) return -1;
    ref_index = b2026_pdf_object_lookup(doc, ref_number, ref_generation);
    if (ref_index < 0) return -1;
    o = &doc->objects[ref_index]; p = 0U;
    while (p < o->body_length && isspace(o->body[p])) ++p;
    if (p >= o->body_length || o->body[p] != '[') return -1;
    ++p;
    for (i = 0; i < 4; ++i) {
        while (p < o->body_length && isspace(o->body[p])) ++p;
        errno = 0; box[i] = strtod((const char *)o->body + p, &end);
        if (end == (char *)o->body + p || errno == ERANGE || !isfinite(box[i])) return -1;
        p = (size_t)(end - (char *)o->body);
    }
    return 1;
}

static int b2026_pdf_page_direct_number(B2026PdfDocument *doc, int object_index,
                                        const char *key, double *value) {
    B2026PdfObject *o; const unsigned char *q; size_t key_length, offset = 0U, p; char *end;
    if (object_index < 0 || object_index >= doc->object_count) return 0;
    o = &doc->objects[object_index]; key_length = strlen(key); q = NULL;
    while (offset < o->body_length &&
           (q = b2026_pdf_memfind(o->body + offset, o->body_length - offset, key, key_length)) != NULL) {
        size_t qi = (size_t)(q - o->body);
        if ((qi == 0U || b2026_pdf_is_delim(o->body[qi - 1U])) &&
            (qi + key_length >= o->body_length || b2026_pdf_is_delim(o->body[qi + key_length]))) break;
        offset = qi + 1U; q = NULL;
    }
    if (q == NULL) return 0;
    p = (size_t)(q - o->body) + key_length; while (p < o->body_length && isspace(o->body[p])) ++p;
    errno = 0; *value = strtod((const char *)o->body + p, &end);
    return end != (char *)o->body + p && errno != ERANGE && isfinite(*value) ? 1 : -1;
}

static int b2026_pdf_position_after(const B2026PdfArg *arg, int *after) {
    const char *s = b2026_pdf_arg_text(arg); if (s == NULL) return 0;
    if (strcmp(s, "前") == 0 || b2026_strcasecmp(s, "BEFORE") == 0) { *after = 0; return 1; }
    if (strcmp(s, "後") == 0 || b2026_strcasecmp(s, "AFTER") == 0) { *after = 1; return 1; }
    return 0;
}

static size_t b2026_pdf_edit_space(const unsigned char *s, size_t n, size_t p) {
    for (;;) {
        while (p < n && isspace(s[p])) ++p;
        if (p < n && s[p] == '%') {
            while (p < n && s[p] != '\r' && s[p] != '\n') ++p;
            continue;
        }
        return p;
    }
}

static size_t b2026_pdf_edit_value_end(const unsigned char *s, size_t n,
                                       size_t p, int depth) {
    size_t q;
    if (depth > 128) return n;
    p = b2026_pdf_edit_space(s, n, p); if (p >= n) return n;
    if (s[p] == '(') {
        int level = 1; ++p;
        while (p < n && level) {
            if (s[p] == '\\') { p += p + 1U < n ? 2U : 1U; continue; }
            if (s[p] == '(') ++level; else if (s[p] == ')') --level; ++p;
        }
        return p;
    }
    if (s[p] == '[') {
        ++p;
        while ((p = b2026_pdf_edit_space(s, n, p)) < n && s[p] != ']')
            p = b2026_pdf_edit_value_end(s, n, p, depth + 1);
        return p < n ? p + 1U : p;
    }
    if (s[p] == '<' && p + 1U < n && s[p + 1U] == '<') {
        p += 2U;
        while ((p = b2026_pdf_edit_space(s, n, p)) < n) {
            if (p + 1U < n && s[p] == '>' && s[p + 1U] == '>') return p + 2U;
            if (s[p] == '/') {
                ++p; while (p < n && !b2026_pdf_is_delim(s[p])) ++p;
                p = b2026_pdf_edit_value_end(s, n, p, depth + 1);
            } else ++p;
        }
        return p;
    }
    if (s[p] == '<') { ++p; while (p < n && s[p] != '>') ++p; return p < n ? p + 1U : p; }
    if (s[p] == '/') { ++p; while (p < n && !b2026_pdf_is_delim(s[p])) ++p; return p; }
    q = p; while (q < n && !b2026_pdf_is_delim(s[q])) ++q;
    if (q > p && (isdigit(s[p]) || s[p] == '+' || s[p] == '-' || s[p] == '.')) {
        size_t r = b2026_pdf_edit_space(s, n, q), t = r;
        while (t < n && !b2026_pdf_is_delim(s[t])) ++t;
        if (t > r && isdigit(s[r])) {
            size_t u = b2026_pdf_edit_space(s, n, t);
            if (u < n && s[u] == 'R') return u + 1U;
        }
    }
    return q;
}

static int b2026_pdf_edit_find_key(const unsigned char *body, size_t n,
                                   const char *key, size_t *key_start,
                                   size_t *value_start, size_t *value_end, int *found) {
    size_t p = 0U, key_length = strlen(key);
    *found = 0;
    while (p + 1U < n && !(body[p] == '<' && body[p + 1U] == '<')) ++p;
    if (p + 1U >= n) return 0;
    p += 2U;
    while ((p = b2026_pdf_edit_space(body, n, p)) < n) {
        size_t ks, ke, vs, ve;
        if (p + 1U < n && body[p] == '>' && body[p + 1U] == '>') return 1;
        if (body[p] != '/') { ++p; continue; }
        ks = p++; while (p < n && !b2026_pdf_is_delim(body[p])) ++p; ke = p;
        vs = b2026_pdf_edit_space(body, n, p);
        ve = b2026_pdf_edit_value_end(body, n, vs, 0);
        if (ve <= vs || ve > n) return 0;
        if (ke - ks == key_length && memcmp(body + ks, key, key_length) == 0) {
            *key_start = ks; *value_start = vs; *value_end = ve; *found = 1; return 1;
        }
        p = ve;
    }
    return 0;
}

static int b2026_pdf_remove_object_key(B2026PdfObject *object, const char *key,
                                       int *removed) {
    size_t start = 0U, value = 0U, finish = 0U, length; int found = 0; unsigned char *body;
    *removed = 0;
    if (!b2026_pdf_edit_find_key(object->body, object->body_length, key,
                                 &start, &value, &finish, &found)) return 0;
    if (!found) return 1;
    length = object->body_length - (finish - start);
    body = (unsigned char *)malloc(length + 1U); if (body == NULL) return 0;
    memcpy(body, object->body, start);
    memcpy(body + start, object->body + finish, object->body_length - finish);
    body[length] = 0;
    free(object->body); object->body = body; object->body_length = length; *removed = 1;
    return 1;
}

static int b2026_pdf_remove_all_annotations(B2026PdfDocument *doc, int *page_count) {
    int i; *page_count = 0;
    for (i = 0; i < doc->page_count; ++i) {
        B2026PdfObject *page = &doc->objects[doc->page_objects[i]]; int removed;
        do {
            if (!b2026_pdf_remove_object_key(page, "/Annots", &removed)) return 0;
            if (removed) ++*page_count;
        } while (removed);
    }
    return 1;
}

static int b2026_pdf_annotations_removed(const char *path) {
    B2026PdfDocument check; int rc, i;
    rc = b2026_pdf_load(&check, path, 0); if (rc <= 0) { b2026_pdf_document_free(&check); return 0; }
    if (b2026_pdf_memfind(check.bytes, check.length, "/AcroForm", 9U) != NULL) {
        b2026_pdf_document_free(&check); return 0;
    }
    for (i = 0; i < check.object_count; ++i) {
        B2026PdfObject *o = &check.objects[i]; size_t a, v, z; int found;
        if (b2026_pdf_dict_name_is(o->body, o->body_length, "/Type", "Annot") ||
            b2026_pdf_dict_name_is(o->body, o->body_length, "/Subtype", "Widget")) {
            b2026_pdf_document_free(&check); return 0;
        }
        if (o->is_page && (!b2026_pdf_edit_find_key(o->body, o->body_length,
                "/Annots", &a, &v, &z, &found) || found)) {
            b2026_pdf_document_free(&check); return 0;
        }
    }
    b2026_pdf_document_free(&check); return 1;
}

static int b2026_pdf_assign_numbers(B2026PdfDocument *docs, int doc_count) {
    int d, i, next = 1;
    for (d = 0; d < doc_count; ++d)
        for (i = 0; i < docs[d].object_count; ++i) { docs[d].objects[i].new_number = 0;if(docs[d].objects[i].included)docs[d].objects[i].new_number = next++; }
    return next;
}

static int b2026_pdf_mark_reachable(B2026PdfDocument *doc,int root,unsigned char *seen) {
    int changed=1,i;if(root>=0&&root<doc->object_count)seen[root]=1U;
    while(changed){changed=0;for(i=0;i<doc->object_count;++i)if(seen[i]){B2026PdfObject *o=&doc->objects[i];const unsigned char *stream=b2026_pdf_memfind(o->body,o->body_length,"stream",6U);size_t n=stream!=NULL?(size_t)(stream-o->body):o->body_length,p=0U;while(p<n){char *end;long a,g;size_t q;if(!isdigit(o->body[p])){++p;continue;}a=strtol((const char*)o->body+p,&end,10);if(end==(char*)o->body+p||a<=0||a>INT_MAX){++p;continue;}q=(size_t)(end-(char*)o->body);while(q<n&&isspace(o->body[q]))++q;if(q>=n||!isdigit(o->body[q])){++p;continue;}g=strtol((const char*)o->body+q,&end,10);if(end==(char*)o->body+q||g<0||g>INT_MAX){++p;continue;}q=(size_t)(end-(char*)o->body);while(q<n&&isspace(o->body[q]))++q;if(q<n&&o->body[q]=='R'){int index=b2026_pdf_object_lookup(doc,(int)a,(int)g);if(index>=0&&!seen[index]){seen[index]=1U;changed=1;}p=q+1U;}else ++p;}}}
    return 1;
}

static int b2026_pdf_select_reachable_objects(B2026PdfDocument *docs,int doc_count,B2026PdfPageList *pages,int preserve_catalog) {
    int d,i;for(d=0;d<doc_count;++d){unsigned char *seen=(unsigned char*)calloc((size_t)docs[d].object_count,1U);if(seen==NULL)return 0;for(i=0;i<pages->count;++i)if(pages->items[i].doc==&docs[d]&&pages->items[i].object_index>=0)b2026_pdf_mark_reachable(&docs[d],pages->items[i].object_index,seen);if(docs[d].info_object>=0)b2026_pdf_mark_reachable(&docs[d],docs[d].info_object,seen);if(docs[d].metadata_object>=0)b2026_pdf_mark_reachable(&docs[d],docs[d].metadata_object,seen);if(preserve_catalog&&docs[d].root_object>=0)b2026_pdf_mark_reachable(&docs[d],docs[d].root_object,seen);for(i=0;i<docs[d].object_count;++i){B2026PdfObject *o=&docs[d].objects[i];o->included=seen[i]?1:0;if(i==docs[d].root_object||b2026_pdf_dict_name_is(o->body,o->body_length,"/Type","Pages")||b2026_pdf_dict_name_is(o->body,o->body_length,"/Type","Catalog")||b2026_pdf_dict_name_is(o->body,o->body_length,"/Type","XRef")||b2026_pdf_dict_name_is(o->body,o->body_length,"/Type","ObjStm")||(preserve_catalog&&(b2026_pdf_dict_name_is(o->body,o->body_length,"/Type","Annot")||b2026_pdf_dict_name_is(o->body,o->body_length,"/Subtype","Widget"))))o->included=0;}free(seen);}return 1;
}

static int b2026_pdf_write_bytes(FILE *fp, const void *p, size_t n, unsigned long long *written) {
    if (*written > B2026_PDF_EDIT_MAX_BYTES || n > (size_t)(B2026_PDF_EDIT_MAX_BYTES - *written)) return 0;
    if (n > 0U && fwrite(p, 1U, n, fp) != n) return 0; *written += (unsigned long long)n; return 1;
}

static int b2026_pdf_write_text(FILE *fp, const char *s, unsigned long long *written) {
    return b2026_pdf_write_bytes(fp, s, strlen(s), written);
}

static int b2026_pdf_annotation_owner_number(B2026PdfDocument *doc,int object_index,
                                              B2026PdfPageList *pages,int clone_pages) {
    char ref[64];int i;
    B2026PdfObject *annotation;
    if(doc==NULL||object_index<0||object_index>=doc->object_count)return -1;
    annotation=&doc->objects[object_index];
    if(!b2026_pdf_dict_name_is(annotation->body,annotation->body_length,"/Type","Annot"))return -1;
    snprintf(ref,sizeof(ref),"%d %d R",annotation->old_number,annotation->old_generation);
    for(i=0;i<pages->count;++i){B2026PdfPage *page=&pages->items[i];B2026PdfObject *page_object;
        if(page->doc!=doc||page->object_index<0||page->object_index>=doc->object_count)continue;
        page_object=&doc->objects[page->object_index];
        if(b2026_pdf_memfind(page_object->body,page_object->body_length,ref,strlen(ref))!=NULL)
            return clone_pages?page->output_number:page_object->new_number;
    }
    return -1;
}

static int b2026_pdf_emit_ref_rewritten(FILE *fp, B2026PdfDocument *doc,
                                        const unsigned char *body, size_t n,
                                        int is_selected_page, int parent_number,
                                        int rotation_override, const double *media_override,
                                        const double *crop_override, int annotation_page_number,
                                        int *disabled_link,
                                        int current_page_index, int current_page_output,
                                        unsigned long long *written) {
    size_t i = 0U, stream_at = n, dictionary_close = SIZE_MAX; const unsigned char *stream;
    int inserted_rotate = 0, link_active = 0;
    stream = b2026_pdf_memfind(body, n, "stream", 6U); if (stream != NULL) stream_at = (size_t)(stream - body);
    if (is_selected_page||annotation_page_number>0) { size_t scan; for (scan = 0U; scan + 1U < stream_at; ++scan) if (body[scan] == '>' && body[scan + 1U] == '>') dictionary_close = scan; }
    if (b2026_pdf_memfind(body, stream_at, "/Subtype /Link", 14U) != NULL) {
        int page_number, page_generation, page_index;
        link_active = is_selected_page;
        if (!link_active && b2026_pdf_named_ref(body, stream_at, "/P", &page_number, &page_generation)) {
            page_index = b2026_pdf_object_lookup(doc, page_number, page_generation);
            link_active = page_index >= 0 && doc->objects[page_index].is_page && doc->objects[page_index].selected;
        } else if (!link_active) link_active = 1;
    }
    if (b2026_pdf_memfind(body, stream_at, "/GoTo", 5U) != NULL) link_active = 1;
    while (i < stream_at) {
        if (is_selected_page && i + 7U <= stream_at && memcmp(body + i, "/Parent", 7U) == 0 &&
            (i + 7U == stream_at || b2026_pdf_is_delim(body[i + 7U]))) {
            size_t p = i + 7U; char buf[64];
            while (p < stream_at && isspace(body[p])) ++p;
            while (p < stream_at && isdigit(body[p])) ++p; while (p < stream_at && isspace(body[p])) ++p;
            while (p < stream_at && isdigit(body[p])) ++p; while (p < stream_at && isspace(body[p])) ++p;
            if (p < stream_at && body[p] == 'R') ++p;
            snprintf(buf, sizeof(buf), "/Parent %d 0 R", parent_number);
            if (!b2026_pdf_write_text(fp, buf, written)) return 0; i = p; continue;
        }
        if (is_selected_page && rotation_override >= 0 && i + 7U <= stream_at && memcmp(body + i, "/Rotate", 7U) == 0) {
            size_t p = i + 7U; while (p < stream_at && isspace(body[p])) ++p;
            if (p < stream_at && (body[p] == '+' || body[p] == '-')) ++p;
            while (p < stream_at && isdigit(body[p])) ++p; i = p; continue;
        }
        if (is_selected_page && i == dictionary_close) {
            char buf[256];
            if (media_override != NULL) { snprintf(buf,sizeof(buf)," /MediaBox [%.17g %.17g %.17g %.17g] ",media_override[0],media_override[1],media_override[2],media_override[3]);if(!b2026_pdf_write_text(fp,buf,written))return 0; }
            if (crop_override != NULL) { snprintf(buf,sizeof(buf)," /CropBox [%.17g %.17g %.17g %.17g] ",crop_override[0],crop_override[1],crop_override[2],crop_override[3]);if(!b2026_pdf_write_text(fp,buf,written))return 0; }
            if (rotation_override >= 0 && !inserted_rotate) { snprintf(buf, sizeof(buf), " /Rotate %d ", rotation_override);if (!b2026_pdf_write_text(fp, buf, written)) return 0;inserted_rotate = 1; }
        }
        if(annotation_page_number>0&&i==dictionary_close){int existing_number,existing_generation;if(!b2026_pdf_named_ref(body,stream_at,"/P",&existing_number,&existing_generation)){char owner[64];snprintf(owner,sizeof(owner)," /P %d 0 R ",annotation_page_number);if(!b2026_pdf_write_text(fp,owner,written))return 0;}}
        if (isdigit(body[i]) && (i == 0U || b2026_pdf_is_delim(body[i - 1U]))) {
            size_t a0 = i, p = i; unsigned long a = 0UL, g = 0UL; int oi; char buf[64];
            while (p < stream_at && isdigit(body[p])) { a = a * 10UL + (unsigned long)(body[p] - '0'); ++p; }
            if (p < stream_at && isspace(body[p])) {
                while (p < stream_at && isspace(body[p])) ++p;
                if (p < stream_at && isdigit(body[p])) {
                    while (p < stream_at && isdigit(body[p])) { g = g * 10UL + (unsigned long)(body[p] - '0'); ++p; }
                    while (p < stream_at && isspace(body[p])) ++p;
                    if (p < stream_at && body[p] == 'R' && a <= INT_MAX && g <= INT_MAX &&
                        (oi = b2026_pdf_object_lookup(doc, (int)a, (int)g)) >= 0) {
                        if (link_active && doc->objects[oi].is_page && !doc->objects[oi].selected) {
                            if (!b2026_pdf_write_text(fp, "null", written)) return 0;
                            if (disabled_link != NULL) *disabled_link = 1;
                            i = p + 1U; continue;
                        }
                        if (doc->objects[oi].is_page && current_page_output > 0 && oi == current_page_index)
                            snprintf(buf, sizeof(buf), "%d 0 R", current_page_output);
                        else if (doc->objects[oi].is_page && doc->objects[oi].page_target_number > 0)
                            snprintf(buf, sizeof(buf), "%d 0 R", doc->objects[oi].page_target_number);
                        else if (doc->objects[oi].new_number > 0) snprintf(buf, sizeof(buf), "%d 0 R", doc->objects[oi].new_number);
                        else snprintf(buf, sizeof(buf), "null");
                        if (!b2026_pdf_write_text(fp, buf, written)) return 0; i = p + 1U; continue;
                    }
                }
            }
            i = a0;
        }
        if (!b2026_pdf_write_bytes(fp, body + i, 1U, written)) return 0; ++i;
    }
    return i >= n || b2026_pdf_write_bytes(fp, body + i, n - i, written);
}

static int b2026_pdf_write_output(const char *target, B2026PdfDocument *docs, int doc_count,
                                  B2026PdfPageList *pages, int clone_pages, int reachable_only,
                                  int preserve_catalog) {
    char *temp; FILE *fp; unsigned long long written = 0U; long *offsets; int next_object;
    int pages_object, catalog_object, total, d, i, k = 1, ok = 0, next_blank, disabled_link = 0; char buf[256];
    char directory_detail[512];
    if (!saveimage_ensure_directories_safe(target, directory_detail, sizeof(directory_detail))) return 0;
    temp = b2026_atomic_temp_path(target); if (temp == NULL) return 0;
    fp = b2026_fopen(temp, "wb"); if (fp == NULL) { free(temp); return 0; }
    for (i = 0; i < pages->count; ++i) if (pages->items[i].object_index >= 0) {
        B2026PdfPage *page = &pages->items[i]; double inherited[4], direct[4]; int direct_rotation;
        if (!b2026_pdf_page_direct_box(page->doc,page->object_index,"/MediaBox",direct) &&
            b2026_pdf_page_box(page->doc,page->object_index,"/MediaBox",inherited)) { memcpy(page->media,inherited,sizeof(inherited));page->has_media=1; }
        if (!b2026_pdf_page_direct_box(page->doc,page->object_index,"/CropBox",direct) &&
            b2026_pdf_page_box(page->doc,page->object_index,"/CropBox",inherited)) { memcpy(page->crop,inherited,sizeof(inherited));page->has_crop=1; }
        if (page->rotation_override < 0 && !b2026_pdf_dict_integer(page->doc->objects[page->object_index].body,page->doc->objects[page->object_index].body_length,"/Rotate",&direct_rotation)) {
            int inherited_rotation=b2026_pdf_page_inherited_integer(page->doc,page->object_index,"/Rotate",0);if(inherited_rotation!=0)page->rotation_override=((inherited_rotation%360)+360)%360;
        }
    }
    for (d = 0; d < doc_count; ++d) for (i = 0; i < docs[d].object_count; ++i) { docs[d].objects[i].selected = 0; docs[d].objects[i].page_target_number = 0; }
    for (i = 0; i < pages->count; ++i) if (pages->items[i].object_index >= 0)
        pages->items[i].doc->objects[pages->items[i].object_index].selected = 1;
    if(reachable_only&&!b2026_pdf_select_reachable_objects(docs,doc_count,pages,preserve_catalog))goto done;
    next_object = b2026_pdf_assign_numbers(docs, doc_count);
    next_blank = next_object;
    if (clone_pages) {
        for (i = 0; i < pages->count; ++i) {
            B2026PdfPage *page=&pages->items[i]; page->output_number=next_blank++;
            if(page->object_index>=0&&page->doc->objects[page->object_index].page_target_number==0)
                page->doc->objects[page->object_index].page_target_number=page->output_number;
        }
    } else {
        for (i = 0; i < pages->count; ++i) if (pages->items[i].object_index < 0) pages->items[i].object_index = -(next_blank++);
    }
    pages_object = next_blank; catalog_object = pages_object + 1; total = catalog_object;
    offsets = (long *)calloc((size_t)total + 1U, sizeof(long)); if (offsets == NULL) goto done;
    snprintf(buf, sizeof(buf), "%%PDF-%d.%d\n%%\xE2\xE3\xCF\xD3\n", docs[0].version_major, docs[0].version_minor);
    if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
    for (d = 0; d < doc_count; ++d) for (i = 0; i < docs[d].object_count; ++i) if(docs[d].objects[i].included) {
        B2026PdfObject *o = &docs[d].objects[i]; int selected = 0, rotation = -1;
        offsets[o->new_number] = ftell(fp); snprintf(buf, sizeof(buf), "%d 0 obj\n", o->new_number);
        if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
        for (k = 0; k < pages->count; ++k) if (pages->items[k].doc == &docs[d] && pages->items[k].object_index == i) {
            selected = 1; if (pages->items[k].rotation_override >= 0) rotation = pages->items[k].rotation_override; break;
        }
        { const double *media=NULL,*crop=NULL;int annotation_page=b2026_pdf_annotation_owner_number(&docs[d],i,pages,clone_pages); if(selected)for(k=0;k<pages->count;++k)if(pages->items[k].doc==&docs[d]&&pages->items[k].object_index==i){media=pages->items[k].has_media?pages->items[k].media:NULL;crop=pages->items[k].has_crop?pages->items[k].crop:NULL;break;}
        if (!b2026_pdf_emit_ref_rewritten(fp, &docs[d], o->body, o->body_length,
                selected, pages_object, rotation, media, crop, annotation_page, &disabled_link, -1, -1, &written) || !b2026_pdf_write_text(fp, "\nendobj\n", &written)) goto done; }
    }
    /* Page composition writes a distinct page object for every output entry. */
    if (clone_pages) for (i = 0; i < pages->count; ++i) if (pages->items[i].object_index >= 0) {
        B2026PdfPage *page=&pages->items[i]; B2026PdfObject *source=&page->doc->objects[page->object_index];
        offsets[page->output_number]=ftell(fp);snprintf(buf,sizeof(buf),"%d 0 obj\n",page->output_number);
        if(!b2026_pdf_write_text(fp,buf,&written))goto done;
        if(!b2026_pdf_emit_ref_rewritten(fp,page->doc,source->body,source->body_length,1,pages_object,
                page->rotation_override,page->has_media?page->media:NULL,page->has_crop?page->crop:NULL,-1,&disabled_link,page->object_index,page->output_number,&written)||
           !b2026_pdf_write_text(fp,"\nendobj\n",&written))goto done;
    }
    /* Generated blank pages follow copied objects. */
    for (i = 0; i < pages->count; ++i) if (pages->items[i].object_index < 0) {
        int id = clone_pages ? pages->items[i].output_number : -pages->items[i].object_index;
        offsets[id] = ftell(fp); snprintf(buf, sizeof(buf), "%d 0 obj\n<< /Type /Page /Parent %d 0 R /MediaBox [%.6g %.6g %.6g %.6g] /Resources << >>",
            id, pages_object, pages->items[i].media[0], pages->items[i].media[1], pages->items[i].media[2], pages->items[i].media[3]);
        if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
        if (pages->items[i].has_crop) {
            snprintf(buf, sizeof(buf), " /CropBox [%.6g %.6g %.6g %.6g]", pages->items[i].crop[0], pages->items[i].crop[1], pages->items[i].crop[2], pages->items[i].crop[3]);
            if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
        }
        if (pages->items[i].rotation_override >= 0) {
            snprintf(buf, sizeof(buf), " /Rotate %d", pages->items[i].rotation_override);
            if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
        }
        if (!b2026_pdf_write_text(fp, " >>\nendobj\n", &written)) goto done;
    }
    offsets[pages_object] = ftell(fp); snprintf(buf, sizeof(buf), "%d 0 obj\n<< /Type /Pages /Count %d /Kids [", pages_object, pages->count);
    if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
    for (i = 0; i < pages->count; ++i) {
        int id = clone_pages ? pages->items[i].output_number :
                 (pages->items[i].object_index < 0 ? -pages->items[i].object_index :
                 pages->items[i].doc->objects[pages->items[i].object_index].new_number);
        snprintf(buf, sizeof(buf), " %d 0 R", id); if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
    }
    if (!b2026_pdf_write_text(fp, " ] >>\nendobj\n", &written)) goto done;
    offsets[catalog_object] = ftell(fp); snprintf(buf, sizeof(buf), "%d 0 obj\n<< /Type /Catalog /Pages %d 0 R", catalog_object, pages_object);
    if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
    if(docs[0].metadata_object>=0){snprintf(buf,sizeof(buf)," /Metadata %d 0 R",docs[0].objects[docs[0].metadata_object].new_number);if(!b2026_pdf_write_text(fp,buf,&written))goto done;}
    if(preserve_catalog&&docs[0].root_object>=0){B2026PdfObject *root=&docs[0].objects[docs[0].root_object];size_t start=0U,end=root->body_length;while(start+1U<end&&!(root->body[start]=='<'&&root->body[start+1U]=='<'))++start;if(start+1U<end){size_t value_end=b2026_pdf_edit_value_end(root->body,end,start,0);if(value_end>=start+4U&&value_end<=end){if(!b2026_pdf_write_text(fp," ",&written)||!b2026_pdf_emit_ref_rewritten(fp,&docs[0],root->body+start+2U,value_end-start-4U,0,pages_object,-1,NULL,NULL,-1,&disabled_link,-1,-1,&written))goto done;}}}
    if (!b2026_pdf_write_text(fp, " >>\nendobj\n", &written)) goto done;
    { long xref = ftell(fp); snprintf(buf, sizeof(buf), "xref\n0 %d\n0000000000 65535 f \n", total + 1); if (!b2026_pdf_write_text(fp, buf, &written)) goto done;
      for (i = 1; i <= total; ++i) { snprintf(buf, sizeof(buf), "%010ld 00000 n \n", offsets[i]); if (!b2026_pdf_write_text(fp, buf, &written)) goto done; }
      if (docs[0].info_object >= 0) snprintf(buf, sizeof(buf), "trailer\n<< /Size %d /Root %d 0 R /Info %d 0 R >>\nstartxref\n%ld\n%%%%EOF\n", total + 1, catalog_object, docs[0].objects[docs[0].info_object].new_number, xref);
      else snprintf(buf, sizeof(buf), "trailer\n<< /Size %d /Root %d 0 R >>\nstartxref\n%ld\n%%%%EOF\n", total + 1, catalog_object, xref);
      if (!b2026_pdf_write_text(fp, buf, &written)) goto done; }
    if (fflush(fp) != 0 || ferror(fp)) goto done; ok = 1;
done:
    free(offsets); if (fclose(fp) != 0) ok = 0;
    if (ok) ok = b2026_atomic_commit_file(temp, target); if (!ok) b2026_atomic_discard_file(temp);
    if (ok && disabled_link) b2026_pdf_log_warning("抽出・削除対象ページへの内部リンクを無効化しました。 / Internal links to omitted pages were disabled.");
    free(temp); return ok;
}

static const char *b2026_pdf_load_error(int rc) {
    if (rc == -2) return "encrypted/password-protected PDF is not supported / 暗号化・パスワード保護PDFには対応していません";
    if (rc == -3) return "digitally signed PDF cannot be edited / 電子署名付きPDFは編集できません";
    if (rc == -4) return "PDF forms, attachments, or JavaScript were detected; edit refused / フォーム、添付ファイル、JavaScriptを検出したため編集を拒否しました";
    if (rc == -5) return "compressed object stream could not be expanded / オブジェクトストリームを展開できません";
    if (rc == -6) return "PDF attachments or JavaScript were detected; edit refused / 添付ファイルまたはJavaScriptを検出したため編集を拒否しました";
    return "file does not exist, is damaged, or is not a supported PDF / ファイルが存在しない、破損している、または対応PDFではありません";
}

static int b2026_pdf_page_size_fail(int code, const char *path, const char *detail) {
    char message[4096];
    snprintf(message, sizeof(message),
             "PDF.PAGESIZE failed: path=%s: %s",
             path != NULL ? path : "", detail != NULL ? detail : "unknown error / 不明なエラー");
    basic_set_error_logged(code, message, "PDF.PAGESIZE", current_source_path, current_line, "PDF");
    b2026_try_append_log_line("__PDF_ERROR.log", message);
    return 0;
}

static int b2026_pdf_page_size_box_name(const B2026PdfArg *arg, int *kind,
                                        const char **canonical) {
    const char *s;
    if (arg == NULL) { *kind = 0; *canonical = "MEDIA"; return 1; }
    if (arg->kind != 1 || arg->text == NULL) return 0;
    s = arg->text;
    if (b2026_strcasecmp(s, "MEDIA") == 0 || b2026_strcasecmp(s, "MEDIABOX") == 0 || strcmp(s, "用紙") == 0) { *kind = 0; *canonical = "MEDIA"; return 1; }
    if (b2026_strcasecmp(s, "CROP") == 0 || b2026_strcasecmp(s, "CROPBOX") == 0 || strcmp(s, "表示") == 0) { *kind = 1; *canonical = "CROP"; return 1; }
    if (b2026_strcasecmp(s, "TRIM") == 0 || b2026_strcasecmp(s, "TRIMBOX") == 0 || strcmp(s, "仕上がり") == 0) { *kind = 2; *canonical = "TRIM"; return 1; }
    if (b2026_strcasecmp(s, "BLEED") == 0 || b2026_strcasecmp(s, "BLEEDBOX") == 0 || strcmp(s, "塗り足し") == 0) { *kind = 3; *canonical = "BLEED"; return 1; }
    if (b2026_strcasecmp(s, "ART") == 0 || b2026_strcasecmp(s, "ARTBOX") == 0 || strcmp(s, "アート") == 0) { *kind = 4; *canonical = "ART"; return 1; }
    return 0;
}

static void b2026_pdf_page_size_set_numbers(const char *target_name, const char *jp,
                                             const char *en, double value) {
    hash_set_number(target_name, jp, value); hash_set_number(target_name, en, value);
}

static void b2026_pdf_page_size_set_strings(const char *target_name, const char *jp,
                                             const char *en, const char *value) {
    hash_set_string(target_name, jp, value); hash_set_string(target_name, en, value);
}

static int b2026_pdf_page_size(const char *target_name, int argc, const B2026PdfArg *args) {
    HashVariable *target; B2026PdfDocument doc; const char *path, *canonical, *orientation, *jp_orientation;
    int page, clamped, box_kind, object_index, rotation, rc; double media[4], raw[4], chosen[4];
    double media_left, media_bottom, media_right, media_top, left, bottom, right, top;
    double user_unit = 1.0, original_width, original_height, width, height, tolerance;
    if (target_name == NULL || *target_name == '\0')
        return b2026_pdf_page_size_fail(4140, "", "result must be assigned to an associative array / 結果は連想配列へ代入してください");
    target = ensure_local_hash(get_hash_frame(target_name), target_name); clear_hash_entries(target);
    if (argc < 2 || argc > 3 || args[0].kind != 1 || args[0].text == NULL ||
        !b2026_pdf_arg_integer(&args[1], &page) ||
        !b2026_pdf_page_size_box_name(argc == 3 ? &args[2] : NULL, &box_kind, &canonical))
        return b2026_pdf_page_size_fail(4141, argc > 0 && args[0].kind == 1 ? args[0].text : "",
            "PAGESIZE requires path, integer page, and optional valid box / 用紙サイズにはPDFパス、整数ページ、任意の有効なボックス名が必要です");
    path = args[0].text; rc = b2026_pdf_load(&doc, path, 0);
    if (rc <= 0) { b2026_pdf_document_free(&doc); return b2026_pdf_page_size_fail(4142, path, b2026_pdf_load_error(rc)); }
    if (b2026_pdf_clamp_page(page, doc.page_count, &clamped))
        b2026_pdf_log_warning("用紙サイズを調べるページ番号を有効な範囲へ調整しました。 / PAGESIZE page number was adjusted.");
    object_index = doc.page_objects[clamped - 1];
    if (!b2026_pdf_page_box(&doc, object_index, "/MediaBox", media)) {
        b2026_pdf_document_free(&doc); return b2026_pdf_page_size_fail(4143, path, "MediaBox could not be resolved / MediaBoxを解決できません");
    }
    memcpy(chosen, media, sizeof(chosen));
    if (box_kind == 1) {
        if (b2026_pdf_page_box(&doc, object_index, "/CropBox", raw)) memcpy(chosen, raw, sizeof(chosen));
    } else if (box_kind >= 2) {
        const char *key = box_kind == 2 ? "/TrimBox" : (box_kind == 3 ? "/BleedBox" : "/ArtBox");
        int box_status = b2026_pdf_page_direct_box(&doc, object_index, key, raw);
        if (box_status < 0) { b2026_pdf_document_free(&doc); return b2026_pdf_page_size_fail(4145, path, "page box coordinates are invalid / ページボックス座標が不正です"); }
        if (box_status > 0) memcpy(chosen, raw, sizeof(chosen));
        else if (b2026_pdf_page_box(&doc, object_index, "/CropBox", raw)) memcpy(chosen, raw, sizeof(chosen));
    }
    memcpy(raw, chosen, sizeof(raw));
    rc = b2026_pdf_page_direct_number(&doc, object_index, "/UserUnit", &user_unit);
    if (rc < 0 || (rc > 0 && (!isfinite(user_unit) || user_unit <= 0.0))) {
        b2026_pdf_document_free(&doc); return b2026_pdf_page_size_fail(4144, path, "UserUnit must be positive and finite / UserUnitが正の有限値ではありません");
    }
    media_left = fmin(media[0], media[2]); media_right = fmax(media[0], media[2]);
    media_bottom = fmin(media[1], media[3]); media_top = fmax(media[1], media[3]);
    left = fmin(chosen[0], chosen[2]); right = fmax(chosen[0], chosen[2]);
    bottom = fmin(chosen[1], chosen[3]); top = fmax(chosen[1], chosen[3]);
    if (box_kind != 0) {
        left = fmax(left, media_left); right = fmin(right, media_right);
        bottom = fmax(bottom, media_bottom); top = fmin(top, media_top);
    }
    if (!isfinite(left) || !isfinite(right) || !isfinite(bottom) || !isfinite(top) ||
        right <= left || top <= bottom) {
        b2026_pdf_document_free(&doc); return b2026_pdf_page_size_fail(4145, path, "page box is empty or invalid / ページボックスが空または不正です");
    }
    left *= user_unit; right *= user_unit; bottom *= user_unit; top *= user_unit;
    original_width = right - left; original_height = top - bottom;
    rotation = b2026_pdf_page_inherited_integer(&doc, object_index, "/Rotate", 0);
    if (rotation % 90 != 0) {
        b2026_pdf_document_free(&doc); return b2026_pdf_page_size_fail(4146, path, "Rotate must be a multiple of 90 / Rotateが90の倍数ではありません");
    }
    rotation = ((rotation % 360) + 360) % 360;
    width = (rotation == 90 || rotation == 270) ? original_height : original_width;
    height = (rotation == 90 || rotation == 270) ? original_width : original_height;
    tolerance = fmax(width, height) * 1.0e-12;
    if (fabs(width - height) <= tolerance) { orientation = "SQUARE"; jp_orientation = "正方形"; }
    else if (width > height) { orientation = "LANDSCAPE"; jp_orientation = "横"; }
    else { orientation = "PORTRAIT"; jp_orientation = "縦"; }
    b2026_pdf_page_size_set_numbers(target_name, "取得", "ok", 1.0);
    b2026_pdf_page_size_set_numbers(target_name, "ページ", "page", (double)clamped);
    b2026_pdf_page_size_set_numbers(target_name, "総ページ数", "pages", (double)doc.page_count);
    b2026_pdf_page_size_set_strings(target_name, "ボックス", "box", canonical);
    b2026_pdf_page_size_set_numbers(target_name, "横幅", "width", width);
    b2026_pdf_page_size_set_numbers(target_name, "縦幅", "height", height);
    b2026_pdf_page_size_set_numbers(target_name, "元横幅", "originalWidth", original_width);
    b2026_pdf_page_size_set_numbers(target_name, "元縦幅", "originalHeight", original_height);
    b2026_pdf_page_size_set_numbers(target_name, "横幅mm", "widthMM", width * 25.4 / 72.0);
    b2026_pdf_page_size_set_numbers(target_name, "縦幅mm", "heightMM", height * 25.4 / 72.0);
    b2026_pdf_page_size_set_numbers(target_name, "横幅inch", "widthInch", width / 72.0);
    b2026_pdf_page_size_set_numbers(target_name, "縦幅inch", "heightInch", height / 72.0);
    b2026_pdf_page_size_set_numbers(target_name, "左", "left", left);
    b2026_pdf_page_size_set_numbers(target_name, "下", "bottom", bottom);
    b2026_pdf_page_size_set_numbers(target_name, "右", "right", right);
    b2026_pdf_page_size_set_numbers(target_name, "上", "top", top);
    hash_set_number(target_name, "rawLeft", raw[0]); hash_set_number(target_name, "rawBottom", raw[1]);
    hash_set_number(target_name, "rawRight", raw[2]); hash_set_number(target_name, "rawTop", raw[3]);
    b2026_pdf_page_size_set_numbers(target_name, "回転", "rotation", (double)rotation);
    hash_set_number(target_name, "userUnit", user_unit);
    b2026_pdf_page_size_set_strings(target_name, "単位", "unit", "pt");
    hash_set_string(target_name, "向き", jp_orientation); hash_set_string(target_name, "orientation", orientation);
    b2026_pdf_document_free(&doc); return 1;
}

static double b2026_pdf_page_count_path(const char *path) {
    B2026PdfDocument doc; int rc = b2026_pdf_load(&doc, path, 0); double result;
    if (rc <= 0) { b2026_pdf_document_free(&doc); return b2026_pdf_fail_number(4101, path, b2026_pdf_load_error(rc)); }
    result = (double)doc.page_count; b2026_pdf_document_free(&doc); return result;
}

static double b2026_pdf_page_count(int argc, const B2026PdfArg *args) {
    if (argc != 1 || args[0].kind != 1 || args[0].text == NULL)
        return b2026_pdf_fail_number(4100, "", "PAGECOUNT requires a PDF path / ページ数にはPDFパスが必要です");
    return b2026_pdf_page_count_path(args[0].text);
}

static int b2026_pdf_flatten_merge(const B2026PdfArg *args, int argc, char ***paths_out, int *count_out) {
    char **paths = NULL; int count = 0, cap = 0, i, j;
    for (i = 1; i < argc; ++i) {
        if (args[i].kind == 1) {
            const char *s = args[i].text;
            if (s == NULL || *s == '\0') { b2026_pdf_log_warning("空のPDFパスを読み飛ばしました。 / Empty PDF path was skipped."); continue; }
            if (count == cap) { int nc = cap == 0 ? 8 : cap * 2; char **p = (char **)realloc(paths, (size_t)nc * sizeof(*p)); if (p == NULL) goto fail; paths = p; cap = nc; }
            paths[count] = b2026_strdup_simple(s); if (paths[count++] == NULL) goto fail;
        } else if (args[i].kind == 2) {
            ArrayVariable *array = require_array_variable(args[i].array_name); int n = array_logical_count(array);
            for (j = 0; j < n; ++j) {
                B2026_Var *slot = &array->data[option_base + j]; const char *s;
                if (!b2026_var_is_string(slot) && !b2026_var_is_null(slot)) goto fail;
                s = b2026_var_is_null(slot) ? "" : b2026_var_as_text(slot);
                if (s == NULL || *s == '\0') { b2026_pdf_log_warning("文字列配列内の空要素を読み飛ばしました。 / Empty string-array element was skipped."); continue; }
                if (count == cap) { int nc = cap == 0 ? 8 : cap * 2; char **p = (char **)realloc(paths, (size_t)nc * sizeof(*p)); if (p == NULL) goto fail; paths = p; cap = nc; }
                paths[count] = b2026_strdup_simple(s); if (paths[count++] == NULL) goto fail;
            }
        } else goto fail;
    }
    *paths_out = paths; *count_out = count; return 1;
fail:
    for (i = 0; i < count; ++i) free(paths[i]); free(paths); return 0;
}

static const char *b2026_pdf_edit(const char *operation, int argc, const B2026PdfArg *args) {
    char *output = NULL; const char *source = NULL; B2026PdfDocument *docs = NULL; int doc_count = 0;
    B2026PdfPageList pages, replacements; unsigned char *selection = NULL; int i, rc, ok = 0;
    memset(&pages, 0, sizeof(pages)); memset(&replacements, 0, sizeof(replacements));
    if (strcmp(operation, "MERGE") == 0) {
        char **paths = NULL; int path_count = 0;
        if (argc < 2 || args[0].kind != 1 || !b2026_pdf_flatten_merge(args, argc, &paths, &path_count) || path_count < 2) {
            if (paths != NULL) { for (i = 0; i < path_count; ++i) free(paths[i]); free(paths); }
            return b2026_pdf_fail(4110, "", "MERGE requires an output path and at least two valid PDF inputs / 連結には保存先と2つ以上のPDFが必要です");
        }
        output = b2026_pdf_output_path(args[0].text); docs = (B2026PdfDocument *)calloc((size_t)path_count, sizeof(*docs)); doc_count = path_count;
        if (output == NULL || docs == NULL) { for (i = 0; i < path_count; ++i) free(paths[i]); free(paths); goto memory_fail; }
        for (i = 0; i < path_count; ++i) {
            rc = b2026_pdf_load(&docs[i], paths[i], 1);
            if (rc <= 0) { const char *bad = docs[i].path; b2026_pdf_fail(4111, bad, b2026_pdf_load_error(rc)); for (i = 0; i < path_count; ++i) free(paths[i]); free(paths); goto cleanup; }
            if (strcmp(output, paths[i]) == 0) b2026_pdf_log_warning("保存先PDF自身を入力として連結します。既存内容を読み込んでから置換します。 / Destination is also an input.");
            { int p; for (p = 0; p < docs[i].page_count; ++p) if (!b2026_pdf_pages_push(&pages, &docs[i], docs[i].page_objects[p], -1)) { for (i = 0; i < path_count; ++i) free(paths[i]); free(paths); goto memory_fail; } }
        }
        for (i = 0; i < path_count; ++i) free(paths[i]); free(paths);
    } else if (strcmp(operation, "REMOVEANNOTATIONS") == 0) {
        int separate = argc == 2, removed_pages = 0, had_form = 0, removed = 0;
        char *stage = NULL; const char *result;
        if ((argc != 1 && argc != 2) || args[0].kind != 1 || args[0].text == NULL ||
            (separate && (args[1].kind != 1 || args[1].text == NULL)))
            return b2026_pdf_fail(4180, "", "REMOVEANNOTATIONS requires path$ or output$, source$ / 注釈削除にはPDFパス、または保存先と入力元が必要です");
        output = b2026_pdf_output_path(args[0].text); source = separate ? args[1].text : args[0].text;
        docs = (B2026PdfDocument *)calloc(1U, sizeof(*docs)); doc_count = 1;
        if (output == NULL || docs == NULL) goto memory_fail;
        rc = b2026_pdf_load(&docs[0], source, 3);
        if (rc <= 0) { b2026_pdf_fail(4181, source, b2026_pdf_load_error(rc)); goto cleanup; }
        had_form = b2026_pdf_memfind(docs[0].bytes, docs[0].length, "/AcroForm", 9U) != NULL;
        if (!b2026_pdf_remove_all_annotations(&docs[0], &removed_pages)) goto memory_fail;
        if (docs[0].root_object >= 0) {
            static const char *remove_keys[] = {"/Type", "/Pages", "/AcroForm", "/Metadata"};
            int key_index;
            for (key_index = 0; key_index < 4; ++key_index) {
                do {
                    if (!b2026_pdf_remove_object_key(&docs[0].objects[docs[0].root_object],
                            remove_keys[key_index], &removed)) goto memory_fail;
                } while (removed);
            }
        }
        for (i = 0; i < docs[0].page_count; ++i)
            if (!b2026_pdf_pages_push(&pages, &docs[0], docs[0].page_objects[i], -1)) goto memory_fail;
        stage = b2026_atomic_temp_path(output);
        if (stage == NULL || !b2026_pdf_write_output(stage, docs, 1, &pages, 0, 1, 1)) {
            if (stage != NULL) b2026_atomic_discard_file(stage); free(stage);
            b2026_pdf_fail(4182, output, "annotation-free PDF could not be written atomically or exceeded 2 GiB / 注釈削除後のPDFを原子的に保存できないか2GiB上限を超えました");
            goto cleanup;
        }
        if (!b2026_pdf_annotations_removed(stage)) {
            b2026_atomic_discard_file(stage); free(stage);
            b2026_pdf_fail(4183, output, "post-write verification found annotations or form data / 保存後の検証で注釈またはフォーム情報が残っています");
            goto cleanup;
        }
        if (!b2026_atomic_commit_file(stage, output)) {
            b2026_atomic_discard_file(stage); free(stage);
            b2026_pdf_fail(4184, output, "verified PDF could not be committed / 検証済みPDFを保存先へ反映できません");
            goto cleanup;
        }
        free(stage);
        if (had_form) b2026_pdf_log_warning("注釈削除によりフォームとAcroFormフィールドツリーを削除しました。 / REMOVEANNOTATIONS removed form widgets and the AcroForm field tree.");
        (void)removed_pages;
        result = runtime_temp_string_copy(output);
        b2026_pdf_document_free(&docs[0]); free(docs); free(pages.items); free(output);
        return result;
    } else {
        int separate = 0, source_index = 0, spec_index = 1;
        if (strcmp(operation, "DELETE") == 0 || strcmp(operation, "EXTRACT") == 0 || strcmp(operation, "REORDER") == 0) separate = argc == 3;
        else if (strcmp(operation, "ROTATE") == 0) separate = argc == 4;
        else if (strcmp(operation, "INSERTBLANK") == 0 || strcmp(operation, "INSERTPDF") == 0) separate = argc == 5;
        else if (strcmp(operation, "REPLACEPAGES") == 0) separate = argc == 5;
        if (argc < 2 || args[0].kind != 1 || (separate && args[1].kind != 1)) return b2026_pdf_fail(4120, "", "invalid PDF edit arguments / PDF編集の引数が正しくありません");
        output = b2026_pdf_output_path(args[0].text); source_index = separate ? 1 : 0; spec_index = separate ? 2 : 1; source = args[source_index].text;
        docs = (B2026PdfDocument *)calloc(2U, sizeof(*docs)); doc_count = 1; if (output == NULL || docs == NULL) goto memory_fail;
        rc = b2026_pdf_load(&docs[0], source, 1); if (rc <= 0) { b2026_pdf_fail(4121, source, b2026_pdf_load_error(rc)); goto cleanup; }
        if (b2026_pdf_memfind(docs[0].bytes, docs[0].length, "/Outlines", 9U) != NULL || b2026_pdf_memfind(docs[0].bytes, docs[0].length, "/PageLabels", 11U) != NULL || b2026_pdf_memfind(docs[0].bytes, docs[0].length, "/StructTreeRoot", 15U) != NULL)
            b2026_pdf_log_warning("ブックマーク、ページラベル、タグ構造は編集後PDFから除外されます。 / Bookmarks, page labels, or tagged structure are removed.");
        if(strcmp(operation,"REORDER")==0){
            if(!b2026_pdf_compose_page_spec(&args[spec_index],&docs[0],&pages)){b2026_pdf_fail(4128,source,"invalid ordered page specification or too many output pages / ページ構成の指定が不正か出力ページ数が多すぎます");goto cleanup;}
        } else if (strcmp(operation, "REPLACEPAGES") == 0) {
            const char *replacement_path = b2026_pdf_arg_text(&args[spec_index + 1]);
            int target_count = 0, replacement_index = 0;
            doc_count = 2;
            if (!b2026_pdf_parse_page_spec(&args[spec_index], docs[0].page_count, &selection)) {
                b2026_pdf_fail(4122, source, "invalid replacement target page specification / 置換対象ページの指定が正しくありません"); goto cleanup;
            }
            if (replacement_path == NULL || (rc = b2026_pdf_load(&docs[1], replacement_path, 1)) <= 0) {
                b2026_pdf_fail(4126, replacement_path, b2026_pdf_load_error(rc)); goto cleanup;
            }
            if (!b2026_pdf_compose_page_spec(&args[spec_index + 2], &docs[1], &replacements)) {
                b2026_pdf_fail(4129, replacement_path, "invalid ordered replacement page specification / 置換元ページの指定が正しくありません"); goto cleanup;
            }
            for (i = 0; i < docs[0].page_count; ++i) if (selection[i]) ++target_count;
            if (replacements.count != 1 && replacements.count != target_count) {
                b2026_pdf_fail(4129, replacement_path, "replacement page count must be one or equal the target page count / 置換元ページ数は1、または置換対象ページ数と同数である必要があります"); goto cleanup;
            }
            for (i = 0; i < docs[0].page_count; ++i) {
                if (selection[i]) {
                    B2026PdfPage *replacement = &replacements.items[replacements.count == 1 ? 0 : replacement_index++];
                    if (!b2026_pdf_pages_push(&pages, replacement->doc, replacement->object_index, -1)) goto memory_fail;
                } else if (!b2026_pdf_pages_push(&pages, &docs[0], docs[0].page_objects[i], -1)) goto memory_fail;
            }
        } else if (strcmp(operation, "DELETE") == 0 || strcmp(operation, "EXTRACT") == 0 || strcmp(operation, "ROTATE") == 0) {
            int rotation = -1;
            if (!b2026_pdf_parse_page_spec(&args[spec_index], docs[0].page_count, &selection)) { b2026_pdf_fail(4122, source, "invalid page specification / ページ指定が正しくありません"); goto cleanup; }
            if (strcmp(operation, "ROTATE") == 0 && !b2026_pdf_rotation(&args[spec_index + 1], &rotation)) { b2026_pdf_fail(4123, source, "invalid rotation; use LEFT, RIGHT, or 180 / 回転は左、右、180のいずれかです"); goto cleanup; }
            for (i = 0; i < docs[0].page_count; ++i) {
                int applied_rotation = -1;
                if (strcmp(operation, "DELETE") == 0 && selection[i]) continue;
                if (strcmp(operation, "EXTRACT") == 0 && !selection[i]) continue;
                if (strcmp(operation, "ROTATE") == 0 && selection[i]) {
                    int existing = b2026_pdf_page_inherited_integer(&docs[0], docs[0].page_objects[i], "/Rotate", 0);
                    applied_rotation = ((existing + rotation) % 360 + 360) % 360;
                }
                if (!b2026_pdf_pages_push(&pages, &docs[0], docs[0].page_objects[i], applied_rotation)) goto memory_fail;
            }
            if (pages.count == 0 && strcmp(operation, "DELETE") == 0) { if (!b2026_pdf_pages_push(&pages, &docs[0], -1, -1)) goto memory_fail; b2026_pdf_log_warning("ページが全て削除され0になったためA4空白ページを1ページ生成しました。 / All pages were deleted; one blank A4 page was generated."); }
            if (pages.count == 0) { b2026_pdf_fail(4127, source, "no pages were selected for extraction / 抽出対象ページがありません"); goto cleanup; }
        } else {
            int page, clamped, after, insert_count = 1, p;
            if (!b2026_pdf_arg_integer(&args[spec_index], &page) || !b2026_pdf_position_after(&args[spec_index + 1], &after)) { b2026_pdf_fail(4124, source, "invalid insertion page or position / 挿入ページまたは前後指定が正しくありません"); goto cleanup; }
            if (b2026_pdf_clamp_page(page, docs[0].page_count, &clamped)) b2026_pdf_log_warning("挿入ページを有効な範囲へ調整しました。 / Insertion page was adjusted.");
            if (strcmp(operation, "INSERTBLANK") == 0) {
                if (!b2026_pdf_arg_integer(&args[spec_index + 2], &insert_count) || insert_count < 1) { b2026_pdf_fail(4125, source, "blank page count must be 1 or greater / 白紙ページ数は1以上です"); goto cleanup; }
            } else {
                const char *insert_path = b2026_pdf_arg_text(&args[spec_index + 2]); doc_count = 2;
                if (insert_path == NULL || (rc = b2026_pdf_load(&docs[1], insert_path, 1)) <= 0) { b2026_pdf_fail(4126, insert_path, b2026_pdf_load_error(rc)); goto cleanup; }
            }
            for (i = 0; i < docs[0].page_count; ++i) {
                if (i == clamped - 1 && !after) {
                    if (strcmp(operation, "INSERTBLANK") == 0) for (p = 0; p < insert_count; ++p) { B2026PdfPage *blank; double box[4]; if (!b2026_pdf_pages_push(&pages, &docs[0], -1, -1)) goto memory_fail; blank = &pages.items[pages.count - 1]; if (b2026_pdf_page_box(&docs[0], docs[0].page_objects[i], "/MediaBox", box)) { memcpy(blank->media, box, sizeof(box)); blank->has_media = 1; } if (b2026_pdf_page_box(&docs[0], docs[0].page_objects[i], "/CropBox", box)) { memcpy(blank->crop, box, sizeof(box)); blank->has_crop = 1; } blank->rotation_override = ((b2026_pdf_page_inherited_integer(&docs[0], docs[0].page_objects[i], "/Rotate", 0) % 360) + 360) % 360; }
                    else for (p = 0; p < docs[1].page_count; ++p) if (!b2026_pdf_pages_push(&pages, &docs[1], docs[1].page_objects[p], -1)) goto memory_fail;
                }
                if (!b2026_pdf_pages_push(&pages, &docs[0], docs[0].page_objects[i], -1)) goto memory_fail;
                if (i == clamped - 1 && after) {
                    if (strcmp(operation, "INSERTBLANK") == 0) for (p = 0; p < insert_count; ++p) { B2026PdfPage *blank; double box[4]; if (!b2026_pdf_pages_push(&pages, &docs[0], -1, -1)) goto memory_fail; blank = &pages.items[pages.count - 1]; if (b2026_pdf_page_box(&docs[0], docs[0].page_objects[i], "/MediaBox", box)) { memcpy(blank->media, box, sizeof(box)); blank->has_media = 1; } if (b2026_pdf_page_box(&docs[0], docs[0].page_objects[i], "/CropBox", box)) { memcpy(blank->crop, box, sizeof(box)); blank->has_crop = 1; } blank->rotation_override = ((b2026_pdf_page_inherited_integer(&docs[0], docs[0].page_objects[i], "/Rotate", 0) % 360) + 360) % 360; }
                    else for (p = 0; p < docs[1].page_count; ++p) if (!b2026_pdf_pages_push(&pages, &docs[1], docs[1].page_objects[p], -1)) goto memory_fail;
                }
            }
        }
    }
    ok = b2026_pdf_write_output(output, docs, doc_count, &pages, strcmp(operation,"REORDER")==0||strcmp(operation,"REPLACEPAGES")==0, 0, 0);
    if (!ok) { b2026_pdf_fail(4130, output, "output could not be written atomically or exceeded 2 GiB / 原子的に保存できないか2GiB上限を超えました"); goto cleanup; }
    { const char *result = runtime_temp_string_copy(output); for (i = 0; i < doc_count; ++i) b2026_pdf_document_free(&docs[i]); free(docs); free(pages.items); free(replacements.items); free(selection); free(output); return result; }
memory_fail:
    b2026_pdf_fail(4199, output, "out of memory / メモリ不足です");
cleanup:
    if (docs != NULL) { for (i = 0; i < doc_count; ++i) b2026_pdf_document_free(&docs[i]); free(docs); }
    free(pages.items); free(replacements.items); free(selection); free(output); return runtime_temp_string_copy("");
}
