/* PDF optimization, rasterization, page-image export and comparison.
 * Rendering is deliberately selected once from envset.txt.  A configured
 * renderer is never silently replaced by another renderer. */
typedef struct { int width,height; unsigned char *rgb; } B2026PdfRaster;

static void b2026_pdf_orc_error(int code,const char *op,const char *path,const char *detail){
    char message[4096];snprintf(message,sizeof(message),"PDF.%s failed: path=%s: %s",op!=NULL?op:"",path!=NULL?path:"",detail!=NULL?detail:"unknown error / 不明なエラー");
    basic_set_error_logged(code,message,op!=NULL?op:"PDF",current_source_path,current_line,"PDF");b2026_try_append_log_line("__PDF_ERROR.log",message);
}
static long b2026_pdf_orc_file_size(const char *path){FILE *fp=b2026_fopen(path,"rb");long n;if(fp==NULL)return -1L;if(fseek(fp,0L,SEEK_END)!=0){fclose(fp);return -1L;}n=ftell(fp);fclose(fp);return n;}
static int b2026_pdf_orc_copy_atomic(const char *source,const char *target){
    char *stage=b2026_atomic_temp_path(target);FILE *in=NULL,*out=NULL;unsigned char buffer[65536];size_t n;unsigned long long total=0U;int ok=0;char detail[512];
    if(stage==NULL||!saveimage_ensure_directories_safe(target,detail,sizeof(detail)))goto done;in=b2026_fopen(source,"rb");out=b2026_fopen(stage,"wb");if(in==NULL||out==NULL)goto done;
    while((n=fread(buffer,1U,sizeof(buffer),in))>0U){total+=(unsigned long long)n;if(total>B2026_PDF_EDIT_MAX_BYTES||fwrite(buffer,1U,n,out)!=n)goto done;}if(ferror(in)||fflush(out)!=0||ferror(out))goto done;
    fclose(in);in=NULL;fclose(out);out=NULL;if(!b2026_atomic_commit_file(stage,target))goto done;ok=1;
done:if(in!=NULL)fclose(in);if(out!=NULL)fclose(out);if(!ok&&stage!=NULL)b2026_atomic_discard_file(stage);free(stage);return ok;
}
static void b2026_pdf_orc_pair_number(const char *target,const char *jp,const char *en,double value){if(target!=NULL){hash_set_number(target,jp,value);hash_set_number(target,en,value);}}
static void b2026_pdf_orc_pair_text(const char *target,const char *jp,const char *en,const char *value){if(target!=NULL){hash_set_string(target,jp,value!=NULL?value:"");hash_set_string(target,en,value!=NULL?value:"");}}
static void b2026_pdf_orc_hash_clear(const char *target){if(target!=NULL&&*target!='\0'){HashVariable *h=ensure_local_hash(get_hash_frame(target),target);clear_hash_entries(h);}}
static int b2026_pdf_orc_number(HashVariable *h,const char *jp,const char *en,double fallback,double minimum,double maximum,double *out){double v=0.0;int present=0;if(!b2026_pdf_tf_number(h,jp,en,&v,&present))return 0;if(!present)v=fallback;if(v<minimum||v>maximum||!isfinite(v))return 0;*out=v;return 1;}
static int b2026_pdf_orc_bool(HashVariable *h,const char *jp,const char *en,int fallback){HashEntry *e=b2026_pdf_tf_entry(h,jp,en);if(e==NULL)return fallback;return e->value.type==TYPE_NUMBER&&!isnan(e->value.val.number)&&e->value.val.number!=0.0;}

static const char *b2026_pdf_optimize(const char *target,int argc,const B2026PdfArg *args){
    B2026PdfDocument doc;B2026PdfPageList pages;HashVariable *h=NULL;const char *output,*source;char *stage=NULL;long before=-1L,after=-1L;int rc,i,only_smaller=1,kept_original=0,ok=0;const char *ret="";
    memset(&doc,0,sizeof(doc));memset(&pages,0,sizeof(pages));b2026_pdf_orc_hash_clear(target);
    if((argc!=2&&argc!=3)||args[0].kind!=1||args[1].kind!=1||args[0].text==NULL||args[1].text==NULL||(argc==3&&!b2026_pdf_tf_hash(&args[2],&h))){b2026_pdf_orc_error(4310,"OPTIMIZE","","output$, source$ [, settings{}] is required / 出力、入力、任意の設定が必要です");goto done;}
    output=args[0].text;source=args[1].text;only_smaller=b2026_pdf_orc_bool(h,"小さい場合のみ","onlyIfSmaller",1);before=b2026_pdf_orc_file_size(source);rc=b2026_pdf_load(&doc,source,4);if(rc<=0){b2026_pdf_orc_error(4311,"OPTIMIZE",source,b2026_pdf_load_error(rc));goto done;}
    for(i=0;i<doc.page_count;++i)if(!b2026_pdf_pages_push(&pages,&doc,doc.page_objects[i],-1))goto memory_fail;stage=b2026_atomic_temp_path(output);if(stage==NULL||!b2026_pdf_write_output(stage,&doc,1,&pages,0,1,1)){b2026_pdf_orc_error(4312,"OPTIMIZE",output,"optimized PDF could not be staged or exceeded 2 GiB / 最適化PDFを準備できないか2GiB上限を超えました");goto done;}after=b2026_pdf_orc_file_size(stage);
    if(only_smaller&&before>=0&&after>=before){b2026_atomic_discard_file(stage);free(stage);stage=NULL;if(!b2026_pdf_orc_copy_atomic(source,output)){b2026_pdf_orc_error(4313,"OPTIMIZE",output,"original PDF could not be copied atomically / 元PDFを原子的に保存できません");goto done;}after=before;kept_original=1;}else if(!b2026_atomic_commit_file(stage,output)){b2026_pdf_orc_error(4314,"OPTIMIZE",output,"optimized PDF could not be committed / 最適化PDFを保存先へ反映できません");goto done;}else{free(stage);stage=NULL;}
    ok=1;ret=output;
done:b2026_pdf_orc_pair_number(target,"成功","ok",ok?1.0:0.0);b2026_pdf_orc_pair_text(target,"出力パス","path",ok?ret:"");b2026_pdf_orc_pair_number(target,"元サイズ","originalSize",(double)before);b2026_pdf_orc_pair_number(target,"出力サイズ","outputSize",(double)after);b2026_pdf_orc_pair_number(target,"元ファイル維持","keptOriginal",kept_original?1.0:0.0);if(before>0&&after>=0)b2026_pdf_orc_pair_number(target,"削減率","reductionPercent",100.0*(double)(before-after)/(double)before);if(stage!=NULL){b2026_atomic_discard_file(stage);free(stage);}free(pages.items);b2026_pdf_document_free(&doc);return runtime_temp_string_copy(ok?ret:"");
memory_fail:b2026_pdf_orc_error(4315,"OPTIMIZE",source,"out of memory / メモリ不足です");goto done;
}

