static int savepdf_write_text_pdf_builtin(const char *path, const char *text, char *detail, size_t detail_size);

#if B2026_HAVE_MACOS_PDF
static CTFontRef savepdf_create_font(double size) {
    const char *names[] = {"HiraginoSans-W3", "Hiragino Sans", "Helvetica"};
    size_t i;
    for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
        CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, names[i], kCFStringEncodingUTF8);
        CTFontRef font;
        if (name == NULL) continue;
        font = CTFontCreateWithName(name, (CGFloat)size, NULL);
        CFRelease(name);
        if (font != NULL) return font;
    }
    return NULL;
}

static int savepdf_write_pdf_macos(const char *path, const char *text, char *detail, size_t detail_size) {
    static const CGRect media_box = {{0, 0}, {595, 842}};
    const CGFloat margin = 50.0f;
    const CGFloat font_size = 12.0f;
    CFURLRef url = NULL;
    CGContextRef ctx = NULL;
    CFStringRef text_cf = NULL;
    CTFontRef font = NULL;
    CFMutableAttributedStringRef attr = NULL;
    CTFramesetterRef framesetter = NULL;
    CFIndex total_length = 0;
    CFIndex start = 0;
    int ok = 0;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    if (text == NULL) text = "";
    url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)path, (CFIndex)strlen(path), false);
    if (url == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "invalid path");
        goto cleanup;
    }
    ctx = CGPDFContextCreateWithURL(url, &media_box, NULL);
    if (ctx == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF context create failed");
        goto cleanup;
    }
    if (*text == '\0') {
        CGPDFContextBeginPage(ctx, NULL);
        CGPDFContextEndPage(ctx);
        ok = 1;
        goto cleanup;
    }
    text_cf = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
    if (text_cf == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "UTF-8 decode failed");
        goto cleanup;
    }
    total_length = CFStringGetLength(text_cf);
    font = savepdf_create_font(font_size);
    if (font == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "font not available");
        goto cleanup;
    }
    attr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
    if (attr == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "attributed string create failed");
        goto cleanup;
    }
    CFAttributedStringReplaceString(attr, CFRangeMake(0, 0), text_cf);
    CFAttributedStringSetAttribute(attr, CFRangeMake(0, total_length), kCTFontAttributeName, font);
    {
        CTLineBreakMode break_mode = kCTLineBreakByWordWrapping;
        CGFloat line_spacing = 2.0f;
        CTParagraphStyleSetting settings[2];
        CTParagraphStyleRef style;
        settings[0].spec = kCTParagraphStyleSpecifierLineBreakMode;
        settings[0].valueSize = sizeof(CTLineBreakMode);
        settings[0].value = &break_mode;
#if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
        settings[1].spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
#else
        /* Snow Leopard's CoreText predates LineSpacingAdjustment. */
        settings[1].spec = kCTParagraphStyleSpecifierLineSpacing;
#endif

        settings[1].valueSize = sizeof(CGFloat);
        settings[1].value = &line_spacing;
        style = CTParagraphStyleCreate(settings, 2);
        if (style != NULL) {
            CFAttributedStringSetAttribute(attr, CFRangeMake(0, total_length), kCTParagraphStyleAttributeName, style);
            CFRelease(style);
        }
    }
    framesetter = CTFramesetterCreateWithAttributedString(attr);
    if (framesetter == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "framesetter create failed");
        goto cleanup;
    }
    while (start < total_length) {
        CGRect text_rect = CGRectMake(margin, margin, media_box.size.width - margin * 2.0f, media_box.size.height - margin * 2.0f);
        CGMutablePathRef path_ref = NULL;
        CTFrameRef frame = NULL;
        CFRange visible;
        CGPDFContextBeginPage(ctx, NULL);
        CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);
        path_ref = CGPathCreateMutable();
        if (path_ref == NULL) {
            CGPDFContextEndPage(ctx);
            if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "path create failed");
            goto cleanup;
        }
        CGPathAddRect(path_ref, NULL, text_rect);
        frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(start, 0), path_ref, NULL);
        if (frame == NULL) {
            CGPathRelease(path_ref);
            CGPDFContextEndPage(ctx);
            if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "frame create failed");
            goto cleanup;
        }
        CTFrameDraw(frame, ctx);
        visible = CTFrameGetVisibleStringRange(frame);
        CFRelease(frame);
        CGPathRelease(path_ref);
        CGPDFContextEndPage(ctx);
        if (visible.length <= 0) {
            if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "layout produced no visible text");
            goto cleanup;
        }
        start += visible.length;
    }
    ok = 1;
cleanup:
    if (ctx != NULL) {
        if (ok) CGPDFContextClose(ctx);
        CGContextRelease(ctx);
    }
    if (framesetter != NULL) CFRelease(framesetter);
    if (attr != NULL) CFRelease(attr);
    if (font != NULL) CFRelease(font);
    if (text_cf != NULL) CFRelease(text_cf);
    if (url != NULL) CFRelease(url);
    return ok;
}
#endif

