/* BASIC2026 portable PDF text extraction.  This is deliberately independent
 * of an OS PDF framework so the same BASIC program works on all targets. */
typedef struct { unsigned long code; int bytes; char *text; } B2026PdfTextMapEntry;
typedef struct {
    char name[96]; B2026PdfTextMapEntry *map; int map_count, map_capacity;
    int code_bytes; int identity; int encoding; int collection; /* collection 1 = Adobe-Japan1 */
} B2026PdfExtractFont;
typedef struct { char *text; double x,y,end_x,size; int sequence; } B2026PdfTextRun;
typedef struct { B2026PdfTextRun *items; int count,capacity; } B2026PdfTextRuns;
typedef struct {
    int coordinate_order, space, line_break; double tolerance; const char *separator;
} B2026PdfTextSettings;
typedef struct { int kind; double number; char name[96]; unsigned char *bytes; size_t length; } B2026PdfTextToken;
static int b2026_pdf_text_last_ok = 0;

static void b2026_pdf_text_extract_error(int code,const char *path,const char *detail){
    char message[1024];snprintf(message,sizeof(message),"PDF text extraction failed: path=%s: %s / 【PDFテキスト抽出エラー】%s",path!=NULL?path:"",detail!=NULL?detail:"unknown error",detail!=NULL?detail:"処理できません");
    basic_set_error_logged(code,message,"PDF.EXTRACTTEXT",current_source_path,current_line,"PDF");
    b2026_try_append_log_line("__PDF_ERROR.log",message);
}
static void b2026_pdf_text_extract_warning(const char *path,const char *detail){
    char message[1024];snprintf(message,sizeof(message),"PDF.EXTRACTTEXT warning: path=%s: %s",path!=NULL?path:"",detail!=NULL?detail:"");
    b2026_try_append_log_line("__PDF_ERROR.log",message);
}
static int b2026_pdf_text_map_add(B2026PdfExtractFont *f,unsigned long code,int bytes,const char *text){
    B2026PdfTextMapEntry *p;if(f->map_count==f->map_capacity){int cap=f->map_capacity==0?32:f->map_capacity*2;p=(B2026PdfTextMapEntry*)realloc(f->map,(size_t)cap*sizeof(*p));if(p==NULL)return 0;f->map=p;f->map_capacity=cap;}
    f->map[f->map_count].code=code;f->map[f->map_count].bytes=bytes;f->map[f->map_count].text=b2026_strdup_simple(text);if(f->map[f->map_count].text==NULL)return 0;++f->map_count;if(bytes>f->code_bytes)f->code_bytes=bytes;return 1;
}
static void b2026_pdf_text_fonts_free(B2026PdfExtractFont *fonts,int count){int i,j;for(i=0;i<count;++i){for(j=0;j<fonts[i].map_count;++j)free(fonts[i].map[j].text);free(fonts[i].map);}free(fonts);}
static int b2026_pdf_text_hex(const unsigned char *s,size_t n,unsigned char **out,size_t *outn){
    B2026PdfTextBuffer b;size_t i;int high=-1,v;memset(&b,0,sizeof(b));for(i=0;i<n;++i){if(isspace(s[i]))continue;v=b2026_pdf_hex_value(s[i]);if(v<0){free(b.data);return 0;}if(high<0)high=v;else{unsigned char c=(unsigned char)((high<<4)|v);if(!b2026_pdf_text_append_bytes(&b,(const char*)&c,1U)){free(b.data);return 0;}high=-1;}}if(high>=0){unsigned char c=(unsigned char)(high<<4);if(!b2026_pdf_text_append_bytes(&b,(const char*)&c,1U)){free(b.data);return 0;}}*out=(unsigned char*)b.data;*outn=b.used;return 1;
}
static char *b2026_pdf_text_utf16_hex(const unsigned char *s,size_t n){unsigned char *bytes=NULL;size_t length=0U;char *r;if(!b2026_pdf_text_hex(s,n,&bytes,&length))return NULL;if(length>=2U&&bytes[0]==0xFEU&&bytes[1]==0xFFU)r=b2026_pdf_decode_text_bytes(bytes,length);else{B2026PdfTextBuffer b;size_t i=0U;memset(&b,0,sizeof(b));while(i+1U<length){unsigned int cp=((unsigned int)bytes[i]<<8)|bytes[i+1U];i+=2U;if(cp>=0xD800U&&cp<=0xDBFFU&&i+1U<length){unsigned int lo=((unsigned int)bytes[i]<<8)|bytes[i+1U];if(lo>=0xDC00U&&lo<=0xDFFFU){i+=2U;cp=0x10000U+((cp-0xD800U)<<10)+(lo-0xDC00U);}else cp=0xFFFDU;}if(!b2026_pdf_text_append_codepoint(&b,cp)){free(b.data);free(bytes);return NULL;}}r=b2026_pdf_text_take(&b);}free(bytes);return r;}
static int b2026_pdf_text_next_hex(const unsigned char *s,size_t n,size_t *p,size_t *start,size_t *len){while(*p<n&&s[*p]!='<')++*p;if(*p>=n)return 0;++*p;*start=*p;while(*p<n&&s[*p]!='>')++*p;if(*p>=n)return 0;*len=*p-*start;++*p;return 1;}
static int b2026_pdf_text_parse_cmap(B2026PdfExtractFont *font,const unsigned char *s,size_t n){
    size_t p=0U,a,an,b,bn;int mode=0,remaining=0;char word[64];
    while(p<n){size_t q;while(p<n&&isspace(s[p]))++p;if(p>=n)break;if(isdigit(s[p])){char *e;long v=strtol((const char*)s+p,&e,10);q=(size_t)(e-(char*)s);while(q<n&&isspace(s[q]))++q;if(q<n){size_t w=0U;while(q<n&&!isspace(s[q])&&w+1U<sizeof(word))word[w++]=(char)s[q++];word[w]='\0';if(strcmp(word,"beginbfchar")==0){mode=1;remaining=(int)v;p=q;continue;}if(strcmp(word,"beginbfrange")==0){mode=2;remaining=(int)v;p=q;continue;}if(strcmp(word,"begincodespacerange")==0){mode=3;remaining=(int)v;p=q;continue;}}}
        if(remaining<=0){++p;continue;}if(!b2026_pdf_text_next_hex(s,n,&p,&a,&an)||!b2026_pdf_text_next_hex(s,n,&p,&b,&bn))return 0;
        if(mode==3){font->code_bytes=(int)(an/2U);--remaining;continue;}
        {unsigned char *src=NULL;size_t srcn=0U;unsigned long first=0UL,last=0UL;char *dest=NULL;size_t i;if(!b2026_pdf_text_hex(s+a,an,&src,&srcn)){free(src);return 0;}for(i=0;i<srcn;++i)first=(first<<8)|src[i];free(src);if(mode==1){dest=b2026_pdf_text_utf16_hex(s+b,bn);if(dest==NULL||!b2026_pdf_text_map_add(font,first,(int)srcn,dest)){free(dest);return 0;}free(dest);}else{unsigned char *endb=NULL;size_t endn=0U;unsigned long range_first=first;if(!b2026_pdf_text_hex(s+b,bn,&endb,&endn)){free(endb);return 0;}for(i=0;i<endn;++i)last=(last<<8)|endb[i];free(endb);while(p<n&&isspace(s[p]))++p;if(p<n&&s[p]=='['){++p;for(;first<=last;++first){if(!b2026_pdf_text_next_hex(s,n,&p,&b,&bn))return 0;dest=b2026_pdf_text_utf16_hex(s+b,bn);if(dest==NULL||!b2026_pdf_text_map_add(font,first,(int)srcn,dest)){free(dest);return 0;}free(dest);}while(p<n&&s[p]!=']')++p;if(p<n)++p;}else{/* A bfrange has three operands: source start, source end, and Unicode start. */if(!b2026_pdf_text_next_hex(s,n,&p,&b,&bn))return 0;dest=b2026_pdf_text_utf16_hex(s+b,bn);if(dest==NULL)return 0;/* Sequential ranges are overwhelmingly single UTF-16 code units. */if(strlen(dest)>0U){unsigned char *db=NULL;size_t dbn=0U;if(!b2026_pdf_text_hex(s+b,bn,&db,&dbn)){free(dest);return 0;}for(;first<=last;++first){B2026PdfTextBuffer u;size_t k;unsigned long delta=first-range_first;memset(&u,0,sizeof(u));if(dbn>=2U){unsigned long cp=0UL;for(k=0;k<dbn;++k)cp=(cp<<8)|db[k];cp+=delta;if(!b2026_pdf_text_append_codepoint(&u,(unsigned int)cp)){free(u.data);free(db);free(dest);return 0;}}if(!b2026_pdf_text_map_add(font,first,(int)srcn,u.data!=NULL?u.data:dest)){free(u.data);free(db);free(dest);return 0;}free(u.data);}free(db);}free(dest);}}
        }--remaining;
    }return 1;
}
static B2026PdfExtractFont *b2026_pdf_text_font_find(B2026PdfExtractFont *fonts,int count,const char *name){int i;for(i=0;i<count;++i)if(strcmp(fonts[i].name,name)==0)return &fonts[i];return NULL;}
static int b2026_pdf_text_first_ref(const unsigned char *s,size_t n,int *number,int *generation){size_t p=0U;while(p<n){char *e;long a,g;size_t q,u;if(!isdigit(s[p])){++p;continue;}a=strtol((const char*)s+p,&e,10);if(e==(char*)s+p||a<=0||a>INT_MAX){++p;continue;}q=b2026_pdf_tf_space(s,n,(size_t)(e-(char*)s));if(q>=n||!isdigit(s[q])){++p;continue;}g=strtol((const char*)s+q,&e,10);if(e==(char*)s+q||g<0||g>INT_MAX){++p;continue;}u=b2026_pdf_tf_space(s,n,(size_t)(e-(char*)s));if(u<n&&s[u]=='R'){*number=(int)a;*generation=(int)g;return 1;}++p;}return 0;}
static int b2026_pdf_text_fonts(B2026PdfDocument *doc,int page,B2026PdfExtractFont **out,int *out_count){
    const unsigned char *rv,*v;size_t rn,p=0U,n;int num,gen,oi;B2026PdfExtractFont *fonts=NULL;int count=0,cap=0;
    *out=NULL;*out_count=0;if(!b2026_pdf_tf_inherited_value(doc,page,"/Resources",&rv,&rn))return 1;if(b2026_pdf_tf_ref(rv,rn,&num,&gen)&&(oi=b2026_pdf_object_lookup(doc,num,gen))>=0){rv=doc->objects[oi].body;rn=doc->objects[oi].body_length;}
    {B2026PdfKeyRange fr;if(!b2026_pdf_tf_find_key(rv,rn,"/Font",&fr)||!fr.found)return 1;v=rv+fr.value_start;n=fr.value_end-fr.value_start;if(b2026_pdf_tf_ref(v,n,&num,&gen)&&(oi=b2026_pdf_object_lookup(doc,num,gen))>=0){v=doc->objects[oi].body;n=doc->objects[oi].body_length;}}
    while(p<n){size_t ns,ne,q,u;char *e;long a,g;while(p<n&&v[p]!='/')++p;if(p>=n)break;ns=++p;while(p<n&&!b2026_pdf_is_delim(v[p]))++p;ne=p;q=b2026_pdf_tf_space(v,n,p);if(q>=n||!isdigit(v[q]))continue;a=strtol((const char*)v+q,&e,10);q=b2026_pdf_tf_space(v,n,(size_t)(e-(char*)v));if(q>=n||!isdigit(v[q]))continue;g=strtol((const char*)v+q,&e,10);u=b2026_pdf_tf_space(v,n,(size_t)(e-(char*)v));if(u>=n||v[u]!='R'||a<=0||a>INT_MAX||g<0||g>INT_MAX||(oi=b2026_pdf_object_lookup(doc,(int)a,(int)g))<0){p=ne;continue;}if(count==cap){int nc=cap==0?8:cap*2;B2026PdfExtractFont *nf=(B2026PdfExtractFont*)realloc(fonts,(size_t)nc*sizeof(*nf));if(nf==NULL){b2026_pdf_text_fonts_free(fonts,count);return 0;}fonts=nf;cap=nc;}memset(&fonts[count],0,sizeof(fonts[count]));if(ne-ns>=sizeof(fonts[count].name))ne=ns+sizeof(fonts[count].name)-1U;memcpy(fonts[count].name,v+ns,ne-ns);fonts[count].name[ne-ns]='\0';fonts[count].code_bytes=1;
        {B2026PdfObject *fo=&doc->objects[oi];B2026PdfKeyRange tr,dr;if(b2026_pdf_memfind(fo->body,fo->body_length,"/Encoding /Identity",19U)!=NULL){fonts[count].identity=1;fonts[count].code_bytes=2;}else if(b2026_pdf_memfind(fo->body,fo->body_length,"/WinAnsiEncoding",16U)!=NULL)fonts[count].encoding=1;else if(b2026_pdf_memfind(fo->body,fo->body_length,"/MacRomanEncoding",17U)!=NULL)fonts[count].encoding=2;if(b2026_pdf_tf_find_key(fo->body,fo->body_length,"/DescendantFonts",&dr)&&dr.found&&b2026_pdf_text_first_ref(fo->body+dr.value_start,dr.value_end-dr.value_start,&num,&gen)){int di=b2026_pdf_object_lookup(doc,num,gen);if(di>=0&&b2026_pdf_memfind(doc->objects[di].body,doc->objects[di].body_length,"Japan1",6U)!=NULL)fonts[count].collection=1;}if(b2026_pdf_tf_find_key(fo->body,fo->body_length,"/ToUnicode",&tr)&&tr.found&&b2026_pdf_tf_ref(fo->body+tr.value_start,tr.value_end-tr.value_start,&num,&gen)&&(oi=b2026_pdf_object_lookup(doc,num,gen))>=0){unsigned char *cm=NULL;size_t cmn=0U;if(b2026_pdf_tf_stream_bytes(&doc->objects[oi],&cm,&cmn)){if(!b2026_pdf_text_parse_cmap(&fonts[count],cm,cmn)){free(cm);b2026_pdf_text_fonts_free(fonts,count+1);return 0;}free(cm);}}}
        ++count;p=u+1U;
    }*out=fonts;*out_count=count;return 1;
}
static unsigned int b2026_pdf_text_encoding_codepoint(int encoding,unsigned int c){
    static const unsigned short win[32]={0x20AC,0xFFFD,0x201A,0x0192,0x201E,0x2026,0x2020,0x2021,0x02C6,0x2030,0x0160,0x2039,0x0152,0xFFFD,0x017D,0xFFFD,0xFFFD,0x2018,0x2019,0x201C,0x201D,0x2022,0x2013,0x2014,0x02DC,0x2122,0x0161,0x203A,0x0153,0xFFFD,0x017E,0x0178};
    static const unsigned short mac[128]={0x00C4,0x00C5,0x00C7,0x00C9,0x00D1,0x00D6,0x00DC,0x00E1,0x00E0,0x00E2,0x00E4,0x00E3,0x00E5,0x00E7,0x00E9,0x00E8,0x00EA,0x00EB,0x00ED,0x00EC,0x00EE,0x00EF,0x00F1,0x00F3,0x00F2,0x00F4,0x00F6,0x00F5,0x00FA,0x00F9,0x00FB,0x00FC,0x2020,0x00B0,0x00A2,0x00A3,0x00A7,0x2022,0x00B6,0x00DF,0x00AE,0x00A9,0x2122,0x00B4,0x00A8,0x2260,0x00C6,0x00D8,0x221E,0x00B1,0x2264,0x2265,0x00A5,0x00B5,0x2202,0x2211,0x220F,0x03C0,0x222B,0x00AA,0x00BA,0x03A9,0x00E6,0x00F8,0x00BF,0x00A1,0x00AC,0x221A,0x0192,0x2248,0x2206,0x00AB,0x00BB,0x2026,0x00A0,0x00C0,0x00C3,0x00D5,0x0152,0x0153,0x2013,0x2014,0x201C,0x201D,0x2018,0x2019,0x00F7,0x25CA,0x00FF,0x0178,0x2044,0x20AC,0x2039,0x203A,0xFB01,0xFB02,0x2021,0x00B7,0x201A,0x201E,0x2030,0x00C2,0x00CA,0x00C1,0x00CB,0x00C8,0x00CD,0x00CE,0x00CF,0x00CC,0x00D3,0x00D4,0xF8FF,0x00D2,0x00DA,0x00DB,0x00D9,0x0131,0x02C6,0x02DC,0x00AF,0x02D8,0x02D9,0x02DA,0x00B8,0x02DD,0x02DB,0x02C7};
    if(encoding==1&&c>=0x80U&&c<=0x9FU)return win[c-0x80U];
    if(encoding==2&&c>=0x80U)return mac[c-0x80U];
    return b2026_pdf_doc_codepoint(c);
}
static char *b2026_pdf_text_decode(B2026PdfExtractFont *font,const unsigned char *bytes,size_t n,int *replacement){
    B2026PdfTextBuffer out;size_t p=0U;memset(&out,0,sizeof(out));while(p<n){int width=font!=NULL?font->code_bytes:1,j,found=0;unsigned long code=0UL;if(width<1)width=1;if(p+(size_t)width>n)width=1;for(j=0;j<width;++j)code=(code<<8)|bytes[p+(size_t)j];if(font!=NULL)for(j=0;j<font->map_count;++j)if(font->map[j].code==code&&font->map[j].bytes==width){if(!b2026_pdf_text_append_bytes(&out,font->map[j].text,strlen(font->map[j].text)))goto fail;found=1;break;}if(!found){unsigned int cp=0U;if(width==1)cp=b2026_pdf_text_encoding_codepoint(font!=NULL?font->encoding:0,(unsigned int)code);else if(font!=NULL&&font->collection==1)cp=b2026_pdf_japan1_unicode((unsigned int)code);if(cp!=0U){if(!b2026_pdf_text_append_codepoint(&out,cp))goto fail;}else{if(!b2026_pdf_text_append_codepoint(&out,0xFFFDU))goto fail;*replacement=1;}}p+=(size_t)width;}return b2026_pdf_text_take(&out);fail:free(out.data);return NULL;
}
static void b2026_pdf_text_token_free(B2026PdfTextToken *t){free(t->bytes);memset(t,0,sizeof(*t));}
static double b2026_pdf_text_em_width(const char *s){
    const unsigned char *p=(const unsigned char*)s;double width=0.0;while(*p){unsigned int cp;int n;if(*p<0x80U){cp=*p;n=1;}else if((*p&0xE0U)==0xC0U&&p[1]!=0U){cp=((unsigned int)(p[0]&0x1FU)<<6)|(unsigned int)(p[1]&0x3FU);n=2;}else if((*p&0xF0U)==0xE0U&&p[1]!=0U&&p[2]!=0U){cp=((unsigned int)(p[0]&0x0FU)<<12)|((unsigned int)(p[1]&0x3FU)<<6)|(unsigned int)(p[2]&0x3FU);n=3;}else if((*p&0xF8U)==0xF0U&&p[1]!=0U&&p[2]!=0U&&p[3]!=0U){cp=((unsigned int)(p[0]&7U)<<18)|((unsigned int)(p[1]&0x3FU)<<12)|((unsigned int)(p[2]&0x3FU)<<6)|(unsigned int)(p[3]&0x3FU);n=4;}else{cp=0xFFFDU;n=1;}if((cp>=0x0300U&&cp<=0x036FU)||(cp>=0x1AB0U&&cp<=0x1AFFU)||(cp>=0xFE00U&&cp<=0xFE0FU)){}else if(cp==0x20U)width+=0.28;else if(cp<0x02E0U||(cp>=0xFF61U&&cp<=0xFF9FU))width+=0.52;else width+=1.0;p+=(size_t)n;}return width;
}
static int b2026_pdf_text_literal(const unsigned char *s,size_t n,size_t *p,unsigned char **out,size_t *outn){B2026PdfTextBuffer b;int depth=1;memset(&b,0,sizeof(b));++*p;while(*p<n&&depth>0){unsigned char c=s[(*p)++];if(c=='\\'&&*p<n){c=s[(*p)++];if(c=='n')c='\n';else if(c=='r')c='\r';else if(c=='t')c='\t';else if(c=='b')c='\b';else if(c=='f')c='\f';else if(c=='\r'||c=='\n'){if(c=='\r'&&*p<n&&s[*p]=='\n')++*p;continue;}else if(c>='0'&&c<='7'){int val=c-'0',k;for(k=0;k<2&&*p<n&&s[*p]>='0'&&s[*p]<='7';++k)val=val*8+(s[(*p)++]-'0');c=(unsigned char)val;}if(!b2026_pdf_text_append_bytes(&b,(const char*)&c,1U))goto fail;}else if(c=='('){++depth;if(!b2026_pdf_text_append_char(&b,'('))goto fail;}else if(c==')'){--depth;if(depth>0&&!b2026_pdf_text_append_char(&b,')'))goto fail;}else if(!b2026_pdf_text_append_bytes(&b,(const char*)&c,1U))goto fail;}if(depth!=0)goto fail;*out=(unsigned char*)b.data;*outn=b.used;return 1;fail:free(b.data);return 0;}
static int b2026_pdf_text_token(const unsigned char *s,size_t n,size_t *p,B2026PdfTextToken *t){size_t start;char *e;memset(t,0,sizeof(*t));while(*p<n&&(isspace(s[*p])||s[*p]=='%')){if(s[*p]=='%')while(*p<n&&s[*p]!='\r'&&s[*p]!='\n')++*p;else ++*p;}if(*p>=n)return 0;if(s[*p]=='('){t->kind=3;return b2026_pdf_text_literal(s,n,p,&t->bytes,&t->length);}if(s[*p]=='<'&&*p+1U<n&&s[*p+1U]!='<'){size_t a=++*p;while(*p<n&&s[*p]!='>')++*p;if(*p>=n)return -1;t->kind=3;if(!b2026_pdf_text_hex(s+a,*p-a,&t->bytes,&t->length))return -1;++*p;return 1;}if(s[*p]=='['){int depth=1;start=(*p)++;while(*p<n&&depth){if(s[*p]=='('){unsigned char *dummy=NULL;size_t dn=0U;if(!b2026_pdf_text_literal(s,n,p,&dummy,&dn)){free(dummy);return -1;}free(dummy);continue;}if(s[*p]=='[')++depth;else if(s[*p]==']')--depth;++*p;}if(depth)return -1;t->kind=4;t->length=*p-start;t->bytes=(unsigned char*)malloc(t->length);if(t->bytes==NULL)return -1;memcpy(t->bytes,s+start,t->length);return 1;}if(s[*p]=='/'){start=++*p;while(*p<n&&!b2026_pdf_is_delim(s[*p]))++*p;t->kind=2;if(*p-start>=sizeof(t->name))return -1;memcpy(t->name,s+start,*p-start);t->name[*p-start]='\0';return 1;}if(isdigit(s[*p])||s[*p]=='-'||s[*p]=='+'||s[*p]=='.'){errno=0;t->number=strtod((const char*)s+*p,&e);if(e!=(char*)s+*p&&errno!=ERANGE&&isfinite(t->number)){*p=(size_t)(e-(char*)s);t->kind=1;return 1;}}start=*p;while(*p<n&&!b2026_pdf_is_delim(s[*p]))++*p;if(*p==start)++*p;if(*p-start>=sizeof(t->name))return -1;t->kind=5;memcpy(t->name,s+start,*p-start);t->name[*p-start]='\0';return 1;}
static int b2026_pdf_text_run_add(B2026PdfTextRuns *runs,char *text,double x,double y,double size,int sequence){B2026PdfTextRun *p;if(text==NULL)return 0;if(*text=='\0'){free(text);return 1;}if(runs->count==runs->capacity){int cap=runs->capacity==0?32:runs->capacity*2;p=(B2026PdfTextRun*)realloc(runs->items,(size_t)cap*sizeof(*p));if(p==NULL){free(text);return 0;}runs->items=p;runs->capacity=cap;}runs->items[runs->count].text=text;runs->items[runs->count].x=x;runs->items[runs->count].y=y;runs->items[runs->count].size=size;runs->items[runs->count].end_x=x+b2026_pdf_text_em_width(text)*size;runs->items[runs->count].sequence=sequence;++runs->count;return 1;}
static int b2026_pdf_text_show(B2026PdfTextRuns *runs,B2026PdfExtractFont *font,const unsigned char *bytes,size_t n,double x,double y,double size,const double ctm[6],int *sequence,int *replacement){double tx=ctm[0]*x+ctm[2]*y+ctm[4],ty=ctm[1]*x+ctm[3]*y+ctm[5],scale=hypot(ctm[0],ctm[1]);char *text=b2026_pdf_text_decode(font,bytes,n,replacement);if(scale<=0.0)scale=1.0;return text!=NULL&&b2026_pdf_text_run_add(runs,text,tx,ty,size*scale,(*sequence)++);}
static int b2026_pdf_text_content(B2026PdfTextRuns *runs,B2026PdfExtractFont *fonts,int font_count,const unsigned char *s,size_t n,int *replacement){
    B2026PdfTextToken stack[32],t;double ctm[6]={1,0,0,1,0,0},saved[16][6];int sp=0,rc,sequence=runs->count,i,saved_count=0;size_t p=0U;double x=0,y=0,leading=0,size=12,text_scale=1.0;B2026PdfExtractFont *font=NULL;for(i=0;i<32;++i)memset(&stack[i],0,sizeof(stack[i]));
    while((rc=b2026_pdf_text_token(s,n,&p,&t))>0){if(t.kind!=5){if(sp==32){b2026_pdf_text_token_free(&t);goto fail;}stack[sp++]=t;continue;}
        if(strcmp(t.name,"q")==0){if(saved_count<16){memcpy(saved[saved_count],ctm,sizeof(ctm));++saved_count;}}
        else if(strcmp(t.name,"Q")==0){if(saved_count>0){--saved_count;memcpy(ctm,saved[saved_count],sizeof(ctm));}}
        else if(strcmp(t.name,"cm")==0&&sp>=6){double m[6],old[6];for(i=0;i<6;++i)m[i]=stack[sp-6+i].number;memcpy(old,ctm,sizeof(old));ctm[0]=old[0]*m[0]+old[2]*m[1];ctm[1]=old[1]*m[0]+old[3]*m[1];ctm[2]=old[0]*m[2]+old[2]*m[3];ctm[3]=old[1]*m[2]+old[3]*m[3];ctm[4]=old[0]*m[4]+old[2]*m[5]+old[4];ctm[5]=old[1]*m[4]+old[3]*m[5]+old[5];}
        else if(strcmp(t.name,"BT")==0){x=y=0.0;text_scale=1.0;}
        else if(strcmp(t.name,"Tf")==0&&sp>=2&&stack[sp-2].kind==2&&stack[sp-1].kind==1){font=b2026_pdf_text_font_find(fonts,font_count,stack[sp-2].name);size=stack[sp-1].number;}
        else if(strcmp(t.name,"Tm")==0&&sp>=6){x=stack[sp-2].number;y=stack[sp-1].number;text_scale=hypot(stack[sp-6].number,stack[sp-5].number);if(text_scale<=0.0)text_scale=1.0;}
        else if((strcmp(t.name,"Td")==0||strcmp(t.name,"TD")==0)&&sp>=2){x+=stack[sp-2].number;y+=stack[sp-1].number;if(strcmp(t.name,"TD")==0)leading=-stack[sp-1].number;}
        else if(strcmp(t.name,"TL")==0&&sp>=1)leading=stack[sp-1].number;
        else if(strcmp(t.name,"T*")==0)y-=leading;
        else if(strcmp(t.name,"Tj")==0&&sp>=1&&stack[sp-1].kind==3){if(!b2026_pdf_text_show(runs,font,stack[sp-1].bytes,stack[sp-1].length,x,y,size*text_scale,ctm,&sequence,replacement))goto fail;}
        else if(strcmp(t.name,"'")==0&&sp>=1&&stack[sp-1].kind==3){y-=leading;if(!b2026_pdf_text_show(runs,font,stack[sp-1].bytes,stack[sp-1].length,x,y,size*text_scale,ctm,&sequence,replacement))goto fail;}
        else if(strcmp(t.name,"\"")==0&&sp>=3&&stack[sp-1].kind==3){y-=leading;if(!b2026_pdf_text_show(runs,font,stack[sp-1].bytes,stack[sp-1].length,x,y,size*text_scale,ctm,&sequence,replacement))goto fail;}
        else if(strcmp(t.name,"TJ")==0&&sp>=1&&stack[sp-1].kind==4){size_t ap=1U;B2026PdfTextToken at;B2026PdfTextBuffer joined;double tx,ty,scale;memset(&joined,0,sizeof(joined));while(ap+1U<stack[sp-1].length&&(rc=b2026_pdf_text_token(stack[sp-1].bytes,stack[sp-1].length-1U,&ap,&at))>0){if(at.kind==3){char *part=b2026_pdf_text_decode(font,at.bytes,at.length,replacement);if(part==NULL){b2026_pdf_text_token_free(&at);free(joined.data);goto fail;}if(!b2026_pdf_text_append_bytes(&joined,part,strlen(part))){free(part);b2026_pdf_text_token_free(&at);free(joined.data);goto fail;}free(part);}else if(at.kind==1&&at.number<-200.0&&joined.used>0U&&joined.data[joined.used-1U]!=' '){if(!b2026_pdf_text_append_char(&joined,' ')){b2026_pdf_text_token_free(&at);free(joined.data);goto fail;}}b2026_pdf_text_token_free(&at);}tx=ctm[0]*x+ctm[2]*y+ctm[4];ty=ctm[1]*x+ctm[3]*y+ctm[5];scale=hypot(ctm[0],ctm[1]);if(scale<=0.0)scale=1.0;if(!b2026_pdf_text_run_add(runs,b2026_pdf_text_take(&joined),tx,ty,size*text_scale*scale,sequence++))goto fail;}
        for(i=0;i<sp;++i)b2026_pdf_text_token_free(&stack[i]);sp=0;b2026_pdf_text_token_free(&t);
    }if(rc<0)goto fail;for(i=0;i<sp;++i)b2026_pdf_text_token_free(&stack[i]);return 1;fail:for(i=0;i<sp;++i)b2026_pdf_text_token_free(&stack[i]);return 0;
}
static void b2026_pdf_text_display(B2026PdfTextRun *r,double box[4],int rotation){double x=r->x,y=r->y,span=r->end_x-r->x,w=box[2]-box[0],h=box[3]-box[1],nx,ny;rotation=((rotation%360)+360)%360;if(rotation==90){nx=y-box[1];ny=x-box[0];}else if(rotation==180){nx=w-(x-box[0]);ny=y-box[1];}else if(rotation==270){nx=h-(y-box[1]);ny=w-(x-box[0]);}else{nx=x-box[0];ny=h-(y-box[1]);}r->x=nx;r->y=ny;r->end_x=nx+span;}
static void b2026_pdf_text_sort(B2026PdfTextRuns *runs,double tolerance){int i,j;for(i=1;i<runs->count;++i){B2026PdfTextRun key=runs->items[i];j=i-1;while(j>=0&&(runs->items[j].y>key.y+tolerance||(fabs(runs->items[j].y-key.y)<=tolerance&&runs->items[j].x>key.x))){runs->items[j+1]=runs->items[j];--j;}runs->items[j+1]=key;}}
static char *b2026_pdf_text_join_runs(B2026PdfTextRuns *runs,const B2026PdfTextSettings *settings){B2026PdfTextBuffer out;int i;double previous_y=0,previous_end=0;memset(&out,0,sizeof(out));for(i=0;i<runs->count;++i){B2026PdfTextRun *r=&runs->items[i];if(i>0){int new_line=settings->coordinate_order?fabs(r->y-previous_y)>settings->tolerance:(r->y!=previous_y);if(new_line){if(settings->line_break&&!b2026_pdf_text_append_char(&out,'\n'))goto fail;}else if(settings->space&&r->x-previous_end>(r->size*0.20)&&out.used>0U&&out.data[out.used-1U]!=' '){if(!b2026_pdf_text_append_char(&out,' '))goto fail;}}if(!b2026_pdf_text_append_bytes(&out,r->text,strlen(r->text)))goto fail;previous_y=r->y;previous_end=r->end_x;}return b2026_pdf_text_take(&out);fail:free(out.data);return NULL;}
static void b2026_pdf_text_runs_free(B2026PdfTextRuns *runs){int i;for(i=0;i<runs->count;++i)free(runs->items[i].text);free(runs->items);memset(runs,0,sizeof(*runs));}
static int b2026_pdf_text_settings(const B2026PdfArg *arg,B2026PdfTextSettings *s){HashVariable *h;HashEntry *e;const char *v;s->coordinate_order=1;s->space=1;s->line_break=1;s->tolerance=-1.0;s->separator="\f";if(arg==NULL)return 1;if(arg->kind!=3||arg->hash_name==NULL)return 0;h=find_local_hash(get_hash_frame(arg->hash_name),arg->hash_name);if(h==NULL)return 0;e=b2026_pdf_tf_entry(h,"順序","order");if(e!=NULL){if(e->value.type==TYPE_NUMBER)return 0;v=hash_value_as_text(&e->value);if(strcmp(v,"座標")==0||b2026_strcasecmp(v,"COORDINATE")==0)s->coordinate_order=1;else if(strcmp(v,"記録順")==0||b2026_strcasecmp(v,"CONTENT")==0)s->coordinate_order=0;else return 0;}e=b2026_pdf_tf_entry(h,"ページ区切り","pageSeparator");if(e!=NULL){if(e->value.type==TYPE_NUMBER)return 0;s->separator=hash_value_as_text(&e->value);}e=b2026_pdf_tf_entry(h,"空白補完","space");if(e!=NULL){if(e->value.type!=TYPE_NUMBER)return 0;s->space=e->value.val.number!=0.0;}e=b2026_pdf_tf_entry(h,"改行補完","lineBreak");if(e!=NULL){if(e->value.type!=TYPE_NUMBER)return 0;s->line_break=e->value.val.number!=0.0;}e=b2026_pdf_tf_entry(h,"行許容差","lineTolerance");if(e!=NULL){if(e->value.type==TYPE_NUMBER){if(!isfinite(e->value.val.number)||e->value.val.number<0.0)return 0;s->tolerance=e->value.val.number;}else if(b2026_strcasecmp(hash_value_as_text(&e->value),"AUTO")!=0)return 0;}return 1;}
/* partial is a bit mask shared by every page in one PDF operation:
 * bit 0: at least one unmapped character was replaced with U+FFFD
 * bit 1: at least one page used unsupported content/font mapping
 * This preserves the public truth-value meaning while logging each warning
 * category only once per PDF operation instead of once per affected page. */