static void b2026_pdf_raster_free(B2026PdfRaster *r){if(r!=NULL){free(r->rgb);r->rgb=NULL;r->width=r->height=0;}}
#if B2026_HAVE_MACOS_PDF
static int b2026_pdf_render_native_document(CGPDFDocumentRef pdf,int page,double dpi,B2026PdfRaster *out){
    CGPDFPageRef p=NULL;CGColorSpaceRef cs=NULL;CGContextRef ctx=NULL;CGRect box,device;CGAffineTransform transform;unsigned char *rgba=NULL,*rgb=NULL;size_t pixels,i;double scale,display_w,display_h;int rotation,w,h,ok=0;
    if(pdf==NULL||page<1||(size_t)page>CGPDFDocumentGetNumberOfPages(pdf))goto done;p=CGPDFDocumentGetPage(pdf,(size_t)page);if(p==NULL)goto done;box=CGPDFPageGetBoxRect(p,kCGPDFCropBox);rotation=((CGPDFPageGetRotationAngle(p)%360)+360)%360;scale=dpi/72.0;display_w=(rotation==90||rotation==270)?box.size.height:box.size.width;display_h=(rotation==90||rotation==270)?box.size.width:box.size.height;w=(int)ceil(display_w*scale);h=(int)ceil(display_h*scale);if(scale<=0.0||display_w<=0.0||display_h<=0.0||w<=0||h<=0||(size_t)w>SIZE_MAX/(size_t)h||(pixels=(size_t)w*(size_t)h)>SIZE_MAX/4U)goto done;rgba=(unsigned char*)malloc(pixels*4U);rgb=(unsigned char*)malloc(pixels*3U);if(rgba==NULL||rgb==NULL)goto done;cs=CGColorSpaceCreateDeviceRGB();if(cs==NULL)goto done;ctx=CGBitmapContextCreate(rgba,(size_t)w,(size_t)h,8U,(size_t)w*4U,cs,kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big);if(ctx==NULL)goto done;CGContextSetRGBFillColor(ctx,1,1,1,1);CGContextFillRect(ctx,CGRectMake(0,0,w,h));CGContextScaleCTM(ctx,scale,scale);device=CGRectMake(0,0,display_w,display_h);transform=CGPDFPageGetDrawingTransform(p,kCGPDFCropBox,device,0,true);CGContextConcatCTM(ctx,transform);CGContextDrawPDFPage(ctx,p);for(i=0U;i<pixels;++i){rgb[i*3U]=rgba[i*4U];rgb[i*3U+1U]=rgba[i*4U+1U];rgb[i*3U+2U]=rgba[i*4U+2U];}out->width=w;out->height=h;out->rgb=rgb;rgb=NULL;ok=1;
done:free(rgb);free(rgba);if(ctx!=NULL)CGContextRelease(ctx);if(cs!=NULL)CGColorSpaceRelease(cs);return ok;
}
static int b2026_pdf_render_native(const char *path,int page,double dpi,B2026PdfRaster *out){
    CFURLRef url=NULL;CGPDFDocumentRef pdf=NULL;int ok=0;
    url=CFURLCreateFromFileSystemRepresentation(NULL,(const UInt8*)path,(CFIndex)strlen(path),false);if(url==NULL)goto done;pdf=CGPDFDocumentCreateWithURL(url);if(pdf==NULL)goto done;ok=b2026_pdf_render_native_document(pdf,page,dpi,out);
done:if(pdf!=NULL)CGPDFDocumentRelease(pdf);if(url!=NULL)CFRelease(url);return ok;
}
#endif
static char *b2026_pdf_render_prefix(const char *path,int page){char *temp=b2026_atomic_temp_path(path);char suffix[48];size_t n;if(temp==NULL)return NULL;snprintf(suffix,sizeof(suffix),".render.%d",page);n=strlen(temp)+strlen(suffix)+1U;{char *r=(char*)malloc(n);if(r!=NULL)snprintf(r,n,"%s%s",temp,suffix);free(temp);return r;}}
static int b2026_pdf_ppm_read(const char *path,B2026PdfRaster *out){FILE *fp=b2026_fopen(path,"rb");char magic[3];int w,h,maxv,c;size_t pixels,bytes;if(fp==NULL)return 0;if(fscanf(fp,"%2s",magic)!=1||strcmp(magic,"P6")!=0){fclose(fp);return 0;}do{c=fgetc(fp);if(c=='#')while(c!=EOF&&c!='\n')c=fgetc(fp);}while(c!=EOF&&isspace(c));if(c!=EOF)ungetc(c,fp);if(fscanf(fp,"%d%d%d",&w,&h,&maxv)!=3||w<=0||h<=0||maxv!=255){fclose(fp);return 0;}c=fgetc(fp);if(c==EOF){fclose(fp);return 0;}if((size_t)w>SIZE_MAX/(size_t)h){fclose(fp);return 0;}pixels=(size_t)w*(size_t)h;if(pixels>SIZE_MAX/3U){fclose(fp);return 0;}bytes=pixels*3U;out->rgb=(unsigned char*)malloc(bytes);if(out->rgb==NULL){fclose(fp);return 0;}if(fread(out->rgb,1U,bytes,fp)!=bytes){b2026_pdf_raster_free(out);fclose(fp);return 0;}out->width=w;out->height=h;fclose(fp);return 1;}
static int b2026_pdf_run_poppler(const char *tool,const char *source,const char *prefix,int page,double dpi){
    char d[64],p[32];int timeout=(int)ceil(g_io_timeout_seconds>0.0?g_io_timeout_seconds:30.0);snprintf(d,sizeof(d),"%.6g",dpi);snprintf(p,sizeof(p),"%d",page);
#if defined(_WIN32)
    {char command[8192];wchar_t *wtool=NULL,*wcommand=NULL;STARTUPINFOW si;PROCESS_INFORMATION pi;DWORD wait_result,code=1U;int ok=0;if(snprintf(command,sizeof(command),"\"%s\" -q -f %s -l %s -singlefile -r %s \"%s\" \"%s\"",tool,p,p,d,source,prefix)<0)return 0;wtool=b2026_utf8_path_to_wide(tool);wcommand=b2026_utf8_to_wide(command);if(wtool==NULL||wcommand==NULL)goto win_done;memset(&si,0,sizeof(si));memset(&pi,0,sizeof(pi));si.cb=sizeof(si);if(!CreateProcessW(wtool,wcommand,NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&si,&pi))goto win_done;wait_result=WaitForSingleObject(pi.hProcess,(DWORD)timeout*1000U);if(wait_result==WAIT_TIMEOUT){TerminateProcess(pi.hProcess,124U);WaitForSingleObject(pi.hProcess,INFINITE);}else if(wait_result==WAIT_OBJECT_0&&GetExitCodeProcess(pi.hProcess,&code)&&code==0U)ok=1;CloseHandle(pi.hThread);CloseHandle(pi.hProcess);win_done:free(wtool);free(wcommand);return ok;}
#else
    {pid_t child=fork();int status=0;struct timeval start,now;if(child<0)return 0;if(child==0){execlp(tool,tool,"-q","-f",p,"-l",p,"-singlefile","-r",d,source,prefix,(char*)NULL);_exit(127);}b2026_gettimeofday(&start,NULL);for(;;){pid_t wr=waitpid(child,&status,WNOHANG);if(wr==child)return WIFEXITED(status)&&WEXITSTATUS(status)==0;if(wr<0)return 0;b2026_gettimeofday(&now,NULL);if((double)(now.tv_sec-start.tv_sec)+(double)(now.tv_usec-start.tv_usec)/1000000.0>=(double)timeout){kill(child,SIGKILL);waitpid(child,&status,0);return 0;}usleep(10000U);}}
#endif
}
static int b2026_pdf_render_page(const char *source,int page,double dpi,B2026PdfRaster *out){const char *renderer=b2026_envset_pdf_renderer();memset(out,0,sizeof(*out));if(b2026_envset_pdf_renderer_disabled()||renderer==NULL)return 0;if(b2026_strcasecmp(renderer,"NATIVE")==0){
#if B2026_HAVE_MACOS_PDF
        return b2026_pdf_render_native(source,page,dpi,out);
#else
        return 0;
#endif
    }if(b2026_strcasecmp(renderer,"POPPLER")==0){const char *tool=b2026_envset_pdf_renderer_path();char *prefix,*ppm;size_t n;int ok=0;if(tool==NULL)return 0;prefix=b2026_pdf_render_prefix(source,page);if(prefix==NULL)return 0;n=strlen(prefix)+5U;ppm=(char*)malloc(n);if(ppm!=NULL){snprintf(ppm,n,"%s.ppm",prefix);if(b2026_pdf_run_poppler(tool,source,prefix,page,dpi))ok=b2026_pdf_ppm_read(ppm,out);b2026_unlink(ppm);free(ppm);}free(prefix);return ok;}return 0;}

