/* BASIC2026 portable, read-only PDF structural inspection. */
typedef struct {
    const char *target;
    int maximum;
    int stored;
    int total;
    int errors;
    int warnings;
    int information;
    int partial;
    int indeterminate;
} B2026PdfStructureReport;

static void b2026_pdf_sc_pair_number(const char *target,const char *jp,const char *en,double value){hash_set_number(target,jp,value);hash_set_number(target,en,value);}
static void b2026_pdf_sc_pair_string(const char *target,const char *jp,const char *en,const char *value){hash_set_string(target,jp,value);hash_set_string(target,en,value);}
static void b2026_pdf_sc_diag_field_string(B2026PdfStructureReport *r,int index,const char *jp,const char *en,const char *value){char key[96];snprintf(key,sizeof(key),"%d,%s",index,en);hash_set_string(r->target,key,value);snprintf(key,sizeof(key),"%d,%s",index,jp);hash_set_string(r->target,key,value);}
static void b2026_pdf_sc_diag_field_number(B2026PdfStructureReport *r,int index,const char *jp,const char *en,double value){char key[96];snprintf(key,sizeof(key),"%d,%s",index,en);hash_set_number(r->target,key,value);snprintf(key,sizeof(key),"%d,%s",index,jp);hash_set_number(r->target,key,value);}
static void b2026_pdf_sc_diag(B2026PdfStructureReport *r,const char *severity,const char *code,const char *category,const char *message,int object,int generation,int page,double offset,const char *pdf_key){int index;++r->total;if(strcmp(severity,"ERROR")==0)++r->errors;else if(strcmp(severity,"WARNING")==0)++r->warnings;else ++r->information;if(r->stored>=r->maximum)return;index=++r->stored;b2026_pdf_sc_diag_field_string(r,index,"重大度","severity",severity);b2026_pdf_sc_diag_field_string(r,index,"コード","code",code);b2026_pdf_sc_diag_field_string(r,index,"分類","category",category);b2026_pdf_sc_diag_field_string(r,index,"内容","message",message);b2026_pdf_sc_diag_field_number(r,index,"オブジェクト","object",object>=0?(double)object:null_value());b2026_pdf_sc_diag_field_number(r,index,"世代","generation",generation>=0?(double)generation:null_value());b2026_pdf_sc_diag_field_number(r,index,"ページ","page",page>0?(double)page:null_value());b2026_pdf_sc_diag_field_number(r,index,"位置","offset",offset>=0.0?offset:null_value());if(pdf_key!=NULL)b2026_pdf_sc_diag_field_string(r,index,"キー","key",pdf_key);else b2026_pdf_sc_diag_field_number(r,index,"キー","key",null_value());}

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

static int b2026_pdf_sc_size_text(const char *text,size_t *value){char *end;double number,multiplier=1.0;if(text==NULL)return 0;while(isspace((unsigned char)*text))++text;errno=0;number=strtod(text,&end);if(end==text||errno==ERANGE||!isfinite(number)||number<=0.0)return 0;while(isspace((unsigned char)*end))++end;if(*end!='\0'){if(b2026_strcasecmp(end,"B")==0)multiplier=1.0;else if(b2026_strcasecmp(end,"KIB")==0||b2026_strcasecmp(end,"KB")==0)multiplier=1024.0;else if(b2026_strcasecmp(end,"MIB")==0||b2026_strcasecmp(end,"MB")==0)multiplier=1024.0*1024.0;else if(b2026_strcasecmp(end,"GIB")==0||b2026_strcasecmp(end,"GB")==0)multiplier=1024.0*1024.0*1024.0;else return 0;}number*=multiplier;if(number>(double)SIZE_MAX)return 0;*value=(size_t)number;return 1;}

static int b2026_pdf_sc_settings(const B2026PdfArg *arg,const char **mode,int *max_diagnostics,size_t *max_stream){HashVariable *h=NULL;HashEntry *e;double number;*mode="STANDARD";*max_diagnostics=1000;*max_stream=256U*1024U*1024U;if(arg==NULL)return 1;if(!b2026_pdf_tf_hash(arg,&h))return 0;e=b2026_pdf_tf_entry(h,"モード","mode");if(e!=NULL){const char *s;if(e->value.type==TYPE_NUMBER)return 0;s=hash_value_as_text(&e->value);if(b2026_strcasecmp(s,"QUICK")==0)*mode="QUICK";else if(b2026_strcasecmp(s,"STANDARD")==0)*mode="STANDARD";else if(b2026_strcasecmp(s,"DEEP")==0)*mode="DEEP";else return 0;}e=b2026_pdf_tf_entry(h,"最大診断数","maxDiagnostics");if(e!=NULL){if(e->value.type!=TYPE_NUMBER)return 0;number=e->value.val.number;if(!isfinite(number)||floor(number)!=number||number<1.0||number>100000.0)return 0;*max_diagnostics=(int)number;}e=b2026_pdf_tf_entry(h,"最大ストリームサイズ","maxStreamSize");if(e!=NULL){if(e->value.type==TYPE_NUMBER){number=e->value.val.number;if(!isfinite(number)||floor(number)!=number||number<1.0||number>(double)SIZE_MAX)return 0;*max_stream=(size_t)number;}else if(!b2026_pdf_sc_size_text(hash_value_as_text(&e->value),max_stream))return 0;}return 1;}

