//+------------------------------------------------------------------+
//| 99_Comment_Object_Output_v1_00.mq5                               |
//| Display Comment-style multiline text with configurable labels.   |
//+------------------------------------------------------------------+
#property strict
#property version   "1.00"
#property description "Comment-style multiline chart labels with configurable font"

input string InpText="CommentOBJ style display\nFont: Segoe UI\nJapanese: 日本語の行も表示できます\nLong text is divided at character boundaries without splitting surrogate pairs.\nNo trading operations are performed.";
input string InpInstance="A";            // Unique per instance on the same chart
input string InpFont="Segoe UI";
input int    InpFontSize=12;
input color  InpTextColor=clrWhite;
input int    InpLeft=18;
input int    InpTop=28;
input int    InpLineGap=23;
input int    InpMaxChars=40;              // UTF-16 units per visual row (1..200)
input int    InpMaxRows=80;               // Safety cap (1..200)
input bool   InpCapture=false;            // Validation option
input string InpCaptureImage="99_Comment_Object_Output.png";

string g_prefix;
string g_rows[];

string LabelName(const int row)
  {
   return(g_prefix+IntegerToString(row));
  }

int SafeChunkEnd(const string text,const int start,const int limit)
  {
   const int length=StringLen(text);
   int finish=(int)MathMin(length,start+limit);
   // MQL strings are UTF-16. Keep a high/low surrogate pair together.
   if(finish<length && finish>start)
     {
      const ushort before=StringGetCharacter(text,finish-1);
      const ushort after=StringGetCharacter(text,finish);
      if(before>=0xD800 && before<=0xDBFF && after>=0xDC00 && after<=0xDFFF)
         finish--;
     }
   return(finish);
  }

bool AppendRow(const string value)
  {
   const int count=ArraySize(g_rows);
   if(count>=InpMaxRows)
      return(false);
   if(ArrayResize(g_rows,count+1)!=count+1)
      return(false);
   g_rows[count]=value;
   return(true);
  }

bool SplitText()
  {
   ArrayResize(g_rows,0);
   string lines[];
   ushort newline=10;
   const int line_count=StringSplit(InpText,newline,lines);
   if(line_count<0)
      return(false);
   for(int line=0;line<line_count;line++)
     {
      string source=lines[line];
      StringReplace(source,"\r","");
      const int length=StringLen(source);
      if(length==0)
        {
         if(!AppendRow(" ")) return(false);
         continue;
        }
      int start=0;
      while(start<length)
        {
         int finish=SafeChunkEnd(source,start,InpMaxChars);
         // Prefer a space boundary for Latin text; Japanese without spaces
         // falls back to the configured UTF-16 length.
         if(finish<length && StringGetCharacter(source,finish-1)!=' ')
           {
            for(int probe=finish-1;probe>start;probe--)
               if(StringGetCharacter(source,probe)==' ')
                 {
                  finish=probe;
                  break;
                 }
           }
         string chunk=StringSubstr(source,start,finish-start);
         while(StringLen(chunk)>0 && StringGetCharacter(chunk,StringLen(chunk)-1)==' ')
            chunk=StringSubstr(chunk,0,StringLen(chunk)-1);
         if(finish<=start || !AppendRow(chunk))
            return(false);
         start=finish;
         while(start<length && StringGetCharacter(source,start)==' ')
            start++;
        }
     }
   return(ArraySize(g_rows)>0);
  }

bool PutRow(const int index,const string value)
  {
   const string name=LabelName(index);
   if(ObjectFind(0,name)<0 && !ObjectCreate(0,name,OBJ_LABEL,0,0,0))
     {
      PrintFormat("O99 object create failed row=%d error=%d",index,GetLastError());
      return(false);
     }
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,InpLeft);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,InpTop+index*InpLineGap);
   ObjectSetInteger(0,name,OBJPROP_COLOR,InpTextColor);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,InpFontSize);
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(0,name,OBJPROP_SELECTED,false);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
   ObjectSetInteger(0,name,OBJPROP_BACK,false);
   ObjectSetString(0,name,OBJPROP_FONT,InpFont);
   ObjectSetString(0,name,OBJPROP_TEXT,value);
   return(true);
  }

int OnInit()
  {
   if(StringLen(InpInstance)<1 || InpFontSize<6 || InpFontSize>48 ||
      InpLineGap<8 || InpLineGap>100 || InpMaxChars<1 || InpMaxChars>200 ||
      InpMaxRows<1 || InpMaxRows>200)
     {
      Print("O99 invalid input: check instance, font size, line gap, and row limits");
      return(INIT_PARAMETERS_INCORRECT);
     }
   g_prefix=StringFormat("O99_%I64d_%s_",ChartID(),InpInstance);
   if(!SplitText())
     {
      PrintFormat("O99 text exceeds the %d row safety limit or could not be split",InpMaxRows);
      return(INIT_PARAMETERS_INCORRECT);
     }
   for(int i=0;i<ArraySize(g_rows);i++)
      if(!PutRow(i,g_rows[i]))
        {
         ObjectsDeleteAll(0,g_prefix);
         return(INIT_FAILED);
        }
   ChartRedraw(0);
   PrintFormat("O99 status=READY rows=%d max_chars=%d instance=%s",ArraySize(g_rows),InpMaxChars,InpInstance);
   if(InpCapture)
     {
      Sleep(1200);
      ResetLastError();
      const bool saved=ChartScreenShot(0,InpCaptureImage,1200,630,ALIGN_LEFT);
      PrintFormat("O99 captured=%s error=%d image=%s",(saved ? "true":"false"),GetLastError(),InpCaptureImage);
     }
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0,g_prefix);
   ChartRedraw(0);
  }

void OnTick()
  {
   // This display-only EA intentionally sends no trade requests.
  }