typedef struct {
    int native_active;
#if B2026_HAVE_MACOS_PDF
    CFURLRef native_url;
    CGPDFDocumentRef native_pdf;
#endif
} B2026PdfRenderSession;

static int b2026_pdf_render_session_open(B2026PdfRenderSession *session,const char *source){
    const char *renderer=b2026_envset_pdf_renderer();memset(session,0,sizeof(*session));if(b2026_envset_pdf_renderer_disabled()||renderer==NULL)return 0;
    if(b2026_strcasecmp(renderer,"NATIVE")==0){
#if B2026_HAVE_MACOS_PDF
        session->native_url=CFURLCreateFromFileSystemRepresentation(NULL,(const UInt8*)source,(CFIndex)strlen(source),false);if(session->native_url==NULL)return 0;session->native_pdf=CGPDFDocumentCreateWithURL(session->native_url);if(session->native_pdf==NULL){CFRelease(session->native_url);session->native_url=NULL;return 0;}session->native_active=1;return 1;
#else
        return 0;
#endif
    }
    return b2026_strcasecmp(renderer,"POPPLER")==0&&b2026_envset_pdf_renderer_path()!=NULL;
}

static int b2026_pdf_render_session_page(B2026PdfRenderSession *session,const char *source,int page,double dpi,B2026PdfRaster *out){
    memset(out,0,sizeof(*out));
#if B2026_HAVE_MACOS_PDF
    if(session!=NULL&&session->native_active)return b2026_pdf_render_native_document(session->native_pdf,page,dpi,out);
#endif
    return b2026_pdf_render_page(source,page,dpi,out);
}

static void b2026_pdf_render_session_close(B2026PdfRenderSession *session){
    if(session==NULL)return;
#if B2026_HAVE_MACOS_PDF
    if(session->native_pdf!=NULL)CGPDFDocumentRelease(session->native_pdf);if(session->native_url!=NULL)CFRelease(session->native_url);session->native_pdf=NULL;session->native_url=NULL;
#endif
    session->native_active=0;
}