static const unsigned char *b2026_pdf_sc_last_find(const unsigned char *data,size_t n,const char *token){const unsigned char *p=data,*last=NULL;size_t len=strlen(token);while((p=b2026_pdf_memfind(p,n-(size_t)(p-data),token,len))!=NULL){last=p;p+=len;}return last;}
static int b2026_pdf_sc_startxref(const unsigned char *data,size_t n,size_t *offset){const unsigned char *p=b2026_pdf_sc_last_find(data,n,"startxref");char *end;unsigned long long value;if(p==NULL)return 0;p+=9;while((size_t)(p-data)<n&&isspace(*p))++p;if((size_t)(p-data)>=n||!isdigit(*p))return -1;errno=0;value=strtoull((const char*)p,&end,10);if(end==(char*)p||errno==ERANGE||value>(unsigned long long)SIZE_MAX)return -1;*offset=(size_t)value;return 1;}
static int b2026_pdf_sc_at_object(const unsigned char *data,size_t n,size_t offset){size_t p=offset;if(p>=n)return 0;while(p<n&&isspace(data[p]))++p;if(p>=n||!isdigit(data[p]))return 0;while(p<n&&isdigit(data[p]))++p;while(p<n&&isspace(data[p]))++p;if(p>=n||!isdigit(data[p]))return 0;while(p<n&&isdigit(data[p]))++p;while(p<n&&isspace(data[p]))++p;return p+3U<=n&&memcmp(data+p,"obj",3U)==0;}
static int b2026_pdf_sc_xref_target(const unsigned char *data,size_t n,size_t offset){return offset+4U<=n&&memcmp(data+offset,"xref",4U)==0?1:b2026_pdf_sc_at_object(data,n,offset);}
static void b2026_pdf_sc_incremental(B2026PdfStructureReport *r,const unsigned char *data,size_t n){const unsigned char *p=data;size_t token_len=9U;while((p=b2026_pdf_memfind(p,n-(size_t)(p-data),"startxref",token_len))!=NULL){const unsigned char *q=p+token_len;char *end;unsigned long long value;while((size_t)(q-data)<n&&isspace(*q))++q;errno=0;value=strtoull((const char*)q,&end,10);if(end==(char*)q||errno==ERANGE||value>(unsigned long long)SIZE_MAX||!b2026_pdf_sc_xref_target(data,n,(size_t)value))b2026_pdf_sc_diag(r,"ERROR","INCREMENTAL_UPDATE_BROKEN","FILE","an incremental-update startxref is invalid / 増分更新のstartxrefが不正です",-1,-1,0,(double)(p-data),NULL);p+=token_len;}p=data;while((p=b2026_pdf_memfind(p,n-(size_t)(p-data),"trailer",7U))!=NULL){const unsigned char *end_section=b2026_pdf_memfind(p,n-(size_t)(p-data),"startxref",9U),*prev;size_t span=end_section!=NULL?(size_t)(end_section-p):n-(size_t)(p-data);prev=b2026_pdf_memfind(p,span,"/Prev",5U);if(prev!=NULL){const unsigned char *q=prev+5U;char *end;unsigned long long value;while((size_t)(q-data)<n&&isspace(*q))++q;errno=0;value=strtoull((const char*)q,&end,10);if(end==(char*)q||errno==ERANGE||value>(unsigned long long)SIZE_MAX||!b2026_pdf_sc_xref_target(data,n,(size_t)value))b2026_pdf_sc_diag(r,"ERROR","PREV_CHAIN_INVALID","XREF","Prev does not point to valid xref data / Prevが有効なxrefを指していません",-1,-1,0,(double)(prev-data),"/Prev");}p+=7U;}}
static long b2026_pdf_sc_object_offset(const unsigned char *data,size_t n,int number,int generation){char marker[80];const unsigned char *p=data;size_t len;snprintf(marker,sizeof(marker),"%d %d obj",number,generation);len=strlen(marker);while((p=b2026_pdf_memfind(p,n-(size_t)(p-data),marker,len))!=NULL){size_t pos=(size_t)(p-data);if((pos==0U||isspace(data[pos-1U]))&&(pos+len==n||isspace(data[pos+len])))return (long)pos;++p;}return -1L;}

static void b2026_pdf_sc_check_xref_table(B2026PdfStructureReport *r,const unsigned char *data,size_t n,size_t offset){size_t p=offset;char *end;long first,count,i;if(p+4U>n||memcmp(data+p,"xref",4U)!=0)return;p+=4U;for(;;){while(p<n&&isspace(data[p]))++p;if(p>=n||memcmp(data+p,"trailer",7U)==0)break;errno=0;first=strtol((const char*)data+p,&end,10);if(end==(char*)data+p||errno==ERANGE){b2026_pdf_sc_diag(r,"ERROR","XREF_INVALID","XREF","xref subsection is invalid / xrefサブセクションが不正です",-1,-1,0,(double)p,NULL);return;}p=(size_t)(end-(char*)data);while(p<n&&isspace(data[p]))++p;count=strtol((const char*)data+p,&end,10);if(end==(char*)data+p||count<0||count>10000000L){b2026_pdf_sc_diag(r,"ERROR","XREF_INVALID","XREF","xref entry count is invalid / xref項目数が不正です",-1,-1,0,(double)p,NULL);return;}p=(size_t)(end-(char*)data);while(p<n&&(data[p]=='\r'||data[p]=='\n'||data[p]==' '||data[p]=='\t'))++p;for(i=0;i<count;++i){unsigned long long recorded;long actual;int generation;char state;if(p>=n)return;errno=0;recorded=strtoull((const char*)data+p,&end,10);if(end==(char*)data+p||errno==ERANGE){b2026_pdf_sc_diag(r,"ERROR","XREF_INVALID","XREF","xref entry is invalid / xref項目が不正です",(int)(first+i),-1,0,(double)p,NULL);return;}p=(size_t)(end-(char*)data);while(p<n&&isspace(data[p])&&data[p]!='\r'&&data[p]!='\n')++p;generation=(int)strtol((const char*)data+p,&end,10);if(end==(char*)data+p){b2026_pdf_sc_diag(r,"ERROR","XREF_INVALID","XREF","xref generation is invalid / xref世代番号が不正です",(int)(first+i),-1,0,(double)p,NULL);return;}p=(size_t)(end-(char*)data);while(p<n&&isspace(data[p])&&data[p]!='\r'&&data[p]!='\n')++p;state=p<n?(char)data[p]:'?';while(p<n&&data[p]!='\r'&&data[p]!='\n')++p;while(p<n&&(data[p]=='\r'||data[p]=='\n'))++p;if(state=='n'&&first+i>0){actual=b2026_pdf_sc_object_offset(data,n,(int)(first+i),generation);if(recorded>=(unsigned long long)n)b2026_pdf_sc_diag(r,"ERROR","XREF_OFFSET_INVALID","XREF","xref offset is outside the file / xref位置がファイル範囲外です",(int)(first+i),generation,0,(double)recorded,NULL);else if(actual<0)b2026_pdf_sc_diag(r,"ERROR","XREF_OBJECT_MISSING","XREF","xref points to a missing object / xrefが存在しないオブジェクトを参照しています",(int)(first+i),generation,0,(double)recorded,NULL);else if((unsigned long long)actual!=recorded)b2026_pdf_sc_diag(r,"ERROR","XREF_OFFSET_MISMATCH","XREF","xref offset does not match the object / xref位置とオブジェクト位置が一致しません",(int)(first+i),generation,0,(double)recorded,NULL);}}}}