static const char *save_pdf_file_direct_safe(const char *path, const char *text, int log_on_fail, int basic_line) {
    char detail[1024];
    char *resolved_path;
    detail[0] = '\0';
    if (!saveword_validate_text(text, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure(path, basic_line, detail);
        return runtime_temp_string_copy("");
    }
    resolved_path = savepdf_normalize_path_safe(path, detail, sizeof(detail));
    if (resolved_path == NULL) {
        if (log_on_fail) log_savepdf_failure(path, basic_line, detail);
        return runtime_temp_string_copy("");
    }
    if (path_targets_lib_dir(resolved_path)) {
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, "write to lib directory denied");
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (is_write_target_locked(resolved_path)) {
        char *fallback_path = saveimage_build_locked_fallback_path(resolved_path, detail, sizeof(detail));
        if (fallback_path == NULL) {
            if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail[0] != '\0' ? detail : "read-locked source file");
            free(resolved_path);
            return runtime_temp_string_copy("");
        }
        if (log_on_fail) {
            char notice[1024];
            snprintf(notice, sizeof(notice), "read-locked source file; saved to %s", fallback_path);
            log_savepdf_notice(resolved_path, basic_line, notice);
        }
        free(resolved_path);
        resolved_path = fallback_path;
    }
    if (!saveimage_ensure_directories_safe(resolved_path, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#if B2026_HAVE_MACOS_PDF
    if (g_option_savepdf_mode != 1 && !savepdf_write_pdf_macos(resolved_path, text, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (g_option_savepdf_mode == 1 && !savepdf_write_text_pdf_builtin(resolved_path, text, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "built-in text PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#else
    if (g_option_savepdf_mode == 2) {
        snprintf(detail, sizeof(detail), "OPTION SAVEPDF SYSTEM is unavailable on this platform / この環境ではSYSTEM方式を利用できません");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (!savepdf_write_text_pdf_builtin(resolved_path, text, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "built-in text PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#endif
    {
        const char *return_path = runtime_temp_string_copy(resolved_path);
        free(resolved_path);
        return return_path;
    }
}

const char *save_pdf_file_safe(const char *path, const char *text, int log_on_fail, int basic_line) {
    char detail[1024]; char *resolved_path=savepdf_normalize_path_safe(path,detail,sizeof(detail)); char *temp_path; const char *result; const char *return_path;
    if(resolved_path==NULL){if(log_on_fail)log_savepdf_failure(path,basic_line,detail);return runtime_temp_string_copy("");}
    if(path_targets_lib_dir(resolved_path)){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,"unsafe write target");free(resolved_path);return runtime_temp_string_copy("");}
    if(is_write_target_locked(resolved_path)){char*fallback_path=saveimage_build_locked_fallback_path(resolved_path,detail,sizeof(detail));if(fallback_path==NULL){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,detail[0]!='\0'?detail:"read-locked source file");free(resolved_path);return runtime_temp_string_copy("");}if(log_on_fail){char notice[1024];snprintf(notice,sizeof(notice),"read-locked source file; saved to %s",fallback_path);log_savepdf_notice(resolved_path,basic_line,notice);}free(resolved_path);resolved_path=fallback_path;}
    temp_path=b2026_atomic_temp_path(resolved_path);if(temp_path==NULL){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,"temporary path allocation error");free(resolved_path);return runtime_temp_string_copy("");}
    result=save_pdf_file_direct_safe(temp_path,text,log_on_fail,basic_line);if(result==NULL||result[0]=='\0'||!b2026_atomic_commit_file(temp_path,resolved_path)){b2026_atomic_discard_file(temp_path);if(log_on_fail&&result!=NULL&&result[0]!='\0')log_savepdf_failure(resolved_path,basic_line,"atomic replace error");free(temp_path);free(resolved_path);return runtime_temp_string_copy("");}
    return_path=runtime_temp_string_copy(resolved_path);free(temp_path);free(resolved_path);return return_path;
}

const char *save_pdf_file_by_index_safe(int target_index, const char *text, int log_on_fail, int basic_line) {
    int actual_index;
    char detail[256];
    if (!resolve_cli_target_index_safe(target_index, &actual_index, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure("", basic_line, detail);
        return runtime_temp_string_copy("");
    }
    if (actual_index == 0) {
        if (log_on_fail) log_savepdf_failure("#0", basic_line, "stdout not supported for pdf");
        return runtime_temp_string_copy("");
    }
    return save_pdf_file_safe(g_runtime_argv[actual_index], text, log_on_fail, basic_line);
}

static unsigned char *savepdf_build_rgba8_from_array(const char *array_name, int *width_out, int *height_out, char *detail, size_t detail_size) {
    ArrayVariable *array_var;
    long long logical_count;
    long long needed;
    long long gray_needed;
    int width;
    int height;
    int source_channels;
    int pixel_mode;
    int auto_scale_unit = 0;
    double source_value_max = 0.0;
    double max_value = 0.0;
    double min_value = 0.0;
    long long i;
    unsigned char *rgba8 = NULL;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    if (width_out != NULL) *width_out = 0;
    if (height_out != NULL) *height_out = 0;
    validate_array_name(array_name);
    array_var = require_array_variable(array_name);
    width = array_var->screen_width;
    height = array_var->screen_height;
    if (width <= 0 || height <= 0) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "array is not a pixel array (missing width/height metadata)");
        return NULL;
    }
    logical_count = array_logical_count(array_var);
    gray_needed = (long long)width * (long long)height;
    needed = gray_needed * 4LL;
    if (gray_needed <= 0 || needed <= 0) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "invalid image size");
        return NULL;
    }
    if (logical_count >= needed) source_channels = 4;
    else if (logical_count >= gray_needed) source_channels = 1;
    else {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "pixel buffer too small");
        return NULL;
    }
    pixel_mode = (array_var->image_pixel_mode >= 0 && array_var->image_pixel_mode <= 2) ? array_var->image_pixel_mode : 1;
    if (pixel_mode == 0) {
        min_value = max_value = saveimage_slot_number(&array_var->data[option_base]);
        for (i = 1; i < (source_channels == 4 ? needed : gray_needed); ++i) {
            double value = saveimage_slot_number(&array_var->data[option_base + i]);
            if (value < min_value) min_value = value;
            if (value > max_value) max_value = value;
        }
        if (min_value >= 0.0 && max_value <= 1.0) auto_scale_unit = 1;
    }
    source_value_max = saveimage_resolve_source_value_max(array_var, pixel_mode, auto_scale_unit);
    rgba8 = (unsigned char *)malloc((size_t)needed);
    if (rgba8 == NULL) runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。");
    if (source_channels == 4) {
        for (i = 0; i < needed; ++i) rgba8[i] = (unsigned char)saveimage_value_to_u8(saveimage_slot_number(&array_var->data[option_base + i]), source_value_max);
    } else {
        for (i = 0; i < gray_needed; ++i) {
            unsigned char gray = (unsigned char)saveimage_value_to_u8(saveimage_slot_number(&array_var->data[option_base + i]), source_value_max);
            size_t base = (size_t)i * 4U;
            rgba8[base + 0] = gray;
            rgba8[base + 1] = gray;
            rgba8[base + 2] = gray;
            rgba8[base + 3] = 255U;
        }
    }
    if (width_out != NULL) *width_out = width;
    if (height_out != NULL) *height_out = height;
    return rgba8;
}

#define B2026_PDF_MAX_BYTES 2147483647ULL

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

static char *savepdf_build_explicit_tounicode_cmap(char *const *overlays, int overlay_count) {
    static const char header[] =
        "/CIDInit /ProcSet findresource begin\n"
        "12 dict begin\n"
        "begincmap\n"
        "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n"
        "/CMapName /B2026Explicit def\n"
        "/CMapType 2 def\n"
        "1 begincodespacerange\n"
        "<0000> <FFFF>\n"
        "endcodespacerange\n";
    static const char footer[] =
        "endcmap\n"
        "CMapName currentdict /CMap defineresource pop\n"
        "end\n"
        "end\n";
    unsigned char *used = (unsigned char *)calloc(65536U, 1U);
    char *cmap;
    size_t capacity;
    size_t length = 0U;
    int unique_count = 0;
    int overlay_index;
    int code;
    int remaining;
    int block_left = 0;
    if (used == NULL) return NULL;
    for (overlay_index = 0; overlay_index < overlay_count; ++overlay_index) {
        const char *cursor = overlays != NULL ? overlays[overlay_index] : NULL;
        if (cursor == NULL) continue;
        while ((cursor = strchr(cursor, '<')) != NULL) {
            const char *end = strchr(cursor + 1, '>');
            const char *after;
            size_t hex_length;
            size_t offset;
            int valid = 1;
            if (end == NULL) break;
            after = end + 1;
            while (*after == ' ' || *after == '\t' || *after == '\r' || *after == '\n') after++;
            hex_length = (size_t)(end - (cursor + 1));
            if (strncmp(after, "Tj", 2U) == 0 && hex_length > 0U && (hex_length % 4U) == 0U) {
                for (offset = 0U; offset < hex_length; offset += 4U) {
                    int h0 = savepdf_pdf_hex_value((unsigned char)cursor[1 + offset]);
                    int h1 = savepdf_pdf_hex_value((unsigned char)cursor[2 + offset]);
                    int h2 = savepdf_pdf_hex_value((unsigned char)cursor[3 + offset]);
                    int h3 = savepdf_pdf_hex_value((unsigned char)cursor[4 + offset]);
                    if (h0 < 0 || h1 < 0 || h2 < 0 || h3 < 0) { valid = 0; break; }
                    code = (h0 << 12) | (h1 << 8) | (h2 << 4) | h3;
                    if (!used[code]) { used[code] = 1U; unique_count++; }
                }
                if (!valid) {
                    memset(used, 0, 65536U);
                    unique_count = 0;
                    break;
                }
            }
            cursor = end + 1;
        }
    }
    capacity = sizeof(header) + sizeof(footer) + (size_t)(unique_count + 1) * 24U + (size_t)(unique_count / 100 + 2) * 40U;
    cmap = (char *)malloc(capacity);
    if (cmap == NULL) { free(used); return NULL; }
    memcpy(cmap + length, header, sizeof(header) - 1U); length += sizeof(header) - 1U;
    remaining = unique_count;
    for (code = 0; code <= 0xFFFF; ++code) {
        int written;
        if (!used[code]) continue;
        if (block_left == 0) {
            block_left = remaining > 100 ? 100 : remaining;
            written = snprintf(cmap + length, capacity - length, "%d beginbfchar\n", block_left);
            if (written < 0 || (size_t)written >= capacity - length) { free(cmap); free(used); return NULL; }
            length += (size_t)written;
        }
        written = snprintf(cmap + length, capacity - length, "<%04X> <%04X>\n", code, code);
        if (written < 0 || (size_t)written >= capacity - length) { free(cmap); free(used); return NULL; }
        length += (size_t)written;
        block_left--;
        remaining--;
        if (block_left == 0) {
            static const char endbfchar[] = "endbfchar\n";
            memcpy(cmap + length, endbfchar, sizeof(endbfchar) - 1U); length += sizeof(endbfchar) - 1U;
        }
    }
    memcpy(cmap + length, footer, sizeof(footer));
    free(used);
    return cmap;
}

static int savepdf_write_image_pdf_builtin_rgba(const char *path, const unsigned char *rgba8, int width, int height, const char *text_overlay, char *detail, size_t detail_size) {
    FILE *fp = NULL;
    long offsets[10];
    long xref;
    unsigned long long pixels;
    unsigned long long image_len;
    char content[160];
    size_t content_len;
    size_t base_content_len;
    unsigned char *row = NULL;
    char *cmap = NULL;
    char *single_overlay[1];
    int x, y, i;
    int searchable = text_overlay != NULL && *text_overlay != '\0';
    int object_count = searchable ? 9 : 5;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    if (rgba8 == NULL || width <= 0 || height <= 0) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "invalid pixel-array image");
        return 0;
    }
    if (searchable) {
        single_overlay[0] = (char *)text_overlay;
        cmap = savepdf_build_explicit_tounicode_cmap(single_overlay, 1);
        if (cmap == NULL) {
            if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "searchable PDF CMap allocation failed");
            return 0;
        }
    }
    pixels = (unsigned long long)(unsigned int)width * (unsigned long long)(unsigned int)height;
    if (pixels > (B2026_PDF_MAX_BYTES - 65536ULL) / 3ULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF output exceeds the 2 GiB file-size limit: estimated image stream=%llu bytes / PDF出力が2GiB上限を超えます", pixels * 3ULL);
        free(cmap);
        return 0;
    }
    image_len = pixels * 3ULL;
    row = (unsigned char *)malloc((size_t)width * 3U);
    if (row == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "pixel-array PDF row allocation failed");
        free(cmap);
        return 0;
    }
    fp = b2026_fopen(path, "wb");
    if (fp == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "open error: %s", strerror(errno));
        free(row);
        free(cmap);
        return 0;
    }
    if (fprintf(fp, "%%PDF-1.4\n%%\xC4\xE5\xF2\xE5\xEB\xA7\n") < 0) goto write_failed;
    offsets[1] = ftell(fp);
    if (fprintf(fp, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n") < 0) goto write_failed;
    offsets[2] = ftell(fp);
    if (fprintf(fp, "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n") < 0) goto write_failed;
    offsets[3] = ftell(fp);
    if (fprintf(fp, "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %d %d] /Resources << /XObject << /Im0 5 0 R >>", width, height) < 0) goto write_failed;
    if (searchable && fprintf(fp, " /Font << /F0 6 0 R >>") < 0) goto write_failed;
    if (fprintf(fp, " >> /Contents 4 0 R >>\nendobj\n") < 0) goto write_failed;
    base_content_len = (size_t)snprintf(content, sizeof(content), "q\n%d 0 0 %d 0 0 cm\n/Im0 Do\nQ\n", width, height);
    content_len = base_content_len + (searchable ? strlen(text_overlay) : 0U);
    offsets[4] = ftell(fp);
    if (fprintf(fp, "4 0 obj\n<< /Length %lu >>\nstream\n", (unsigned long)content_len) < 0) goto write_failed;
    if (fwrite(content, 1U, base_content_len, fp) != base_content_len) goto write_failed;
    if (searchable && fwrite(text_overlay, 1U, strlen(text_overlay), fp) != strlen(text_overlay)) goto write_failed;
    if (fprintf(fp, "endstream\nendobj\n") < 0) goto write_failed;
    offsets[5] = ftell(fp);
    if (fprintf(fp, "5 0 obj\n<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length %llu >>\nstream\n", width, height, image_len) < 0) goto write_failed;
    for (y = 0; y < height; ++y) {
        const unsigned char *src = rgba8 + (size_t)y * (size_t)width * 4U;
        for (x = 0; x < width; ++x) {
            unsigned int a = src[x * 4 + 3];
            row[x * 3 + 0] = (unsigned char)((src[x * 4 + 0] * a + 255U * (255U - a) + 127U) / 255U);
            row[x * 3 + 1] = (unsigned char)((src[x * 4 + 1] * a + 255U * (255U - a) + 127U) / 255U);
            row[x * 3 + 2] = (unsigned char)((src[x * 4 + 2] * a + 255U * (255U - a) + 127U) / 255U);
        }
        if (fwrite(row, 1U, (size_t)width * 3U, fp) != (size_t)width * 3U) goto write_failed;
        if ((unsigned long long)ftell(fp) >= B2026_PDF_MAX_BYTES) {
            if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF output exceeded the 2 GiB file-size limit; PDF creation was cancelled and the original destination was preserved / PDF出力が2GiB上限を超えたため生成を中止しました");
            goto write_failed_keep_detail;
        }
    }
    if (fprintf(fp, "\nendstream\nendobj\n") < 0) goto write_failed;
    if (searchable) {
        /*
         * The searchable layer is invisible and obtains its Unicode value
         * exclusively from ToUnicode.  Use the standard Japanese substitute
         * CID font with CIDFontType0: older PDFKit/CoreGraphics versions (notably Catalina)
         * log an error when a non-embedded CIDFontType2 uses Identity
         * CIDToGIDMap, even though the page renders successfully.
         */
        offsets[6] = ftell(fp);
        if (fprintf(fp, "6 0 obj\n<< /Type /Font /Subtype /Type0 /BaseFont /HeiseiKakuGo-W5-UniJIS-UTF16-H /Encoding /UniJIS-UTF16-H /DescendantFonts [7 0 R] /ToUnicode 9 0 R >>\nendobj\n") < 0) goto write_failed;
        offsets[7] = ftell(fp);
        if (fprintf(fp, "7 0 obj\n<< /Type /Font /Subtype /CIDFontType0 /BaseFont /HeiseiKakuGo-W5 /CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 5 >> /FontDescriptor 8 0 R /DW 1000 >>\nendobj\n") < 0) goto write_failed;
        offsets[8] = ftell(fp);
        if (fprintf(fp, "8 0 obj\n<< /Type /FontDescriptor /FontName /HeiseiKakuGo-W5 /Flags 4 /FontBBox [-92 -250 1010 922] /ItalicAngle 0 /Ascent 752 /Descent -271 /CapHeight 737 /StemV 114 >>\nendobj\n") < 0) goto write_failed;
        offsets[9] = ftell(fp);
        if (fprintf(fp, "9 0 obj\n<< /Length %lu >>\nstream\n", (unsigned long)strlen(cmap)) < 0) goto write_failed;
        if (fwrite(cmap, 1U, strlen(cmap), fp) != strlen(cmap)) goto write_failed;
        if (fprintf(fp, "endstream\nendobj\n") < 0) goto write_failed;
    }
    xref = ftell(fp);
    if (xref < 0 || (unsigned long long)xref >= B2026_PDF_MAX_BYTES) goto size_failed;
    if (fprintf(fp, "xref\n0 %d\n0000000000 65535 f \n", object_count + 1) < 0) goto write_failed;
    for (i = 1; i <= object_count; ++i) if (fprintf(fp, "%010ld 00000 n \n", offsets[i]) < 0) goto write_failed;
    if (fprintf(fp, "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%ld\n%%%%EOF\n", object_count + 1, xref) < 0) goto write_failed;
    if (fflush(fp) != 0) goto write_failed;
    if (fclose(fp) != 0) {
        fp = NULL;
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "close error: %s", strerror(errno));
        free(row);
        free(cmap);
        return 0;
    }
    free(row);
    free(cmap);
    return 1;
size_failed:
    if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF output exceeded the 2 GiB file-size limit; PDF creation was cancelled and the original destination was preserved / PDF出力が2GiB上限を超えたため生成を中止しました");
    goto write_failed_keep_detail;
write_failed:
    if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "write error: %s", strerror(errno));
write_failed_keep_detail:
    if (fp != NULL) fclose(fp);
    free(row);
    free(cmap);
    return 0;
}

static int savepdf_write_image_pdf_builtin_overlay(const char *path, const char *array_name, const char *text_overlay, char *detail, size_t detail_size) {
    int width = 0, height = 0;
    unsigned char *rgba8 = savepdf_build_rgba8_from_array(array_name, &width, &height, detail, detail_size);
    int ok;
    if (rgba8 == NULL) return 0;
    ok = savepdf_write_image_pdf_builtin_rgba(path, rgba8, width, height, text_overlay, detail, detail_size);
    free(rgba8);
    return ok;
}