static unsigned int b2026_pdf_png_crc(unsigned int crc,const unsigned char *data,size_t n){size_t i;int bit;crc=~crc;for(i=0;i<n;++i){crc^=data[i];for(bit=0;bit<8;++bit)crc=(crc&1U)?(crc>>1)^0xEDB88320U:crc>>1;}return ~crc;}
static unsigned int b2026_pdf_png_adler(const unsigned char *data,size_t n){unsigned int a=1U,b=0U;size_t i;for(i=0;i<n;++i){a=(a+data[i])%65521U;b=(b+a)%65521U;}return (b<<16)|a;}
static int b2026_pdf_png_u32(FILE *fp,unsigned int v){unsigned char b[4];b[0]=(unsigned char)(v>>24);b[1]=(unsigned char)(v>>16);b[2]=(unsigned char)(v>>8);b[3]=(unsigned char)v;return fwrite(b,1U,4U,fp)==4U;}
static int b2026_pdf_png_chunk(FILE *fp,const char type[4],const unsigned char *data,unsigned int n){unsigned int crc;if(!b2026_pdf_png_u32(fp,n)||fwrite(type,1U,4U,fp)!=4U||(n>0U&&fwrite(data,1U,n,fp)!=n))return 0;crc=b2026_pdf_png_crc(0U,(const unsigned char*)type,4U);if(n>0U)crc=b2026_pdf_png_crc(crc,data,n);return b2026_pdf_png_u32(fp,crc);}
static int b2026_pdf_write_png_rgb(const char *target,const B2026PdfRaster *r){char *stage=NULL;FILE *fp=NULL;unsigned char *raw=NULL,*z=NULL,ihdr[13];size_t row,raw_n,zcap,zp=0U,left,off=0U;unsigned int adler;int y,ok=0;char detail[512];if(r==NULL||r->rgb==NULL||r->width<=0||r->height<=0)return 0;row=(size_t)r->width*3U;if((size_t)r->height>SIZE_MAX/(row+1U))return 0;raw_n=(row+1U)*(size_t)r->height;raw=(unsigned char*)malloc(raw_n);if(raw==NULL)return 0;for(y=0;y<r->height;++y){raw[(row+1U)*(size_t)y]=0;memcpy(raw+(row+1U)*(size_t)y+1U,r->rgb+row*(size_t)y,row);}zcap=raw_n+(raw_n/65535U+1U)*5U+6U;z=(unsigned char*)malloc(zcap);if(z==NULL)goto done;z[zp++]=0x78;z[zp++]=0x01;left=raw_n;while(left>0U){unsigned int n=(unsigned int)(left>65535U?65535U:left);z[zp++]=(unsigned char)(left<=65535U);z[zp++]=(unsigned char)n;z[zp++]=(unsigned char)(n>>8);z[zp++]=(unsigned char)(~n);z[zp++]=(unsigned char)((~n)>>8);memcpy(z+zp,raw+off,n);zp+=n;off+=n;left-=n;}adler=b2026_pdf_png_adler(raw,raw_n);z[zp++]=(unsigned char)(adler>>24);z[zp++]=(unsigned char)(adler>>16);z[zp++]=(unsigned char)(adler>>8);z[zp++]=(unsigned char)adler;if(zp>UINT_MAX||!saveimage_ensure_directories_safe(target,detail,sizeof(detail)))goto done;stage=b2026_atomic_temp_path(target);if(stage==NULL||(fp=b2026_fopen(stage,"wb"))==NULL)goto done;if(fwrite("\x89PNG\r\n\x1a\n",1U,8U,fp)!=8U)goto done;memset(ihdr,0,sizeof(ihdr));ihdr[0]=(unsigned char)((unsigned int)r->width>>24);ihdr[1]=(unsigned char)((unsigned int)r->width>>16);ihdr[2]=(unsigned char)((unsigned int)r->width>>8);ihdr[3]=(unsigned char)r->width;ihdr[4]=(unsigned char)((unsigned int)r->height>>24);ihdr[5]=(unsigned char)((unsigned int)r->height>>16);ihdr[6]=(unsigned char)((unsigned int)r->height>>8);ihdr[7]=(unsigned char)r->height;ihdr[8]=8;ihdr[9]=2;if(!b2026_pdf_png_chunk(fp,"IHDR",ihdr,13U)||!b2026_pdf_png_chunk(fp,"IDAT",z,(unsigned int)zp)||!b2026_pdf_png_chunk(fp,"IEND",NULL,0U)||fflush(fp)!=0||ferror(fp))goto done;fclose(fp);fp=NULL;if(!b2026_atomic_commit_file(stage,target))goto done;ok=1;
done:if(fp!=NULL)fclose(fp);if(!ok&&stage!=NULL)b2026_atomic_discard_file(stage);free(stage);free(raw);free(z);return ok;}