static int b2026_pdf_sc_ref_at(const unsigned char *s,size_t n,size_t p,int *number,int *generation,size_t *after){char *end;long a,g;size_t q;if(p>=n||!isdigit(s[p]))return 0;errno=0;a=strtol((const char*)s+p,&end,10);if(end==(char*)s+p||errno==ERANGE||a<=0||a>INT_MAX)return 0;q=(size_t)(end-(char*)s);while(q<n&&isspace(s[q]))++q;if(q>=n||!isdigit(s[q]))return 0;g=strtol((const char*)s+q,&end,10);if(end==(char*)s+q||g<0||g>INT_MAX)return 0;q=(size_t)(end-(char*)s);while(q<n&&isspace(s[q]))++q;if(q>=n||s[q]!='R')return 0;*number=(int)a;*generation=(int)g;*after=q+1U;return 1;}
static void b2026_pdf_sc_check_refs(B2026PdfStructureReport *r,B2026PdfDocument *doc,int deep,unsigned char *reachable){int i;for(i=0;i<doc->object_count;++i){B2026PdfObject *o=&doc->objects[i];const unsigned char *stream=b2026_pdf_memfind(o->body,o->body_length,"stream",6U);size_t n=stream!=NULL?(size_t)(stream-o->body):o->body_length,p=0U;while(p<n){int a,g,index;size_t after;if(b2026_pdf_sc_ref_at(o->body,n,p,&a,&g,&after)){index=b2026_pdf_object_lookup(doc,a,g);if(index<0)b2026_pdf_sc_diag(r,"ERROR","OBJECT_REFERENCE_INVALID","OBJECT","indirect reference points to a missing object / 間接参照先のオブジェクトがありません",o->old_number,o->old_generation,0,-1.0,NULL);else if(deep&&reachable!=NULL)reachable[index]=1U;p=after;}else ++p;}}}

static int b2026_pdf_sc_asciihex(const unsigned char *input,size_t n,unsigned char **out,size_t *out_n,size_t limit){unsigned char *buffer;size_t i,used=0U;int high=-1;if(n>limit||n>SIZE_MAX-1U)return 0;buffer=(unsigned char*)malloc(n+1U);if(buffer==NULL)return 0;for(i=0U;i<n;++i){int value;unsigned char c=input[i];if(isspace(c))continue;if(c=='>')break;if(c>='0'&&c<='9')value=c-'0';else if(c>='A'&&c<='F')value=c-'A'+10;else if(c>='a'&&c<='f')value=c-'a'+10;else{free(buffer);return 0;}if(high<0)high=value;else{buffer[used++]=(unsigned char)((high<<4)|value);high=-1;}}if(high>=0)buffer[used++]=(unsigned char)(high<<4);buffer[used]=0;*out=buffer;*out_n=used;return 1;}
static int b2026_pdf_sc_ascii85(const unsigned char *input,size_t n,unsigned char **out,size_t *out_n,size_t limit){unsigned char *buffer;size_t i=0U,used=0U,extra=n/4U,cap;unsigned long tuple=0UL;int count=0,j;if(n>SIZE_MAX-extra-8U)return 0;cap=n+extra+8U;if(cap>limit)cap=limit;if(cap==0U)cap=1U;buffer=(unsigned char*)malloc(cap+1U);if(buffer==NULL)return 0;while(i<n){unsigned char c=input[i++];if(isspace(c)||c=='<'||c=='~')continue;if(c=='>')break;if(c=='z'&&count==0){if(used+4U>cap){free(buffer);return 0;}memset(buffer+used,0,4U);used+=4U;continue;}if(c<'!'||c>'u'){free(buffer);return 0;}tuple=tuple*85UL+(unsigned long)(c-'!');if(++count==5){if(used+4U>cap){free(buffer);return 0;}for(j=3;j>=0;--j){buffer[used+(size_t)j]=(unsigned char)(tuple&255UL);tuple>>=8;}used+=4U;tuple=0UL;count=0;}}if(count>1){int original=count;while(count<5){tuple=tuple*85UL+84UL;++count;}if(used+(size_t)(original-1)>cap){free(buffer);return 0;}for(j=3;j>=0;--j){unsigned char byte=(unsigned char)(tuple&255UL);if(j<original-1)buffer[used+(size_t)j]=byte;tuple>>=8;}used+=(size_t)(original-1);}else if(count==1){free(buffer);return 0;}buffer[used]=0;*out=buffer;*out_n=used;return 1;}
static int b2026_pdf_sc_flate(const unsigned char *input,size_t n,unsigned char **out,size_t *out_n,size_t limit){size_t cap=65536U;char *buffer;int decoded;if(n>(size_t)INT_MAX)return 0;if(cap>limit)cap=limit;if(cap==0U)return 0;for(;;){if(cap>(size_t)INT_MAX)return 0;buffer=(char*)malloc(cap+1U);if(buffer==NULL)return 0;decoded=stbi_zlib_decode_buffer(buffer,(int)cap,(const char*)input,(int)n);if(decoded>=0){buffer[decoded]='\0';*out=(unsigned char*)buffer;*out_n=(size_t)decoded;return 1;}free(buffer);if(cap>=limit)return 0;if(cap>limit/2U)cap=limit;else cap*=2U;}}

