浏览代码

Merge branch 'support-utf8nopreamble'

Martijn Laan 1 年之前
父节点
当前提交
d9378dee97
共有 10 个文件被更改,包括 128 次插入56 次删除
  1. 4 4
      ISHelp/isetup.xml
  2. 13 4
      ISHelp/isxfunc.xml
  3. 15 13
      Projects/CompForm.dfm
  4. 38 14
      Projects/CompForm.pas
  5. 8 6
      Projects/CompFunc.pas
  6. 4 3
      Projects/CompScintEdit.pas
  7. 24 8
      Projects/FileClass.pas
  8. 2 1
      Projects/ScriptFunc.pas
  9. 8 3
      Projects/ScriptFunc_R.pas
  10. 12 0
      whatsnew.htm

+ 4 - 4
ISHelp/isetup.xml

@@ -164,7 +164,7 @@ Includes integrated support for "deflate", bzip2, and 7-Zip LZMA/LZMA2 file <lin
 <keyword value="Creating Installations" />
 <body>
 
-<p>Installations are created by means of <i>scripts</i>, which are ASCII or Unicode (UTF-8 encoded with a BOM) text files with a format somewhat similar to .INI files. (No, it's not as complicated as you might be thinking!).</p>
+<p>Installations are created by means of <i>scripts</i>, which are ASCII or Unicode (UTF-8 encoded) text files with a format somewhat similar to .INI files. (No, it's not as complicated as you might be thinking!).</p>
 
 <p>Scripts have an ".iss" (meaning Inno Setup Script) extension. The script controls every aspect of the installation. It specifies which files are to be installed and where, what shortcuts are to be created and what they are to be named, and so on.</p>
 
@@ -228,7 +228,7 @@ Source: "MYPROG.EXE"; DestDir: "{app}"
 
 <p>By default, scripts use ISPP if available, and .isl files use the built-in preprocessor.</p>
 
-<p>If an Unicode file is used, it must be UTF-8 encoded with a BOM.</p>
+<p>If an Unicode file is used, it must be UTF-8 encoded.</p>
 
 <p><br/><b>See also:</b><br/>
 <link topic="params">Parameters in Sections</link><br/>
@@ -2124,7 +2124,7 @@ Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl"
 <p>Specifies the name(s) of the .isl file(s) to read the default messages from. The file(s) must be located in your installation's <link topic="sourcedirectorynotes">source directory</link> when running the compiler, unless a fully qualified pathname is specified or the pathname is prefixed by "compiler:", in which case it looks for the file in the compiler directory.</p>
 <p>Each message file may contain a <link topic="langoptionssection">[LangOptions] section</link>, a <link topic="messagessection">[Messages] section</link>, and a <link topic="custommessagessection">[CustomMessages] section.</link></p>
 <p>When multiple files are specified, they are read in the order they are specified, thus the last message file overrides any language options or messages from previous files. Any language options or messages in the main script override the ones from message files.</p>
-<p>If an Unicode file is used, it must be UTF-8 encoded with a BOM.</p>
+<p>If an Unicode file is used, it must be UTF-8 encoded.</p>
 <examples>
 <pre>
 MessagesFile: "compiler:Dutch.isl"
@@ -2769,7 +2769,7 @@ Type: files; Name: "{win}\MYPROG.INI"
 <p>If you don't remember which version you installed, click the "Inno Setup Compiler" shortcut created in the Start Menu. If the version number displayed in its title bar says "(a)" you are running Non Unicode Inno Setup. Otherwise you are running Unicode Inno Setup.</p>
 <p>For the most part the two versions are used identically, and any differences between them are noted throughout the help file. However, the following overview lists the primary differences:</p>
   <ul>
-  <li>Unicode Inno Setup supports UTF-8 encoded .iss and .isl files. A BOM is required. UTF-16 is not supported.</li>
+  <li>Unicode Inno Setup supports UTF-8 encoded .iss and .isl files. Starting with Inno Setup 6.2.3 an UTF-8 preamble (also called BOM) is no longer required. UTF-16 is not supported.</li>
   <li>Any existing ANSI .isl language files are automatically converted during compilation using the <tt>LanguageCodePage</tt> setting of the language.</li>
   <li>Any [Messages] and [CustomMessages] entries in existing ANSI .iss script files must to be converted to Unicode manually if the language used a special <tt>LanguageCodePage</tt>.</li>
   <li>Unicode Inno Setup supports UTF-8 and UTF-16LE encoded .txt files for <tt>LicenseFile</tt>, <tt>InfoBeforeFile</tt>, and <tt>InfoAfterFile</tt>.</li>

+ 13 - 4
ISHelp/isxfunc.xml

@@ -1861,7 +1861,7 @@ end;</pre>
       <function>
         <name>LoadStringsFromFile</name>
         <prototype>function LoadStringsFromFile(const FileName: String; var S: TArrayOfString): Boolean;</prototype>
-        <description><p>Loads the specified text file into the specified string array. Returns True if successful, False otherwise.</p></description>
+        <description><p>Loads the specified text file into the specified string array. UTF-8 encoded files with or without a preamble (also called BOM) are also supported. Returns True if successful, False otherwise.</p></description>
       </function>
       <function>
         <name>SaveStringToFile</name>
@@ -1874,13 +1874,22 @@ end;</pre>
         <name>SaveStringsToFile</name>
         <prototype>function SaveStringsToFile(const FileName: String; const S: TArrayOfString; const Append: Boolean): Boolean;</prototype>
         <description><p>Saves the specified string array to the specified file with ASCII encoding. If Append is True and the specified file already exists, it will be appended to instead of overwritten. Returns True if successful, False otherwise.</p></description>
-        <seealso><p><link topic="isxfunc_SaveStringsToUTF8File">SaveStringsToUTF8File</link></p></seealso>
+        <seealso><p><link topic="isxfunc_SaveStringsToUTF8File">SaveStringsToUTF8File</link><br />
+<link topic="isxfunc_SaveStringsToUTF8FileNoPreamble">SaveStringsToUTF8FileNoPreamble</link></p></seealso>
       </function>
       <function>
         <name>SaveStringsToUTF8File</name>
         <prototype>function SaveStringsToUTF8File(const FileName: String; const S: TArrayOfString; const Append: Boolean): Boolean;</prototype>
-        <description><p>Saves the specified string array to the specified file with UTF8 encoding. If Append is True and the specified file already exists, it will be appended to instead of overwritten. Returns True if successful, False otherwise.</p></description>
-        <seealso><p><link topic="isxfunc_SaveStringsToFile">SaveStringsToFile</link></p></seealso>
+        <description><p>Saves the specified string array to the specified file with UTF-8 encoding with a preamble (also called BOM). If Append is True and the specified file already exists, it will be appended to instead of overwritten. Returns True if successful, False otherwise.</p></description>
+        <seealso><p><link topic="isxfunc_SaveStringsToFile">SaveStringsToFile</link><br />
+<link topic="isxfunc_SaveStringsToUTF8FileNoPreamble">SaveStringsToUTF8FileNoPreamble</link></p></seealso>
+      </function>
+      <function>
+        <name>SaveStringsToUTF8FileNoPreamble</name>
+        <prototype>function SaveStringsToUTF8FileNoPreamble(const FileName: String; const S: TArrayOfString; const Append: Boolean): Boolean;</prototype>
+        <description><p>Saves the specified string array to the specified file with UTF-8 encoding without a preamble (also called BOM). If Append is True and the specified file already exists, it will be appended to instead of overwritten. Returns True if successful, False otherwise.</p></description>
+        <seealso><p><link topic="isxfunc_SaveStringsToFile">SaveStringsToFile</link><br />
+<link topic="isxfunc_SaveStringsToUTF8File">SaveStringsToUTF8File</link></p></seealso>
       </function>
     </subcategory>
     <subcategory>

+ 15 - 13
Projects/CompForm.dfm

@@ -203,6 +203,7 @@ object CompileForm: TCompileForm
         Top = 0
         Hint = 'New Main Script (Ctrl+N)'
         ImageIndex = 0
+        ImageName = 'document-new'
         OnClick = FNewMainFileClick
       end
       object OpenMainFileButton: TToolButton
@@ -210,6 +211,7 @@ object CompileForm: TCompileForm
         Top = 0
         Hint = 'Open Main Script (Ctrl+O)'
         ImageIndex = 1
+        ImageName = 'folder-open-filled-arrow-down-right'
         OnClick = FOpenMainFileClick
       end
       object SaveButton: TToolButton
@@ -217,6 +219,7 @@ object CompileForm: TCompileForm
         Top = 0
         Hint = 'Save (Ctrl+S)'
         ImageIndex = 2
+        ImageName = 'save-filled'
         OnClick = FSaveClick
       end
       object ToolButton4: TToolButton
@@ -231,6 +234,7 @@ object CompileForm: TCompileForm
         Top = 0
         Hint = 'Compile (Ctrl+F9)'
         ImageIndex = 3
+        ImageName = 'build'
         OnClick = BCompileClick
       end
       object StopCompileButton: TToolButton
@@ -239,6 +243,7 @@ object CompileForm: TCompileForm
         Hint = 'Stop Compile (Esc)'
         Enabled = False
         ImageIndex = 4
+        ImageName = 'build-cancel-2'
         OnClick = BStopCompileClick
       end
       object ToolButton7: TToolButton
@@ -253,6 +258,7 @@ object CompileForm: TCompileForm
         Top = 0
         Hint = 'Run (F9)'
         ImageIndex = 5
+        ImageName = 'debug-start-filled'
         OnClick = RRunClick
       end
       object PauseButton: TToolButton
@@ -261,6 +267,7 @@ object CompileForm: TCompileForm
         Hint = 'Pause'
         Enabled = False
         ImageIndex = 6
+        ImageName = 'debug-break-all-filled'
         OnClick = RPauseClick
       end
       object TerminateButton: TToolButton
@@ -269,6 +276,7 @@ object CompileForm: TCompileForm
         Hint = 'Terminate (Ctrl+F2)'
         Enabled = False
         ImageIndex = 10
+        ImageName = 'debug-stop-filled'
         OnClick = RTerminateClick
       end
       object ToolButton10: TToolButton
@@ -284,6 +292,7 @@ object CompileForm: TCompileForm
         Hint = 'Target Setup (Ctrl+Q)'
         Grouped = True
         ImageIndex = 7
+        ImageName = 'install'
         Style = tbsCheck
         OnClick = RTargetClick
       end
@@ -293,6 +302,7 @@ object CompileForm: TCompileForm
         Hint = 'Target Uninstall (Ctrl+W)'
         Grouped = True
         ImageIndex = 8
+        ImageName = 'uninstall'
         Style = tbsCheck
         OnClick = RTargetClick
       end
@@ -308,6 +318,7 @@ object CompileForm: TCompileForm
         Top = 0
         Hint = 'Help (F1)'
         ImageIndex = 9
+        ImageName = 'button-help'
         OnClick = HDocClick
       end
     end
@@ -364,6 +375,10 @@ object CompileForm: TCompileForm
           RadioItem = True
           OnClick = FSaveEncodingItemClick
         end
+        object FSaveEncodingUTF8NoPreamble: TMenuItem
+          Caption = 'UTF-8 without &BOM'
+          OnClick = FSaveEncodingItemClick
+        end
       end
       object FSaveAll: TMenuItem
         Caption = 'Sa&ve All'
@@ -2638,73 +2653,60 @@ object CompileForm: TCompileForm
   end
   object ToolBarVirtualImageList: TVirtualImageList
     AutoFill = True
-    DisabledGrayscale = False
-    DisabledSuffix = '_Disabled'
     Images = <
       item
         CollectionIndex = 0
         CollectionName = 'document-new'
-        Disabled = False
         Name = 'document-new'
       end
       item
         CollectionIndex = 1
         CollectionName = 'folder-open-filled-arrow-down-right'
-        Disabled = False
         Name = 'folder-open-filled-arrow-down-right'
       end
       item
         CollectionIndex = 2
         CollectionName = 'save-filled'
-        Disabled = False
         Name = 'save-filled'
       end
       item
         CollectionIndex = 3
         CollectionName = 'build'
-        Disabled = False
         Name = 'build'
       end
       item
         CollectionIndex = 4
         CollectionName = 'build-cancel-2'
-        Disabled = False
         Name = 'build-cancel-2'
       end
       item
         CollectionIndex = 5
         CollectionName = 'debug-start-filled'
-        Disabled = False
         Name = 'debug-start-filled'
       end
       item
         CollectionIndex = 6
         CollectionName = 'debug-break-all-filled'
-        Disabled = False
         Name = 'debug-break-all-filled'
       end
       item
         CollectionIndex = 7
         CollectionName = 'install'
-        Disabled = False
         Name = 'install'
       end
       item
         CollectionIndex = 8
         CollectionName = 'uninstall'
-        Disabled = False
         Name = 'uninstall'
       end
       item
         CollectionIndex = 9
         CollectionName = 'button-help'
-        Disabled = False
         Name = 'button-help'
       end
       item
         CollectionIndex = 10
         CollectionName = 'debug-stop-filled'
-        Disabled = False
         Name = 'debug-stop-filled'
       end>
     ImageCollection = LightToolBarImageCollection

+ 38 - 14
Projects/CompForm.pas

@@ -209,6 +209,7 @@ type
     FPrint: TMenuItem;
     N22: TMenuItem;
     PrintDialog: TPrintDialog;
+    FSaveEncodingUTF8NoPreamble: TMenuItem;
     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
     procedure FExitClick(Sender: TObject);
     procedure FOpenMainFileClick(Sender: TObject);
@@ -513,7 +514,7 @@ var
 implementation
 
 uses
-  ActiveX, Clipbrd, ShellApi, ShlObj, IniFiles, Registry, Consts, Types, UITypes, Math,
+  ActiveX, Clipbrd, ShellApi, ShlObj, IniFiles, Registry, Consts, Types, UITypes, Math, WideStrUtils,
   PathFunc, CmnFunc, CmnFunc2, FileClass, CompMsgs, TmSchema, BrowseFunc,
   HtmlHelpFunc, TaskbarProgressFunc,
   {$IFDEF STATICCOMPILER} Compile, {$ENDIF}
@@ -1015,7 +1016,7 @@ begin
 
   FMainMemo.Filename := '';
   UpdateCaption;
-  FMainMemo.SaveInUTF8Encoding := False;
+  FMainMemo.SaveEncoding := seUTF8;
   FMainMemo.Lines.Clear;
   FModifiedAnySinceLastCompile := True;
   FPreprocessorOutput := '';
@@ -1106,7 +1107,7 @@ begin
     end;
 
     if CommandLineWizard then begin
-      SaveTextToFile(CommandLineFileName, WizardForm.ResultScript, False);
+      SaveTextToFile(CommandLineFileName, WizardForm.ResultScript, seUtf8);
     end else begin
       NewMainFile;
       FMainMemo.Lines.Text := WizardForm.ResultScript;
@@ -1125,14 +1126,31 @@ end;
 procedure TCompileForm.OpenFile(AMemo: TCompScintFileEdit; AFilename: String;
   const MainMemoAddToRecentDocs: Boolean);
 
-  function IsStreamUTF8Encoded(const Stream: TStream): Boolean;
+  function GetStreamSaveEncoding(const Stream: TStream): TSaveEncoding;
   var
     Buf: array[0..2] of Byte;
   begin
-    Result := False;
-    if Stream.Read(Buf, SizeOf(Buf)) = SizeOf(Buf) then
-      if (Buf[0] = $EF) and (Buf[1] = $BB) and (Buf[2] = $BF) then
-        Result := True;
+    Result := seAuto;
+    var Size: Integer := Stream.Size;
+    if (Size >= SizeOf(Buf)) and (Stream.Read(Buf, SizeOf(Buf)) = SizeOf(Buf)) and
+       (Buf[0] = $EF) and (Buf[1] = $BB) and (Buf[2] = $BF) then
+      Result := seUTF8
+    else begin
+      Stream.Seek(0, soFromBeginning);
+      var S: AnsiString;
+      SetLength(S, Size);
+      SetLength(S, Stream.Read(S[1], Size));
+      if IsUTF8String(S) then
+        Result := seUTF8NoPreamble;
+    end;
+  end;
+
+  function GetEncoding(const SaveEncoding: TSaveEncoding): TEncoding;
+  begin
+    if SaveEncoding in [seUTF8, seUTF8NoPreamble] then
+      Result := TEncoding.UTF8
+    else
+      Result := nil;
   end;
 
 var
@@ -1145,9 +1163,9 @@ begin
     if AMemo = FMainMemo then
       NewMainFile;
     GetFileTime(Stream.Handle, nil, nil, @AMemo.FileLastWriteTime);
-    AMemo.SaveInUTF8Encoding := IsStreamUTF8Encoded(Stream);
+    AMemo.SaveEncoding := GetStreamSaveEncoding(Stream);
     Stream.Seek(0, soFromBeginning);
-    AMemo.Lines.LoadFromStream(Stream);
+    AMemo.Lines.LoadFromStream(Stream, GetEncoding(AMemo.SaveEncoding));
   finally
     Stream.Free;
   end;
@@ -1193,7 +1211,7 @@ function TCompileForm.SaveFile(const AMemo: TCompScintFileEdit; const SaveAs: Bo
         [GetLastError]);
     TempFN := Buf;
     try
-      SaveTextToFile(TempFN, AMemo.Lines.Text, AMemo.SaveInUTF8Encoding);
+      SaveTextToFile(TempFN, AMemo.Lines.Text, AMemo.SaveEncoding);
 
       { Back up existing file if needed }
       if FOptions.MakeBackups and NewFileExists(FN) then begin
@@ -1728,8 +1746,9 @@ var
 begin
   FSaveMainFileAs.Enabled := FActiveMemo = FMainMemo;
   FSaveEncoding.Enabled := FSave.Enabled; { FSave.Enabled is kept up-to-date by UpdateSaveMenuItemAndButton }
-  FSaveEncodingAuto.Checked := FSaveEncoding.Enabled and not (FActiveMemo as TCompScintFileEdit).SaveInUTF8Encoding;
-  FSaveEncodingUTF8.Checked := FSaveEncoding.Enabled and (FActiveMemo as TCompScintFileEdit).SaveInUTF8Encoding;
+  FSaveEncodingAuto.Checked := FSaveEncoding.Enabled and ((FActiveMemo as TCompScintFileEdit).SaveEncoding = seAuto);
+  FSaveEncodingUTF8.Checked := FSaveEncoding.Enabled and ((FActiveMemo as TCompScintFileEdit).SaveEncoding = seUTF8);
+  FSaveEncodingUTF8NoPreamble.Checked := FSaveEncoding.Enabled and ((FActiveMemo as TCompScintFileEdit).SaveEncoding = seUTF8NoPreamble);
   FSaveAll.Visible := FOptions.OpenIncludedFiles;
   ReadMRUMainFilesList;
   FMRUMainFilesSep.Visible := FMRUMainFilesList.Count <> 0;
@@ -1785,7 +1804,12 @@ end;
 
 procedure TCompileForm.FSaveEncodingItemClick(Sender: TObject);
 begin
-  (FActiveMemo as TCompScintFileEdit).SaveInUTF8Encoding := (Sender = FSaveEncodingUTF8);
+  if Sender = FSaveEncodingUTF8  then
+    (FActiveMemo as TCompScintFileEdit).SaveEncoding := seUTF8
+  else if Sender = FSaveEncodingUTF8NoPreamble  then
+    (FActiveMemo as TCompScintFileEdit).SaveEncoding := seUTF8NoPreamble
+  else
+    (FActiveMemo as TCompScintFileEdit).SaveEncoding := seAuto;
 end;
 
 procedure TCompileForm.FSaveAllClick(Sender: TObject);

+ 8 - 6
Projects/CompFunc.pas

@@ -2,7 +2,7 @@ unit CompFunc;
 
 {
   Inno Setup
-  Copyright (C) 1997-2020 Jordan Russell
+  Copyright (C) 1997-2024 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -16,7 +16,7 @@ interface
 uses
   Windows,
   Classes, Forms, Dialogs, Menus, StdCtrls,
-  ScintEdit, ModernColors;
+  ScintEdit, CompScintEdit, ModernColors;
 
 const
   MRUListMaxCount = 10;
@@ -48,7 +48,7 @@ procedure SetFakeShortCutText(const MenuItem: TMenuItem; const S: String);
 procedure SetFakeShortCut(const MenuItem: TMenuItem; const Key: Word;
   const Shift: TShiftState);
 procedure SaveTextToFile(const Filename: String;
-  const S: String; const ForceUTF8Encoding: Boolean);
+  const S: String; const SaveEncoding: TSaveEncoding);
 procedure AddLines(const ListBox: TListBox; const S: String; const AObject: TObject; const LineBreaks: Boolean; const Prefix: TAddLinesPrefix; const PrefixParam: Cardinal);
 procedure SetLowPriority(ALowPriority: Boolean; var SavePriorityClass: DWORD);
 function GetHelpFile: String;
@@ -306,14 +306,14 @@ begin
 end;
 
 procedure SaveTextToFile(const Filename: String;
-  const S: String; const ForceUTF8Encoding: Boolean);
+  const S: String; const SaveEncoding: TSaveEncoding);
 var
   AnsiMode: Boolean;
   AnsiStr: AnsiString;
   F: TTextFileWriter;
 begin
   AnsiMode := False;
-  if not ForceUTF8Encoding then begin
+  if SaveEncoding = seAuto then begin
     AnsiStr := AnsiString(S);
     if S = String(AnsiStr) then
       AnsiMode := True;
@@ -323,8 +323,10 @@ begin
   try
     if AnsiMode then
       F.WriteAnsi(AnsiStr)
-    else
+    else begin
+      F.UTF8NoPreamble := SaveEncoding = seUTF8NoPreamble;
       F.Write(S);
+    end;
   finally
     F.Free;
   end;

+ 4 - 3
Projects/CompScintEdit.pas

@@ -2,7 +2,7 @@ unit CompScintEdit;
 
 {
   Inno Setup
-  Copyright (C) 1997-2020 Jordan Russell
+  Copyright (C) 1997-2024 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -37,6 +37,7 @@ type
   TLineState = (lnUnknown, lnHasEntry, lnEntryProcessed);
   PLineStateArray = ^TLineStateArray;
   TLineStateArray = array[0..0] of TLineState;
+  TSaveEncoding = (seAuto, seUTF8, seUTF8NoPreamble);
 
   TCompScintEdit = class(TScintEdit)
   private
@@ -56,7 +57,7 @@ type
     FCompilerFileIndex: Integer;
     FFilename: String;
     FFileLastWriteTime: TFileTime;
-    FSaveInUTF8Encoding: Boolean;
+    FSaveEncoding: TSaveEncoding;
   public
     ErrorLine, ErrorCaretPosition: Integer;
     StepLine: Integer;
@@ -68,7 +69,7 @@ type
     property Filename: String read FFileName write FFilename;
     property CompilerFileIndex: Integer read FCompilerFileIndex write FCompilerFileIndex;
     property FileLastWriteTime: TFileTime read FFileLastWriteTime write FFileLastWriteTime;
-    property SaveInUTF8Encoding: Boolean read FSaveInUTF8Encoding write FSaveInUTF8Encoding;
+    property SaveEncoding: TSaveEncoding read FSaveEncoding write FSaveEncoding;
   end;
 
 implementation

+ 24 - 8
Projects/FileClass.pas

@@ -2,7 +2,7 @@ unit FileClass;
 
 {
   Inno Setup
-  Copyright (C) 1997-2010 Jordan Russell
+  Copyright (C) 1997-2024 Jordan Russell
   Portions by Martijn Laan
   For conditions of distribution and use, see LICENSE.TXT.
 
@@ -11,8 +11,6 @@ unit FileClass;
   and uses descriptive, localized system error messages.
 
   TTextFileReader and TTextFileWriter support ANSI and UTF8 textfiles only.
-
-  $jrsoftware: issrc/Projects/FileClass.pas,v 1.31 2010/01/26 06:26:18 jr Exp $
 }
 
 {$I VERSION.INC}
@@ -117,12 +115,14 @@ type
   TTextFileWriter = class(TFile)
   private
     FSeekedToEnd: Boolean;
+    FUTF8NoPreamble: Boolean;
     procedure DoWrite(const S: AnsiString{$IFDEF UNICODE}; const UTF8: Boolean{$ENDIF});
   protected
     function CreateHandle(const AFilename: String;
       ACreateDisposition: TFileCreateDisposition; AAccess: TFileAccess;
       ASharing: TFileSharing): THandle; override;
   public
+    property UTF8NoPreamble: Boolean read FUTF8NoPreamble write FUTF8NoPreamble;
     procedure Write(const S: String);
     procedure WriteLine(const S: String);
 {$IFDEF UNICODE}
@@ -155,6 +155,7 @@ type
 implementation
 
 uses
+  WideStrUtils,
   CmnFunc2;
 
 const
@@ -503,10 +504,25 @@ begin
   end;
   {$IFDEF UNICODE}
   if not FSawFirstLine then begin
-    { Handle UTF8 BOM if requested }
-    if UTF8 and (Length(S) > 2) and (S[1] = #$EF) and (S[2] = #$BB) and (S[3] = #$BF) then begin
-      Delete(S, 1, 3);
-      FCodePage := CP_UTF8;
+    if UTF8 then begin
+      { Handle UTF8 as requested: check for a BOM at the start and if not found then check entire file }
+      if (Length(S) > 2) and (S[1] = #$EF) and (S[2] = #$BB) and (S[3] = #$BF) then begin
+        Delete(S, 1, 3);
+        FCodePage := CP_UTF8;
+      end else begin
+        var OldPosition := GetPosition;
+        try
+          var Size := CappedSize; //can't be 0
+          Seek(0);
+          var S2: AnsiString;
+          SetLength(S2, Size);
+          SetLength(S2, Read(S2[1], Size));
+          if IsUTF8String(S2) then
+            FCodePage := CP_UTF8;
+        finally
+          Seek64(OldPosition);
+        end;
+      end;
     end;
     FSawFirstLine := True;
   end;
@@ -563,7 +579,7 @@ begin
         WriteBuffer(CRLF, SizeOf(CRLF));
       end;
 {$IFDEF UNICODE}
-    end else if UTF8 then
+    end else if UTF8 and not FUTF8NoPreamble then
       WriteBuffer(UTF8Preamble, SizeOf(UTF8Preamble));
 {$ELSE}
     end;

+ 2 - 1
Projects/ScriptFunc.pas

@@ -349,7 +349,7 @@ const
   );
 
   { Other }
-  OtherTable: array [0..33] of AnsiString =
+  OtherTable: array [0..34] of AnsiString =
   (
     'procedure BringToFrontAndRestore;',
     'function WizardDirValue: String;',
@@ -379,6 +379,7 @@ const
     'function SaveStringToFile(const FileName: String; const S: AnsiString; const Append: Boolean): Boolean;',
     'function SaveStringsToFile(const FileName: String; const S: TArrayOfString; const Append: Boolean): Boolean;',
     'function SaveStringsToUTF8File(const FileName: String; const S: TArrayOfString; const Append: Boolean): Boolean;',
+    'function SaveStringsToUTF8FileNoPreamble(const FileName: String; const S: TArrayOfString; const Append: Boolean): Boolean;',
     'function EnableFsRedirection(const Enable: Boolean): Boolean;',
     'function GetUninstallProgressForm: TUninstallProgressForm;',
     'function CreateCallback(Method: AnyMethod): Longword;',

+ 8 - 3
Projects/ScriptFunc_R.pas

@@ -1793,7 +1793,7 @@ function OtherProc(Caller: TPSExec; Proc: TPSExternalProcRec; Global, Stack: TPS
     end;
   end;
 
-  function SaveStringsToFile(const FileName: String; const Arr: PPSVariantIFC; Append, UTF8: Boolean): Boolean;
+  function SaveStringsToFile(const FileName: String; const Arr: PPSVariantIFC; Append, UTF8, UTF8NoPreamble: Boolean): Boolean;
   var
     F: TTextFileWriter;
     I, N: Integer;
@@ -1809,6 +1809,8 @@ function OtherProc(Caller: TPSExec; Proc: TPSExternalProcRec; Global, Stack: TPS
       else
         F := TTextFileWriterRedir.Create(ScriptFuncDisableFsRedir, FileName, fdCreateAlways, faWrite, fsNone);
       try
+        if UTF8 and UTF8NoPreamble then
+          F.UTF8NoPreamble := UTF8NoPreamble;
         N := PSDynArrayGetLength(Pointer(Arr.Dta^), Arr.aType);
         for I := 0 to N-1 do begin
           S := VNGetString(PSGetArrayField(Arr^, I));
@@ -2027,10 +2029,13 @@ begin
     Stack.SetBool(PStart, SaveStringToFile(Stack.GetString(PStart-1), StackGetAnsiString(Stack, PStart-2), Stack.GetBool(PStart-3)));
   end else if Proc.Name = 'SAVESTRINGSTOFILE' then begin
     Arr := NewTPSVariantIFC(Stack[PStart-2], True);
-    Stack.SetBool(PStart, SaveStringsToFile(Stack.GetString(PStart-1), @Arr, Stack.GetBool(PStart-3), False));
+    Stack.SetBool(PStart, SaveStringsToFile(Stack.GetString(PStart-1), @Arr, Stack.GetBool(PStart-3), False, False));
   end else if Proc.Name = 'SAVESTRINGSTOUTF8FILE' then begin
     Arr := NewTPSVariantIFC(Stack[PStart-2], True);
-    Stack.SetBool(PStart, SaveStringsToFile(Stack.GetString(PStart-1), @Arr, Stack.GetBool(PStart-3), True));
+    Stack.SetBool(PStart, SaveStringsToFile(Stack.GetString(PStart-1), @Arr, Stack.GetBool(PStart-3), True, False));
+  end else if Proc.Name = 'SAVESTRINGSTOUTF8FILENOPREAMBLE' then begin
+    Arr := NewTPSVariantIFC(Stack[PStart-2], True);
+    Stack.SetBool(PStart, SaveStringsToFile(Stack.GetString(PStart-1), @Arr, Stack.GetBool(PStart-3), True, True));
   end else if Proc.Name = 'ENABLEFSREDIRECTION' then begin
     Stack.SetBool(PStart, not ScriptFuncDisableFsRedir);
     if Stack.GetBool(PStart-1) then

+ 12 - 0
whatsnew.htm

@@ -30,6 +30,18 @@ For conditions of distribution and use, see <a href="https://jrsoftware.org/file
 
 <p><a name="6.2.3"></a><span class="ver">6.2.3-dev </span><span class="date">(?)</span></p>
 <ul>
+  <li>Inno Setup now supports UTF-8 encoded .iss and .isl files without an UTF-8 preamble (also called BOM.)</li>
+  <li>Compiler IDE changes:
+  <ul>
+  <li>Added new <i>UTF-8 without BOM</i> menu item to the <i>Save Encoding</i> submenu of the <i>File</i> menu.</li>
+  <li>New files are now saved as UTF-8 with BOM by default. Existing files are still saved as they were.</li>
+  </ul>
+  </li>
+  <li>Pascal Scripting changes:
+  <ul>
+    <li>Support fuction <tt>LoadStringsFromFile</tt> now also supports UT8-encoded files without an UTF-8 preamble.</li>
+    <li>Added new <tt>SaveStringsToUTF8FileNoPreamble</tt> support function.</li>
+  </ul>
   <li>During startup Setup would always ask Windows to create any missing <tt>{usercf}</tt>, <tt>{userpf}</tt>, and <tt>{usersavedgames}</tt> folders. It no longer does until the script asks for the folder. Note that scripts running in administrative install mode should not do this because it violates the <a href="https://jrsoftware.org/ishelp/index.php?topic=setup_useduserareaswarning">used user areas warning</a>.</li>
   <li>Added support for IIS group users identifiers (<tt>iisiusrs</tt>) for use in <tt>Permissions</tt> parameters, contributed by Achim Stuy.</li> 
   <li>Pascal Scripting change: type <tt>TShellFolderID</tt> was removed because it wasn't used by any support function.</a>