static int b2026_pdf_image_pdf(const char *target,B2026PdfDocument *doc,B2026PdfRaster *images,const int *numbers,char **overlays,int count){
    char *stage=NULL,*cmap=NULL;FILE *fp=NULL;long *offsets=NULL;int searchable=0,font_base,total,i,j,ok=0;long xref;char detail[512];
    for(i=0;i<count;++i)if(overlays!=NULL&&overlays[i]!=NULL&&*overlays[i]!='\0')searchable=1;
    font_base=3+count*3;total=2+count*3+(searchable?4:0);if(searchable&&(cmap=savepdf_build_explicit_tounicode_cmap(overlays,count))==NULL)return 0;
    if(!saveimage_ensure_directories_safe(target,detail,sizeof(detail)))goto done;stage=b2026_atomic_temp_path(target);offsets=(long*)calloc((size_t)total+1U,sizeof(long));if(stage==NULL||offsets==NULL||(fp=b2026_fopen(stage,"wb"))==NULL)goto done;
    if(fprintf(fp,"%%PDF-1.4\n%%\xE2\xE3\xCF\xD3\n")<0)goto done;offsets[1]=ftell(fp);if(fprintf(fp,"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")<0)goto done;offsets[2]=ftell(fp);if(fprintf(fp,"2 0 obj\n<< /Type /Pages /Count %d /Kids [",count)<0)goto done;for(i=0;i<count;++i)if(fprintf(fp," %d 0 R",5+i*3)<0)goto done;if(fprintf(fp," ] >>\nendobj\n")<0)goto done;
    for(i=0;i<count;++i){int im=3+i*3,ct=4+i*3,pg=5+i*3,page=(numbers!=NULL?numbers[i]:i+1)-1,rot;double box[4]={0,0,612,792},w,h;char stream[160];size_t n=(size_t)images[i].width*(size_t)images[i].height*3U,overlay_n=(searchable&&overlays[i]!=NULL)?strlen(overlays[i]):0U;if(page<0||page>=doc->page_count)goto done;if(!b2026_pdf_page_box(doc,doc->page_objects[page],"/CropBox",box))b2026_pdf_page_box(doc,doc->page_objects[page],"/MediaBox",box);w=box[2]-box[0];h=box[3]-box[1];rot=((b2026_pdf_page_inherited_integer(doc,doc->page_objects[page],"/Rotate",0)%360)+360)%360;if(rot==90||rot==270){double t=w;w=h;h=t;}if(w<=0||h<=0){w=612;h=792;}
        /* The line feed written before endstream is part of the stream data. */
        offsets[im]=ftell(fp);if(fprintf(fp,"%d 0 obj\n<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length %lu >>\nstream\n",im,images[i].width,images[i].height,(unsigned long)(n+1U))<0||fwrite(images[i].rgb,1U,n,fp)!=n||fprintf(fp,"\nendstream\nendobj\n")<0)goto done;
        snprintf(stream,sizeof(stream),"q %.17g 0 0 %.17g 0 0 cm /Im%d Do Q\n",w,h,i+1);offsets[ct]=ftell(fp);if(fprintf(fp,"%d 0 obj\n<< /Length %lu >>\nstream\n",ct,(unsigned long)(strlen(stream)+overlay_n))<0||fwrite(stream,1U,strlen(stream),fp)!=strlen(stream)||(overlay_n>0U&&fwrite(overlays[i],1U,overlay_n,fp)!=overlay_n)||fprintf(fp,"endstream\nendobj\n")<0)goto done;
        offsets[pg]=ftell(fp);if(fprintf(fp,"%d 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %.17g %.17g] /Resources << /XObject << /Im%d %d 0 R >>",pg,w,h,i+1,im)<0)goto done;if(searchable&&fprintf(fp," /Font << /F0 %d 0 R >>",font_base)<0)goto done;if(fprintf(fp," >> /Contents %d 0 R >>\nendobj\n",ct)<0)goto done;if(ftell(fp)<0||(unsigned long long)ftell(fp)>B2026_PDF_EDIT_MAX_BYTES)goto done;
    }
    if(searchable){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 done;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 done;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 done;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||fwrite(cmap,1U,strlen(cmap),fp)!=strlen(cmap)||fprintf(fp,"endstream\nendobj\n")<0)goto done;}
    xref=ftell(fp);if(fprintf(fp,"xref\n0 %d\n0000000000 65535 f \n",total+1)<0)goto done;for(j=1;j<=total;++j)if(fprintf(fp,"%010ld 00000 n \n",offsets[j])<0)goto done;if(fprintf(fp,"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%ld\n%%%%EOF\n",total+1,xref)<0||fflush(fp)!=0||ferror(fp))goto done;fclose(fp);fp=NULL;if(!b2026_atomic_commit_file(stage,target))goto done;ok=1;
done:if(fp!=NULL)fclose(fp);if(!ok&&stage!=NULL)b2026_atomic_discard_file(stage);free(stage);free(offsets);free(cmap);return ok;
}

static char **b2026_pdf_search_overlays(B2026PdfDocument *doc,const char *source,const int *numbers,int count){B2026PdfTextSettings s;char **out=(char**)calloc((size_t)count,sizeof(*out));int i,partial=0;if(out==NULL)return NULL;memset(&s,0,sizeof(s));s.coordinate_order=1;s.separator="\n";s.tolerance=-1.0;for(i=0;i<count;++i){char *text=b2026_pdf_text_page(doc,numbers[i]-1,&s,source,&partial),*hex;size_t n;if(text==NULL)goto fail;hex=savepdf_utf8_to_utf16be_hex(text);free(text);if(hex==NULL)goto fail;n=strlen(hex)+96U;out[i]=(char*)malloc(n);if(out[i]==NULL){free(hex);goto fail;}snprintf(out[i],n,"BT /F0 1 Tf 3 Tr 1 0 0 1 0 0 Tm <%s> Tj ET\n",hex);free(hex);}return out;fail:for(i=0;i<count;++i)free(out[i]);free(out);return NULL;}