static int b2026_pdf_sc_filter_list(const unsigned char *dict,size_t n,char names[8][24],int *count,int *opaque){B2026PdfKeyRange range;size_t p;*count=0;*opaque=0;if(!b2026_pdf_tf_find_key(dict,n,"/Filter",&range)||!range.found)return 1;p=range.value_start;while(p<range.value_end){size_t start,length;while(p<range.value_end&&dict[p]!='/')++p;if(p>=range.value_end)break;start=++p;while(p<range.value_end&&!b2026_pdf_is_delim(dict[p]))++p;length=p-start;if(length==0U)continue;if(*count>=8||length>=24U)return 0;memcpy(names[*count],dict+start,length);names[*count][length]='\0';if(strcmp(names[*count],"DCTDecode")==0||strcmp(names[*count],"JPXDecode")==0||strcmp(names[*count],"JBIG2Decode")==0||strcmp(names[*count],"CCITTFaxDecode")==0)*opaque=1;else if(strcmp(names[*count],"FlateDecode")!=0&&strcmp(names[*count],"ASCIIHexDecode")!=0&&strcmp(names[*count],"ASCII85Decode")!=0)return 0;++*count;}return 1;}
static int b2026_pdf_sc_decode_filters(const unsigned char *payload,size_t payload_n,char names[8][24],int count,size_t limit,unsigned char **result,size_t *result_n){unsigned char *current=NULL,*next=NULL;size_t current_n=payload_n,next_n=0U;int i,ok;*result=NULL;*result_n=0U;if(payload_n>limit)return -1;current=(unsigned char*)malloc(payload_n+1U);if(current==NULL)return 0;memcpy(current,payload,payload_n);current[payload_n]=0;for(i=0;i<count;++i){next=NULL;next_n=0U;if(strcmp(names[i],"FlateDecode")==0)ok=b2026_pdf_sc_flate(current,current_n,&next,&next_n,limit);else if(strcmp(names[i],"ASCIIHexDecode")==0)ok=b2026_pdf_sc_asciihex(current,current_n,&next,&next_n,limit);else if(strcmp(names[i],"ASCII85Decode")==0)ok=b2026_pdf_sc_ascii85(current,current_n,&next,&next_n,limit);else{free(current);return 0;}free(current);if(!ok)return 0;if(next_n>limit){free(next);return -1;}current=next;current_n=next_n;}*result=current;*result_n=current_n;return 1;}
static void b2026_pdf_sc_check_streams(B2026PdfStructureReport *r,B2026PdfDocument *doc,int deep,size_t limit){int i;for(i=0;i<doc->object_count;++i){B2026PdfObject *o=&doc->objects[i];const unsigned char *stream=b2026_pdf_memfind(o->body,o->body_length,"stream",6U),*payload,*endstream;size_t dict_n,payload_n;int declared,known_filter=1,filter_count=0,opaque=0;char filters[8][24];if(stream==NULL)continue;dict_n=(size_t)(stream-o->body);payload=stream+6U;if(payload<o->body+o->body_length&&*payload=='\r')++payload;if(payload<o->body+o->body_length&&*payload=='\n')++payload;endstream=b2026_pdf_memfind(payload,o->body_length-(size_t)(payload-o->body),"endstream",9U);if(endstream==NULL){b2026_pdf_sc_diag(r,"ERROR","STREAM_END_MISSING","STREAM","stream has no endstream marker / ストリームにendstreamがありません",o->old_number,o->old_generation,0,-1.0,"/Length");continue;}payload_n=(size_t)(endstream-payload);if(b2026_pdf_dict_integer(o->body,dict_n,"/Length",&declared)){const unsigned char *expected;if(declared<0||(size_t)declared>payload_n){b2026_pdf_sc_diag(r,"ERROR","STREAM_LENGTH_INVALID","STREAM","stream Length is outside its data / ストリームLengthがデータ範囲外です",o->old_number,o->old_generation,0,-1.0,"/Length");}else{expected=payload+(size_t)declared;while(expected<endstream&&(*expected=='\r'||*expected=='\n'))++expected;if(expected!=endstream)b2026_pdf_sc_diag(r,"ERROR","STREAM_LENGTH_MISMATCH","STREAM","stream Length does not match endstream / ストリームLengthとendstream位置が一致しません",o->old_number,o->old_generation,0,-1.0,"/Length");}}if(b2026_pdf_memfind(o->body,dict_n,"/Filter",7U)!=NULL){known_filter=b2026_pdf_sc_filter_list(o->body,dict_n,filters,&filter_count,&opaque);if(!known_filter){b2026_pdf_sc_diag(r,"WARNING","UNSUPPORTED_FILTER","STREAM","unsupported stream filter prevents a complete check / 未対応フィルターのため完全検査できません",o->old_number,o->old_generation,0,-1.0,"/Filter");r->partial=1;r->indeterminate=1;}}
        if(deep&&known_filter&&!opaque){unsigned char *decoded=NULL;size_t decoded_n=0U;int decode_status=b2026_pdf_sc_decode_filters(payload,payload_n,filters,filter_count,limit,&decoded,&decoded_n);if(decode_status<0){b2026_pdf_sc_diag(r,"WARNING","STREAM_LIMIT_EXCEEDED","STREAM","decoded stream exceeds maxStreamSize / 復号ストリームが最大ストリームサイズを超えました",o->old_number,o->old_generation,0,-1.0,"/Length");r->partial=1;r->indeterminate=1;}else if(decode_status==0)b2026_pdf_sc_diag(r,"ERROR","STREAM_DECODE_FAILED","STREAM","supported stream filter could not be decoded / 対応フィルターのストリームを復号できません",o->old_number,o->old_generation,0,-1.0,"/Filter");free(decoded);}}
}