static int savepdf_write_image_pdf_builtin(const char *path, const char *array_name, char *detail, size_t detail_size) {
    return savepdf_write_image_pdf_builtin_overlay(path, array_name, NULL, detail, detail_size);
}

static int savepdf_write_text_pages_pdf_builtin(const char *path, unsigned char **pages, char **overlays, int page_count, int width, int height, char *detail, size_t detail_size) {
    FILE *fp = NULL;
    long *offsets = NULL;
    unsigned char *row = NULL;
    char *cmap = NULL;
    unsigned long long pixels, estimated;
    int searchable = 0;
    int font_base, object_count;
    int p, x, y, i;
    long xref;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    if (pages == NULL || page_count <= 0 || width <= 0 || height <= 0) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "invalid text PDF pages");
        return 0;
    }
    for (p = 0; p < page_count; ++p) if (overlays != NULL && overlays[p] != NULL && overlays[p][0] != '\0') searchable = 1;
    if (searchable) {
        cmap = savepdf_build_explicit_tounicode_cmap(overlays, page_count);
        if (cmap == NULL) {
            if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "searchable PDF CMap allocation failed");
            return 0;
        }
    }
    pixels = (unsigned long long)(unsigned int)width * (unsigned long long)(unsigned int)height;
    if (pixels > (B2026_PDF_MAX_BYTES - 65536ULL) / (3ULL * (unsigned long long)page_count)) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF output exceeds the 2 GiB file-size limit / PDF出力が2GiB上限を超えます");
        free(cmap);
        return 0;
    }
    estimated = pixels * 3ULL * (unsigned long long)page_count + 65536ULL;
    if (estimated >= B2026_PDF_MAX_BYTES) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF output exceeds the 2 GiB file-size limit / PDF出力が2GiB上限を超えます");
        free(cmap);
        return 0;
    }
    font_base = 3 + page_count * 3;
    object_count = 2 + page_count * 3 + (searchable ? 4 : 0);
    offsets = (long *)calloc((size_t)object_count + 1U, sizeof(long));
    row = (unsigned char *)malloc((size_t)width * 3U);
    if (offsets == NULL || row == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "text PDF allocation failed");
        free(offsets); free(row); free(cmap); return 0;
    }
    fp = b2026_fopen(path, "wb");
    if (fp == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "open error: %s", strerror(errno));
        free(offsets); free(row); free(cmap); return 0;
    }
    if (fprintf(fp, "%%PDF-1.4\n%%\xC4\xE5\xF2\xE5\xEB\xA7\n") < 0) goto failed;
    offsets[1] = ftell(fp);
    if (fprintf(fp, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n") < 0) goto failed;
    offsets[2] = ftell(fp);
    if (fprintf(fp, "2 0 obj\n<< /Type /Pages /Kids [") < 0) goto failed;
    for (p = 0; p < page_count; ++p) if (fprintf(fp, "%d 0 R ", 3 + p * 3) < 0) goto failed;
    if (fprintf(fp, "] /Count %d >>\nendobj\n", page_count) < 0) goto failed;
    for (p = 0; p < page_count; ++p) {
        int page_obj = 3 + p * 3, content_obj = page_obj + 1, image_obj = page_obj + 2;
        char content[160];
        size_t base_len = (size_t)snprintf(content, sizeof(content), "q\n%d 0 0 %d 0 0 cm\n/Im0 Do\nQ\n", width, height);
        size_t overlay_len = (searchable && overlays != NULL && overlays[p] != NULL) ? strlen(overlays[p]) : 0U;
        offsets[page_obj] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %d %d] /Resources << /XObject << /Im0 %d 0 R >>", page_obj, width, height, image_obj) < 0) goto failed;
        if (searchable && fprintf(fp, " /Font << /F0 %d 0 R >>", font_base) < 0) goto failed;
        if (fprintf(fp, " >> /Contents %d 0 R >>\nendobj\n", content_obj) < 0) goto failed;
        offsets[content_obj] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Length %lu >>\nstream\n", content_obj, (unsigned long)(base_len + overlay_len)) < 0) goto failed;
        if (fwrite(content, 1U, base_len, fp) != base_len) goto failed;
        if (overlay_len > 0U && fwrite(overlays[p], 1U, overlay_len, fp) != overlay_len) goto failed;
        if (fprintf(fp, "endstream\nendobj\n") < 0) goto failed;
        offsets[image_obj] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length %llu >>\nstream\n", image_obj, width, height, pixels * 3ULL) < 0) goto failed;
        for (y = 0; y < height; ++y) {
            const unsigned char *src = pages[p] + (size_t)y * (size_t)width * 4U;
            for (x = 0; x < width; ++x) {
                unsigned int a = src[x * 4 + 3];
                row[x * 3 + 0] = (unsigned char)((src[x * 4 + 0] * a + 255U * (255U - a) + 127U) / 255U);
                row[x * 3 + 1] = (unsigned char)((src[x * 4 + 1] * a + 255U * (255U - a) + 127U) / 255U);
                row[x * 3 + 2] = (unsigned char)((src[x * 4 + 2] * a + 255U * (255U - a) + 127U) / 255U);
            }
            if (fwrite(row, 1U, (size_t)width * 3U, fp) != (size_t)width * 3U) goto failed;
            if ((unsigned long long)ftell(fp) >= B2026_PDF_MAX_BYTES) goto too_large;
        }
        if (fprintf(fp, "\nendstream\nendobj\n") < 0) goto failed;
    }
    if (searchable) {
        /* Keep this font shape in sync with the single-page writer above. */
        offsets[font_base] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Type /Font /Subtype /Type0 /BaseFont /HeiseiKakuGo-W5-UniJIS-UTF16-H /Encoding /UniJIS-UTF16-H /DescendantFonts [%d 0 R] /ToUnicode %d 0 R >>\nendobj\n", font_base, font_base + 1, font_base + 3) < 0) goto failed;
        offsets[font_base + 1] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Type /Font /Subtype /CIDFontType0 /BaseFont /HeiseiKakuGo-W5 /CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 5 >> /FontDescriptor %d 0 R /DW 1000 >>\nendobj\n", font_base + 1, font_base + 2) < 0) goto failed;
        offsets[font_base + 2] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Type /FontDescriptor /FontName /HeiseiKakuGo-W5 /Flags 4 /FontBBox [-92 -250 1010 922] /ItalicAngle 0 /Ascent 752 /Descent -271 /CapHeight 737 /StemV 114 >>\nendobj\n", font_base + 2) < 0) goto failed;
        offsets[font_base + 3] = ftell(fp);
        if (fprintf(fp, "%d 0 obj\n<< /Length %lu >>\nstream\n", font_base + 3, (unsigned long)strlen(cmap)) < 0) goto failed;
        if (fwrite(cmap, 1U, strlen(cmap), fp) != strlen(cmap) || fprintf(fp, "endstream\nendobj\n") < 0) goto failed;
    }
    xref = ftell(fp);
    if (xref < 0 || (unsigned long long)xref >= B2026_PDF_MAX_BYTES) goto too_large;
    if (fprintf(fp, "xref\n0 %d\n0000000000 65535 f \n", object_count + 1) < 0) goto failed;
    for (i = 1; i <= object_count; ++i) if (fprintf(fp, "%010ld 00000 n \n", offsets[i]) < 0) goto failed;
    if (fprintf(fp, "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%ld\n%%%%EOF\n", object_count + 1, xref) < 0 || fflush(fp) != 0) goto failed;
    if (fclose(fp) != 0) { fp = NULL; goto failed; }
    free(offsets); free(row); free(cmap); return 1;
too_large:
    if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF output exceeded the 2 GiB file-size limit; PDF creation was cancelled / PDF出力が2GiB上限を超えたため生成を中止しました");
    goto failed_keep;
failed:
    if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "write error: %s", strerror(errno));
failed_keep:
    if (fp != NULL) fclose(fp);
    free(offsets); free(row); free(cmap); return 0;
}

#if B2026_HAVE_MACOS_PDF
static int savepdf_write_image_pdf_macos(const char *path, const char *array_name, char *detail, size_t detail_size) {
    int width = 0;
    int height = 0;
    unsigned char *rgba8 = NULL;
    CFDataRef data = NULL;
    CGDataProviderRef provider = NULL;
    CGColorSpaceRef cs = NULL;
    CGImageRef image = NULL;
    CFURLRef url = NULL;
    CGContextRef ctx = NULL;
    CGRect media_box;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    rgba8 = savepdf_build_rgba8_from_array(array_name, &width, &height, detail, detail_size);
    if (rgba8 == NULL) return 0;
    data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)rgba8, (CFIndex)((size_t)width * (size_t)height * 4U));
    free(rgba8);
    rgba8 = NULL;
    if (data == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "CFData create failed");
        return 0;
    }
    provider = CGDataProviderCreateWithCFData(data);
    if (provider == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "CGDataProvider create failed");
        CFRelease(data);
        return 0;
    }
    cs = CGColorSpaceCreateDeviceRGB();
    if (cs == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "RGB colorspace create failed");
        CGDataProviderRelease(provider);
        CFRelease(data);
        return 0;
    }
    image = CGImageCreate((size_t)width, (size_t)height, 8, 32, (size_t)width * 4U, cs, (CGBitmapInfo)(kCGImageAlphaLast | kCGBitmapByteOrderDefault), provider, NULL, false, kCGRenderingIntentDefault);
    if (image == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "CGImage create failed");
        CGColorSpaceRelease(cs);
        CGDataProviderRelease(provider);
        CFRelease(data);
        return 0;
    }
    media_box = CGRectMake(0, 0, (CGFloat)width, (CGFloat)height);
    url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)path, (CFIndex)strlen(path), false);
    if (url == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "invalid path");
        CGImageRelease(image);
        CGColorSpaceRelease(cs);
        CGDataProviderRelease(provider);
        CFRelease(data);
        return 0;
    }
    ctx = CGPDFContextCreateWithURL(url, &media_box, NULL);
    if (ctx == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF context create failed");
        CFRelease(url);
        CGImageRelease(image);
        CGColorSpaceRelease(cs);
        CGDataProviderRelease(provider);
        CFRelease(data);
        return 0;
    }
    CGPDFContextBeginPage(ctx, NULL);
    CGContextSaveGState(ctx);
    CGContextTranslateCTM(ctx, 0.0f, (CGFloat)height);
    CGContextScaleCTM(ctx, 1.0f, -1.0f);
    CGContextDrawImage(ctx, CGRectMake(0, 0, (CGFloat)width, (CGFloat)height), image);
    CGContextRestoreGState(ctx);
    CGPDFContextEndPage(ctx);
    CGPDFContextClose(ctx);
    CGContextRelease(ctx);
    CFRelease(url);
    CGImageRelease(image);
    CGColorSpaceRelease(cs);
    CGDataProviderRelease(provider);
    CFRelease(data);
    return 1;
}
#endif