static int b2026_pdf_render_settings(HashVariable *h,double *dpi,const char **pages,const char **format){const char *s;if(!b2026_pdf_orc_number(h,"解像度","dpi",150.0,36.0,1200.0,dpi))return 0;*pages=b2026_pdf_tf_text(h,"対象ページ","pages");if(*pages==NULL)*pages="ALL";s=b2026_pdf_tf_text(h,"形式","format");*format=s!=NULL?s:"PNG";return 1;}
static int b2026_pdf_render_selected(B2026PdfDocument *doc,const char *source,HashVariable *h,B2026PdfRaster **images_out,int **pages_out,int *count_out,double *dpi_out){double dpi,threshold_value=128.0;const char *spec,*format,*color=b2026_pdf_tf_text(h,"色モード","color");B2026PdfArg a;unsigned char *sel=NULL;B2026PdfRaster *images=NULL;int *numbers=NULL,count=0,i,k=0,mode=0;if(color==NULL)color="COLOR";if(b2026_strcasecmp(color,"GRAY")==0||b2026_strcasecmp(color,"GRAYSCALE")==0||strcmp(color,"グレー")==0)mode=1;else if(b2026_strcasecmp(color,"MONO")==0||b2026_strcasecmp(color,"MONOCHROME")==0||strcmp(color,"白黒")==0)mode=2;else if(b2026_strcasecmp(color,"COLOR")!=0&&strcmp(color,"カラー")!=0)return 0;if(!b2026_pdf_render_settings(h,&dpi,&spec,&format)||!b2026_pdf_orc_number(h,"二値化しきい値","threshold",128.0,0.0,255.0,&threshold_value))return 0;memset(&a,0,sizeof(a));a.kind=1;a.text=spec;if(!b2026_pdf_parse_page_spec(&a,doc->page_count,&sel))return 0;for(i=0;i<doc->page_count;++i)if(sel[i])++count;images=(B2026PdfRaster*)calloc((size_t)count,sizeof(*images));numbers=(int*)calloc((size_t)count,sizeof(*numbers));if(images==NULL||numbers==NULL)goto fail;for(i=0;i<doc->page_count;++i)if(sel[i]){size_t q,n;numbers[k]=i+1;if(!b2026_pdf_render_page(source,i+1,dpi,&images[k]))goto fail;n=(size_t)images[k].width*(size_t)images[k].height;for(q=0U;mode!=0&&q<n;++q){unsigned char *p=images[k].rgb+q*3U;int gray=((int)p[0]*299+(int)p[1]*587+(int)p[2]*114+500)/1000;if(mode==2)gray=gray>=(int)threshold_value?255:0;p[0]=p[1]=p[2]=(unsigned char)gray;}++k;}free(sel);*images_out=images;*pages_out=numbers;*count_out=count;*dpi_out=dpi;return 1;fail:if(images!=NULL)for(i=0;i<count;++i)b2026_pdf_raster_free(&images[i]);free(images);free(numbers);free(sel);return 0;}
static const char *b2026_pdf_rasterize(const char *target,int argc,const B2026PdfArg *args){B2026PdfDocument doc;HashVariable *h=NULL;B2026PdfRaster *images=NULL;int *numbers=NULL,count=0,i,rc,ok=0,searchable;double dpi=0.0;const char *output,*source,*ret="";char **overlays=NULL;memset(&doc,0,sizeof(doc));b2026_pdf_orc_hash_clear(target);if((argc!=2&&argc!=3)||args[0].kind!=1||args[1].kind!=1||args[0].text==NULL||args[1].text==NULL||(argc==3&&!b2026_pdf_tf_hash(&args[2],&h))){b2026_pdf_orc_error(4320,"RASTERIZE","","output$, source$ [, settings{}] is required / 出力、入力、任意の設定が必要です");goto done;}output=args[0].text;source=args[1].text;rc=b2026_pdf_load(&doc,source,0);if(rc<=0){b2026_pdf_orc_error(4321,"RASTERIZE",source,b2026_pdf_load_error(rc));goto done;}if(!b2026_pdf_render_selected(&doc,source,h,&images,&numbers,&count,&dpi)){b2026_pdf_orc_error(4322,"RASTERIZE",source,"configured PDF renderer failed or is unavailable / 設定されたPDFレンダラーを利用できないか描画に失敗しました");goto done;}searchable=b2026_pdf_orc_bool(h,"検索文字を維持","searchable",0);if(searchable){overlays=b2026_pdf_search_overlays(&doc,source,numbers,count);if(overlays==NULL){b2026_pdf_orc_error(4324,"RASTERIZE",source,"search text layer could not be built / 検索文字レイヤーを生成できません");goto done;}b2026_pdf_log_warning("画像化PDFの検索文字は抽出可能な文字に限られます。 / Search text in an image PDF is limited to extractable text.");}if(b2026_pdf_orc_bool(h,"文書情報を維持","keepMetadata",0))b2026_pdf_log_warning("画像化PDFでは元PDFの文書情報を維持せず、新しいPDFとして保存します。 / Rasterization creates a new PDF without source document metadata.");if(!b2026_pdf_image_pdf(output,&doc,images,numbers,overlays,count)){b2026_pdf_orc_error(4323,"RASTERIZE",output,"image PDF could not be written atomically or exceeded 2 GiB / 画像PDFを原子的に保存できないか2GiB上限を超えました");goto done;}ok=1;ret=output;
done:b2026_pdf_orc_pair_number(target,"成功","ok",ok?1.0:0.0);b2026_pdf_orc_pair_text(target,"出力パス","path",ok?ret:"");b2026_pdf_orc_pair_number(target,"ページ数","pages",(double)count);b2026_pdf_orc_pair_number(target,"解像度","dpi",dpi);b2026_pdf_orc_pair_text(target,"レンダラー","renderer",b2026_envset_pdf_renderer());for(i=0;i<count;++i){b2026_pdf_raster_free(&images[i]);if(overlays!=NULL)free(overlays[i]);}free(overlays);free(images);free(numbers);b2026_pdf_document_free(&doc);return runtime_temp_string_copy(ok?ret:"");}