static unsigned long long b2026_pdf_sc_be_integer(const unsigned char *p,int width){unsigned long long value=0ULL;int i;for(i=0;i<width;++i)value=(value<<8)|(unsigned long long)p[i];return value;}
static void b2026_pdf_sc_check_xref_streams(B2026PdfStructureReport *r,B2026PdfDocument *doc,size_t limit){int oi;for(oi=0;oi<doc->object_count;++oi){B2026PdfObject *o=&doc->objects[oi];const unsigned char *stream,*payload,*endstream;double wv[3],iv[64];int wc,ic,size_value,prev_value,filter_count=0,opaque=0,decode_status,pair,entry_width;char filters[8][24];unsigned char *decoded=NULL;size_t decoded_n=0U,pos=0U,payload_n;if(b2026_pdf_memfind(o->body,o->body_length,"/Type /XRef",11U)==NULL)continue;if(b2026_pdf_dict_integer(o->body,o->body_length,"/Prev",&prev_value)&&!b2026_pdf_sc_xref_target(doc->bytes,doc->length,(size_t)prev_value))b2026_pdf_sc_diag(r,"ERROR","PREV_CHAIN_INVALID","XREF","xref-stream Prev does not point to valid xref data / xrefストリームのPrevが有効なxrefを指していません",o->old_number,o->old_generation,0,-1.0,"/Prev");wc=b2026_pdf_number_array(o->body,o->body_length,"/W",wv,3);if(wc!=3||!b2026_pdf_dict_integer(o->body,o->body_length,"/Size",&size_value)||size_value<1){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream W or Size is invalid / xrefストリームのWまたはSizeが不正です",o->old_number,o->old_generation,0,-1.0,"/W");continue;}if(floor(wv[0])!=wv[0]||floor(wv[1])!=wv[1]||floor(wv[2])!=wv[2]||wv[0]<0.0||wv[1]<0.0||wv[2]<0.0||wv[0]>8.0||wv[1]>8.0||wv[2]>8.0||(entry_width=(int)(wv[0]+wv[1]+wv[2]))<=0){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream field widths are invalid / xrefストリームのフィールド幅が不正です",o->old_number,o->old_generation,0,-1.0,"/W");continue;}ic=b2026_pdf_number_array(o->body,o->body_length,"/Index",iv,64);if(ic==0){iv[0]=0.0;iv[1]=(double)size_value;ic=2;}if(ic<0||ic%2!=0){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream Index is invalid / xrefストリームのIndexが不正です",o->old_number,o->old_generation,0,-1.0,"/Index");continue;}stream=b2026_pdf_memfind(o->body,o->body_length,"stream",6U);if(stream==NULL)continue;payload=stream+6U;if(payload<o->body+o->body_length&&*payload=='\r')++payload;if(payload<o->body+o->body_length&&*payload=='\n')++payload;endstream=b2026_pdf_memfind(payload,o->body_length-(size_t)(payload-o->body),"endstream",9U);if(endstream==NULL)continue;payload_n=(size_t)(endstream-payload);if(!b2026_pdf_sc_filter_list(o->body,(size_t)(stream-o->body),filters,&filter_count,&opaque)||opaque){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream uses an unsupported filter / xrefストリームのフィルターに対応できません",o->old_number,o->old_generation,0,-1.0,"/Filter");continue;}decode_status=b2026_pdf_sc_decode_filters(payload,payload_n,filters,filter_count,limit,&decoded,&decoded_n);if(decode_status<0){b2026_pdf_sc_diag(r,"WARNING","STREAM_LIMIT_EXCEEDED","XREF","xref stream exceeds maxStreamSize / xrefストリームが最大ストリームサイズを超えました",o->old_number,o->old_generation,0,-1.0,"/Length");r->partial=1;r->indeterminate=1;continue;}if(decode_status==0){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream cannot be decoded / xrefストリームを復号できません",o->old_number,o->old_generation,0,-1.0,"/Filter");continue;}for(pair=0;pair<ic;pair+=2){double first_value=iv[pair],count_value=iv[pair+1];long first,count,j;size_t bytes;if(first_value<0.0||count_value<0.0||first_value>(double)LONG_MAX||count_value>(double)LONG_MAX||floor(first_value)!=first_value||floor(count_value)!=count_value){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream entry range is invalid / xrefストリームの項目範囲が不正です",o->old_number,o->old_generation,0,-1.0,"/Index");break;}first=(long)first_value;count=(long)count_value;if((unsigned long long)count>(unsigned long long)(SIZE_MAX/(size_t)entry_width)){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream entry range overflows / xrefストリームの項目範囲がオーバーフローします",o->old_number,o->old_generation,0,-1.0,"/Index");break;}bytes=(size_t)count*(size_t)entry_width;if(pos>decoded_n||bytes>decoded_n-pos){b2026_pdf_sc_diag(r,"ERROR","XREF_STREAM_INVALID","XREF","xref stream is shorter than its Index / xrefストリームがIndexで示す長さより短いです",o->old_number,o->old_generation,0,-1.0,"/Index");break;}for(j=0;j<count;++j){const unsigned char *entry=decoded+pos;unsigned long long type=wv[0]==0.0?1ULL:b2026_pdf_sc_be_integer(entry,(int)wv[0]);unsigned long long field2=b2026_pdf_sc_be_integer(entry+(int)wv[0],(int)wv[1]);unsigned long long field3=b2026_pdf_sc_be_integer(entry+(int)wv[0]+(int)wv[1],(int)wv[2]);long actual;if(type==1ULL&&first+j>0&&first+j<=INT_MAX&&field3<=INT_MAX){if(field2>=(unsigned long long)doc->length)b2026_pdf_sc_diag(r,"ERROR","XREF_OFFSET_INVALID","XREF","xref stream offset is outside the file / xrefストリーム位置がファイル範囲外です",(int)(first+j),(int)field3,0,(double)field2,NULL);else{actual=b2026_pdf_sc_object_offset(doc->bytes,doc->length,(int)(first+j),(int)field3);if(actual<0)b2026_pdf_sc_diag(r,"ERROR","XREF_OBJECT_MISSING","XREF","xref stream points to a missing object / xrefストリームが存在しないオブジェクトを参照しています",(int)(first+j),(int)field3,0,(double)field2,NULL);else if((unsigned long long)actual!=field2)b2026_pdf_sc_diag(r,"ERROR","XREF_OFFSET_MISMATCH","XREF","xref stream offset does not match the object / xrefストリーム位置とオブジェクト位置が一致しません",(int)(first+j),(int)field3,0,(double)field2,NULL);}}else if(type==2ULL&&(field2>INT_MAX||b2026_pdf_object_lookup(doc,(int)field2,0)<0))b2026_pdf_sc_diag(r,"ERROR","XREF_OBJECT_MISSING","XREF","xref stream names a missing object stream / xrefストリームが存在しないオブジェクトストリームを参照しています",(int)(first+j),0,0,-1.0,NULL);pos+=(size_t)entry_width;}}free(decoded);}}

static void b2026_pdf_sc_check_pages(B2026PdfStructureReport *r,B2026PdfDocument *doc){int i,catalog=doc->root_object,pages_root=-1,pn=0,pg=0,declared=-1;if(catalog<0||catalog>=doc->object_count||!b2026_pdf_dict_name_is(doc->objects[catalog].body,doc->objects[catalog].body_length,"/Type","Catalog")){b2026_pdf_sc_diag(r,"ERROR","CATALOG_MISSING","DOCUMENT","document catalog is missing / 文書Catalogがありません",-1,-1,0,-1.0,"/Root");return;}if(!b2026_pdf_named_ref(doc->objects[catalog].body,doc->objects[catalog].body_length,"/Pages",&pn,&pg)||(pages_root=b2026_pdf_object_lookup(doc,pn,pg))<0){b2026_pdf_sc_diag(r,"ERROR","PAGES_ROOT_MISSING","DOCUMENT","Pages root is missing / Pagesルートがありません",doc->objects[catalog].old_number,doc->objects[catalog].old_generation,0,-1.0,"/Pages");return;}if(b2026_pdf_dict_integer(doc->objects[pages_root].body,doc->objects[pages_root].body_length,"/Count",&declared)&&declared!=doc->page_count)b2026_pdf_sc_diag(r,"ERROR","PAGE_COUNT_MISMATCH","DOCUMENT","Pages Count does not match resolved pages / PagesのCountと実ページ数が一致しません",doc->objects[pages_root].old_number,doc->objects[pages_root].old_generation,0,-1.0,"/Count");for(i=0;i<doc->page_count;++i){int oi=doc->page_objects[i],rotation;double media[4],box[4];if(!b2026_pdf_page_box(doc,oi,"/MediaBox",media)||!isfinite(media[0])||!isfinite(media[1])||!isfinite(media[2])||!isfinite(media[3])||media[2]<=media[0]||media[3]<=media[1])b2026_pdf_sc_diag(r,"ERROR","MEDIABOX_MISSING","PAGE","MediaBox is missing or invalid / MediaBoxがないか不正です",doc->objects[oi].old_number,doc->objects[oi].old_generation,i+1,-1.0,"/MediaBox");else if(b2026_pdf_page_box(doc,oi,"/CropBox",box)&&(box[0]<media[0]||box[1]<media[1]||box[2]>media[2]||box[3]>media[3]))b2026_pdf_sc_diag(r,"WARNING","PAGE_BOX_OUTSIDE_MEDIA","PAGE","page box extends outside MediaBox / ページボックスがMediaBox外にあります",doc->objects[oi].old_number,doc->objects[oi].old_generation,i+1,-1.0,"/CropBox");rotation=b2026_pdf_page_inherited_integer(doc,oi,"/Rotate",0);if(rotation%90!=0)b2026_pdf_sc_diag(r,"WARNING","ROTATE_INVALID","PAGE","Rotate is not a multiple of 90 / Rotateが90の倍数ではありません",doc->objects[oi].old_number,doc->objects[oi].old_generation,i+1,-1.0,"/Rotate");}}

static void b2026_pdf_sc_feature(B2026PdfStructureReport *r,int present,const char *code,const char *message){if(present)b2026_pdf_sc_diag(r,"INFO",code,"FEATURE",message,-1,-1,0,-1.0,NULL);}
static void b2026_pdf_sc_deep_unreachable(B2026PdfStructureReport *r,B2026PdfDocument *doc){unsigned char *seen;int i,changed=1,catalog=doc->root_object;seen=(unsigned char*)calloc((size_t)doc->object_count,1U);if(seen==NULL)return;for(i=0;i<doc->object_count;++i)if(b2026_pdf_dict_name_is(doc->objects[i].body,doc->objects[i].body_length,"/Type","XRef"))seen[i]=1U;if(catalog>=0&&catalog<doc->object_count)seen[catalog]=1U;if(doc->info_object>=0&&doc->info_object<doc->object_count)seen[doc->info_object]=1U;while(changed){changed=0;for(i=0;i<doc->object_count;++i)if(seen[i]){B2026PdfObject *o=&doc->objects[i];const unsigned char *stream=b2026_pdf_memfind(o->body,o->body_length,"stream",6U);size_t n=stream!=NULL?(size_t)(stream-o->body):o->body_length,p=0U;while(p<n){int a,g,index;size_t after;if(b2026_pdf_sc_ref_at(o->body,n,p,&a,&g,&after)){index=b2026_pdf_object_lookup(doc,a,g);if(index>=0&&!seen[index]){seen[index]=1U;changed=1;}p=after;}else ++p;}}}for(i=0;i<doc->object_count;++i)if(!seen[i])b2026_pdf_sc_diag(r,"WARNING","UNREACHABLE_OBJECT","OBJECT","object is unreachable from the document roots / 文書ルートから到達できないオブジェクトです",doc->objects[i].old_number,doc->objects[i].old_generation,0,-1.0,NULL);free(seen);}

static int b2026_pdf_structure_check(const char *target_name,int argc,const B2026PdfArg *args){HashVariable *target;B2026PdfStructureReport report;B2026PdfDocument doc;unsigned char *raw=NULL;size_t raw_n=0U,startxref=0U,max_stream;const char *path,*mode;int max_diagnostics,start_status,rc,deep,quick,xref_table,xref_stream,encrypted,signed_pdf,has_form,has_js,has_attachments,has_bookmarks,has_labels,tagged,has_objstm,starts,editable,complete,valid;char version[32];const unsigned char *eof,*header;
    if(target_name==NULL||*target_name=='\0')return 0;target=ensure_local_hash(get_hash_frame(target_name),target_name);clear_hash_entries(target);if(argc<1||argc>2||args[0].kind!=1||args[0].text==NULL)return b2026_pdf_sc_fail(target_name,4180,"","STRUCTURECHECK requires a PDF path and optional settings / 構造検査にはPDFパスと任意の設定が必要です");path=args[0].text;if(!b2026_pdf_sc_settings(argc==2?&args[1]:NULL,&mode,&max_diagnostics,&max_stream))return b2026_pdf_sc_fail(target_name,4181,path,"invalid structure-check settings / 構造検査の設定が不正です");if(!b2026_pdf_read_file(path,&raw,&raw_n))return b2026_pdf_sc_fail(target_name,4182,path,"file could not be read / ファイルを読み込めません");header=raw_n>=5U?b2026_pdf_memfind(raw,raw_n<1024U?raw_n:1024U,"%PDF-",5U):NULL;if(header==NULL&&!(b2026_pdf_memfind(raw,raw_n," obj",4U)!=NULL&&b2026_pdf_memfind(raw,raw_n,"%%EOF",5U)!=NULL)){free(raw);return b2026_pdf_sc_fail(target_name,4183,path,"file is not a PDF / PDFファイルではありません");}
    memset(&report,0,sizeof(report));report.target=target_name;report.maximum=max_diagnostics;deep=strcmp(mode,"DEEP")==0;quick=strcmp(mode,"QUICK")==0;if(header==NULL){version[0]='\0';b2026_pdf_sc_diag(&report,"ERROR","HEADER_MISSING","FILE","PDF header is missing / PDFヘッダーがありません",-1,-1,0,0.0,NULL);}else{size_t hp=(size_t)(header-raw);if(hp!=0U)b2026_pdf_sc_diag(&report,"ERROR","HEADER_MISSING","FILE","PDF header is not at the beginning of the file / PDFヘッダーがファイル先頭にありません",-1,-1,0,(double)hp,NULL);if(hp+8U<=raw_n)snprintf(version,sizeof(version),"%c.%c",header[5],header[7]);else version[0]='\0';if(hp+8U>raw_n||!(isdigit(header[5])&&header[6]=='.'&&isdigit(header[7])))b2026_pdf_sc_diag(&report,"ERROR","VERSION_INVALID","FILE","PDF header version is invalid / PDFヘッダーの版が不正です",-1,-1,0,(double)hp,NULL);}eof=b2026_pdf_sc_last_find(raw,raw_n,"%%EOF");if(eof==NULL)b2026_pdf_sc_diag(&report,"ERROR","EOF_MISSING","FILE","PDF EOF marker is missing / PDFのEOFマーカーがありません",-1,-1,0,-1.0,NULL);start_status=b2026_pdf_sc_startxref(raw,raw_n,&startxref);if(start_status==0)b2026_pdf_sc_diag(&report,"ERROR","STARTXREF_MISSING","FILE","startxref is missing / startxrefがありません",-1,-1,0,-1.0,NULL);else if(start_status<0||startxref>=raw_n)b2026_pdf_sc_diag(&report,"ERROR","STARTXREF_INVALID","FILE","startxref offset is invalid / startxref位置が不正です",-1,-1,0,start_status>0?(double)startxref:-1.0,NULL);else if(!(startxref+4U<=raw_n&&memcmp(raw+startxref,"xref",4U)==0)&&!b2026_pdf_sc_at_object(raw,raw_n,startxref))b2026_pdf_sc_diag(&report,"ERROR","STARTXREF_INVALID","FILE","startxref does not point to xref data / startxrefがxrefを指していません",-1,-1,0,(double)startxref,NULL);
    xref_table=b2026_pdf_has_xref_table(raw,raw_n);xref_stream=b2026_pdf_memfind(raw,raw_n,"/Type /XRef",11U)!=NULL;if(!xref_table&&!xref_stream)b2026_pdf_sc_diag(&report,"ERROR","XREF_INVALID","XREF","xref table or stream is missing / xrefテーブルまたはストリームがありません",-1,-1,0,-1.0,NULL);else if(!quick&&start_status>0&&startxref+4U<=raw_n&&xref_table&&memcmp(raw+startxref,"xref",4U)==0)b2026_pdf_sc_check_xref_table(&report,raw,raw_n,startxref);if(!quick)b2026_pdf_sc_incremental(&report,raw,raw_n);
    encrypted=b2026_pdf_memfind(raw,raw_n,"/Encrypt",8U)!=NULL;signed_pdf=b2026_pdf_memfind(raw,raw_n,"/ByteRange",10U)!=NULL;has_form=b2026_pdf_memfind(raw,raw_n,"/AcroForm",9U)!=NULL;has_js=b2026_pdf_memfind(raw,raw_n,"/JavaScript",11U)!=NULL||b2026_pdf_memfind(raw,raw_n,"/JS",3U)!=NULL;has_attachments=b2026_pdf_memfind(raw,raw_n,"/EmbeddedFiles",14U)!=NULL;has_bookmarks=b2026_pdf_memfind(raw,raw_n,"/Outlines",9U)!=NULL;has_labels=b2026_pdf_memfind(raw,raw_n,"/PageLabels",11U)!=NULL;tagged=b2026_pdf_memfind(raw,raw_n,"/StructTreeRoot",15U)!=NULL;has_objstm=b2026_pdf_memfind(raw,raw_n,"/ObjStm",7U)!=NULL;starts=b2026_pdf_count_token(raw,raw_n,"startxref");b2026_pdf_sc_feature(&report,signed_pdf,"SIGNED","digital signature detected / 電子署名を検出しました");b2026_pdf_sc_feature(&report,has_form,"FORM_DETECTED","interactive form detected / フォームを検出しました");b2026_pdf_sc_feature(&report,has_js,"JAVASCRIPT_DETECTED","JavaScript detected / JavaScriptを検出しました");b2026_pdf_sc_feature(&report,has_attachments,"ATTACHMENT_DETECTED","attachment detected / 添付ファイルを検出しました");b2026_pdf_sc_feature(&report,has_bookmarks,"BOOKMARK_DETECTED","bookmarks detected / しおりを検出しました");b2026_pdf_sc_feature(&report,has_labels,"PAGE_LABEL_DETECTED","page labels detected / ページラベルを検出しました");b2026_pdf_sc_feature(&report,tagged,"TAGGED_PDF_DETECTED","tagged PDF structure detected / タグ付きPDF構造を検出しました");
    memset(&doc,0,sizeof(doc));rc=b2026_pdf_load(&doc,path,0);if(encrypted||rc==-2){b2026_pdf_sc_diag(&report,"WARNING","ENCRYPTED","FEATURE","encrypted PDF can only be checked partially / 暗号化PDFは部分検査のみ可能です",-1,-1,0,-1.0,NULL);report.partial=1;report.indeterminate=1;}else{if(rc<=0)b2026_pdf_sc_diag(&report,"ERROR","OBJECT_INVALID","OBJECT","indirect objects or page tree could not be parsed / 間接オブジェクトまたはページツリーを解析できません",-1,-1,0,-1.0,NULL);if(doc.object_count>0){b2026_pdf_sc_check_pages(&report,&doc);b2026_pdf_sc_check_refs(&report,&doc,0,NULL);if(!quick){b2026_pdf_sc_check_streams(&report,&doc,deep,max_stream);if(xref_stream)b2026_pdf_sc_check_xref_streams(&report,&doc,max_stream);}if(deep)b2026_pdf_sc_deep_unreachable(&report,&doc);}}
    complete=!report.partial&&rc>0&&!encrypted;valid=report.errors>0?0:(complete?1:-1);editable=report.errors==0&&!encrypted&&!signed_pdf&&!has_form&&!has_js&&!has_attachments&&!has_bookmarks&&!has_labels&&!tagged&&!report.indeterminate;b2026_pdf_sc_pair_number(target_name,"取得","ok",1.0);b2026_pdf_sc_pair_string(target_name,"状態","status",report.errors>0?"ERROR":(report.warnings>0||report.partial?"WARNING":"OK"));if(valid<0)b2026_pdf_sc_pair_number(target_name,"正常","valid",null_value());else b2026_pdf_sc_pair_number(target_name,"正常","valid",(double)valid);b2026_pdf_sc_pair_number(target_name,"完全検査","complete",complete?1.0:0.0);b2026_pdf_sc_pair_number(target_name,"一部取得","partial",report.partial?1.0:0.0);b2026_pdf_sc_pair_number(target_name,"読み取り可能","readable",rc>0?1.0:0.0);b2026_pdf_sc_pair_number(target_name,"編集可能","editable",editable?1.0:0.0);b2026_pdf_sc_pair_string(target_name,"モード","mode",mode);b2026_pdf_sc_pair_string(target_name,"パス","path",path);b2026_pdf_sc_pair_number(target_name,"ファイルサイズ","fileSize",(double)raw_n);b2026_pdf_sc_pair_string(target_name,"PDF版","version",version);b2026_pdf_sc_pair_number(target_name,"総ページ数","pages",rc>0?(double)doc.page_count:null_value());b2026_pdf_sc_pair_number(target_name,"オブジェクト数","objectCount",(double)doc.object_count);b2026_pdf_sc_pair_number(target_name,"診断数","diagnosticCount",(double)report.stored);b2026_pdf_sc_pair_number(target_name,"全診断数","totalDiagnosticCount",(double)report.total);b2026_pdf_sc_pair_number(target_name,"エラー数","errorCount",(double)report.errors);b2026_pdf_sc_pair_number(target_name,"警告数","warningCount",(double)report.warnings);b2026_pdf_sc_pair_number(target_name,"情報数","infoCount",(double)report.information);b2026_pdf_sc_pair_number(target_name,"診断省略","diagnosticsTruncated",report.total>report.stored?1.0:0.0);hash_set_number(target_name,"encrypted",encrypted?1.0:0.0);hash_set_number(target_name,"signed",signed_pdf?1.0:0.0);hash_set_number(target_name,"hasForm",has_form?1.0:0.0);hash_set_number(target_name,"hasJavaScript",has_js?1.0:0.0);hash_set_number(target_name,"hasAttachments",has_attachments?1.0:0.0);hash_set_number(target_name,"hasBookmarks",has_bookmarks?1.0:0.0);hash_set_number(target_name,"hasPageLabels",has_labels?1.0:0.0);hash_set_number(target_name,"tagged",tagged?1.0:0.0);hash_set_number(target_name,"hasObjectStreams",has_objstm?1.0:0.0);hash_set_number(target_name,"incrementalUpdates",(double)(starts>0?starts-1:0));hash_set_string(target_name,"xrefType",xref_table&&xref_stream?"HYBRID":(xref_stream?"STREAM":(xref_table?"TABLE":"NONE")));if(report.total>report.stored)b2026_pdf_log_warning("構造検査の診断件数が最大診断数を超えたため一部を省略しました。 / PDF structure diagnostics were truncated by maxDiagnostics.");b2026_pdf_document_free(&doc);free(raw);return 1;
}