static const char *save_pdf_image_file_direct_safe(const char *path, const char *array_name, int log_on_fail, int basic_line) {
    char detail[1024];
    char *resolved_path;
    detail[0] = '\0';
    resolved_path = savepdf_normalize_path_safe(path, detail, sizeof(detail));
    if (resolved_path == NULL) {
        if (log_on_fail) log_savepdf_failure(path, basic_line, detail);
        return runtime_temp_string_copy("");
    }
    if (path_targets_lib_dir(resolved_path)) {
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, "write to lib directory denied");
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (is_write_target_locked(resolved_path)) {
        char *fallback_path = saveimage_build_locked_fallback_path(resolved_path, detail, sizeof(detail));
        if (fallback_path == NULL) {
            if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail[0] != '\0' ? detail : "read-locked source file");
            free(resolved_path);
            return runtime_temp_string_copy("");
        }
        if (log_on_fail) {
            char notice[1024];
            snprintf(notice, sizeof(notice), "read-locked source file; saved to %s", fallback_path);
            log_savepdf_notice(resolved_path, basic_line, notice);
        }
        free(resolved_path);
        resolved_path = fallback_path;
    }
    if (!saveimage_ensure_directories_safe(resolved_path, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#if B2026_HAVE_MACOS_PDF
    if (g_option_savepdf_mode != 1 && !savepdf_write_image_pdf_macos(resolved_path, array_name, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "pixel-array PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (g_option_savepdf_mode == 1 && !savepdf_write_image_pdf_builtin(resolved_path, array_name, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "built-in pixel-array PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#else
    if (g_option_savepdf_mode == 2) {
        snprintf(detail, sizeof(detail), "OPTION SAVEPDF SYSTEM is unavailable on this platform / この環境ではSYSTEM方式を利用できません");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (!savepdf_write_image_pdf_builtin(resolved_path, array_name, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "built-in pixel-array PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#endif
    {
        const char *return_path = runtime_temp_string_copy(resolved_path);
        free(resolved_path);
        return return_path;
    }
}

const char *save_pdf_image_file_safe(const char *path, const char *array_name, int log_on_fail, int basic_line) {
    char detail[1024]; char *resolved_path=savepdf_normalize_path_safe(path,detail,sizeof(detail)); char *temp_path; const char *result; const char *return_path;
    if(resolved_path==NULL){if(log_on_fail)log_savepdf_failure(path,basic_line,detail);return runtime_temp_string_copy("");}
    if(path_targets_lib_dir(resolved_path)){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,"unsafe write target");free(resolved_path);return runtime_temp_string_copy("");}
    if(is_write_target_locked(resolved_path)){char*fallback_path=saveimage_build_locked_fallback_path(resolved_path,detail,sizeof(detail));if(fallback_path==NULL){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,detail[0]!='\0'?detail:"read-locked source file");free(resolved_path);return runtime_temp_string_copy("");}if(log_on_fail){char notice[1024];snprintf(notice,sizeof(notice),"read-locked source file; saved to %s",fallback_path);log_savepdf_notice(resolved_path,basic_line,notice);}free(resolved_path);resolved_path=fallback_path;}
    temp_path=b2026_atomic_temp_path(resolved_path);if(temp_path==NULL){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,"temporary path allocation error");free(resolved_path);return runtime_temp_string_copy("");}
    result=save_pdf_image_file_direct_safe(temp_path,array_name,log_on_fail,basic_line);if(result==NULL||result[0]=='\0'||!b2026_atomic_commit_file(temp_path,resolved_path)){b2026_atomic_discard_file(temp_path);if(log_on_fail&&result!=NULL&&result[0]!='\0')log_savepdf_failure(resolved_path,basic_line,"atomic replace error");free(temp_path);free(resolved_path);return runtime_temp_string_copy("");}
    return_path=runtime_temp_string_copy(resolved_path);free(temp_path);free(resolved_path);return return_path;
}

const char *save_pdf_image_file_by_index_safe(int target_index, const char *array_name, int log_on_fail, int basic_line) {
    int actual_index;
    char detail[256];
    if (!resolve_cli_target_index_safe(target_index, &actual_index, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure("", basic_line, detail);
        return runtime_temp_string_copy("");
    }
    if (actual_index == 0) {
        if (log_on_fail) log_savepdf_failure("#0", basic_line, "stdout not supported for pdf");
        return runtime_temp_string_copy("");
    }
    return save_pdf_image_file_safe(g_runtime_argv[actual_index], array_name, log_on_fail, basic_line);
}

static int savepdf_parse_attr_text(const char *tag, const char *attr, char *dest, size_t dest_size) {
    char pattern[64];
    const char *p;
    const char *end;
    char quote;
    size_t len;
    if (dest == NULL || dest_size == 0U || tag == NULL || attr == NULL) return 0;
    dest[0] = '\0';
    snprintf(pattern, sizeof(pattern), "%s=", attr);
    p = strstr(tag, pattern);
    if (p == NULL) return 0;
    p += strlen(pattern);
    if (*p != '\'' && *p != '"') return 0;
    quote = *p++;
    end = strchr(p, quote);
    if (end == NULL) return 0;
    len = (size_t)(end - p);
    if (len >= dest_size) len = dest_size - 1U;
    memcpy(dest, p, len);
    dest[len] = '\0';
    return 1;
}

#if B2026_HAVE_MACOS_PDF
static CTFontRef savepdf_graph_font(const char *tag,double size) {
    char raw[3200],decoded[520],family[256];size_t i=0,j=0,k=0,n;CFStringRef name;CTFontRef font;
    if(!strstr(tag,"data-graph-font=\"1\"") || !savepdf_parse_attr_text(tag,"font-family",raw,sizeof(raw)))return savepdf_create_font(size);
    while(raw[i] && j+1<sizeof(decoded)) {
        if(!strncmp(raw+i,"&quot;",6)){decoded[j++]='"';i+=6;}
        else if(!strncmp(raw+i,"&amp;",5)){decoded[j++]='&';i+=5;}
        else if(!strncmp(raw+i,"&lt;",4)){decoded[j++]='<';i+=4;}
        else if(!strncmp(raw+i,"&gt;",4)){decoded[j++]='>';i+=4;}
        else decoded[j++]=raw[i++];
    }
    decoded[j]=0;n=j;i=0;
    if(n>=2 && decoded[0]=='"' && decoded[n-1]=='"'){i=1;--n;}
    while(i<n && k+1<sizeof(family)){if(decoded[i]=='\\' && i+1<n)++i;family[k++]=decoded[i++];}
    family[k]=0;name=CFStringCreateWithCString(kCFAllocatorDefault,family,kCFStringEncodingUTF8);
    if(!name)return savepdf_create_font(size);
    font=CTFontCreateWithName(name,(CGFloat)size,NULL);CFRelease(name);
    return font?font:savepdf_create_font(size);
}
#endif

static int savepdf_parse_attr_double(const char *tag, const char *attr, double *out_value) {
    char buffer[128];
    char *endptr;
    double value;
    if (out_value == NULL) return 0;
    if (!savepdf_parse_attr_text(tag, attr, buffer, sizeof(buffer))) return 0;
    value = strtod(buffer, &endptr);
    if (endptr == buffer) return 0;
    *out_value = value;
    return 1;
}

static int savepdf_parse_rgba_color(const char *text, double *r, double *g, double *b, double *a) {
    double rr = 0.0, gg = 0.0, bb = 0.0, aa = 1.0;
    if (text == NULL) return 0;
    if (sscanf(text, "rgba(%lf,%lf,%lf,%lf)", &rr, &gg, &bb, &aa) == 4) {
        if (r != NULL) *r = rr;
        if (g != NULL) *g = gg;
        if (b != NULL) *b = bb;
        if (a != NULL) *a = aa;
        return 1;
    }
    if (sscanf(text, "rgb(%lf,%lf,%lf)", &rr, &gg, &bb) == 3) {
        if (r != NULL) *r = rr;
        if (g != NULL) *g = gg;
        if (b != NULL) *b = bb;
        if (a != NULL) *a = 1.0;
        return 1;
    }
    return 0;
}

static int savepdf_parse_text_content(const char *tag, char *dest, size_t dest_size) {
    const char *start;
    const char *end;
    size_t len;
    if (dest == NULL || dest_size == 0U || tag == NULL) return 0;
    dest[0] = '\0';
    start = strchr(tag, '>');
    if (start == NULL) return 0;
    ++start;
    end = strstr(start, "</text>");
    if (end == NULL || end < start) return 0;
    len = (size_t)(end - start);
    if (len >= dest_size) len = dest_size - 1U;
    memcpy(dest, start, len);
    dest[len] = '\0';
    return 1;
}

static int savepdf_svg_parse_line(const char *tag, double *x1, double *y1, double *x2, double *y2, double *r, double *g, double *b, double *a) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<line", 5) != 0) return 0;
    if (!savepdf_parse_attr_double(tag, "x1", x1)) return 0;
    if (!savepdf_parse_attr_double(tag, "y1", y1)) return 0;
    if (!savepdf_parse_attr_double(tag, "x2", x2)) return 0;
    if (!savepdf_parse_attr_double(tag, "y2", y2)) return 0;
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (!savepdf_parse_rgba_color(color, r, g, b, a)) {
            if (r != NULL) *r = 0.0;
            if (g != NULL) *g = 0.0;
            if (b != NULL) *b = 0.0;
            if (a != NULL) *a = 1.0;
        }
    } else {
        if (r != NULL) *r = 0.0;
        if (g != NULL) *g = 0.0;
        if (b != NULL) *b = 0.0;
        if (a != NULL) *a = 1.0;
    }
    return 1;
}

static double savepdf_svg_parse_stroke_width_value(const char *tag) {
    double stroke_width = 1.0;
    if (tag != NULL && savepdf_parse_attr_double(tag, "stroke-width", &stroke_width)) {
        if (stroke_width < 1.0) stroke_width = 1.0;
        return stroke_width;
    }
    return 1.0;
}

static int savepdf_svg_parse_linecap_value(const char *tag) {
    char buffer[64];
    if (tag != NULL && savepdf_parse_attr_text(tag, "stroke-linecap", buffer, sizeof(buffer))) {
        if (strcmp(buffer, "round") == 0) return 1;
        if (strcmp(buffer, "square") == 0) return 2;
    }
    return 0;
}

static int savepdf_svg_parse_linejoin_value(const char *tag) {
    char buffer[64];
    if (tag != NULL && savepdf_parse_attr_text(tag, "stroke-linejoin", buffer, sizeof(buffer))) {
        if (strcmp(buffer, "round") == 0) return 1;
        if (strcmp(buffer, "bevel") == 0) return 2;
    }
    return 0;
}

static double savepdf_svg_parse_miterlimit_value(const char *tag) {
    double value = 10.0;
    if (tag != NULL && savepdf_parse_attr_double(tag, "stroke-miterlimit", &value)) {
        if (value < 1.0) value = 1.0;
        return value;
    }
    return 10.0;
}

static int savepdf_svg_parse_rect(const char *tag, double *x, double *y, double *w, double *h, int *has_fill, double *fill_r, double *fill_g, double *fill_b, double *fill_a, int *has_stroke, double *stroke_r, double *stroke_g, double *stroke_b, double *stroke_a) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<rect", 5) != 0) return 0;
    if (!savepdf_parse_attr_double(tag, "x", x)) return 0;
    if (!savepdf_parse_attr_double(tag, "y", y)) return 0;
    if (!savepdf_parse_attr_double(tag, "width", w)) return 0;
    if (!savepdf_parse_attr_double(tag, "height", h)) return 0;
    if (has_fill != NULL) *has_fill = 0;
    if (has_stroke != NULL) *has_stroke = 0;
    if (savepdf_parse_attr_text(tag, "fill", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, fill_r, fill_g, fill_b, fill_a)) {
            if (has_fill != NULL) *has_fill = 1;
        }
    }
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, stroke_r, stroke_g, stroke_b, stroke_a)) {
            if (has_stroke != NULL) *has_stroke = 1;
        }
    }
    return 1;
}

static int savepdf_svg_parse_circle(const char *tag, double *cx, double *cy, double *radius, int *has_fill, double *fill_r, double *fill_g, double *fill_b, double *fill_a, int *has_stroke, double *stroke_r, double *stroke_g, double *stroke_b, double *stroke_a) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<circle", 7) != 0) return 0;
    if (!savepdf_parse_attr_double(tag, "cx", cx)) return 0;
    if (!savepdf_parse_attr_double(tag, "cy", cy)) return 0;
    if (!savepdf_parse_attr_double(tag, "r", radius)) return 0;
    if (has_fill != NULL) *has_fill = 0;
    if (has_stroke != NULL) *has_stroke = 0;
    if (savepdf_parse_attr_text(tag, "fill", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, fill_r, fill_g, fill_b, fill_a)) {
            if (has_fill != NULL) *has_fill = 1;
        }
    }
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, stroke_r, stroke_g, stroke_b, stroke_a)) {
            if (has_stroke != NULL) *has_stroke = 1;
        }
    }
    return 1;
}