static char *b2026_pdf_text_page(B2026PdfDocument *doc,int page,const B2026PdfTextSettings *settings,const char *path,int *partial){int oi=doc->page_objects[page],rotation,replacement=0,i;const unsigned char *cv;size_t cn;B2026PdfTextBuffer content;B2026PdfExtractFont *fonts=NULL;int font_count=0;B2026PdfTextRuns runs;double box[4],tol;char *result=NULL;memset(&content,0,sizeof(content));memset(&runs,0,sizeof(runs));if(!b2026_pdf_text_fonts(doc,oi,&fonts,&font_count))goto fail;if(!b2026_pdf_tf_inherited_value(doc,oi,"/Contents",&cv,&cn)){result=b2026_strdup_simple("");goto done;}if(!b2026_pdf_tf_collect_content(doc,cv,cn,&content,0)){/* An empty content stream is a valid empty page. */result=b2026_strdup_simple("");goto done;}if(!b2026_pdf_text_content(&runs,fonts,font_count,(const unsigned char*)content.data,content.used,&replacement))goto fail;if(!b2026_pdf_page_box(doc,oi,"/CropBox",box)&&!b2026_pdf_page_box(doc,oi,"/MediaBox",box)){box[0]=box[1]=0;box[2]=612;box[3]=792;}rotation=b2026_pdf_page_inherited_integer(doc,oi,"/Rotate",0);for(i=0;i<runs.count;++i)b2026_pdf_text_display(&runs.items[i],box,rotation);tol=settings->tolerance>=0.0?settings->tolerance:((runs.count>0?runs.items[0].size:12.0)*0.35);if(tol<0.5)tol=0.5;if(settings->coordinate_order)b2026_pdf_text_sort(&runs,tol);{B2026PdfTextSettings local=*settings;local.tolerance=tol;result=b2026_pdf_text_join_runs(&runs,&local);}if(replacement){if((*partial&1)==0)b2026_pdf_text_extract_warning(path,"one or more character codes could not be mapped and were replaced with U+FFFD / 一部の文字を復号できないためU+FFFDへ置換しました");*partial|=1;}goto done;fail:if((*partial&2)==0)b2026_pdf_text_extract_warning(path,"page content or font mapping is unsupported; partial extraction continues / 未対応のページ内容またはフォント対応表があるため部分抽出を継続します");*partial|=2;result=b2026_strdup_simple("");done:free(content.data);b2026_pdf_text_fonts_free(fonts,font_count);b2026_pdf_text_runs_free(&runs);return result;}
static int b2026_pdf_text_prepare(int argc,B2026PdfArg *args,const char **path,int *single_page,B2026PdfTextSettings *settings){const B2026PdfArg *setting=NULL;*single_page=-1;if(argc<1||argc>3||args[0].kind!=1||args[0].text==NULL)return 0;*path=args[0].text;if(argc>=2){if(args[argc-1].kind==3)setting=&args[argc-1];if(args[1].kind!=3){if(args[1].kind!=0||!isfinite(args[1].number)||floor(args[1].number)!=args[1].number||args[1].number<(double)INT_MIN||args[1].number>(double)INT_MAX)return 0;*single_page=(int)args[1].number;}}if(argc==3&&(args[1].kind==3||args[2].kind!=3))return 0;return b2026_pdf_text_settings(setting,settings);}
static const char *b2026_pdf_extract_text_string(int argc,B2026PdfArg *args){B2026PdfDocument doc;B2026PdfTextSettings settings;const char *path;int page=-1,first,last,i,rc,partial=0;char **texts=NULL;B2026PdfTextBuffer out;b2026_pdf_text_last_ok=0;memset(&doc,0,sizeof(doc));memset(&out,0,sizeof(out));if(!b2026_pdf_text_prepare(argc,args,&path,&page,&settings)){b2026_pdf_text_extract_error(4201,"","invalid parameters / パラメーターが正しくありません");return runtime_temp_string_copy("");}rc=b2026_pdf_load(&doc,path,0);if(rc<=0){b2026_pdf_text_extract_error(4202,path,b2026_pdf_load_error(rc));b2026_pdf_document_free(&doc);return runtime_temp_string_copy("");}if(page>=0){int adjusted=page;if(adjusted<1)adjusted=1;if(adjusted>doc.page_count)adjusted=doc.page_count;if(adjusted!=page)b2026_pdf_log_warning("ページ番号を有効な範囲へ調整しました。 / Page number was adjusted.");first=adjusted-1;last=adjusted;}else{first=0;last=doc.page_count;}texts=(char**)calloc((size_t)(last-first),sizeof(*texts));if(texts==NULL)goto memory_fail;for(i=first;i<last;++i){texts[i-first]=b2026_pdf_text_page(&doc,i,&settings,path,&partial);if(texts[i-first]==NULL)goto memory_fail;}for(i=0;i<last-first;++i){if(i>0&&!b2026_pdf_text_append_bytes(&out,settings.separator,strlen(settings.separator)))goto memory_fail;if(!b2026_pdf_text_append_bytes(&out,texts[i],strlen(texts[i])))goto memory_fail;}for(i=0;i<last-first;++i)free(texts[i]);free(texts);b2026_pdf_document_free(&doc);{char *owned=b2026_pdf_text_take(&out);const char *r=runtime_temp_string_copy(owned!=NULL?owned:"");free(owned);b2026_pdf_text_last_ok=1;return r;}memory_fail:if(texts!=NULL)for(i=0;i<last-first;++i)free(texts[i]);free(texts);free(out.data);b2026_pdf_document_free(&doc);b2026_pdf_text_extract_error(4203,path,"memory or output limit exceeded; destination was not changed / メモリまたは出力上限を超えました。代入先は変更していません");return runtime_temp_string_copy("");}
static int b2026_pdf_extract_text_array(const char *target,int argc,B2026PdfArg *args){B2026PdfDocument doc;B2026PdfTextSettings settings;const char *path;int page=-1,i,rc,partial=0;char **texts=NULL;Frame *frame;ArrayVariable *old,*result=NULL;if(target!=NULL&&!b2026_pdf_split_array_commit(target,NULL,0)){b2026_pdf_text_extract_error(4203,"","out of memory while initializing the result array / 結果配列の初期化に必要なメモリを確保できません");return 0;}if(!b2026_pdf_text_prepare(argc,args,&path,&page,&settings)||page>=0){b2026_pdf_text_extract_error(4201,"","array extraction accepts path and optional settings only / 配列抽出ではパスと設定だけを指定してください");return 0;}memset(&doc,0,sizeof(doc));rc=b2026_pdf_load(&doc,path,0);if(rc<=0){b2026_pdf_text_extract_error(4202,path,b2026_pdf_load_error(rc));b2026_pdf_document_free(&doc);return 0;}texts=(char**)calloc((size_t)doc.page_count,sizeof(*texts));if(texts==NULL)goto fail;for(i=0;i<doc.page_count;++i){texts[i]=b2026_pdf_text_page(&doc,i,&settings,path,&partial);if(texts[i]==NULL)goto fail;}frame=get_array_frame(target);if(frame==NULL)goto fail;result=(ArrayVariable*)calloc(1,sizeof(*result));if(result==NULL)goto fail;{size_t slots=(size_t)doc.page_count+(size_t)option_base;if(slots==0U||slots>SIZE_MAX/sizeof(B2026_Var))goto fail;result->data=(B2026_Var*)calloc(slots,sizeof(B2026_Var));if(result->data==NULL)goto fail;}result->ref_count_ptr=(int*)malloc(sizeof(int));if(result->ref_count_ptr==NULL)goto fail;*result->ref_count_ptr=1;strncpy(result->name,target,sizeof(result->name)-1U);result->size=option_base+doc.page_count-1;result->read_count=doc.page_count;basic_reset_canvas_pen_state(result);for(i=0;i<doc.page_count;++i){result->data[option_base+i].type=1;result->data[option_base+i].data.ptr=texts[i];texts[i]=NULL;}old=find_local_array(frame,target);if(old!=NULL){ArrayVariable *next=old->next;release_array_storage(old);*old=*result;old->next=next;free(result);}else{result->next=frame->arrays;frame->arrays=result;}free(texts);b2026_pdf_document_free(&doc);return 1;fail:if(texts!=NULL)for(i=0;i<doc.page_count;++i)free(texts[i]);free(texts);if(result!=NULL){free(result->data);free(result->ref_count_ptr);free(result);}b2026_pdf_document_free(&doc);b2026_pdf_text_extract_error(4203,path,"memory or output limit exceeded; an empty result array was returned / メモリまたは出力上限を超えたため空の結果配列を返しました");return 0;}