static char *b2026_pdf_page_image_path(const char *base,int page,const char *format){const char *ext=(b2026_strcasecmp(format,"JPEG")==0||b2026_strcasecmp(format,"JPG")==0)?".jpg":".png";size_t stem=strlen(base),n;char *p;if(stem>=4U&&base[stem-4U]=='.')stem-=4U;n=stem+32U;p=(char*)malloc(n);if(p!=NULL)snprintf(p,n,"%.*s_%04d%s",(int)stem,base,page,ext);return p;}
static int b2026_pdf_write_jpeg_rgb(const char *target,const B2026PdfRaster *r,int quality){char *stage=NULL;FILE *fp=NULL;int ok=0;char detail[512];if(r==NULL||r->rgb==NULL||r->width<=0||r->height<=0||!saveimage_ensure_directories_safe(target,detail,sizeof(detail)))return 0;stage=b2026_atomic_temp_path(target);if(stage==NULL||(fp=b2026_fopen(stage,"wb"))==NULL)goto done;ok=stbi_write_jpg_to_func(saveimage_stbi_write_callback,fp,r->width,r->height,3,r->rgb,quality)!=0&&fflush(fp)==0&&!ferror(fp);fclose(fp);fp=NULL;if(ok)ok=b2026_atomic_commit_file(stage,target);done:if(fp!=NULL)fclose(fp);if(!ok&&stage!=NULL)b2026_atomic_discard_file(stage);free(stage);return ok;}
static int b2026_pdf_page_images(const char *target,int argc,const B2026PdfArg *args){B2026PdfDocument doc;HashVariable *h=NULL;B2026PdfRaster *images=NULL;int *numbers=NULL,count=0,i,rc,ok=0;double dpi=0.0,quality_value=90.0;int jpeg,quality;const char *source,*base,*spec,*format;char **paths=NULL;memset(&doc,0,sizeof(doc));if((argc!=2&&argc!=3)||args[0].kind!=1||args[1].kind!=1||args[0].text==NULL||args[1].text==NULL||(argc==3&&!b2026_pdf_tf_hash(&args[2],&h))){b2026_pdf_orc_error(4330,"PAGEIMAGES","","source$, output-base$ [, settings{}] is required / 入力、出力基準パス、任意の設定が必要です");return 0;}source=args[0].text;base=args[1].text;if(!b2026_pdf_render_settings(h,&dpi,&spec,&format)||!b2026_pdf_orc_number(h,"品質","quality",90.0,1.0,100.0,&quality_value)){b2026_pdf_orc_error(4331,"PAGEIMAGES",source,"invalid settings / 設定が正しくありません");return 0;}jpeg=b2026_strcasecmp(format,"JPEG")==0||b2026_strcasecmp(format,"JPG")==0;quality=(int)quality_value;if(!jpeg&&b2026_strcasecmp(format,"PNG")!=0){b2026_pdf_orc_error(4332,"PAGEIMAGES",source,"format must be PNG or JPEG / 形式はPNGまたはJPEGを指定してください");return 0;}rc=b2026_pdf_load(&doc,source,0);if(rc<=0){b2026_pdf_orc_error(4333,"PAGEIMAGES",source,b2026_pdf_load_error(rc));goto done;}if(!b2026_pdf_render_selected(&doc,source,h,&images,&numbers,&count,&dpi)){b2026_pdf_orc_error(4334,"PAGEIMAGES",source,"configured PDF renderer failed or is unavailable / 設定されたPDFレンダラーを利用できないか描画に失敗しました");goto done;}paths=(char**)calloc((size_t)count,sizeof(*paths));if(paths==NULL)goto done;for(i=0;i<count;++i){paths[i]=b2026_pdf_page_image_path(base,numbers[i],format);if(paths[i]==NULL||(jpeg?!b2026_pdf_write_jpeg_rgb(paths[i],&images[i],quality):!b2026_pdf_write_png_rgb(paths[i],&images[i]))){b2026_pdf_orc_error(4335,"PAGEIMAGES",paths[i],"page image could not be written atomically / ページ画像を原子的に保存できません");goto done;}}if(!b2026_pdf_split_array_commit(target,paths,count))goto done;ok=1;
done:if(!ok&&target!=NULL)b2026_pdf_split_array_commit(target,NULL,0);for(i=0;i<count;++i){b2026_pdf_raster_free(&images[i]);if(paths!=NULL)free(paths[i]);}free(images);free(numbers);free(paths);b2026_pdf_document_free(&doc);return ok;}