static int savepdf_svg_parse_ellipse(const char *tag, double *cx, double *cy, double *rx, double *ry, int *has_fill, double *fill_r, double *fill_g, double *fill_b, double *fill_a, int *has_stroke, double *stroke_r, double *stroke_g, double *stroke_b, double *stroke_a) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<ellipse", 8) != 0) return 0;
    if (!savepdf_parse_attr_double(tag, "cx", cx)) return 0;
    if (!savepdf_parse_attr_double(tag, "cy", cy)) return 0;
    if (!savepdf_parse_attr_double(tag, "rx", rx)) return 0;
    if (!savepdf_parse_attr_double(tag, "ry", ry)) return 0;
    if (has_fill != NULL) *has_fill = 0;
    if (has_stroke != NULL) *has_stroke = 0;
    if (savepdf_parse_attr_text(tag, "fill", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, fill_r, fill_g, fill_b, fill_a)) {
            if (has_fill != NULL) *has_fill = 1;
        }
    }
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, stroke_r, stroke_g, stroke_b, stroke_a)) {
            if (has_stroke != NULL) *has_stroke = 1;
        }
    }
    return 1;
}

static int savepdf_svg_parse_polyline(const char *tag, char *points_out, size_t points_out_size, int *has_stroke, double *stroke_r, double *stroke_g, double *stroke_b, double *stroke_a) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<polyline", 9) != 0) return 0;
    if (!savepdf_parse_attr_text(tag, "points", points_out, points_out_size)) return 0;
    if (has_stroke != NULL) *has_stroke = 0;
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, stroke_r, stroke_g, stroke_b, stroke_a)) {
            if (has_stroke != NULL) *has_stroke = 1;
        }
    }
    return 1;
}

static int savepdf_svg_parse_polygon(const char *tag, char *points_out, size_t points_out_size, int *has_fill, double *fill_r, double *fill_g, double *fill_b, double *fill_a, int *has_stroke, double *stroke_r, double *stroke_g, double *stroke_b, double *stroke_a) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<polygon", 8) != 0) return 0;
    if (!savepdf_parse_attr_text(tag, "points", points_out, points_out_size)) return 0;
    if (has_fill != NULL) *has_fill = 0;
    if (has_stroke != NULL) *has_stroke = 0;
    if (savepdf_parse_attr_text(tag, "fill", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, fill_r, fill_g, fill_b, fill_a)) {
            if (has_fill != NULL) *has_fill = 1;
        }
    }
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, stroke_r, stroke_g, stroke_b, stroke_a)) {
            if (has_stroke != NULL) *has_stroke = 1;
        }
    }
    return 1;
}

static int savepdf_svg_parse_path_bezier(const char *tag, double *x1, double *y1, double *x2, double *y2, double *x3, double *y3, double *x4, double *y4, int *has_stroke, double *stroke_r, double *stroke_g, double *stroke_b, double *stroke_a) {
    char d_buf[512];
    char color[128];
    if (tag == NULL || strncmp(tag, "<path", 5) != 0) return 0;
    if (!savepdf_parse_attr_text(tag, "d", d_buf, sizeof(d_buf))) return 0;
    if (sscanf(d_buf, "M %lf %lf C %lf %lf, %lf %lf, %lf %lf", x1, y1, x2, y2, x3, y3, x4, y4) != 8) return 0;
    if (has_stroke != NULL) *has_stroke = 0;
    if (savepdf_parse_attr_text(tag, "stroke", color, sizeof(color))) {
        if (strcmp(color, "none") != 0 && savepdf_parse_rgba_color(color, stroke_r, stroke_g, stroke_b, stroke_a)) {
            if (has_stroke != NULL) *has_stroke = 1;
        }
    }
    return 1;
}

static int savepdf_svg_parse_text(const char *tag, double *x, double *y, double *font_size, double *r, double *g, double *b, double *a, char *text_out, size_t text_out_size) {
    char color[128];
    if (tag == NULL || strncmp(tag, "<text", 5) != 0) return 0;
    if (!savepdf_parse_attr_double(tag, "x", x)) return 0;
    if (!savepdf_parse_attr_double(tag, "y", y)) return 0;
    if (!savepdf_parse_text_content(tag, text_out, text_out_size)) return 0;
    if (!savepdf_parse_attr_double(tag, "font-size", font_size)) *font_size = 12.0;
    if (savepdf_parse_attr_text(tag, "fill", color, sizeof(color))) {
        if (!savepdf_parse_rgba_color(color, r, g, b, a)) {
            if (r != NULL) *r = 0.0;
            if (g != NULL) *g = 0.0;
            if (b != NULL) *b = 0.0;
            if (a != NULL) *a = 1.0;
        }
    } else {
        if (r != NULL) *r = 0.0;
        if (g != NULL) *g = 0.0;
        if (b != NULL) *b = 0.0;
        if (a != NULL) *a = 1.0;
    }
    return 1;
}

static void savepdf_append_chunk(char **buffer, size_t *length, size_t *capacity, const char *text) {
    size_t add_len = text != NULL ? strlen(text) : 0U;
    size_t needed;
    char *new_buf;
    if (text == NULL || add_len == 0U) return;
    needed = *length + add_len + 1U;
    if (*capacity < needed) {
        size_t new_cap = *capacity > 0U ? *capacity : 1024U;
        while (new_cap < needed) new_cap *= 2U;
        new_buf = (char *)realloc(*buffer, new_cap);
        if (new_buf == NULL) runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。");
        *buffer = new_buf;
        *capacity = new_cap;
    }
    memcpy(*buffer + *length, text, add_len);
    *length += add_len;
    (*buffer)[*length] = '\0';
}

static char *savepdf_xml_text_decode(const char *text) {
    size_t i = 0U, j = 0U, n = text != NULL ? strlen(text) : 0U;
    char *out = (char *)malloc(n + 1U);
    if (out == NULL) runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。");
    while (i < n) {
        if (strncmp(text + i, "&amp;", 5U) == 0) { out[j++] = '&'; i += 5U; }
        else if (strncmp(text + i, "&lt;", 4U) == 0) { out[j++] = '<'; i += 4U; }
        else if (strncmp(text + i, "&gt;", 4U) == 0) { out[j++] = '>'; i += 4U; }
        else if (strncmp(text + i, "&quot;", 6U) == 0) { out[j++] = '"'; i += 6U; }
        else if (strncmp(text + i, "&apos;", 6U) == 0) { out[j++] = '\''; i += 6U; }
        else out[j++] = text[i++];
    }
    out[j] = '\0';
    return out;
}

static char *savepdf_utf8_to_utf16be_hex(const char *text) {
    const unsigned char *p = (const unsigned char *)(text != NULL ? text : "");
    size_t cap = strlen((const char *)p) * 8U + 16U;
    size_t len = 0U;
    char *out = (char *)malloc(cap);
    if (out == NULL) runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。");
    while (*p != '\0') {
        int span = utf8_char_span(p);
        unsigned int cp;
        unsigned int units[2];
        int count = 1, k;
        if (span <= 0) span = 1;
        cp = utf8_decode_codepoint(p, span);
        if (cp > 0x10FFFFU || (cp >= 0xD800U && cp <= 0xDFFFU)) cp = 0xFFFDU;
        if (cp > 0xFFFFU) {
            cp -= 0x10000U;
            units[0] = 0xD800U + (cp >> 10);
            units[1] = 0xDC00U + (cp & 0x3FFU);
            count = 2;
        } else units[0] = cp;
        for (k = 0; k < count; ++k) {
            if (len + 5U >= cap) {
                char *grown;
                cap *= 2U;
                grown = (char *)realloc(out, cap);
                if (grown == NULL) { free(out); runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。"); }
                out = grown;
            }
            len += (size_t)snprintf(out + len, cap - len, "%04X", units[k]);
        }
        p += span;
    }
    out[len] = '\0';
    return out;
}

static void savepdf_append_invisible_text(char **overlay, size_t *length, size_t *capacity, double x, double y, double font_size, int page_height, const char *text) {
    char prefix[256];
    char *hex;
    if (!g_option_pdftext_searchable || text == NULL || *text == '\0') return;
    hex = savepdf_utf8_to_utf16be_hex(text);
    /*
     * Use the font's explicit ToUnicode CMap as the sole source of searchable
     * text.  ActualText is intended as replacement text; older PDFKit
     * versions (notably macOS Catalina) can treat an ActualText sequence as
     * an indivisible item and then fail Japanese prefix/substring searches.
     * A normal show string plus a per-CID ToUnicode map preserves character
     * boundaries in those readers.
     */
    snprintf(prefix, sizeof(prefix), "BT /F0 %.6g Tf 3 Tr 1 0 0 1 %.6g %.6g Tm <", font_size > 0.0 ? font_size : 12.0, x, (double)page_height - y);
    savepdf_append_chunk(overlay, length, capacity, prefix);
    savepdf_append_chunk(overlay, length, capacity, hex);
    savepdf_append_chunk(overlay, length, capacity, "> Tj ET\n");
    free(hex);
}

static int savepdf_write_text_pdf_builtin(const char *path, const char *text, char *detail, size_t detail_size) {
    const char *array_name = "__b2026_pdf_text_canvas__";
    const unsigned char *cursor = (const unsigned char *)(text != NULL ? text : "");
    ArrayVariable *array_var;
    ArrayVariable *saved_canvas = current_draw_canvas;
    BasicTextFontState saved_font = current_text_font;
    char *overlay = NULL;
    size_t overlay_len = 0U, overlay_cap = 0U;
    unsigned char **pages = NULL;
    char **overlays = NULL;
    int page_count = 0, page_capacity = 0;
    long long pixel_count;
    long long i;
    int y = 50;
    int ok = 0;
    const char *family;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    screen_array(array_name, 595.0, 842.0, NULL, 0, NULL, NULL, NULL, NULL, 1, 255.0, 0, current_line);
    array_var = require_array_variable(array_name);
    pixel_count = (long long)595 * 842;
    for (i = 0; i < pixel_count; ++i) {
        int base = option_base + (int)(i * 4LL);
        set_b2026_number(&array_var->data[base + 0], 255.0);
        set_b2026_number(&array_var->data[base + 1], 255.0);
        set_b2026_number(&array_var->data[base + 2], 255.0);
        set_b2026_number(&array_var->data[base + 3], 255.0);
    }
    array_var->read_count = (int)(pixel_count * 4LL);
    if (text_contains_non_ascii(text != NULL ? text : "") && g_option_font_japanese[0] != '\0') family = g_option_font_japanese;
    else if (!text_contains_non_ascii(text != NULL ? text : "") && g_option_font_latin[0] != '\0') family = g_option_font_latin;
#if defined(_WIN32)
    else family = text_contains_non_ascii(text != NULL ? text : "") ? "Yu Gothic" : "Arial";
#elif defined(__APPLE__)
    else family = text_contains_non_ascii(text != NULL ? text : "") ? "Hiragino Sans" : "Helvetica";
#else
    else family = "sans-serif";
#endif
    basic_set_text_font(family, "Regular", 12.0, 1, 0.0, 0.0, 0.0);
    while (*cursor != '\0') {
        char line[4096];
        size_t used = 0U;
        int graphemes = 0;
        while (*cursor == '\r') cursor++;
        while (*cursor != '\0' && *cursor != '\n' && graphemes < 72) {
            int span = utf8_grapheme_span(cursor);
            if (span <= 0) span = 1;
            if (used + (size_t)span + 1U >= sizeof(line)) break;
            memcpy(line + used, cursor, (size_t)span);
            used += (size_t)span;
            cursor += span;
            graphemes++;
        }
        line[used] = '\0';
        if (*cursor == '\n') cursor++;
        if (y + 16 > 792) {
            int page_width = 0, page_height = 0;
            unsigned char *page = savepdf_build_rgba8_from_array(array_name, &page_width, &page_height, detail, detail_size);
            if (page == NULL) goto cleanup;
            if (page_count >= page_capacity) {
                int new_capacity = page_capacity == 0 ? 4 : page_capacity * 2;
                unsigned char **new_pages = (unsigned char **)realloc(pages, (size_t)new_capacity * sizeof(unsigned char *));
                char **new_overlays;
                if (new_pages == NULL) { free(page); runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。"); }
                pages = new_pages;
                new_overlays = (char **)realloc(overlays, (size_t)new_capacity * sizeof(char *));
                if (new_overlays == NULL) { free(page); runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。"); }
                overlays = new_overlays;
                page_capacity = new_capacity;
            }
            pages[page_count] = page;
            overlays[page_count] = overlay;
            page_count++;
            overlay = NULL; overlay_len = 0U; overlay_cap = 0U;
            for (i = 0; i < pixel_count; ++i) {
                int base = option_base + (int)(i * 4LL);
                set_b2026_number(&array_var->data[base + 0], 255.0);
                set_b2026_number(&array_var->data[base + 1], 255.0);
                set_b2026_number(&array_var->data[base + 2], 255.0);
                set_b2026_number(&array_var->data[base + 3], 255.0);
            }
            y = 50;
        }
        draw_text_on_array(array_name, 50, y, line);
        savepdf_append_invisible_text(&overlay, &overlay_len, &overlay_cap, 50.0, (double)(y + 12), 12.0, 842, line);
        y += 16;
    }
    {
        int page_width = 0, page_height = 0;
        unsigned char *page = savepdf_build_rgba8_from_array(array_name, &page_width, &page_height, detail, detail_size);
        if (page == NULL) goto cleanup;
        if (page_count >= page_capacity) {
            int new_capacity = page_capacity == 0 ? 4 : page_capacity * 2;
            unsigned char **new_pages = (unsigned char **)realloc(pages, (size_t)new_capacity * sizeof(unsigned char *));
            char **new_overlays;
            if (new_pages == NULL) { free(page); runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。"); }
            pages = new_pages;
            new_overlays = (char **)realloc(overlays, (size_t)new_capacity * sizeof(char *));
            if (new_overlays == NULL) { free(page); runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。"); }
            overlays = new_overlays;
            page_capacity = new_capacity;
        }
        pages[page_count] = page;
        overlays[page_count] = overlay;
        page_count++;
        overlay = NULL;
    }
    ok = savepdf_write_text_pages_pdf_builtin(path, pages, overlays, page_count, 595, 842, detail, detail_size);
cleanup:
    current_text_font = saved_font;
    current_draw_canvas = saved_canvas;
    free(overlay);
    for (i = 0; i < page_count; ++i) { free(pages[i]); free(overlays[i]); }
    free(pages);
    free(overlays);
    return ok;
}

static int savepdf_write_simple_pdf(const char *path, int width, int height, const char *content, char *detail, size_t detail_size) {
    FILE *fp;
    long offsets[5];
    size_t content_len = content != NULL ? strlen(content) : 0U;
    int i;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    fp = b2026_fopen(path, "wb");
    if (fp == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "open error: %s", strerror(errno));
        return 0;
    }
    fprintf(fp, "%%PDF-1.4\n%%\xC4\xE5\xF2\xE5\xEB\xA7\n");
    offsets[1] = ftell(fp);
    fprintf(fp, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
    offsets[2] = ftell(fp);
    fprintf(fp, "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
    offsets[3] = ftell(fp);
    fprintf(fp, "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %d %d] /Contents 4 0 R >>\nendobj\n", width, height);
    offsets[4] = ftell(fp);
    fprintf(fp, "4 0 obj\n<< /Length %u >>\nstream\n", (unsigned int)content_len);
    if (content_len > 0U && fwrite(content, 1, content_len, fp) != content_len) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "write error: %s", strerror(errno));
        fclose(fp);
        return 0;
    }
    fprintf(fp, "\nendstream\nendobj\n");
    {
        long xref = ftell(fp);
        fprintf(fp, "xref\n0 5\n0000000000 65535 f \n");
        for (i = 1; i <= 4; ++i) fprintf(fp, "%010ld 00000 n \n", offsets[i]);
        fprintf(fp, "trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n%ld\n%%%%EOF\n", xref);
    }
    if (fflush(fp) != 0) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "flush error: %s", strerror(errno));
        fclose(fp);
        return 0;
    }
    if (fclose(fp) != 0) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "close error: %s", strerror(errno));
        return 0;
    }
    return 1;
}

#if B2026_HAVE_MACOS_PDF
static int savepdf_write_svg_pdf_macos(const char *path, const char *hash_name, int width, int height, int *skipped_text_out, int *unsupported_out, char *detail, size_t detail_size) {
    Frame *frame = get_hash_frame(hash_name);
    HashVariable *hash_var = find_local_hash(frame, hash_name);
    CGRect media_box;
    CFURLRef url = NULL;
    CGContextRef ctx = NULL;
    int i;
    int skipped_text = 0;
    int unsupported = 0;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    if (skipped_text_out != NULL) *skipped_text_out = 0;
    if (unsupported_out != NULL) *unsupported_out = 0;
    if (hash_var == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "hash not found");
        return 0;
    }
    media_box = CGRectMake(0, 0, (CGFloat)width, (CGFloat)height);
    url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)path, (CFIndex)strlen(path), false);
    if (url == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "invalid path");
        return 0;
    }
    ctx = CGPDFContextCreateWithURL(url, &media_box, NULL);
    if (ctx == NULL) {
        CFRelease(url);
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "PDF context create failed");
        return 0;
    }
    CGPDFContextBeginPage(ctx, NULL);
    rebuild_hash_sorted_keys(hash_var);
    for (i = 0; i < hash_var->sorted_count; ++i) {
        const char *key = hash_var->sorted_keys[i];
        HashEntry *entry;
        const char *tag;
        if (!is_visible_hash_key(key)) continue;
        entry = find_hash_entry(hash_var, key);
        if (entry == NULL) continue;
        tag = hash_value_as_text(&entry->value);
        if (tag == NULL) continue;
        if (strncmp(tag, "<line", 5) == 0) {
            double x1, y1, x2, y2, r, g, b, a;
            if (savepdf_svg_parse_line(tag, &x1, &y1, &x2, &y2, &r, &g, &b, &a)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                int line_cap = savepdf_svg_parse_linecap_value(tag);
                CGContextSaveGState(ctx);
                CGContextSetRGBStrokeColor(ctx, (CGFloat)(r / 255.0), (CGFloat)(g / 255.0), (CGFloat)(b / 255.0), (CGFloat)a);
                CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                CGContextSetLineCap(ctx, (CGLineCap)line_cap);
                CGContextMoveToPoint(ctx, (CGFloat)x1, (CGFloat)(height - y1));
                CGContextAddLineToPoint(ctx, (CGFloat)x2, (CGFloat)(height - y2));
                CGContextStrokePath(ctx);
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<polyline", 9) == 0) {
            char points_buf[8192];
            int has_stroke = 0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_polyline(tag, points_buf, sizeof(points_buf), &has_stroke, &sr, &sg, &sb, &sa) && has_stroke) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                int line_cap = savepdf_svg_parse_linecap_value(tag);
                int line_join = savepdf_svg_parse_linejoin_value(tag);
                double miter_limit = savepdf_svg_parse_miterlimit_value(tag);
                char *p = points_buf;
                int have_prev = 0;
                CGContextSaveGState(ctx);
                CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                CGContextSetLineCap(ctx, (CGLineCap)line_cap);
                CGContextSetLineJoin(ctx, (CGLineJoin)line_join);
                CGContextSetMiterLimit(ctx, (CGFloat)miter_limit);
                CGContextBeginPath(ctx);
                while (*p != '\0') {
                    char *endptr;
                    double x, y;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    x = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    y = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    if (!have_prev) {
                        CGContextMoveToPoint(ctx, (CGFloat)x, (CGFloat)(height - y));
                        have_prev = 1;
                    } else {
                        CGContextAddLineToPoint(ctx, (CGFloat)x, (CGFloat)(height - y));
                    }
                }
                if (have_prev) CGContextStrokePath(ctx);
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<polygon", 8) == 0) {
            size_t points_size = strlen(tag) + 1;
            char *points_buf = (char *)malloc(points_size);
            if (points_buf == NULL) runtime_error("SAVEPDF polygon allocation failed");
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_polygon(tag, points_buf, points_size, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa) && (has_fill || has_stroke)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                int line_cap = savepdf_svg_parse_linecap_value(tag);
                int line_join = savepdf_svg_parse_linejoin_value(tag);
                double miter_limit = savepdf_svg_parse_miterlimit_value(tag);
                char *p = points_buf;
                int have_prev = 0;
                CGContextSaveGState(ctx);
                CGContextBeginPath(ctx);
                while (*p != '\0') {
                    char *endptr;
                    double x, y;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    x = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    y = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    if (!have_prev) {
                        CGContextMoveToPoint(ctx, (CGFloat)x, (CGFloat)(height - y));
                        have_prev = 1;
                    } else {
                        CGContextAddLineToPoint(ctx, (CGFloat)x, (CGFloat)(height - y));
                    }
                }
                if (have_prev) {
                    CGContextClosePath(ctx);
                    if (has_fill && has_stroke) {
                        CGContextSetRGBFillColor(ctx, (CGFloat)(fr / 255.0), (CGFloat)(fg / 255.0), (CGFloat)(fb / 255.0), (CGFloat)fa);
                        CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                        CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                        CGContextSetLineCap(ctx, (CGLineCap)line_cap);
                        CGContextSetLineJoin(ctx, (CGLineJoin)line_join);
                        CGContextSetMiterLimit(ctx, (CGFloat)miter_limit);
                        CGContextDrawPath(ctx, kCGPathFillStroke);
                    } else if (has_fill) {
                        CGContextSetRGBFillColor(ctx, (CGFloat)(fr / 255.0), (CGFloat)(fg / 255.0), (CGFloat)(fb / 255.0), (CGFloat)fa);
                        CGContextFillPath(ctx);
                    } else if (has_stroke) {
                        CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                        CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                        CGContextSetLineCap(ctx, (CGLineCap)line_cap);
                        CGContextSetLineJoin(ctx, (CGLineJoin)line_join);
                        CGContextSetMiterLimit(ctx, (CGFloat)miter_limit);
                        CGContextStrokePath(ctx);
                    }
                }
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
            free(points_buf);
        } else if (strncmp(tag, "<path", 5) == 0) {
            double x1, y1, x2, y2, x3, y3, x4, y4;
            int has_stroke = 0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_path_bezier(tag, &x1, &y1, &x2, &y2, &x3, &y3, &x4, &y4, &has_stroke, &sr, &sg, &sb, &sa) && has_stroke) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                int line_cap = savepdf_svg_parse_linecap_value(tag);
                int line_join = savepdf_svg_parse_linejoin_value(tag);
                double miter_limit = savepdf_svg_parse_miterlimit_value(tag);
                CGContextSaveGState(ctx);
                CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                CGContextSetLineCap(ctx, (CGLineCap)line_cap);
                CGContextSetLineJoin(ctx, (CGLineJoin)line_join);
                CGContextSetMiterLimit(ctx, (CGFloat)miter_limit);
                CGContextBeginPath(ctx);
                CGContextMoveToPoint(ctx, (CGFloat)x1, (CGFloat)(height - y1));
                CGContextAddCurveToPoint(ctx, (CGFloat)x2, (CGFloat)(height - y2), (CGFloat)x3, (CGFloat)(height - y3), (CGFloat)x4, (CGFloat)(height - y4));
                CGContextStrokePath(ctx);
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<rect", 5) == 0) {
            double x, y, w, h;
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_rect(tag, &x, &y, &w, &h, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                CGContextSaveGState(ctx);
                if (has_fill) {
                    CGContextSetRGBFillColor(ctx, (CGFloat)(fr / 255.0), (CGFloat)(fg / 255.0), (CGFloat)(fb / 255.0), (CGFloat)fa);
                    CGContextFillRect(ctx, CGRectMake((CGFloat)x, (CGFloat)(height - y - h), (CGFloat)w, (CGFloat)h));
                }
                if (has_stroke) {
                    CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                    CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                    CGContextStrokeRect(ctx, CGRectMake((CGFloat)x, (CGFloat)(height - y - h), (CGFloat)w, (CGFloat)h));
                }
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<circle", 7) == 0) {
            double cx, cy, radius;
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_circle(tag, &cx, &cy, &radius, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                CGContextSaveGState(ctx);
                if (has_fill) {
                    CGContextSetRGBFillColor(ctx, (CGFloat)(fr / 255.0), (CGFloat)(fg / 255.0), (CGFloat)(fb / 255.0), (CGFloat)fa);
                    CGContextFillEllipseInRect(ctx, CGRectMake((CGFloat)(cx - radius), (CGFloat)(height - cy - radius), (CGFloat)(radius * 2.0), (CGFloat)(radius * 2.0)));
                }
                if (has_stroke) {
                    CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                    CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                    CGContextStrokeEllipseInRect(ctx, CGRectMake((CGFloat)(cx - radius), (CGFloat)(height - cy - radius), (CGFloat)(radius * 2.0), (CGFloat)(radius * 2.0)));
                }
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<ellipse", 8) == 0) {
            double cx, cy, rx, ry;
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_ellipse(tag, &cx, &cy, &rx, &ry, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                CGContextSaveGState(ctx);
                if (has_fill) {
                    CGContextSetRGBFillColor(ctx, (CGFloat)(fr / 255.0), (CGFloat)(fg / 255.0), (CGFloat)(fb / 255.0), (CGFloat)fa);
                    CGContextFillEllipseInRect(ctx, CGRectMake((CGFloat)(cx - rx), (CGFloat)(height - cy - ry), (CGFloat)(rx * 2.0), (CGFloat)(ry * 2.0)));
                }
                if (has_stroke) {
                    CGContextSetRGBStrokeColor(ctx, (CGFloat)(sr / 255.0), (CGFloat)(sg / 255.0), (CGFloat)(sb / 255.0), (CGFloat)sa);
                    CGContextSetLineWidth(ctx, (CGFloat)stroke_width);
                    CGContextStrokeEllipseInRect(ctx, CGRectMake((CGFloat)(cx - rx), (CGFloat)(height - cy - ry), (CGFloat)(rx * 2.0), (CGFloat)(ry * 2.0)));
                }
                CGContextRestoreGState(ctx);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<text", 5) == 0) {
            double x, y, font_size, r, g, b, a;
            char text_buf[2048];
            if (savepdf_svg_parse_text(tag, &x, &y, &font_size, &r, &g, &b, &a, text_buf, sizeof(text_buf))) {
                CFStringRef text_cf = CFStringCreateWithCString(kCFAllocatorDefault, text_buf, kCFStringEncodingUTF8);
                CTFontRef font = savepdf_graph_font(tag, font_size);
                if (text_cf != NULL && font != NULL) {
                    CFMutableAttributedStringRef attr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
                    if (attr != NULL) {
                        CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
                        CGFloat comps[4] = {(CGFloat)(r / 255.0), (CGFloat)(g / 255.0), (CGFloat)(b / 255.0), (CGFloat)a};
                        CGColorRef color = cs != NULL ? CGColorCreate(cs, comps) : NULL;
                        CFAttributedStringReplaceString(attr, CFRangeMake(0, 0), text_cf);
                        CFAttributedStringSetAttribute(attr, CFRangeMake(0, CFStringGetLength(text_cf)), kCTFontAttributeName, font);
                        if (color != NULL) CFAttributedStringSetAttribute(attr, CFRangeMake(0, CFStringGetLength(text_cf)), kCTForegroundColorAttributeName, color);
                        {
                            CTLineRef line = CTLineCreateWithAttributedString(attr);
                            if (line != NULL) {
                                CGContextSaveGState(ctx);
                                CGContextSetTextPosition(ctx, (CGFloat)x, (CGFloat)(height - y - (strstr(tag, "data-graph-font=\"1\"") ? 0 : font_size)));
                                CTLineDraw(line, ctx);
                                CGContextRestoreGState(ctx);
                                CFRelease(line);
                            }
                        }
                        if (color != NULL) CGColorRelease(color);
                        if (cs != NULL) CGColorSpaceRelease(cs);
                        CFRelease(attr);
                    }
                }
                if (font != NULL) CFRelease(font);
                if (text_cf != NULL) CFRelease(text_cf);
            } else {
                unsupported++;
            }
        } else {
            unsupported++;
        }
    }
    CGPDFContextEndPage(ctx);
    CGPDFContextClose(ctx);
    CGContextRelease(ctx);
    CFRelease(url);
    if (skipped_text_out != NULL) *skipped_text_out = skipped_text;
    if (unsupported_out != NULL) *unsupported_out = unsupported;
    return 1;
}
#endif

static int savepdf_write_svg_pdf_portable(const char *path, const char *hash_name, int width, int height, int *skipped_text_out, int *unsupported_out, char *detail, size_t detail_size) {
    Frame *frame = get_hash_frame(hash_name);
    HashVariable *hash_var = find_local_hash(frame, hash_name);
    char *content = NULL;
    size_t content_len = 0U;
    size_t content_cap = 0U;
    char *text_overlay = NULL;
    size_t text_overlay_len = 0U;
    size_t text_overlay_cap = 0U;
    int i;
    int skipped_text = 0;
    int unsupported = 0;
    if (detail != NULL && detail_size > 0U) detail[0] = '\0';
    if (skipped_text_out != NULL) *skipped_text_out = 0;
    if (unsupported_out != NULL) *unsupported_out = 0;
    if (hash_var == NULL) {
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "hash not found");
        return 0;
    }
    rebuild_hash_sorted_keys(hash_var);
    for (i = 0; i < hash_var->sorted_count; ++i) {
        const char *key = hash_var->sorted_keys[i];
        HashEntry *entry;
        const char *tag;
        if (!is_visible_hash_key(key)) continue;
        entry = find_hash_entry(hash_var, key);
        if (entry == NULL) continue;
        tag = hash_value_as_text(&entry->value);
        if (tag == NULL) continue;
        if (strncmp(tag, "<line", 5) == 0) {
            double x1, y1, x2, y2, r, g, b, a;
            char linebuf[256];
            if (savepdf_svg_parse_line(tag, &x1, &y1, &x2, &y2, &r, &g, &b, &a)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                snprintf(linebuf, sizeof(linebuf), "q %.6g %.6g %.6g RG %.6g w %.6g %.6g m %.6g %.6g l S Q\n", r / 255.0, g / 255.0, b / 255.0, stroke_width, x1, (double)height - y1, x2, (double)height - y2);
                savepdf_append_chunk(&content, &content_len, &content_cap, linebuf);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<polyline", 9) == 0) {
            char points_buf[8192];
            int has_stroke = 0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_polyline(tag, points_buf, sizeof(points_buf), &has_stroke, &sr, &sg, &sb, &sa) && has_stroke) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                int line_cap = savepdf_svg_parse_linecap_value(tag);
                int line_join = savepdf_svg_parse_linejoin_value(tag);
                double miter_limit = savepdf_svg_parse_miterlimit_value(tag);
                char *p = points_buf;
                int have_prev = 0;
                char linebuf[8192];
                size_t used = 0U;
                used += (size_t)snprintf(linebuf + used, sizeof(linebuf) - used, "q %d J %d j %.6g M %.6g %.6g %.6g RG %.6g w ", line_cap, line_join, miter_limit, sr / 255.0, sg / 255.0, sb / 255.0, stroke_width);
                while (*p != '\0') {
                    char *endptr;
                    double x, y;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    x = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    y = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    if (!have_prev) {
                        used += (size_t)snprintf(linebuf + used, sizeof(linebuf) - used, "%.6g %.6g m ", x, (double)height - y);
                        have_prev = 1;
                    } else {
                        used += (size_t)snprintf(linebuf + used, sizeof(linebuf) - used, "%.6g %.6g l ", x, (double)height - y);
                    }
                }
                if (have_prev) {
                    used += (size_t)snprintf(linebuf + used, sizeof(linebuf) - used, "S Q\n");
                    savepdf_append_chunk(&content, &content_len, &content_cap, linebuf);
                }
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<polygon", 8) == 0) {
            size_t points_size = strlen(tag) + 1;
            char *points_buf = (char *)malloc(points_size);
            if (points_buf == NULL) runtime_error("SAVEPDF polygon allocation failed");
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_polygon(tag, points_buf, points_size, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa) && (has_fill || has_stroke)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                int line_cap = savepdf_svg_parse_linecap_value(tag);
                int line_join = savepdf_svg_parse_linejoin_value(tag);
                double miter_limit = savepdf_svg_parse_miterlimit_value(tag);
                char *p = points_buf;
                int have_prev = 0;
                char *polybuf = NULL;
                size_t polylen = 0, polycap = 0;
                char piece[512];
                snprintf(piece, sizeof(piece), "q ");
                savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                while (*p != '\0') {
                    char *endptr;
                    double x, y;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    x = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++;
                    if (*p == '\0') break;
                    y = strtod(p, &endptr);
                    if (endptr == p) break;
                    p = endptr;
                    if (!have_prev) {
                        snprintf(piece, sizeof(piece), "%.6g %.6g m ", x, (double)height - y);
                        savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                        have_prev = 1;
                    } else {
                        snprintf(piece, sizeof(piece), "%.6g %.6g l ", x, (double)height - y);
                        savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                    }
                }
                if (have_prev) {
                    snprintf(piece, sizeof(piece), "h ");
                    savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                    if (has_fill && has_stroke) {
                        snprintf(piece, sizeof(piece), "%.6g %.6g %.6g rg %d J %d j %.6g M %.6g %.6g %.6g RG %.6g w B Q\n", fr / 255.0, fg / 255.0, fb / 255.0, line_cap, line_join, miter_limit, sr / 255.0, sg / 255.0, sb / 255.0, stroke_width);
                        savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                    } else if (has_fill) {
                        snprintf(piece, sizeof(piece), "%.6g %.6g %.6g rg f Q\n", fr / 255.0, fg / 255.0, fb / 255.0);
                        savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                    } else if (has_stroke) {
                        snprintf(piece, sizeof(piece), "%d J %d j %.6g M %.6g %.6g %.6g RG %.6g w S Q\n", line_cap, line_join, miter_limit, sr / 255.0, sg / 255.0, sb / 255.0, stroke_width);
                        savepdf_append_chunk(&polybuf, &polylen, &polycap, piece);
                    }
                    savepdf_append_chunk(&content, &content_len, &content_cap, polybuf);
                }
                free(polybuf);
            } else {
                unsupported++;
            }
            free(points_buf);
        } else if (strncmp(tag, "<rect", 5) == 0) {
            double x, y, w, h;
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            char rectbuf[256];
            if (savepdf_svg_parse_rect(tag, &x, &y, &w, &h, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                if (has_fill) {
                    snprintf(rectbuf, sizeof(rectbuf), "q %.6g %.6g %.6g rg %.6g %.6g %.6g %.6g re f Q\n", fr / 255.0, fg / 255.0, fb / 255.0, x, (double)height - y - h, w, h);
                    savepdf_append_chunk(&content, &content_len, &content_cap, rectbuf);
                }
                if (has_stroke) {
                    snprintf(rectbuf, sizeof(rectbuf), "q %.6g %.6g %.6g RG %.6g w %.6g %.6g %.6g %.6g re S Q\n", sr / 255.0, sg / 255.0, sb / 255.0, stroke_width, x, (double)height - y - h, w, h);
                    savepdf_append_chunk(&content, &content_len, &content_cap, rectbuf);
                }
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<circle", 7) == 0) {
            double cx, cy, radius;
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            char circlebuf[1024];
            if (savepdf_svg_parse_circle(tag, &cx, &cy, &radius, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                double k = radius * 0.5522847498307936;
                double py = (double)height - cy;
                if (has_fill) {
                    snprintf(circlebuf, sizeof(circlebuf), "q %.6g %.6g %.6g rg %.6g %.6g m %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c f Q\n", fr / 255.0, fg / 255.0, fb / 255.0, cx + radius, py, cx + radius, py + k, cx + k, py + radius, cx, py + radius, cx - k, py + radius, cx - radius, py + k, cx - radius, py, cx - radius, py - k, cx - k, py - radius, cx, py - radius, cx + k, py - radius, cx + radius, py - k, cx + radius, py);
                    savepdf_append_chunk(&content, &content_len, &content_cap, circlebuf);
                }
                if (has_stroke) {
                    snprintf(circlebuf, sizeof(circlebuf), "q %.6g %.6g %.6g RG %.6g w %.6g %.6g m %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c S Q\n", sr / 255.0, sg / 255.0, sb / 255.0, stroke_width, cx + radius, py, cx + radius, py + k, cx + k, py + radius, cx, py + radius, cx - k, py + radius, cx - radius, py + k, cx - radius, py, cx - radius, py - k, cx - k, py - radius, cx, py - radius, cx + k, py - radius, cx + radius, py - k, cx + radius, py);
                    savepdf_append_chunk(&content, &content_len, &content_cap, circlebuf);
                }
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<path", 5) == 0) {
            double x1, y1, x2, y2, x3, y3, x4, y4;
            int has_stroke = 0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            if (savepdf_svg_parse_path_bezier(tag, &x1, &y1, &x2, &y2, &x3, &y3, &x4, &y4, &has_stroke, &sr, &sg, &sb, &sa) && has_stroke) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                char pathbuf[512];
                snprintf(pathbuf, sizeof(pathbuf), "q %.6g %.6g %.6g RG %.6g w %.6g %.6g m %.6g %.6g %.6g %.6g %.6g %.6g c S Q\n", sr / 255.0, sg / 255.0, sb / 255.0, stroke_width, x1, (double)height - y1, x2, (double)height - y2, x3, (double)height - y3, x4, (double)height - y4);
                savepdf_append_chunk(&content, &content_len, &content_cap, pathbuf);
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<ellipse", 8) == 0) {
            double cx, cy, rx, ry;
            int has_fill = 0, has_stroke = 0;
            double fr = 0.0, fg = 0.0, fb = 0.0, fa = 1.0;
            double sr = 0.0, sg = 0.0, sb = 0.0, sa = 1.0;
            char ellbuf[1024];
            if (savepdf_svg_parse_ellipse(tag, &cx, &cy, &rx, &ry, &has_fill, &fr, &fg, &fb, &fa, &has_stroke, &sr, &sg, &sb, &sa)) {
                double stroke_width = savepdf_svg_parse_stroke_width_value(tag);
                double kx = rx * 0.5522847498307936;
                double ky = ry * 0.5522847498307936;
                double py = (double)height - cy;
                if (has_fill) {
                    snprintf(ellbuf, sizeof(ellbuf), "q %.6g %.6g %.6g rg %.6g %.6g m %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c f Q\n", fr / 255.0, fg / 255.0, fb / 255.0, cx + rx, py, cx + rx, py + ky, cx + kx, py + ry, cx, py + ry, cx - kx, py + ry, cx - rx, py + ky, cx - rx, py, cx - rx, py - ky, cx - kx, py - ry, cx, py - ry, cx + kx, py - ry, cx + rx, py - ky, cx + rx, py);
                    savepdf_append_chunk(&content, &content_len, &content_cap, ellbuf);
                }
                if (has_stroke) {
                    snprintf(ellbuf, sizeof(ellbuf), "q %.6g %.6g %.6g RG %.6g w %.6g %.6g m %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c %.6g %.6g %.6g %.6g %.6g %.6g c S Q\n", sr / 255.0, sg / 255.0, sb / 255.0, stroke_width, cx + rx, py, cx + rx, py + ky, cx + kx, py + ry, cx, py + ry, cx - kx, py + ry, cx - rx, py + ky, cx - rx, py, cx - rx, py - ky, cx - kx, py - ry, cx, py - ry, cx + kx, py - ry, cx + rx, py - ky, cx + rx, py);
                    savepdf_append_chunk(&content, &content_len, &content_cap, ellbuf);
                }
            } else {
                unsupported++;
            }
        } else if (strncmp(tag, "<text", 5) == 0) {
            double tx, ty, font_size, tr, tg, tb, ta;
            char raw_text[8192];
            if (savepdf_svg_parse_text(tag, &tx, &ty, &font_size, &tr, &tg, &tb, &ta, raw_text, sizeof(raw_text))) {
                char *decoded = savepdf_xml_text_decode(raw_text);
                savepdf_append_invisible_text(&text_overlay, &text_overlay_len, &text_overlay_cap, tx, ty, font_size, height, decoded);
                free(decoded);
                skipped_text++;
            } else unsupported++;
        } else {
            unsupported++;
        }
    }
    if (content == NULL) {
        content = strdup("");
        if (content == NULL) runtime_error("SAVEPDF failed: out of memory / 【PDF保存】メモリ不足です。");
    }
    if (skipped_text > 0 || unsupported > 0) {
        const char *canvas_name = hash_get_text(hash_name, "__svgmeta.canvas");
        if (canvas_name != NULL && *canvas_name != '\0' && get_array_variable(canvas_name) != NULL) {
            int image_ok;
            free(content);
            image_ok = savepdf_write_image_pdf_builtin_overlay(path, canvas_name, text_overlay, detail, detail_size);
            free(text_overlay);
            if (image_ok) {
                if (skipped_text_out != NULL) *skipped_text_out = skipped_text;
                if (unsupported_out != NULL) *unsupported_out = unsupported;
            }
            return image_ok;
        }
        free(content);
        free(text_overlay);
        if (detail != NULL && detail_size > 0U) snprintf(detail, detail_size, "unsupported or text SVG element cannot be rasterized because source canvas metadata is unavailable / 未対応要素または文字を画像化するための元キャンバス情報がありません");
        return 0;
    }
    if (!savepdf_write_simple_pdf(path, width, height, content, detail, detail_size)) {
        free(content);
        free(text_overlay);
        return 0;
    }
    free(content);
    free(text_overlay);
    if (skipped_text_out != NULL) *skipped_text_out = skipped_text;
    if (unsupported_out != NULL) *unsupported_out = unsupported;
    return 1;
}

static const char *save_pdf_svg_file_direct_safe(const char *path, const char *hash_name, int log_on_fail, int basic_line) {
    Frame *frame = get_hash_frame(hash_name);
    HashVariable *hash_var = find_local_hash(frame, hash_name);
    char detail[1024];
    char *resolved_path;
    int width = 256;
    int height = 256;
    int skipped_text = 0;
    int unsupported = 0;
    const char *meta;
    detail[0] = '\0';
    if (hash_var == NULL) {
        if (log_on_fail) log_savepdf_failure(path, basic_line, "hash not found");
        return runtime_temp_string_copy("");
    }
    resolved_path = savepdf_normalize_path_safe(path, detail, sizeof(detail));
    if (resolved_path == NULL) {
        if (log_on_fail) log_savepdf_failure(path, basic_line, detail);
        return runtime_temp_string_copy("");
    }
    if (path_targets_lib_dir(resolved_path)) {
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, "write to lib directory denied");
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (is_write_target_locked(resolved_path)) {
        char *fallback_path = saveimage_build_locked_fallback_path(resolved_path, detail, sizeof(detail));
        if (fallback_path == NULL) {
            if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail[0] != '\0' ? detail : "read-locked source file");
            free(resolved_path);
            return runtime_temp_string_copy("");
        }
        if (log_on_fail) {
            char notice[1024];
            snprintf(notice, sizeof(notice), "read-locked source file; saved to %s", fallback_path);
            log_savepdf_notice(resolved_path, basic_line, notice);
        }
        free(resolved_path);
        resolved_path = fallback_path;
    }
    if (!saveimage_ensure_directories_safe(resolved_path, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    meta = hash_get_text(hash_name, "__svgmeta.width");
    if (meta != NULL && *meta != '\0') width = atoi(meta);
    meta = hash_get_text(hash_name, "__svgmeta.height");
    if (meta != NULL && *meta != '\0') height = atoi(meta);
    if (width <= 0) width = 256;
    if (height <= 0) height = 256;
#if B2026_HAVE_MACOS_PDF
    if (g_option_savepdf_mode != 1 && !savepdf_write_svg_pdf_macos(resolved_path, hash_name, width, height, &skipped_text, &unsupported, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "SVG PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (g_option_savepdf_mode == 1 && !savepdf_write_svg_pdf_portable(resolved_path, hash_name, width, height, &skipped_text, &unsupported, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "built-in SVG PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#else
    if (g_option_savepdf_mode == 2) {
        snprintf(detail, sizeof(detail), "OPTION SAVEPDF SYSTEM is unavailable on this platform / この環境ではSYSTEM方式を利用できません");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
    if (!savepdf_write_svg_pdf_portable(resolved_path, hash_name, width, height, &skipped_text, &unsupported, detail, sizeof(detail))) {
        if (detail[0] == '\0') snprintf(detail, sizeof(detail), "SVG PDF write error");
        if (log_on_fail) log_savepdf_failure(resolved_path, basic_line, detail);
        free(resolved_path);
        return runtime_temp_string_copy("");
    }
#endif
    if (skipped_text > 0) {
        log_savepdf_notice(resolved_path, basic_line, "text element was rasterized or omitted by the selected PDF backend");
    }
    if (unsupported > 0) {
        log_savepdf_notice(resolved_path, basic_line, "unsupported SVG elements were ignored");
    }
    {
        const char *return_path = runtime_temp_string_copy(resolved_path);
        free(resolved_path);
        return return_path;
    }
}

const char *save_pdf_svg_file_safe(const char *path, const char *hash_name, int log_on_fail, int basic_line) {
    char detail[1024]; char *resolved_path=savepdf_normalize_path_safe(path,detail,sizeof(detail)); char *temp_path; const char *result; const char *return_path;
    if(resolved_path==NULL){if(log_on_fail)log_savepdf_failure(path,basic_line,detail);return runtime_temp_string_copy("");}
    if(path_targets_lib_dir(resolved_path)){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,"unsafe write target");free(resolved_path);return runtime_temp_string_copy("");}
    if(is_write_target_locked(resolved_path)){char*fallback_path=saveimage_build_locked_fallback_path(resolved_path,detail,sizeof(detail));if(fallback_path==NULL){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,detail[0]!='\0'?detail:"read-locked source file");free(resolved_path);return runtime_temp_string_copy("");}if(log_on_fail){char notice[1024];snprintf(notice,sizeof(notice),"read-locked source file; saved to %s",fallback_path);log_savepdf_notice(resolved_path,basic_line,notice);}free(resolved_path);resolved_path=fallback_path;}
    temp_path=b2026_atomic_temp_path(resolved_path);if(temp_path==NULL){if(log_on_fail)log_savepdf_failure(resolved_path,basic_line,"temporary path allocation error");free(resolved_path);return runtime_temp_string_copy("");}
    result=save_pdf_svg_file_direct_safe(temp_path,hash_name,log_on_fail,basic_line);if(result==NULL||result[0]=='\0'||!b2026_atomic_commit_file(temp_path,resolved_path)){b2026_atomic_discard_file(temp_path);if(log_on_fail&&result!=NULL&&result[0]!='\0')log_savepdf_failure(resolved_path,basic_line,"atomic replace error");free(temp_path);free(resolved_path);return runtime_temp_string_copy("");}
    return_path=runtime_temp_string_copy(resolved_path);free(temp_path);free(resolved_path);return return_path;
}

const char *save_pdf_svg_file_by_index_safe(int target_index, const char *hash_name, int log_on_fail, int basic_line) {
    int actual_index;
    char detail[256];
    if (!resolve_cli_target_index_safe(target_index, &actual_index, detail, sizeof(detail))) {
        if (log_on_fail) log_savepdf_failure("", basic_line, detail);
        return runtime_temp_string_copy("");
    }
    if (actual_index == 0) {
        if (log_on_fail) log_savepdf_failure("#0", basic_line, "stdout not supported for pdf");
        return runtime_temp_string_copy("");
    }
    return save_pdf_svg_file_safe(g_runtime_argv[actual_index], hash_name, log_on_fail, basic_line);
}