static char *b2026_pdf_compare_text(B2026PdfDocument *doc,const char *path){B2026PdfTextSettings s;B2026PdfTextBuffer b;int i,partial=0;memset(&s,0,sizeof(s));memset(&b,0,sizeof(b));s.coordinate_order=1;s.separator="\n\f\n";s.tolerance=-1.0;for(i=0;i<doc->page_count;++i){char *t=b2026_pdf_text_page(doc,i,&s,path,&partial);const unsigned char *p=(const unsigned char*)(t!=NULL?t:"");int space=0;while(*p){if(isspace(*p)){space=1;}else{if(space&&b.used>0U)b2026_pdf_text_append_char(&b,' ');space=0;b2026_pdf_text_append_char(&b,(char)*p);}++p;}b2026_pdf_text_append_char(&b,'\n');free(t);}return b2026_pdf_text_take(&b);}
static int b2026_pdf_compare_structure(B2026PdfDocument *a,B2026PdfDocument *b){int i;if(a->page_count!=b->page_count)return 0;for(i=0;i<a->page_count;++i){double x[4],y[4];int rx,ry,j;if(!b2026_pdf_page_box(a,a->page_objects[i],"/MediaBox",x)||!b2026_pdf_page_box(b,b->page_objects[i],"/MediaBox",y))return 0;for(j=0;j<4;++j)if(fabs(x[j]-y[j])>0.001)return 0;rx=((b2026_pdf_page_inherited_integer(a,a->page_objects[i],"/Rotate",0)%360)+360)%360;ry=((b2026_pdf_page_inherited_integer(b,b->page_objects[i],"/Rotate",0)%360)+360)%360;if(rx!=ry)return 0;}return 1;}
static int b2026_pdf_compare_visual_page(const B2026PdfRaster *a,const B2026PdfRaster *b,double tolerance,unsigned long long *changed,double *maximum){size_t i,n;if(a->width!=b->width||a->height!=b->height){*changed+=(unsigned long long)(a->width>a->height?a->width:a->height);*maximum=255.0;return 0;}n=(size_t)a->width*(size_t)a->height;for(i=0;i<n;++i){int dr=abs((int)a->rgb[i*3U]-(int)b->rgb[i*3U]);int dg=abs((int)a->rgb[i*3U+1U]-(int)b->rgb[i*3U+1U]);int db=abs((int)a->rgb[i*3U+2U]-(int)b->rgb[i*3U+2U]);int d=dr;if(dg>d)d=dg;if(db>d)d=db;if((double)d>*maximum)*maximum=(double)d;if((double)d>tolerance)++*changed;}return 1;}
static int b2026_pdf_write_diff(const char *dir,int page,const B2026PdfRaster *a,const B2026PdfRaster *b,double tolerance){B2026PdfRaster d;char *path;size_t n,i,base=strlen(dir);int slash=base>0U&&dir[base-1U]!='/'&&dir[base-1U]!='\\',ok;if(a->width!=b->width||a->height!=b->height)return 1;path=(char*)malloc(base+40U);if(path==NULL)return 0;snprintf(path,base+40U,"%s%scompare_diff_%04d.png",dir,slash?"/":"",page);d.width=a->width;d.height=a->height;n=(size_t)d.width*(size_t)d.height;d.rgb=(unsigned char*)malloc(n*3U);if(d.rgb==NULL){free(path);return 0;}for(i=0;i<n;++i){int dr=abs((int)a->rgb[i*3U]-(int)b->rgb[i*3U]),dg=abs((int)a->rgb[i*3U+1U]-(int)b->rgb[i*3U+1U]),db=abs((int)a->rgb[i*3U+2U]-(int)b->rgb[i*3U+2U]),q=dr;if(dg>q)q=dg;if(db>q)q=db;if((double)q>tolerance){d.rgb[i*3U]=255;d.rgb[i*3U+1U]=(unsigned char)(255-q);d.rgb[i*3U+2U]=(unsigned char)(255-q);}else d.rgb[i*3U]=d.rgb[i*3U+1U]=d.rgb[i*3U+2U]=255;}ok=b2026_pdf_write_png_rgb(path,&d);free(d.rgb);free(path);return ok;}
static int b2026_pdf_compare(const char *target,int argc,const B2026PdfArg *args){B2026PdfDocument a,b;HashVariable *h=NULL;const char *p1,*p2,*mode,*diff_dir;double dpi=144.0,tolerance=0.0;int rc1,rc2,i,pages_equal,structure_equal,text_equal=1,visual_equal=1,do_text,do_visual,valid_mode,ok=0,diff_count=0;unsigned long long changed=0U,total_pixels=0U;double maxdiff=0.0;char *ta=NULL,*tb=NULL;memset(&a,0,sizeof(a));memset(&b,0,sizeof(b));b2026_pdf_orc_hash_clear(target);if(target==NULL||(argc!=2&&argc!=3)||args[0].kind!=1||args[1].kind!=1||args[0].text==NULL||args[1].text==NULL||(argc==3&&!b2026_pdf_tf_hash(&args[2],&h))){b2026_pdf_orc_error(4340,"COMPARE","","pdf1$, pdf2$ [, settings{}] and hash result are required / 2つのPDF、任意の設定、結果用連想配列が必要です");goto done;}p1=args[0].text;p2=args[1].text;mode=b2026_pdf_tf_text(h,"比較方法","mode");diff_dir=b2026_pdf_tf_text(h,"差分画像出力先","diffDirectory");if(mode==NULL)mode="ALL";do_text=b2026_strcasecmp(mode,"ALL")==0||b2026_strcasecmp(mode,"TEXT")==0||strcmp(mode,"文字")==0;do_visual=b2026_strcasecmp(mode,"ALL")==0||b2026_strcasecmp(mode,"VISUAL")==0||strcmp(mode,"画像")==0;valid_mode=do_text||do_visual||b2026_strcasecmp(mode,"STRUCTURE")==0||strcmp(mode,"構造")==0;if(!valid_mode||!b2026_pdf_orc_number(h,"解像度","dpi",144.0,36.0,1200.0,&dpi)||!b2026_pdf_orc_number(h,"許容差","tolerance",0.0,0.0,255.0,&tolerance)){b2026_pdf_orc_error(4341,"COMPARE","","invalid comparison settings / 比較設定が正しくありません");goto done;}rc1=b2026_pdf_load(&a,p1,0);rc2=b2026_pdf_load(&b,p2,0);if(rc1<=0||rc2<=0){b2026_pdf_orc_error(4342,"COMPARE",rc1<=0?p1:p2,b2026_pdf_load_error(rc1<=0?rc1:rc2));goto done;}pages_equal=a.page_count==b.page_count;structure_equal=b2026_pdf_compare_structure(&a,&b);if(do_text){ta=b2026_pdf_compare_text(&a,p1);tb=b2026_pdf_compare_text(&b,p2);if(ta==NULL||tb==NULL)goto done;text_equal=strcmp(ta,tb)==0;}if(do_visual){int n=a.page_count<b.page_count?a.page_count:b.page_count;for(i=0;i<n;++i){B2026PdfRaster ra={0},rb={0};unsigned long long before=changed;if(!b2026_pdf_render_page(p1,i+1,dpi,&ra)||!b2026_pdf_render_page(p2,i+1,dpi,&rb)){b2026_pdf_raster_free(&ra);b2026_pdf_raster_free(&rb);b2026_pdf_orc_error(4343,"COMPARE",p1,"configured PDF renderer failed during visual comparison / 画像比較中に設定されたPDFレンダラーが失敗しました");goto done;}total_pixels+=(unsigned long long)ra.width*(unsigned long long)ra.height;if(!b2026_pdf_compare_visual_page(&ra,&rb,tolerance,&changed,&maxdiff))visual_equal=0;if(diff_dir!=NULL&&*diff_dir!='\0'&&changed>before){if(!b2026_pdf_write_diff(diff_dir,i+1,&ra,&rb,tolerance)){b2026_pdf_raster_free(&ra);b2026_pdf_raster_free(&rb);b2026_pdf_orc_error(4344,"COMPARE",diff_dir,"difference image could not be written / 差分画像を保存できません");goto done;}++diff_count;}b2026_pdf_raster_free(&ra);b2026_pdf_raster_free(&rb);}if(!pages_equal)visual_equal=0;if(changed>0U)visual_equal=0;}ok=1;
done:b2026_pdf_orc_pair_number(target,"成功","ok",ok?1.0:0.0);b2026_pdf_orc_pair_number(target,"同一","equal",ok&&structure_equal&&(!do_text||text_equal)&&(!do_visual||visual_equal)?1.0:0.0);b2026_pdf_orc_pair_number(target,"構造同一","structureEqual",structure_equal?1.0:0.0);b2026_pdf_orc_pair_number(target,"文字同一","textEqual",text_equal?1.0:0.0);b2026_pdf_orc_pair_number(target,"画像同一","visualEqual",visual_equal?1.0:0.0);b2026_pdf_orc_pair_number(target,"変更画素数","changedPixels",(double)changed);b2026_pdf_orc_pair_number(target,"総画素数","totalPixels",(double)total_pixels);b2026_pdf_orc_pair_number(target,"最大差","maximumDifference",maxdiff);b2026_pdf_orc_pair_number(target,"差分画像数","differenceImages",(double)diff_count);b2026_pdf_orc_pair_number(target,"ページ数1","pages1",(double)a.page_count);b2026_pdf_orc_pair_number(target,"ページ数2","pages2",(double)b.page_count);b2026_pdf_orc_pair_text(target,"レンダラー","renderer",b2026_envset_pdf_renderer());free(ta);free(tb);b2026_pdf_document_free(&a);b2026_pdf_document_free(&b);return ok;}
