Curtis Hamilton 4 недель назад
Родитель
Сommit
b3e2705226

+ 1 - 1
Makefile

@@ -2,7 +2,7 @@
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 #
 #
 default: help
 default: help
-MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-netbsd m68k-amiga m68k-atari m68k-palmos m68k-macosclassic m68k-embedded m68k-sinclairql m68k-human68k powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macosclassic powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-haiku x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-android x86_64-aros x86_64-dragonfly arm-linux arm-netbsd arm-palmos arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros arm-freertos arm-ios powerpc64-linux powerpc64-freebsd powerpc64-darwin powerpc64-embedded powerpc64-aix avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android mipsel-ps1 mips64-linux mips64el-linux jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-freebsd aarch64-darwin aarch64-win64 aarch64-embedded aarch64-iphonesim aarch64-android aarch64-ios wasm32-embedded wasm32-wasip1 wasm32-wasip1threads wasm32-wasip2 sparc64-linux riscv32-linux riscv32-embedded riscv32-freertos riscv64-linux riscv64-embedded xtensa-linux xtensa-embedded xtensa-freertos z80-embedded z80-zxspectrum z80-msxdos z80-amstradcpc loongarch64-linux
+MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-netbsd m68k-amiga m68k-atari m68k-palmos m68k-macosclassic m68k-embedded m68k-sinclairql m68k-human68k powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macosclassic powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-haiku x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-android x86_64-aros x86_64-dragonfly arm-linux arm-netbsd arm-palmos arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros arm-freertos arm-ios powerpc64-linux powerpc64-darwin powerpc64-embedded powerpc64-aix powerpc64-freebsd avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android mipsel-ps1 mips64-linux mips64el-linux jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-freebsd aarch64-darwin aarch64-win64 aarch64-embedded aarch64-iphonesim aarch64-android aarch64-ios wasm32-embedded wasm32-wasip1 wasm32-wasip1threads wasm32-wasip2 sparc64-linux riscv32-linux riscv32-embedded riscv32-freertos riscv64-linux riscv64-embedded xtensa-linux xtensa-embedded xtensa-freertos z80-embedded z80-zxspectrum z80-msxdos z80-amstradcpc loongarch64-linux
 BSDs = freebsd netbsd openbsd darwin dragonfly
 BSDs = freebsd netbsd openbsd darwin dragonfly
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k

+ 4 - 3
packages/fcl-md/demo/README.md

@@ -6,8 +6,9 @@ This directory contains 3 markdown parser examples:
 They are:
 They are:
 
 
 * demomd - simple markdown parser and html renderer.
 * demomd - simple markdown parser and html renderer.
-* md2html - slightly more complete markdown-to-html converter/
-* md2fpdoc - simple version of a markdown - to fpdoc converter.
+* md2html - slightly more complete markdown-to-html converter.
+* md2fpdoc - simple version of a markdown to fpdoc converter.
+* md2latex - simple version of a markdown to LaTeX converter.
 
 
 ## conversion to fpdoc 
 ## conversion to fpdoc 
 
 
@@ -24,5 +25,5 @@ The headers determine what is generated for a given section:
 
 
 links must be rendered as \[text\]\(text\) or \[\]\(text\)
 links must be rendered as \[text\]\(text\) or \[\]\(text\)
 
 
-You can find a simple example in the [sample.md](sample.md) file.
+You can find a simple example in the [sampledoc.md](sampledoc.md) file.
 
 

+ 6 - 36
packages/fcl-md/demo/demomd.lpi

@@ -13,6 +13,11 @@
       <UseAppBundle Value="False"/>
       <UseAppBundle Value="False"/>
       <ResourceType Value="res"/>
       <ResourceType Value="res"/>
     </General>
     </General>
+    <CustomData Count="3">
+      <Item0 Name="OpenAPIBase"/>
+      <Item1 Name="OpenAPIConfig"/>
+      <Item2 Name="OpenAPIFile"/>
+    </CustomData>
     <BuildModes>
     <BuildModes>
       <Item Name="Default" Default="True"/>
       <Item Name="Default" Default="True"/>
     </BuildModes>
     </BuildModes>
@@ -28,42 +33,6 @@
         <Filename Value="demomd.lpr"/>
         <Filename Value="demomd.lpr"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit>
       </Unit>
-      <Unit>
-        <Filename Value="markdown.elements.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.htmlrender.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.scanner.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.utils.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.parser.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.inlinetext.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.render.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.line.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
-      <Unit>
-        <Filename Value="markdown.delimiter.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit>
     </Units>
     </Units>
   </ProjectOptions>
   </ProjectOptions>
   <CompilerOptions>
   <CompilerOptions>
@@ -73,6 +42,7 @@
     </Target>
     </Target>
     <SearchPaths>
     <SearchPaths>
       <IncludeFiles Value="$(ProjOutDir)"/>
       <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="../src"/>
       <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
       <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
     </SearchPaths>
     </SearchPaths>
     <Linking>
     <Linking>

+ 3 - 1
packages/fcl-md/demo/demomd.lpr

@@ -8,7 +8,9 @@ uses
   markdown.parser,
   markdown.parser,
   markdown.inlinetext,
   markdown.inlinetext,
   markdown.htmlrender,
   markdown.htmlrender,
-  markdown.render, markdown.line, markdown.delimiter;
+  markdown.processors,
+  markdown.render
+  ;
 
 
 var
 var
   Source,Dest : TStringList;
   Source,Dest : TStringList;

+ 103 - 0
packages/fcl-md/demo/md2latex.lpi

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="md2latex"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <CustomData Count="3">
+      <Item0 Name="OpenAPIBase"/>
+      <Item1 Name="OpenAPIConfig"/>
+      <Item2 Name="OpenAPIFile"/>
+    </CustomData>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="md2latex.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.elements.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.htmlrender.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.scanner.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.utils.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.parser.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.inlinetext.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.render.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.line.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="markdown.delimiter.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="md2latex"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="../src"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsDwarf3"/>
+      </Debugging>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 49 - 0
packages/fcl-md/demo/md2latex.lpr

@@ -0,0 +1,49 @@
+program md2latex;
+
+{$mode objfpc}
+{$h+}
+
+uses
+  classes,
+  markdown.utils,
+  markdown.elements,
+  markdown.scanner,
+  markdown.parser,
+  markdown.inlinetext,
+  markdown.latexrender,
+  markdown.processors,
+  markdown.render
+  ;
+
+var
+  Source,Dest : TStringList;
+  Doc : TMarkDownDocument;
+  Renderer: TMarkDownLatexRenderer;
+
+begin
+  Dest:=Nil;
+  Renderer:=Nil;
+  Source:=TStringList.Create;
+  try
+    Dest:=TStringList.Create;
+    Source.LoadFromFile(ParamStr(1));
+    Doc:=TMarkDownParser.FastParse(Source,[]);
+    Renderer:=TMarkDownLatexRenderer.Create(Nil);
+    With Renderer do
+      begin 
+      Options:=[loEnvelope];
+      If ParamStr(2)='' then
+        Writeln(RenderLaTeX(Doc))
+      else
+        begin
+        RenderDocument(Doc,Dest);
+        Dest.SaveToFile(ParamStr(2));
+        end;
+      end;
+  finally
+    Renderer.Free;
+    Source.Free;
+    Dest.Free;
+  end;
+end.
+

+ 8 - 0
packages/fcl-md/fpmake.pp

@@ -90,6 +90,14 @@ begin
       AddUnit('markdown.render');
       AddUnit('markdown.render');
       end;
       end;
 
 
+    T:=P.Targets.AddUnit('markdown.latexrender.pas');
+    with T.Dependencies do
+      begin
+      AddUnit('markdown.elements');
+      AddUnit('markdown.utils');
+      AddUnit('markdown.render');
+      end;
+
     T:=P.Targets.AddUnit('markdown.fpdocrender.pas');
     T:=P.Targets.AddUnit('markdown.fpdocrender.pas');
     with T.Dependencies do
     with T.Dependencies do
       begin
       begin

+ 52 - 0
packages/fcl-md/src/markdown.htmlrender.pas

@@ -53,6 +53,9 @@ type
     Procedure RenderDocument(aDocument : TMarkDownDocument; aDest : TStrings); overload;
     Procedure RenderDocument(aDocument : TMarkDownDocument; aDest : TStrings); overload;
     procedure RenderChildren(aBlock : TMarkDownContainerBlock; aAppendNewLine : Boolean); overload;
     procedure RenderChildren(aBlock : TMarkDownContainerBlock; aAppendNewLine : Boolean); overload;
     function RenderHTML(aDocument : TMarkDownDocument) : string;
     function RenderHTML(aDocument : TMarkDownDocument) : string;
+    procedure RenderHTMLToFile(aDocument : TMarkDownDocument; const aFileName : string);
+    class function FastRender(aDocument : TMarkDownDocument; aOptions : THTMLOptions; aTitle : String = ''; aHead : TStrings = Nil) : String;
+    class procedure FastRenderToFile(aDocument : TMarkDownDocument; const aFileName : string; aOptions : THTMLOptions; aTitle : String = ''; aHead : TStrings = Nil);
     Property HTML : String Read FHTML;
     Property HTML : String Read FHTML;
   published
   published
     Property Options : THTMLOptions Read FOptions Write FOptions;
     Property Options : THTMLOptions Read FOptions Write FOptions;
@@ -313,6 +316,55 @@ begin
   FHTML:='';
   FHTML:='';
 end;
 end;
 
 
+procedure TMarkDownHTMLRenderer.RenderHTMLToFile(aDocument: TMarkDownDocument; const aFileName: string);
+var
+  lHTML : String;
+  lFile : THandle;
+begin
+  lHTML:=RenderHTML(aDocument);
+  lFile:=FileCreate(aFileName);
+  try
+    if lHTML<>'' then
+      FileWrite(lFile,lHTML[1],Length(lHTML)*SizeOf(Char));
+  finally
+    FileClose(lFile);
+  end;
+end;
+
+class function TMarkDownHTMLRenderer.FastRender(aDocument: TMarkDownDocument; aOptions: THTMLOptions; aTitle: String;
+  aHead: TStrings): String;
+var
+  lRender : TMarkDownHTMLRenderer;
+begin
+  lRender:=TMarkDownHTMLRenderer.Create(Nil);
+  try
+    lRender.Options:=aOptions;
+    lRender.Title:=aTitle;
+    if assigned(aHead) then
+      lRender.Head.Assign(aHead);
+    Result:=lRender.RenderHTML(aDocument);
+  finally
+    lRender.Free;
+  end;
+end;
+
+class procedure TMarkDownHTMLRenderer.FastRenderToFile(aDocument: TMarkDownDocument; const aFileName: string;
+  aOptions: THTMLOptions; aTitle: String; aHead: TStrings);
+var
+  lRender : TMarkDownHTMLRenderer;
+begin
+  lRender:=TMarkDownHTMLRenderer.Create(Nil);
+  try
+    lRender.Options:=aOptions;
+    lRender.Title:=aTitle;
+    if assigned(aHead) then
+      lRender.Head.Assign(aHead);
+    lRender.RenderHTMLToFile(aDocument,aFileName);
+  finally
+    lRender.Free;
+  end;
+end;
+
 
 
 procedure THTMLMarkDownTextRenderer.Append(const S: String);
 procedure THTMLMarkDownTextRenderer.Append(const S: String);
 begin
 begin

+ 791 - 0
packages/fcl-md/src/markdown.latexrender.pas

@@ -0,0 +1,791 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2025 by Michael Van Canneyt
+
+    Markdown LaTeX renderer.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ *********************************************************************}
+
+unit MarkDown.LatexRender;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+{$IFDEF FPC_DOTTEDUNITS}
+  System.Classes, System.SysUtils, System.StrUtils, System.Contnrs, 
+{$ELSE}
+  Classes, SysUtils, strutils, contnrs, 
+{$ENDIF}  
+  MarkDown.Elements, 
+  MarkDown.Render, 
+  MarkDown.Utils;
+
+type
+  { TMarkDownLaTeXRenderer }
+  TLaTeXOption = (loEnvelope, loNumberedSections);
+  TLaTeXOptions = set of TLaTeXOption;
+
+  TMarkDownLaTeXRenderer = class(TMarkDownRenderer)
+  private
+    FBuilder: TStringBuilder;
+    FHead: TStrings;
+    FLaTeX: String;
+    FOptions: TLaTeXOptions;
+    FTitle: String;
+    FAuthor: String;
+    procedure SetHead(const aValue: TStrings);
+  Protected
+    Procedure Append(const aContent : String);
+    Procedure AppendNL(const aContent : String = '');
+    Property Builder : TStringBuilder Read FBuilder;
+  public
+    constructor Create(aOwner : TComponent); override;
+    destructor destroy; override;
+    Procedure RenderDocument(aDocument : TMarkDownDocument); override;overload;
+    Procedure RenderDocument(aDocument : TMarkDownDocument; aDest : TStrings); overload;
+    procedure RenderChildren(aBlock : TMarkDownContainerBlock; aAppendNewLine : Boolean); overload;
+    function RenderLaTeX(aDocument : TMarkDownDocument) : string;
+    function EscapeLaTeX(const S: String): String;
+  published
+    Property Options : TLaTeXOptions Read FOptions Write FOptions;
+    property Title : String Read FTitle Write FTitle;
+    property Author : String Read FAuthor Write FAuthor;
+    property Head : TStrings Read FHead Write SetHead;
+  end;
+
+  { TLaTeXMarkDownBlockRenderer }
+
+  TLaTeXMarkDownBlockRenderer = Class (TMarkDownBlockRenderer)
+  Private
+    function GetLaTeXRenderer: TMarkDownLaTeXRenderer;
+  protected
+    procedure Append(const S : String); inline;
+    procedure AppendNl(const S : String = ''); inline;
+    function HasOption(aOption : TLaTeXOption) : Boolean;
+    function Escape(const S: String): String;
+  public
+    property LaTeXRenderer : TMarkDownLaTeXRenderer Read GetLaTeXRenderer;
+  end;
+  TLaTeXMarkDownBlockRendererClass = class of TLaTeXMarkDownBlockRenderer;
+
+  { TLaTeXMarkDownTextRenderer }
+
+  TLaTeXMarkDownTextRenderer = class(TMarkDownTextRenderer)
+  Private
+    FStyleStack: Array of TNodeStyle;
+    FStyleStackLen : Integer;
+    FLastStyles : TNodeStyles;
+    function GetLaTeXRenderer: TMarkDownLaTeXRenderer;
+    function GetNodeTag(aElement: TMarkDownTextNode; Closing: Boolean): string;
+  protected
+    procedure PushStyle(aStyle : TNodeStyle);
+    function PopStyles(aStyle: TNodeStyles): TNodeStyle;
+    procedure PopStyle(aStyle : TNodeStyle);
+    procedure Append(const S : String); inline;
+    procedure DoRender(aElement: TMarkDownTextNode); override;
+    function Escape(const S: String): String;
+    procedure EmitStyleDiff(aStyles : TNodeStyles);
+  Public
+    procedure BeginBlock; override;
+    procedure EndBlock; override;
+    property LaTeXRenderer : TMarkDownLaTeXRenderer Read GetLaTeXRenderer;
+  end;
+  TLaTeXMarkDownTextRendererClass = class of TLaTeXMarkDownTextRenderer;
+
+  { TLaTeXParagraphBlockRenderer }
+
+  TLaTeXParagraphBlockRenderer = class (TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure DoRender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownQuoteBlockRenderer }
+
+  TLaTeXMarkDownQuoteBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownTextBlockRenderer }
+
+  TLaTeXMarkDownTextBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure DoRender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownListBlockRenderer }
+
+  TLaTeXMarkDownListBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownListItemBlockRenderer }
+
+  TLaTeXMarkDownListItemBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownCodeBlockRenderer }
+
+  TLaTeXMarkDownCodeBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownHeadingBlockRenderer }
+
+  TLaTeXMarkDownHeadingBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownThematicBreakBlockRenderer }
+
+  TLaTeXMarkDownThematicBreakBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownTableBlockRenderer }
+
+  TLaTeXMarkDownTableBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownTableRowBlockRenderer }
+
+  TLaTeXMarkDownTableRowBlockRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+  { TLaTeXMarkDownDocumentRenderer }
+
+  TLaTeXMarkDownDocumentRenderer = class(TLaTeXMarkDownBlockRenderer)
+  protected
+    procedure Dorender(aElement : TMarkDownBlock); override;
+  public
+    class function BlockClass : TMarkDownBlockClass; override;
+  end;
+
+
+implementation
+
+type
+
+  { TStringBuilderHelper }
+
+  TStringBuilderHelper = class helper for TAnsiStringBuilder
+    function Append(aAnsiString : Ansistring) : TAnsiStringBuilder;
+  end;
+
+
+function TStringBuilderHelper.Append(aAnsiString: Ansistring): TAnsiStringBuilder;
+begin
+  Result:=Inherited Append(aAnsiString,0,System.Length(aAnsistring))
+end;
+
+{ TLaTeXMarkDownBlockRenderer }
+
+function TLaTeXMarkDownBlockRenderer.GetLaTeXRenderer: TMarkDownLaTeXRenderer;
+begin
+  if Renderer is TMarkDownLaTeXRenderer then
+    Result:=TMarkDownLaTeXRenderer(Renderer)
+  else
+    Result:=Nil;
+end;
+
+procedure TLaTeXMarkDownBlockRenderer.Append(const S: String);
+begin
+  LaTeXRenderer.Append(S);
+end;
+
+procedure TLaTeXMarkDownBlockRenderer.AppendNl(const S: String);
+begin
+  LaTeXRenderer.AppendNL(S);
+end;
+
+function TLaTeXMarkDownBlockRenderer.HasOption(aOption: TLaTeXOption): Boolean;
+begin
+  Result:=(Self.Renderer is TMarkDownLaTeXRenderer);
+  if Result then
+    Result:=aOption in TMarkDownLaTeXRenderer(Renderer).Options;
+end;
+
+function TLaTeXMarkDownBlockRenderer.Escape(const S: String): String;
+begin
+  Result:=LaTeXRenderer.EscapeLaTeX(S);
+end;
+
+{ TMarkDownLaTeXRenderer }
+
+procedure TMarkDownLaTeXRenderer.SetHead(const aValue: TStrings);
+begin
+  if FHead=aValue then Exit;
+  FHead:=aValue;
+end;
+
+procedure TMarkDownLaTeXRenderer.Append(const aContent: String);
+begin
+  FBuilder.Append(aContent);
+end;
+
+procedure TMarkDownLaTeXRenderer.AppendNL(const aContent: String);
+begin
+  if aContent<>'' then
+    FBuilder.Append(aContent);
+  FBuilder.Append(sLineBreak);
+end;
+
+constructor TMarkDownLaTeXRenderer.Create(aOwner: TComponent);
+begin
+  inherited Create(aOwner);
+  FHead:=TStringList.Create;
+end;
+
+destructor TMarkDownLaTeXRenderer.destroy;
+begin
+  FreeAndNil(FHead);
+  inherited destroy;
+end;
+
+procedure TMarkDownLaTeXRenderer.RenderDocument(aDocument: TMarkDownDocument);
+begin
+  FBuilder:=TStringBuilder.Create;
+  try
+    RenderBlock(aDocument);
+    FLaTeX:=FBuilder.ToString;
+  finally
+    FreeAndNil(FBuilder);
+  end;
+end;
+
+procedure TMarkDownLaTeXRenderer.RenderDocument(aDocument: TMarkDownDocument; aDest: TStrings);
+begin
+  aDest.Text:=RenderLaTeX(aDocument);
+end;
+
+procedure TMarkDownLaTeXRenderer.RenderChildren(aBlock: TMarkDownContainerBlock; aAppendNewLine: Boolean);
+var
+  i,iMax : integer;
+begin
+  iMax:=aBlock.Blocks.Count-1;
+  for I:=0 to iMax do
+    begin
+    if aAppendNewLine and (I>0) then
+      AppendNl();
+    RenderBlock(aBlock.Blocks[I]);
+    end;
+end;
+
+function TMarkDownLaTeXRenderer.RenderLaTeX(aDocument: TMarkDownDocument): string;
+begin
+  RenderDocument(aDocument);
+  Result:=FLaTeX;
+  FLaTeX:='';
+end;
+
+function TMarkDownLaTeXRenderer.EscapeLaTeX(const S: String): String;
+var
+  i: Integer;
+  c: Char;
+begin
+  Result := '';
+  for i := 1 to Length(S) do
+  begin
+    c := S[i];
+    case c of
+      '\': Result := Result + '\textbackslash{}';
+      '{': Result := Result + '\{';
+      '}': Result := Result + '\}';
+      '$': Result := Result + '\$';
+      '&': Result := Result + '\&';
+      '#': Result := Result + '\#';
+      '^': Result := Result + '\textasciicircum{}';
+      '_': Result := Result + '\_';
+      '%': Result := Result + '\%';
+      '~': Result := Result + '\textasciitilde{}';
+    else
+      Result := Result + c;
+    end;
+  end;
+end;
+
+{ TLaTeXMarkDownTextRenderer }
+
+procedure TLaTeXMarkDownTextRenderer.Append(const S: String);
+begin
+  LaTeXRenderer.Append(S);
+end;
+
+function TLaTeXMarkDownTextRenderer.Escape(const S: String): String;
+begin
+  Result:=LaTeXRenderer.EscapeLaTeX(S);
+end;
+
+function TLaTeXMarkDownTextRenderer.GetLaTeXRenderer: TMarkDownLaTeXRenderer;
+begin
+  if Renderer is TMarkDownLaTeXRenderer then
+    Result:=TMarkDownLaTeXRenderer(Renderer)
+  else
+    Result:=Nil;
+end;
+
+function TLaTeXMarkDownTextRenderer.GetNodeTag(aElement: TMarkDownTextNode; Closing: Boolean): string;
+var
+  lUrl: String;
+begin
+  Result := '';
+  case aElement.Kind of
+    nkCode:
+      if Closing then Result := '}' else Result := '\texttt{';
+    nkURI, nkEmail:
+      begin
+        lUrl := '';
+        if aElement.HasAttrs then
+          aElement.Attrs.TryGet('href', lUrl);
+          
+        if Closing then 
+          Result := '}' 
+        else 
+          Result := '\href{' + lUrl + '}{';
+      end;
+    nkImg:
+      begin
+        lUrl := '';
+        if aElement.HasAttrs then
+          aElement.Attrs.TryGet('src', lUrl);
+
+        if Closing then 
+          Result := '}' 
+        else 
+          Result := '\includegraphics{' + lUrl + '}{';
+      end;
+  end;
+end;
+
+procedure TLaTeXMarkDownTextRenderer.PushStyle(aStyle: TNodeStyle);
+begin
+  case aStyle of
+    nsStrong: Append('\textbf{');
+    nsEmph: Append('\textit{');
+    nsDelete: Append('\sout{'); // Requires ulem package
+  end;
+  if FStyleStackLen=Length(FStyleStack) then
+    SetLength(FStyleStack,FStyleStackLen+3);
+  FStyleStack[FStyleStackLen]:=aStyle;
+  Inc(FStyleStackLen);
+end;
+
+function TLaTeXMarkDownTextRenderer.Popstyles(aStyle: TNodeStyles) : TNodeStyle;
+begin
+  if (FStyleStackLen>0) and (FStyleStack[FStyleStackLen-1] in aStyle) then
+    begin
+    Result:=FStyleStack[FStyleStackLen-1];
+    Append('}');
+    Dec(FStyleStackLen);
+    end;
+end;
+
+procedure TLaTeXMarkDownTextRenderer.PopStyle(aStyle: TNodeStyle);
+begin
+  if (FStyleStackLen>0) and (FStyleStack[FStyleStackLen-1]=aStyle) then
+    begin
+    Append('}');
+    Dec(FStyleStackLen);
+    end;
+end;
+
+procedure TLaTeXMarkDownTextRenderer.EmitStyleDiff(aStyles : TNodeStyles);
+var
+  lRemove : TNodeStyles;
+  lAdd : TNodeStyles;
+  S : TNodeStyle;
+begin
+  lRemove:=[];
+  lAdd:=[];
+  For S in TNodeStyle do
+    begin
+    if (S in Self.FLastStyles) and Not (S in aStyles) then
+      Include(lRemove,S);
+    if (S in aStyles) and Not (S in Self.FLastStyles) then
+      Include(lAdd,S);
+    end;
+  While lRemove<>[] do
+    begin
+    S:=Self.PopStyles(lRemove);
+    Exclude(lRemove,S);
+    end;
+  For S in TNodeStyle do
+    if S in lAdd then
+      Self.PushStyle(S);
+  Self.FLastStyles:=aStyles;
+end;
+
+procedure TLaTeXMarkDownTextRenderer.DoRender(aElement: TMarkDownTextNode);
+var
+  lTag : string;
+begin
+  Self.EmitStyleDiff(aElement.Styles);
+  if aElement.Kind <> nkText then
+  begin
+    lTag := Self.GetNodeTag(aElement, False);
+    Append(lTag);
+  end;
+
+  if aElement.Kind = nkImg then
+  begin
+     // Img handling logic here...
+  end;
+
+  if aElement.NodeText<>'' then
+    Append(Self.Escape(aElement.NodeText));
+
+  if aElement.Kind <> nkText then
+  begin
+    lTag := Self.GetNodeTag(aElement, True);
+    Append(lTag);
+  end;
+  
+  aElement.Active:=False;
+end;
+
+procedure TLaTeXMarkDownTextRenderer.BeginBlock;
+begin
+  inherited BeginBlock;
+  Self.FStyleStackLen:=0;
+  Self.FLastStyles:=[];
+end;
+
+procedure TLaTeXMarkDownTextRenderer.EndBlock;
+begin
+  While (Self.FStyleStackLen>0) do
+    Self.Popstyle(Self.FStyleStack[Self.FStyleStackLen-1]);
+  Self.FLastStyles:=[];
+  inherited EndBlock;
+end;
+
+{ TLaTeXParagraphBlockRenderer }
+
+procedure TLaTeXParagraphBlockRenderer.DoRender(aElement: TMarkDownBlock);
+var
+  lNode : TMarkDownParagraphBlock absolute aElement;
+begin
+  // LaTeX paragraphs are separated by blank lines.
+  // No special environment needed usually, unless we want to enforce spacing.
+  Renderer.RenderChildren(lNode);
+  AppendNl; // Blank line after paragraph
+  AppendNl;
+end;
+
+class function TLaTeXParagraphBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownParagraphBlock;
+end;
+
+{ TLaTeXMarkDownTextBlockRenderer }
+
+class function TLaTeXMarkDownTextBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownTextBlock;
+end;
+
+procedure TLaTeXMarkDownTextBlockRenderer.DoRender(aElement: TMarkDownBlock);
+var
+  lNode : TMarkDownTextBlock absolute aElement;
+begin
+  if assigned(lNode) and assigned(lNode.Nodes) then
+    Renderer.RenderTextNodes(lNode.Nodes);
+end;
+
+{ TLaTeXMarkDownQuoteBlockRenderer }
+
+procedure TLaTeXMarkDownQuoteBlockRenderer.dorender(aElement: TMarkDownBlock);
+var
+  lNode : TMarkdownQuoteBlock absolute aElement;
+begin
+  AppendNl('\begin{quote}');
+  Renderer.RenderChildren(lNode);
+  AppendNl('\end{quote}');
+end;
+
+class function TLaTeXMarkDownQuoteBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownQuoteBlock;
+end;
+
+{ TLaTeXMarkDownListBlockRenderer }
+
+procedure TLaTeXMarkDownListBlockRenderer.Dorender(aElement : TMarkDownBlock);
+var
+  lNode : TMarkDownListBlock absolute aElement;
+begin
+  if not lNode.Ordered then
+    AppendNl('\begin{itemize}')
+  else
+    AppendNl('\begin{enumerate}');
+    
+  Renderer.RenderChildren(lNode);
+  
+  if lNode.Ordered then
+    AppendNl('\end{enumerate}')
+  else
+    AppendNl('\end{itemize}');
+end;
+
+class function TLaTeXMarkDownListBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownListBlock;
+end;
+
+
+{ TLaTeXMarkDownListItemBlockRenderer }
+
+procedure TLaTeXMarkDownListItemBlockRenderer.Dorender(aElement : TMarkDownBlock);
+var
+  lItemBlock : TMarkDownListItemBlock absolute aElement;
+  lBlock : TMarkDownBlock;
+  lPar : TMarkDownParagraphBlock absolute lBlock;
+  
+  function IsPlainBlock(aBlock : TMarkDownBlock) : boolean;
+  begin
+    Result:=(aBlock is TMarkDownParagraphBlock)
+             and (aBlock as TMarkDownParagraphBlock).isPlainPara
+             and not (lItemblock.parent as TMarkDownListBlock).loose
+  end;
+
+begin
+  Append('\item ');
+  For lBlock in lItemBlock.Blocks do
+    if IsPlainBlock(lBlock) then
+      LaTeXRenderer.RenderChildren(lPar,True)
+    else
+      Renderer.RenderBlock(lBlock);
+  AppendNl;
+end;
+
+class function TLaTeXMarkDownListItemBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownListItemBlock;
+end;
+
+{ TLaTeXMarkDownCodeBlockRenderer }
+
+procedure TLaTeXMarkDownCodeBlockRenderer.Dorender(aElement : TMarkDownBlock);
+var
+  lNode : TMarkDownCodeBlock absolute aElement;
+  lBlock : TMarkDownBlock;
+begin
+  AppendNl('\begin{verbatim}');
+  for lBlock in LNode.Blocks do
+    begin
+    Renderer.RenderCodeBlock(LBlock,lNode.Lang);
+    AppendNl;
+    end;
+  AppendNl('\end{verbatim}');
+end;
+
+class function TLaTeXMarkDownCodeBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownCodeBlock;
+end;
+
+{ TLaTeXMarkDownThematicBreakBlockRenderer }
+
+procedure TLaTeXMarkDownThematicBreakBlockRenderer.Dorender(aElement : TMarkDownBlock);
+begin
+  if Not Assigned(aElement) then
+    exit;
+  AppendNl('\hrule');
+end;
+
+class function TLaTeXMarkDownThematicBreakBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownThematicBreakBlock;
+end;
+
+{ TLaTeXMarkDownTableBlockRenderer }
+
+procedure TLaTeXMarkDownTableBlockRenderer.Dorender(aElement: TMarkDownBlock);
+var
+  lNode : TMarkDownTableBlock absolute aElement;
+  i : integer;
+  lCols: String;
+  c: TCellAlign;
+begin
+  // Construct column definition
+  lCols := '';
+  for c in lNode.Columns do
+  begin
+    case c of
+      caLeft: lCols := lCols + 'l|';
+      caRight: lCols := lCols + 'r|';
+      caCenter: lCols := lCols + 'c|';
+    end;
+  end;
+  if Length(lCols) > 0 then
+    lCols := '|' + lCols;
+
+  AppendNl('\begin{tabular}{' + lCols + '}');
+  AppendNl('\hline');
+  
+  // Header
+  Renderer.RenderBlock(lNode.blocks[0]);
+  AppendNl('\hline');
+  
+  if lNode.blocks.Count > 1 then
+  begin
+    for i := 1 to lNode.blocks.Count -1  do
+      Renderer.RenderBlock(lnode.blocks[i]);
+    AppendNl('\hline');
+  end;
+  AppendNl('\end{tabular}');
+end;
+
+class function TLaTeXMarkDownTableBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownTableBlock;
+end;
+
+{ TLaTeXMarkDownTableRowBlockRenderer }
+
+procedure TLaTeXMarkDownTableRowBlockRenderer.Dorender(aElement : TMarkDownBlock);
+var
+  lNode : TMarkDownTableRowBlock absolute aElement;
+  i, lCount : integer;
+begin
+  lCount:=lNode.blocks.Count;
+  for i:=0 to lCount-1 do
+    begin
+    if i > 0 then Append(' & ');
+    Renderer.RenderBlock(lNode.blocks[i]);
+    end;
+  AppendNl(' \');
+end;
+
+class function TLaTeXMarkDownTableRowBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownTableRowBlock;
+end;
+
+{ TLaTeXMarkDownHeadingBlockRenderer }
+
+procedure TLaTeXMarkDownHeadingBlockRenderer.Dorender(aElement : TMarkDownBlock);
+var
+  lNode : TMarkDownHeadingBlock absolute aElement;
+  lSection: String;
+  lNumbered: Boolean;
+begin
+  lNumbered := HasOption(loNumberedSections);
+  case lNode.Level of
+    1: lSection := 'section';
+    2: lSection := 'subsection';
+    3: lSection := 'subsubsection';
+    4: lSection := 'paragraph';
+    5: lSection := 'subparagraph';
+    else lSection := 'textbf'; // Fallback
+  end;
+  
+  if not lNumbered then
+    lSection := lSection + '*';
+
+  Append('\' + lSection + '{');
+  Renderer.RenderChildren(lNode);
+  Append('}');
+  AppendNl;
+end;
+
+class function TLaTeXMarkDownHeadingBlockRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownHeadingBlock;
+end;
+
+
+{ TLaTeXMarkDownDocumentRenderer }
+
+procedure TLaTeXMarkDownDocumentRenderer.Dorender(aElement: TMarkDownBlock);
+var
+  H : String;
+begin
+  if HasOption(loEnvelope) then
+    begin
+    AppendNL('\documentclass{article}');
+    AppendNL('\usepackage[utf8]{inputenc}');
+    AppendNL('\usepackage{graphicx}');
+    AppendNL('\usepackage{hyperref}');
+    AppendNL('\usepackage{ulem}'); // For strikethrough
+    
+    if LaTeXRenderer.Title<>'' then
+      AppendNL('\title{' + LaTeXRenderer.EscapeLaTeX(LaTeXRenderer.Title) + '}');
+    if LaTeXRenderer.Author<>'' then
+      AppendNL('\author{' + LaTeXRenderer.EscapeLaTeX(LaTeXRenderer.Author) + '}');
+      
+    for H in LaTeXRenderer.Head do
+      AppendNL(H);
+      
+    AppendNL('\begin{document}');
+    
+    if LaTeXRenderer.Title<>'' then
+      AppendNL('\maketitle');
+    end;
+    
+  Renderer.RenderChildren(aElement as TMarkDownDocument);
+  
+  if HasOption(loEnvelope) then
+    begin
+    AppendNL('\end{document}');
+    end;
+end;
+
+class function TLaTeXMarkDownDocumentRenderer.BlockClass: TMarkDownBlockClass;
+begin
+  Result:=TMarkDownDocument
+end;
+
+
+initialization
+  TLaTeXMarkDownHeadingBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXParagraphBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownQuoteBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownTextBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownListBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownListItemBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownCodeBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownThematicBreakBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownTableBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownTableRowBlockRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownDocumentRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+  TLaTeXMarkDownTextRenderer.RegisterRenderer(TMarkDownLaTeXRenderer);
+end.

+ 30 - 0
packages/fcl-md/src/markdown.parser.pas

@@ -158,10 +158,14 @@ type
     procedure ParseInline(aParent : TMarkDownContainerBlock; const aLine : String);
     procedure ParseInline(aParent : TMarkDownContainerBlock; const aLine : String);
     // Parse the markDown in strings
     // Parse the markDown in strings
     function Parse(aSource: TStrings): TMarkDownDocument; overload;
     function Parse(aSource: TStrings): TMarkDownDocument; overload;
+    // Utility function: Parse the markDown in file aFileName.
+    function ParseFile(const aFilename : string): TMarkDownDocument;
     // Helper : is the last block a plain paragraph ?
     // Helper : is the last block a plain paragraph ?
     class function InPara(blocks : TMarkDownBlockList; canBeQuote : boolean) : boolean;
     class function InPara(blocks : TMarkDownBlockList; canBeQuote : boolean) : boolean;
     // Helper to quickly parse a stringlist into a markdown document
     // Helper to quickly parse a stringlist into a markdown document
     class function FastParse(aSource: TStrings; aOptions: TMarkDownOptions): TMarkDownDocument;
     class function FastParse(aSource: TStrings; aOptions: TMarkDownOptions): TMarkDownDocument;
+    // Helper to quickly parse a stringlist into a markdown document
+    class function FastParseFile(const aFileName : string; aOptions: TMarkDownOptions): TMarkDownDocument;
     // State control in lazy continuation .
     // State control in lazy continuation .
     property Lazy : Boolean Read FLazy Write FLazy;
     property Lazy : Boolean Read FLazy Write FLazy;
     // HTML entities to convert
     // HTML entities to convert
@@ -348,6 +352,19 @@ begin
   end;
   end;
 end;
 end;
 
 
+class function TMarkDownParser.FastParseFile(const aFileName: string; aOptions: TMarkDownOptions): TMarkDownDocument;
+var
+  lFile : TStrings;
+begin
+  lFile:=TStringList.Create;
+  try
+    lFile.LoadFromFile(aFileName,TEncoding.UTF8);
+    Result:=FastParse(lFile,aOptions);
+  finally
+    lFile.Free;
+  end;
+end;
+
 
 
 procedure TMarkDownParser.CollectEntities(aList :TFPStringHashTable);
 procedure TMarkDownParser.CollectEntities(aList :TFPStringHashTable);
 
 
@@ -424,6 +441,19 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TMarkDownParser.ParseFile(const aFilename: string): TMarkDownDocument;
+var
+  lFile : TStrings;
+begin
+  lFile:=TStringList.Create;
+  try
+    lFile.LoadFromFile(aFileName,TEncoding.UTF8);
+    Result:=Parse(lFile);
+  finally
+    lFile.Free;
+  end;
+end;
+
 
 
 function TMarkDownParser.NextLine: TMarkDownLine;
 function TMarkDownParser.NextLine: TMarkDownLine;
 
 

+ 10 - 0
packages/fcl-md/tests/testmd.lpi

@@ -121,6 +121,16 @@
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <UnitName Value="Markdown.FPDocRender"/>
         <UnitName Value="Markdown.FPDocRender"/>
       </Unit>
       </Unit>
+      <Unit>
+        <Filename Value="../src/markdown.latexrender.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="MarkDown.LatexRender"/>
+      </Unit>
+      <Unit>
+        <Filename Value="utest.markdown.latexrender.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="UTest.Markdown.LaTeXRender"/>
+      </Unit>
     </Units>
     </Units>
   </ProjectOptions>
   </ProjectOptions>
   <CompilerOptions>
   <CompilerOptions>

+ 1 - 1
packages/fcl-md/tests/testmd.lpr

@@ -6,7 +6,7 @@ uses
   cwstring,Classes, consoletestrunner, utest.markdown.utils, markdown.elements, Markdown.HTMLEntities,
   cwstring,Classes, consoletestrunner, utest.markdown.utils, markdown.elements, Markdown.HTMLEntities,
   markdown.htmlrender, markdown.inlinetext, markdown.line, markdown.parser, markdown.render, markdown.scanner,
   markdown.htmlrender, markdown.inlinetext, markdown.line, markdown.parser, markdown.render, markdown.scanner,
   markdown.utils, utest.markdown.scanner, utest.markdown.inlinetext, utest.markdown.htmlrender, utest.markdown.parser,
   markdown.utils, utest.markdown.scanner, utest.markdown.inlinetext, utest.markdown.htmlrender, utest.markdown.parser,
-  utest.markdown.fpdocrender,markdown.processors;
+  utest.markdown.fpdocrender,markdown.latexrender,utest.markdown.latexrender,markdown.processors;
 
 
 type
 type
 
 

+ 352 - 0
packages/fcl-md/tests/utest.markdown.latexrender.pas

@@ -0,0 +1,352 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2025 by Michael Van Canneyt
+
+    Markdown LaTeX renderer tests
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ *********************************************************************}
+unit UTest.Markdown.LaTeXRender;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry,
+  MarkDown.Elements,
+  MarkDown.LatexRender;
+
+type
+
+  { TTestLaTeXRender }
+
+  TTestLaTeXRender = Class(TTestCase)
+  private
+    FLaTeXRenderer : TMarkDownLaTeXRenderer;
+    FDocument: TMarkDownDocument;
+  Public
+    Procedure SetUp; override;
+    Procedure TearDown; override;
+    function CreateTextBlock(aParent: TMarkdownBlock; const aText,aTextNode: string; aNodeStyle : TNodeStyles=[]): TMarkDownTextBlock;
+    function CreateParagraphBlock(const aTextNode: string): TMarkdownBlock;
+    function CreateQuotedBlock(const aTextNode: string): TMarkdownBlock;
+    function CreateHeadingBlock(const aTextNode: string; aLevel : integer): TMarkdownBlock;
+    function CreateListBlock(aOrdered : boolean; const aListItemText : string): TMarkDownListBlock;
+    function CreateListItemBlock(aParent: TMarkDownContainerBlock; const aText: string): TMarkDownListItemBlock;
+    function AppendTextNode(aBlock: TMarkDownTextBlock; const aText: string; aNodeStyle : TNodeStyles) : TMarkDownTextNode;
+    procedure TestRender(const aLaTeX : string);
+    Property Renderer : TMarkDownLaTeXRenderer Read FLaTeXRenderer;
+    Property Document : TMarkDownDocument Read FDocument;
+  Published
+    procedure TestHookup;
+    procedure TestEmpty;
+    procedure TestEmptyNoEnvelope;
+    procedure TestEmptyTitle;
+    procedure TestTextBlockEmpty;
+    procedure TestTextBlockText;
+    procedure TestTextBlockTextEscaping;
+    procedure TestTextBlockTextStrong;
+    procedure TestTextBlockTextEmph;
+    procedure TestTextBlockTextDelete;
+    procedure TestTextBlockTextStrongEmph;
+    procedure TestPragraphBlockEmpty;
+    procedure TestPragraphBlockText;
+    procedure TestQuotedBlockEmpty;
+    procedure TestQuotedBlockText;
+    procedure TestHeadingBlockEmpty;
+    procedure TestHeadingBlockText;
+    procedure TestHeadingBlockTextLevel2;
+    procedure TestUnorderedListEmpty;
+    procedure TestUnorderedListOneItem;
+  end;
+
+implementation
+
+{ TTestLaTeXRender }
+
+procedure TTestLaTeXRender.SetUp;
+
+begin
+  FLaTeXRenderer:=TMarkDownLaTeXRenderer.Create(Nil);
+  FDocument:=TMarkDownDocument.Create(Nil,1);
+end;
+
+
+procedure TTestLaTeXRender.TearDown;
+
+begin
+  FreeAndNil(FDocument);
+  FreeAndNil(FLaTeXRenderer);
+end;
+
+
+function TTestLaTeXRender.CreateTextBlock(aParent: TMarkdownBlock; const aText, aTextNode: string; aNodeStyle: TNodeStyles): TMarkDownTextBlock;
+
+begin
+  Result:=TMarkDownTextBlock.Create(aParent,1,aText);
+  if aTextNode<>'' then
+    AppendTextNode(Result,aTextNode,aNodeStyle);
+end;
+
+function TTestLaTeXRender.CreateParagraphBlock(const aTextNode: string): TMarkdownBlock;
+
+begin
+  Result:=TMarkDownParagraphBlock.Create(FDocument,1);
+  if aTextNode<>'' then
+    CreateTextBlock(Result,aTextNode,aTextNode);
+end;
+
+function TTestLaTeXRender.CreateQuotedBlock(const aTextNode: string): TMarkdownBlock;
+
+begin
+  Result:=TMarkDownQuoteBlock.Create(FDocument,1);
+  if aTextNode<>'' then
+    CreateTextBlock(Result,aTextNode,aTextNode);
+end;
+
+function TTestLaTeXRender.CreateHeadingBlock(const aTextNode: string; aLevel: integer): TMarkdownBlock;
+
+begin
+  Result:=TMarkDownHeadingBlock.Create(FDocument,1,aLevel);
+  if aTextNode<>'' then
+    CreateTextBlock(Result,aTextNode,aTextNode);
+end;
+
+function TTestLaTeXRender.CreateListItemBlock(aParent: TMarkDownContainerBlock; const aText: string): TMarkDownListItemBlock;
+
+var
+  lPar : TMarkDownParagraphBlock;
+begin
+  Result:=TMarkDownListItemBlock.Create(aParent,1);
+  lPar:=TMarkDownParagraphBlock.Create(Result,1);
+  CreateTextBlock(lPar,'',aText);
+end;
+
+
+function TTestLaTeXRender.CreateListBlock(aOrdered: boolean; const aListItemText: string): TMarkDownListBlock;
+
+begin
+  Result:=TMarkDownListBlock.Create(FDocument,1);
+  Result.ordered:=aOrdered;
+  if aListItemText<>'' then
+    CreateListItemBlock(Result,aListItemText);
+end;
+
+function TTestLaTeXRender.AppendTextNode(aBlock: TMarkDownTextBlock; const aText: string; aNodeStyle: TNodeStyles): TMarkDownTextNode;
+
+var
+  p : TPosition;
+  t : TMarkdownTextNode;
+
+begin
+  if aBlock.Nodes=Nil then
+    aBlock.Nodes:=TMarkDownTextNodeList.Create(True);
+  p.col:=Length(aBlock.Text);
+  p.line:=1;
+  t:=TMarkDownTextNode.Create(p,nkText);
+  t.addText(aText);
+  t.active:=False;
+  T.Styles:=aNodeStyle;
+  aBlock.Nodes.Add(t);
+  Result:=T;
+end;
+
+procedure TTestLaTeXRender.TestRender(const aLaTeX: string);
+
+var
+  L : TStrings;
+
+begin
+  L:=TstringList.Create;
+  try
+    L.SkipLastLineBreak:=True;
+    Renderer.RenderDocument(FDocument,L);
+    assertEquals('Correct latex: ',aLaTeX,L.Text);
+  finally
+    L.Free;
+  end;
+end;
+
+
+procedure TTestLaTeXRender.TestHookup;
+
+begin
+  AssertNotNull('Have renderer',FLaTeXRenderer);
+  AssertNotNull('Have document',FDocument);
+  AssertEquals('Have empty document',0,FDocument.blocks.Count);
+end;
+
+
+procedure TTestLaTeXRender.TestEmpty;
+
+begin
+  Renderer.Options:=[loEnvelope];
+  TestRender('\documentclass{article}'+sLineBreak+
+             '\usepackage[utf8]{inputenc}'+sLineBreak+
+             '\usepackage{graphicx}'+sLineBreak+
+             '\usepackage{hyperref}'+sLineBreak+
+             '\usepackage{ulem}'+sLineBreak+
+             '\begin{document}'+sLineBreak+
+             '\end{document}');
+end;
+
+
+procedure TTestLaTeXRender.TestEmptyNoEnvelope;
+
+begin
+  Renderer.Options:=[];
+  TestRender('');
+end;
+
+
+procedure TTestLaTeXRender.TestEmptyTitle;
+
+begin
+  Renderer.Options:=[loEnvelope];
+  Renderer.Title:='a';
+  TestRender('\documentclass{article}'+sLineBreak+
+             '\usepackage[utf8]{inputenc}'+sLineBreak+
+             '\usepackage{graphicx}'+sLineBreak+
+             '\usepackage{hyperref}'+sLineBreak+
+             '\usepackage{ulem}'+sLineBreak+
+             '\title{a}'+sLineBreak+
+             '\begin{document}'+sLineBreak+
+             '\maketitle'+sLineBreak+
+             '\end{document}');
+end;
+
+
+procedure TTestLaTeXRender.TestTextBlockEmpty;
+
+begin
+  CreateTextBlock(Document,'a','');
+  TestRender('');
+end;
+
+
+procedure TTestLaTeXRender.TestTextBlockText;
+
+begin
+  CreateTextBlock(Document,'a','a');
+  TestRender('a');
+end;
+
+procedure TTestLaTeXRender.TestTextBlockTextEscaping;
+
+begin
+  CreateTextBlock(Document,'a','# $ % ^ & _ { } ~ \');
+  // Expected: \# \$ \% \textasciicircum{} \& \_ \{ \} \textasciitilde{} \textbackslash{}
+  TestRender('\# \$ \% \textasciicircum{} \& \_ \{ \} \textasciitilde{} \textbackslash{}');
+end;
+
+
+procedure TTestLaTeXRender.TestTextBlockTextStrong;
+
+begin
+  CreateTextBlock(Document,'a','a',[nsStrong]);
+  TestRender('\textbf{a}');
+end;
+
+
+procedure TTestLaTeXRender.TestTextBlockTextEmph;
+
+begin
+  CreateTextBlock(Document,'a','a',[nsEmph]);
+  TestRender('\textit{a}');
+end;
+
+
+procedure TTestLaTeXRender.TestTextBlockTextDelete;
+
+begin
+  CreateTextBlock(Document,'a','a',[nsDelete]);
+  TestRender('\sout{a}');
+end;
+
+
+procedure TTestLaTeXRender.TestTextBlockTextStrongEmph;
+
+begin
+  CreateTextBlock(Document,'a','a',[nsStrong,nsEmph]);
+  TestRender('\textbf{\textit{a}}');
+end;
+
+
+procedure TTestLaTeXRender.TestPragraphBlockEmpty;
+
+begin
+  CreateParagraphBlock('');
+  TestRender(sLineBreak); // Blank line
+end;
+
+procedure TTestLaTeXRender.TestQuotedBlockEmpty;
+
+begin
+  CreateQuotedBlock('');
+  TestRender('\begin{quote}'+sLineBreak+'\end{quote}');
+end;
+
+procedure TTestLaTeXRender.TestUnorderedListEmpty;
+
+begin
+  CreateListBlock(false,'');
+  TestRender('\begin{itemize}'+sLineBreak+'\end{itemize}');
+end;
+
+procedure TTestLaTeXRender.TestPragraphBlockText;
+
+begin
+  CreateParagraphBlock('a');
+  TestRender('a'+sLineBreak);
+end;
+
+procedure TTestLaTeXRender.TestQuotedBlockText;
+
+begin
+  CreateQuotedBlock('a');
+  TestRender('\begin{quote}'+sLineBreak+'a\end{quote}');
+end;
+
+procedure TTestLaTeXRender.TestHeadingBlockEmpty;
+
+begin
+  CreateHeadingBlock('',1);
+  TestRender('\section*{}');
+end;
+
+procedure TTestLaTeXRender.TestHeadingBlockText;
+
+begin
+  CreateHeadingBlock('a',1);
+  TestRender('\section*{a}');
+end;
+
+procedure TTestLaTeXRender.TestHeadingBlockTextLevel2;
+
+begin
+  CreateHeadingBlock('a',2);
+  TestRender('\subsection*{a}');
+end;
+
+procedure TTestLaTeXRender.TestUnorderedListOneItem;
+
+begin
+  CreateListBlock(false,'a');
+  // ListItem appends '\item ' then renders children.
+  // Children = Paragraph 'a'. Plain paragraph renders children (text 'a').
+  // Item renderer now adds a newline.
+  // List renderer adds \begin{itemize}\n ... \end{itemize}\n
+  TestRender('\begin{itemize}'+sLineBreak+'\item a'+sLineBreak+'\end{itemize}');
+end;
+
+
+initialization
+  Registertest(TTestLaTeXRender);
+end.

+ 1 - 8
packages/fpmake.pp

@@ -4,14 +4,7 @@
 {$mode objfpc}{$H+}
 {$mode objfpc}{$H+}
 program fpmake;
 program fpmake;
 
 
-uses
-{$ifdef unix}
- cwstring,
-{$ifndef NO_THREADING}
- cthreads,
-{$endif}
-{$endif}
- sysutils, Classes, fpmkunit;
+uses {$ifdef unix}cwstring,cthreads,{$endif} sysutils, Classes, fpmkunit;
 
 
 Var
 Var
   TBuild,T : TTarget;
   TBuild,T : TTarget;

+ 66 - 0
packages/rtl-objpas/src/inc/cvarutil.inc

@@ -1400,6 +1400,72 @@ begin
 end;
 end;
 
 
 
 
+Function VariantToUnicodeString(const VargSrc : TVarData) : UnicodeString;
+begin
+  {$IFDEF DEBUG_VARUTILS} if __DEBUG_VARUTILS then begin
+  DumpVariant('VariantToUnicodeString', VargSrc);
+  end; {$ENDIF}
+
+  with VargSrc do
+    case vType and not varTypeMask of
+      0: case vType of
+        varEmpty    : Result := '';
+        varSmallInt : Result := IntToStr(vSmallInt);
+        varShortInt : Result := IntToStr(vShortInt);
+        varInteger  : Result := IntToStr(vInteger);
+{$ifndef FPUNONE}
+        varSingle   : Result := FloatToStr(vSingle);
+        varDouble   : Result := FloatToStr(vDouble);
+        varCurrency : Result := CurrToStr(vCurrency);
+        varDate     : Result := VarDateToString(vDate);
+{$endif}
+        varBoolean  : Result := BoolToStr(vBoolean, True);
+        varVariant  : Result := VariantToUnicodeString(PVarData(vPointer)^);
+        varByte     : Result := IntToStr(vByte);
+        varWord     : Result := IntToStr(vWord);
+        varLongWord : Result := IntToStr(vLongWord);
+        varInt64    : Result := IntToStr(vInt64);
+        varQword    : Result := IntToStr(vQWord);
+        varOleStr   : Result := WideString(Pointer(vOleStr));
+        varString   : Result := AnsiString(vString);
+        varUString  : Result := UnicodeString(vString);
+      else
+        VariantTypeMismatch(vType, varOleStr);
+      end;
+      varByRef: if Assigned(vPointer) then case vType and varTypeMask of
+        varSmallInt : Result := IntToStr(PSmallInt(vPointer)^);
+        varShortInt : Result := IntToStr(PShortInt(vPointer)^);
+        varInteger  : Result := IntToStr(PInteger(vPointer)^);
+{$ifndef FPUNONE}
+        varSingle   : Result := FloatToStr(PSingle(vPointer)^);
+        varDouble   : Result := FloatToStr(PDouble(vPointer)^);
+        varCurrency : Result := CurrToStr(PCurrency(vPointer)^);
+        varDate     : Result := VarDateToString(PDate(vPointer)^);
+{$endif}
+        varBoolean  : Result := BoolToStr(PWordBool(vPointer)^, True);
+        varVariant  : Result := VariantToUnicodeString(PVarData(vPointer)^);
+        varByte     : Result := IntToStr(PByte(vPointer)^);
+        varWord     : Result := IntToStr(PWord(vPointer)^);
+        varLongWord : Result := IntToStr(PLongWord(vPointer)^);
+        varInt64    : Result := IntToStr(PInt64(vPointer)^);
+        varQword    : Result := IntToStr(PQWord(vPointer)^);
+        varOleStr   : Result := WideString(PPointer(vPointer)^);
+        varString   : Result := AnsiString(PPointer(vPointer)^);
+        varUString  : Result := UnicodeString(PPointer(vPointer)^);
+      else { other vtype }
+        VariantTypeMismatch(vType, varUString);
+      end else { pointer is nil }
+        VariantTypeMismatch(vType, varUString);
+    else { array or something like that }
+      VariantTypeMismatch(vType, varUString);
+    end;
+
+  {$IFDEF DEBUG_VARUTILS} if __DEBUG_VARUTILS then begin
+  WriteLn('VariantToUnicodeString -> ', Result);
+  end; {$ENDIF}
+end;
+
+
 Function VariantToShortString(const VargSrc : TVarData) : ShortString;
 Function VariantToShortString(const VargSrc : TVarData) : ShortString;
 begin
 begin
   Result:=VariantToAnsiString(VargSrc);
   Result:=VariantToAnsiString(VargSrc);

+ 34 - 75
packages/rtl-objpas/src/inc/dateutil.inc

@@ -88,6 +88,7 @@ Const
   ApproxDaysPerMonth: Double = 30.4375;
   ApproxDaysPerMonth: Double = 30.4375;
   ApproxDaysPerYear: Double  = 365.25;
   ApproxDaysPerYear: Double  = 365.25;
 
 
+  DateTimeEpsilon: Double = 1E-10;    // about 0.1 ms
 
 
 
 
 { ---------------------------------------------------------------------
 { ---------------------------------------------------------------------
@@ -654,7 +655,6 @@ uses
 {$ENDIF FPC_DOTTEDUNITS}
 {$ENDIF FPC_DOTTEDUNITS}
 
 
 const
 const
-  TDateTimeEpsilon = 2.2204460493e-16;
   HalfMilliSecond = OneMillisecond /2 ;
   HalfMilliSecond = OneMillisecond /2 ;
   SErrLocalTimeInvalid = 'Invalid local time: %s';
   SErrLocalTimeInvalid = 'Invalid local time: %s';
 
 
@@ -1525,33 +1525,48 @@ end;
     Period functions.
     Period functions.
   ---------------------------------------------------------------------}
   ---------------------------------------------------------------------}
 
 
-{
-  These functions are declared as approximate by Borland.
-  A bit strange, since it can be calculated exactly ?
-
-  -- No, because you need rounding or truncating (JM)
-}
+{ Converts a TDateTime variable to a linear number which can be used in
+  arithmetics, in particular for negative values.
 
 
+  TDateTime is not defined in the interval [-1.0..0.0[. Additionally, when
+  negative the time part must be treated using its absolute value (0.25 always
+  means "6 a.m."). And take care of rounding errors
 
 
-function DateTimeToNumber(ADateTime: TDateTime): Double;
+  Example:
+    Dec 29, 1899-12-29 6:00 corresponds to value -1.25, the "linear number" is -0.75 }
+    // Converts TDateTime to linear date number
+function DateTimeToNumber(ADateTime: TDateTime):double;
 begin
 begin
   if ADateTime >= 0  then
   if ADateTime >= 0  then
     Result := ADateTime
     Result := ADateTime
   else
   else
-    Result := int(ADateTime) - frac(ADateTime);
+    Result := Trunc(ADateTime) - Frac(ADateTime);
 end;
 end;
 
 
-function NumberToDateTime(AValue: Double): TDateTime;
+function NumberToDateTime(AValue: double): TDateTime;
+var
+  f: Double;
+  d: Integer;
 begin
 begin
   if AValue >= 0 then
   if AValue >= 0 then
     Result := AValue
     Result := AValue
   else
   else
-    Result := int(AValue) + frac(AValue);
+  begin
+    d := Trunc(AValue) - 1;
+    f := -Frac(AValue);
+    if (f < DateTimeEpsilon) then
+      f := -1.0
+    else if (1.0 - f < DateTimeEpsilon) then
+      f := 0.0
+    else
+      f := 1.0 - f;
+    Result := d - f;
+  end;
 end;
 end;
 
 
-Function DateTimeDiff(const ANow, AThen: TDateTime): TDateTime;
+Function DateTimeDiff(const ANow, AThen: TDateTime): Double;
 begin
 begin
-  Result := NumberToDateTime(DateTimeToNumber(ANow) - DateTimeToNumber(AThen));
+  Result := DateTimeToNumber(ANow) - DateTimeToNumber(AThen);
 end;
 end;
 
 
 Function YearsBetween(const ANow, AThen: TDateTime; AExact : Boolean = False): Integer;
 Function YearsBetween(const ANow, AThen: TDateTime; AExact : Boolean = False): Integer;
@@ -1713,44 +1728,6 @@ end;
     Increment/decrement functions.
     Increment/decrement functions.
   ---------------------------------------------------------------------}
   ---------------------------------------------------------------------}
 
 
-{ TDateTime is not defined in the interval [-1.0..0.0[. Additionally, when
-  negative the time part must be treated using its absolute value (0.25 always
-  means "6 a.m.") -> skip the gap and convert the time part when crossing the
-  gap -- and take care of rounding errors }
-Procedure MaybeSkipTimeWarp(OldDate: TDateTime; var NewDate: TDateTime);
-begin
-  if (OldDate>=0) and (NewDate<-TDateTimeEpsilon) then
-    NewDate:=int(NewDate-1.0+TDateTimeEpsilon)-frac(1.0+frac(NewDate))
-  else if (OldDate<=-1.0) and (NewDate>-1.0+TDateTimeEpsilon) then
-    NewDate:=int(NewDate+1.0-TDateTimeEpsilon)+frac(1.0-abs(frac(1.0+NewDate)));
-end;
-
-
-function IncNegativeTime(AValue, Addend: TDateTime): TDateTime;
-var
-  newtime: tdatetime;
-begin
-  newtime:=-frac(Avalue)+frac(Addend);
-  { handle rounding errors }
-  if SameValue(newtime,int(newtime)+1,TDateTimeEpsilon) then
-    newtime:=int(newtime)+1
-  else if SameValue(newtime,int(newtime),TDateTimeEpsilon) then
-    newtime:=int(newtime);
-  { time underflow -> previous day }
-  if newtime<-TDateTimeEpsilon then
-    begin
-      newtime:=1.0+newtime;
-      avalue:=int(avalue)-1;
-    end
-  { time overflow -> next day }
-  else if newtime>=1.0-TDateTimeEpsilon then
-    begin
-      newtime:=newtime-1.0;
-      avalue:=int(avalue)+1;
-    end;
-  Result:=int(AValue)+int(Addend)-newtime;
-end;
-
 Function IncYear(const AValue: TDateTime; const ANumberOfYears: Integer ): TDateTime;
 Function IncYear(const AValue: TDateTime; const ANumberOfYears: Integer ): TDateTime;
 
 
 Var
 Var
@@ -1772,8 +1749,7 @@ end;
 
 
 Function IncWeek(const AValue: TDateTime; const ANumberOfWeeks: Integer): TDateTime;
 Function IncWeek(const AValue: TDateTime; const ANumberOfWeeks: Integer): TDateTime;
 begin
 begin
-  Result:=AValue+ANumberOfWeeks*7;
-  MaybeSkipTimeWarp(AValue,Result);
+  Result := NumberToDateTime(DateTimeToNumber(AValue) + ANumberOfWeeks * 7);
 end;
 end;
 
 
 
 
@@ -1785,8 +1761,7 @@ end;
 
 
 Function IncDay(const AValue: TDateTime; const ANumberOfDays: Integer): TDateTime;
 Function IncDay(const AValue: TDateTime; const ANumberOfDays: Integer): TDateTime;
 begin
 begin
-  Result:=AValue+ANumberOfDays;
-  MaybeSkipTimeWarp(AValue,Result);
+  Result := NumberToDateTime(DateTimeToNumber(AValue) + ANumberOfDays);
 end;
 end;
 
 
 
 
@@ -1798,11 +1773,7 @@ end;
 
 
 Function IncHour(const AValue: TDateTime; const ANumberOfHours: Int64): TDateTime;
 Function IncHour(const AValue: TDateTime; const ANumberOfHours: Int64): TDateTime;
 begin
 begin
-  if AValue>=0 then
-    Result:=AValue+ANumberOfHours/HoursPerDay
-  else
-    Result:=IncNegativeTime(Avalue,ANumberOfHours/HoursPerDay);
-  MaybeSkipTimeWarp(AValue,Result);
+  Result := NumberToDateTime(DateTimeToNumber(AValue) + ANumberOfHours/24);
 end;
 end;
 
 
 
 
@@ -1814,11 +1785,7 @@ end;
 
 
 Function IncMinute(const AValue: TDateTime; const ANumberOfMinutes: Int64): TDateTime;
 Function IncMinute(const AValue: TDateTime; const ANumberOfMinutes: Int64): TDateTime;
 begin
 begin
-  if AValue>=0 then
-    Result:=AValue+ANumberOfMinutes/MinsPerDay
-  else
-    Result:=IncNegativeTime(Avalue,ANumberOfMinutes/MinsPerDay);
-  MaybeSkipTimeWarp(AValue,Result);
+  Result := NumberToDateTime(DateTimeToNumber(AValue) + ANumberOfMinutes/MinsPerDay);
 end;
 end;
 
 
 
 
@@ -1830,11 +1797,7 @@ end;
 
 
 Function IncSecond(const AValue: TDateTime; const ANumberOfSeconds: Int64): TDateTime;
 Function IncSecond(const AValue: TDateTime; const ANumberOfSeconds: Int64): TDateTime;
 begin
 begin
-  if AValue>=0 then
-    Result:=AValue+ANumberOfSeconds/SecsPerDay
-  else
-    Result:=IncNegativeTime(Avalue,ANumberOfSeconds/SecsPerDay);
-  MaybeSkipTimeWarp(AValue,Result);
+  Result := NumberToDateTime(DateTimeToNumber(AValue) + ANumberOfSeconds/SecsPerDay);
 end;
 end;
 
 
 
 
@@ -1846,11 +1809,7 @@ end;
 
 
 Function IncMilliSecond(const AValue: TDateTime; const ANumberOfMilliSeconds: Int64): TDateTime;
 Function IncMilliSecond(const AValue: TDateTime; const ANumberOfMilliSeconds: Int64): TDateTime;
 begin
 begin
-  if Avalue>=0 then
-    Result:=AValue+ANumberOfMilliSeconds/MSecsPerDay
-  else
-    Result:=IncNegativeTime(Avalue,ANumberOfMilliSeconds/MSecsPerDay);
-  MaybeSkipTimeWarp(AValue,Result);
+  Result := NumberToDateTime(DateTimeToNumber(AValue) + ANumberOfMilliSeconds/MSecsPerDay);
 end;
 end;
 
 
 
 

+ 25 - 10
packages/rtl-objpas/src/inc/rtti.pp

@@ -28,6 +28,20 @@ unit Rtti;
 {$goto on}
 {$goto on}
 {$Assertions on}
 {$Assertions on}
 
 
+{ Note: the Lazarus IDE might have problems to correctly handle some syntax
+        elements or to navigate to the invoke.inc if the main source is
+        navigated inside the IDE; to allow ensure that the InLazIDE define
+        is defined for the CodeTools. To do this do this:
+
+  - go to Tools -> Codetools Defines Editor
+  - go to Edit -> Insert Node Below -> Define Recurse
+  - enter the following values:
+      Name: InLazIDE
+      Description: Define InLazIDE everywhere
+      Variable: InLazIDE
+      Value from text: 1
+}
+
 {$WARN 4055 off : Conversion between ordinals and pointers is not portable}
 {$WARN 4055 off : Conversion between ordinals and pointers is not portable}
 interface
 interface
 
 
@@ -238,7 +252,7 @@ type
     function AsDouble : Double;
     function AsDouble : Double;
     function AsInteger: Integer;
     function AsInteger: Integer;
     function AsError: HRESULT;
     function AsError: HRESULT;
-    function AsChar: AnsiChar; inline;
+    function AsChar: Char; inline;
     function AsAnsiChar: AnsiChar;
     function AsAnsiChar: AnsiChar;
     function AsWideChar: WideChar;
     function AsWideChar: WideChar;
     function AsInt64: Int64;
     function AsInt64: Int64;
@@ -643,8 +657,8 @@ type
   TRttiParameterArray = specialize TArray<TRttiParameter>;
   TRttiParameterArray = specialize TArray<TRttiParameter>;
 
 
   TMethodImplementationCallback = reference to procedure(aUserData: Pointer; const aArgs: TValueArray; out aResult: TValue);
   TMethodImplementationCallback = reference to procedure(aUserData: Pointer; const aArgs: TValueArray; out aResult: TValue);
-  TMethodImplementationCallbackMethod = procedure(aUserData: Pointer; const aArgs: TValueArray; out aResult: TValue) of object; deprecated 'Use TMethodImplementationCallback';
-  TMethodImplementationCallbackProc = procedure(aUserData: Pointer; const aArgs: TValueArray; out aResult: TValue); deprecated 'Use TMethodImplementationCallback';
+  TMethodImplementationCallbackMethod = procedure(aUserData: Pointer; const aArgs: TValueArray; out aResult: TValue) of object; {$ifndef InLazIDE}deprecated 'Use TMethodImplementationCallback';{$endif}
+  TMethodImplementationCallbackProc = procedure(aUserData: Pointer; const aArgs: TValueArray; out aResult: TValue); {$ifndef InLazIDE}deprecated 'Use TMethodImplementationCallback';{$endif}
   TFunctionCallParameterInfoArray = specialize TArray<TFunctionCallParameterInfo>;
   TFunctionCallParameterInfoArray = specialize TArray<TFunctionCallParameterInfo>;
   TPointerArray = specialize TArray<Pointer>;
   TPointerArray = specialize TArray<Pointer>;
 
 
@@ -677,8 +691,8 @@ type
     function GetFlags: TFunctionCallFlags; virtual; abstract;
     function GetFlags: TFunctionCallFlags; virtual; abstract;
   public type
   public type
     TCallback = reference to procedure(aInvokable: TRttiInvokableType; const aArgs: TValueArray; out aResult: TValue);
     TCallback = reference to procedure(aInvokable: TRttiInvokableType; const aArgs: TValueArray; out aResult: TValue);
-    TCallbackMethod = procedure(aInvokable: TRttiInvokableType; const aArgs: TValueArray; out aResult: TValue) of object; deprecated 'Use TRttiInvokableType.TCallback';
-    TCallbackProc = procedure(aInvokable: TRttiInvokableType; const aArgs: TValueArray; out aResult: TValue); deprecated 'Use TRttiInvokableType.TCallback';
+    TCallbackMethod = procedure(aInvokable: TRttiInvokableType; const aArgs: TValueArray; out aResult: TValue) of object; {$ifndef InLazIDE}deprecated 'Use TRttiInvokableType.TCallback';{$endif}
+    TCallbackProc = procedure(aInvokable: TRttiInvokableType; const aArgs: TValueArray; out aResult: TValue); {$ifndef InLazIDE}deprecated 'Use TRttiInvokableType.TCallback';{$endif}
   public
   public
     function GetParameters: TRttiParameterArray; inline;
     function GetParameters: TRttiParameterArray; inline;
     property CallingConvention: TCallConv read GetCallingConvention;
     property CallingConvention: TCallConv read GetCallingConvention;
@@ -1082,7 +1096,8 @@ begin
     tkChar,
     tkChar,
     tkWideChar,
     tkWideChar,
     tkString,
     tkString,
-    tkLString:
+    tkLString,
+    tkAString:
       aType:=varString;
       aType:=varString;
     tkUString:
     tkUString:
       aType:=varUString;
       aType:=varUString;
@@ -3063,7 +3078,7 @@ begin
       TValue.Make(@Tmp,System.TypeInfo(WideString),aDest);
       TValue.Make(@Tmp,System.TypeInfo(WideString),aDest);
     tkUString:
     tkUString:
       TValue.Make(@Tmp,System.TypeInfo(UnicodeString),aDest);
       TValue.Make(@Tmp,System.TypeInfo(UnicodeString),aDest);
-    tkLString:
+    tkAString:
       begin
       begin
       SetString(S, PAnsiChar(@Tmp), 1);
       SetString(S, PAnsiChar(@Tmp), 1);
       SetCodePage(S,GetTypeData(aDestType)^.CodePage);
       SetCodePage(S,GetTypeData(aDestType)^.CodePage);
@@ -3103,7 +3118,7 @@ begin
       US:=Tmp;
       US:=Tmp;
       TValue.Make(@US,System.TypeInfo(UnicodeString),aDest);
       TValue.Make(@US,System.TypeInfo(UnicodeString),aDest);
       end;
       end;
-    tkLString:
+    tkAString:
       begin
       begin
       SetString(RS,PAnsiChar(@Tmp),1);
       SetString(RS,PAnsiChar(@Tmp),1);
       SetCodePage(RS,GetTypeData(aDestType)^.CodePage);
       SetCodePage(RS,GetTypeData(aDestType)^.CodePage);
@@ -4643,9 +4658,9 @@ begin
     raise EInvalidCast.Create(SErrInvalidTypecast);
     raise EInvalidCast.Create(SErrInvalidTypecast);
 end;
 end;
 
 
-function TValue.AsChar: AnsiChar;
+function TValue.AsChar: Char;
 begin
 begin
-{$if SizeOf(AnsiChar) = 1}
+{$if SizeOf(Char) = 1}
   Result := AsAnsiChar;
   Result := AsAnsiChar;
 {$else}
 {$else}
   Result := AsWideChar;
   Result := AsWideChar;

+ 11 - 5
packages/rtl-objpas/src/inc/variants.pp

@@ -2327,6 +2327,10 @@ begin
       Dest.vType := varOleStr;
       Dest.vType := varOleStr;
       Dest.vOleStr := nil;
       Dest.vOleStr := nil;
       WideString(Pointer(Dest.vOleStr)) := WideString(Pointer(vOleStr));
       WideString(Pointer(Dest.vOleStr)) := WideString(Pointer(vOleStr));
+    end else if vType = varUString then begin
+      Dest.vType := varUString;
+      Dest.vustring := Nil;
+      UnicodeString(Dest.vustring) := UnicodeString(vustring);
     end else if vType = varAny then begin
     end else if vType = varAny then begin
       Dest := Source;
       Dest := Source;
       RefAnyProc(Dest);
       RefAnyProc(Dest);
@@ -2456,6 +2460,11 @@ begin
         varDate:     SysVarFromTDateTime(Variant(aDest), VariantToDate(aSource));
         varDate:     SysVarFromTDateTime(Variant(aDest), VariantToDate(aSource));
 {$endif}
 {$endif}
         varOleStr:   DoVarCastWStr(aDest, aSource);
         varOleStr:   DoVarCastWStr(aDest, aSource);
+        varUString:  begin
+          DoVarClearIfComplex(aDest);
+          aDest.vType := aVarType;
+          UnicodeString(aDest.vustring) := VariantToUnicodeString(aSource);
+        end;
         varBoolean:  SysVarFromBool(Variant(aDest), VariantToBoolean(aSource));
         varBoolean:  SysVarFromBool(Variant(aDest), VariantToBoolean(aSource));
         varShortInt: SysVarFromInt(Variant(aDest), VariantToShortInt(aSource), -1);
         varShortInt: SysVarFromInt(Variant(aDest), VariantToShortInt(aSource), -1);
         varByte:     SysVarFromInt(Variant(aDest), VariantToByte(aSource), 1);
         varByte:     SysVarFromInt(Variant(aDest), VariantToByte(aSource), 1);
@@ -2466,13 +2475,10 @@ begin
 
 
         varDispatch: DoVarCastDispatch(aDest, aSource);
         varDispatch: DoVarCastDispatch(aDest, aSource);
         varUnknown:  DoVarCastInterface(aDest, aSource);
         varUnknown:  DoVarCastInterface(aDest, aSource);
-      else
-        case aVarType of
-          varString: DoVarCastLStr(aDest, aSource);
-          varAny:    VarCastError(vType, varAny);
+        varString:   DoVarCastLStr(aDest, aSource);
+        varAny:      VarCastError(vType, varAny);
         else
         else
           DoVarCastComplex(aDest, aSource, aVarType);
           DoVarCastComplex(aDest, aSource, aVarType);
-        end;
       end;
       end;
     end;
     end;
 
 

+ 1 - 0
packages/rtl-objpas/src/inc/varutilh.inc

@@ -71,6 +71,7 @@ function VariantToInt64(const VargSrc : TVarData ) : Int64;
 function VariantToQWord(const VargSrc : TVarData ) : Qword;
 function VariantToQWord(const VargSrc : TVarData ) : Qword;
 function VariantToWideString(const VargSrc : TVarData) : WideString;
 function VariantToWideString(const VargSrc : TVarData) : WideString;
 function VariantToAnsiString(const VargSrc : TVarData) : AnsiString;
 function VariantToAnsiString(const VargSrc : TVarData) : AnsiString;
+function VariantToUnicodeString(const VargSrc : TVarData) : UnicodeString;
 function VariantToShortString(const VargSrc : TVarData) : ShortString;
 function VariantToShortString(const VargSrc : TVarData) : ShortString;
 
 
 {$ifdef USE_WINDOWS_OLE_FUNCTIONS}
 {$ifdef USE_WINDOWS_OLE_FUNCTIONS}

+ 6 - 0
packages/rtl-objpas/src/inc/varutils.inc

@@ -77,6 +77,8 @@ begin
             ;
             ;
           varOleStr:
           varOleStr:
             WideString(Pointer(VOleStr)):='';
             WideString(Pointer(VOleStr)):='';
+          varUString:
+            UnicodeString(VUString):='';
           varDispatch,
           varDispatch,
           varUnknown:
           varUnknown:
             iinterface(vunknown):=nil;
             iinterface(vunknown):=nil;
@@ -115,6 +117,8 @@ begin
             Move(VBytes, VargDest.VBytes, SizeOf(VargDest.VBytes));
             Move(VBytes, VargDest.VBytes, SizeOf(VargDest.VBytes));
           varOleStr:
           varOleStr:
             CopyAsWideString(VargDest.VOleStr,VOleStr);
             CopyAsWideString(VargDest.VOleStr,VOleStr);
+          varUString:
+            UnicodeString(VargDest.vustring):=UnicodeString(vustring);
           varDispatch:
           varDispatch:
             IUnknown(VargDest.vdispatch):=IUnknown(VargSrc.vdispatch);
             IUnknown(VargDest.vdispatch):=IUnknown(VargSrc.vdispatch);
           varUnknown:
           varUnknown:
@@ -156,6 +160,7 @@ begin
       VarQWord    : VargDest.VQWord:=PQWord(VPointer)^;
       VarQWord    : VargDest.VQWord:=PQWord(VPointer)^;
       varVariant  : Variant(VargDest):=Variant(PVarData(VPointer)^);
       varVariant  : Variant(VargDest):=Variant(PVarData(VPointer)^);
       varOleStr   : CopyAsWideString(VargDest.VOleStr,PVarData(VPointer)^.VoleStr);
       varOleStr   : CopyAsWideString(VargDest.VOleStr,PVarData(VPointer)^.VoleStr);
+      varUString  : UnicodeString(VargDest.vustring):=PUnicodeString(VPointer)^;
       varDispatch,
       varDispatch,
       varUnknown  : IInterface(VargDest.vUnknown):=IInterface(PInterface(VargSrc.VPointer)^);
       varUnknown  : IInterface(VargDest.vUnknown):=IInterface(PInterface(VargSrc.VPointer)^);
       else
       else
@@ -200,6 +205,7 @@ begin
           VarInt64    : VargDest.Vint64:=VariantToInt64(Tmp);
           VarInt64    : VargDest.Vint64:=VariantToInt64(Tmp);
           VarLongWord : VargDest.VLongWord:=VariantToCardinal(Tmp);
           VarLongWord : VargDest.VLongWord:=VariantToCardinal(Tmp);
           VarQWord    : VargDest.VQWord:=VariantToQword(tmp);
           VarQWord    : VargDest.VQWord:=VariantToQword(tmp);
+          varUString  : UnicodeString(VargDest.vustring):=UnicodeString(tmp.vustring);
        else
        else
           Result:=VAR_BADVARTYPE;
           Result:=VAR_BADVARTYPE;
        end;
        end;

+ 61 - 0
packages/rtl-objpas/tests/tests.rtti.value.pas

@@ -130,6 +130,12 @@ Type
     Procedure TestFromVarRecQWord;
     Procedure TestFromVarRecQWord;
     Procedure TestFromVarRecUnicodeString;
     Procedure TestFromVarRecUnicodeString;
     Procedure TestArrayOfConstToTValue;
     Procedure TestArrayOfConstToTValue;
+    procedure TestCastAnsiString;
+{$ifndef FPC_WIDESTRING_EQUAL_UNICODESTRING}
+    procedure TestCastUnicodeString;
+{$endif}
+    procedure TestCastWideString;
+    procedure TestCastShortString;
   end;
   end;
 
 
   { TMyUNknown }
   { TMyUNknown }
@@ -719,6 +725,61 @@ begin
   CheckEquals(1.23,S[2].AsDouble,0.01,'Value 3');
   CheckEquals(1.23,S[2].AsDouble,0.01,'Value 3');
 end;
 end;
 
 
+procedure TTestValueVariant.TestCastAnsiString;
+var
+  s: AnsiString;
+  v: Variant;
+  vvar, vstr: TValue;
+begin
+  s := 'Test';
+  v := s;
+  vvar := TValue.{$ifdef fpc}specialize{$endif}From<Variant>(v);
+  CheckTrue(vvar.TryCast(TypeInfo(AnsiString), vstr));
+  CheckEquals(s, vstr.AsAnsiString);
+end;
+
+{$ifndef FPC_WIDESTRING_EQUAL_UNICODESTRING}
+procedure TTestValueVariant.TestCastUnicodeString;
+var
+  u: UnicodeString;
+  v: Variant;
+  vvar, vstr: TValue;
+begin
+  u := 'Test';
+  TVarData(v).vType := varUString;
+  TVarData(v).vuString := Pointer(u);
+  vvar := TValue.{$ifdef fpc}specialize{$endif}From<Variant>(v);
+  CheckTrue(vvar.TryCast(TypeInfo(UnicodeString), vstr));
+  CheckEquals(u, vstr.AsUnicodeString);
+end;
+{$endif}
+
+procedure TTestValueVariant.TestCastWideString;
+var
+  w: WideString;
+  v: Variant;
+  vvar, vstr: TValue;
+begin
+  w := 'Test';
+  v := w;
+  vvar := TValue.{$ifdef fpc}specialize{$endif}From<Variant>(v);
+  CheckTrue(vvar.TryCast(TypeInfo(WideString), vstr));
+  CheckEquals(w, vstr.AsUnicodeString);
+end;
+
+procedure TTestValueVariant.TestCastShortString;
+var
+  s: ShortString;
+  v: Variant;
+  vvar, vstr: TValue;
+begin
+  s := 'Test';
+  v := s;
+  vvar := TValue.{$ifdef fpc}specialize{$endif}From<Variant>(v);
+  CheckTrue(vvar.TryCast(TypeInfo(ShortString), vstr));
+  CheckEquals(s, vstr.AsAnsiString);
+end;
+
 { TMyUNknown }
 { TMyUNknown }
 
 
 function TMyUNknown.GetTypeInfoCount(out count: longint): HResult; stdcall;
 function TMyUNknown.GetTypeInfoCount(out count: longint): HResult; stdcall;

+ 7 - 7
rtl/Makefile

@@ -2,7 +2,7 @@
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 #
 #
 default: all
 default: all
-MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-netbsd m68k-amiga m68k-atari m68k-palmos m68k-macosclassic m68k-embedded m68k-sinclairql m68k-human68k powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macosclassic powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-haiku x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-android x86_64-aros x86_64-dragonfly arm-linux arm-netbsd arm-palmos arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros arm-freertos arm-ios powerpc64-linux powerpc64-freebsd powerpc64-darwin powerpc64-embedded powerpc64-aix avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android mipsel-ps1 mips64-linux mips64el-linux jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-freebsd aarch64-darwin aarch64-win64 aarch64-embedded aarch64-iphonesim aarch64-android aarch64-ios wasm32-embedded wasm32-wasip1 wasm32-wasip1threads wasm32-wasip2 sparc64-linux riscv32-linux riscv32-embedded riscv32-freertos riscv64-linux riscv64-embedded xtensa-linux xtensa-embedded xtensa-freertos z80-embedded z80-zxspectrum z80-msxdos z80-amstradcpc loongarch64-linux
+MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-netbsd m68k-amiga m68k-atari m68k-palmos m68k-macosclassic m68k-embedded m68k-sinclairql m68k-human68k powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macosclassic powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-haiku x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-android x86_64-aros x86_64-dragonfly arm-linux arm-netbsd arm-palmos arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros arm-freertos arm-ios powerpc64-linux powerpc64-darwin powerpc64-embedded powerpc64-aix powerpc64-freebsd avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android mipsel-ps1 mips64-linux mips64el-linux jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-freebsd aarch64-darwin aarch64-win64 aarch64-embedded aarch64-iphonesim aarch64-android aarch64-ios wasm32-embedded wasm32-wasip1 wasm32-wasip1threads wasm32-wasip2 sparc64-linux riscv32-linux riscv32-embedded riscv32-freertos riscv64-linux riscv64-embedded xtensa-linux xtensa-embedded xtensa-freertos z80-embedded z80-zxspectrum z80-msxdos z80-amstradcpc loongarch64-linux
 BSDs = freebsd netbsd openbsd darwin dragonfly
 BSDs = freebsd netbsd openbsd darwin dragonfly
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k
@@ -581,9 +581,6 @@ endif
 ifeq ($(CPU_OS_TARGET),powerpc64-linux)
 ifeq ($(CPU_OS_TARGET),powerpc64-linux)
 override TARGET_DIRS+=linux
 override TARGET_DIRS+=linux
 endif
 endif
-ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
-override TARGET_DIRS+=freebsd
-endif
 ifeq ($(CPU_OS_TARGET),powerpc64-darwin)
 ifeq ($(CPU_OS_TARGET),powerpc64-darwin)
 override TARGET_DIRS+=darwin
 override TARGET_DIRS+=darwin
 endif
 endif
@@ -593,6 +590,9 @@ endif
 ifeq ($(CPU_OS_TARGET),powerpc64-aix)
 ifeq ($(CPU_OS_TARGET),powerpc64-aix)
 override TARGET_DIRS+=aix
 override TARGET_DIRS+=aix
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override TARGET_DIRS+=freebsd
+endif
 ifeq ($(CPU_OS_TARGET),avr-embedded)
 ifeq ($(CPU_OS_TARGET),avr-embedded)
 override TARGET_DIRS+=embedded
 override TARGET_DIRS+=embedded
 endif
 endif
@@ -2276,9 +2276,6 @@ endif
 ifeq ($(CPU_OS_TARGET),powerpc64-linux)
 ifeq ($(CPU_OS_TARGET),powerpc64-linux)
 TARGET_DIRS_LINUX=1
 TARGET_DIRS_LINUX=1
 endif
 endif
-ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
-TARGET_DIRS_FREEBSD=1
-endif
 ifeq ($(CPU_OS_TARGET),powerpc64-darwin)
 ifeq ($(CPU_OS_TARGET),powerpc64-darwin)
 TARGET_DIRS_DARWIN=1
 TARGET_DIRS_DARWIN=1
 endif
 endif
@@ -2288,6 +2285,9 @@ endif
 ifeq ($(CPU_OS_TARGET),powerpc64-aix)
 ifeq ($(CPU_OS_TARGET),powerpc64-aix)
 TARGET_DIRS_AIX=1
 TARGET_DIRS_AIX=1
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+TARGET_DIRS_FREEBSD=1
+endif
 ifeq ($(CPU_OS_TARGET),avr-embedded)
 ifeq ($(CPU_OS_TARGET),avr-embedded)
 TARGET_DIRS_EMBEDDED=1
 TARGET_DIRS_EMBEDDED=1
 endif
 endif

+ 2 - 5
rtl/bsd/system.pp

@@ -208,11 +208,8 @@ begin
   e[j]:=1 shl i;
   e[j]:=1 shl i;
   { this routine is called from a signal handler, so must not change errno }
   { this routine is called from a signal handler, so must not change errno }
   olderrno:=geterrno;
   olderrno:=geterrno;
-  seterrno(0);
-  if fpsigprocmask(SIG_UNBLOCK,@e,@oe)<>0 then
-    reenable_signal:=geterrno=0
-  else
-    reenable_signal:=true;
+  fpsigprocmask(SIG_UNBLOCK,@e,@oe);
+  reenable_signal:=geterrno=0;
   seterrno(olderrno);
   seterrno(olderrno);
 end;
 end;
 
 

+ 22 - 1
rtl/freebsd/Makefile

@@ -2,7 +2,7 @@
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 # Don't edit, this file is generated by FPCMake Version 2.0.0
 #
 #
 default: all
 default: all
-MAKEFILETARGETS=i386-freebsd x86_64-freebsd aarch64-freebsd
+MAKEFILETARGETS=i386-freebsd x86_64-freebsd aarch64-freebsd powerpc64-freebsd
 BSDs = freebsd netbsd openbsd darwin dragonfly
 BSDs = freebsd netbsd openbsd darwin dragonfly
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 UNIXs = linux $(BSDs) solaris qnx haiku aix
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k
 LIMIT83fs = go32v2 os2 emx watcom msdos win16 atari human68k
@@ -943,6 +943,9 @@ ifeq ($(ARCH),x86_64)
 CPU_UNITS=$(X86UNIT) $(PORTSUNIT) $(CPUUNIT) $(INTRINSICSUNIT)
 CPU_UNITS=$(X86UNIT) $(PORTSUNIT) $(CPUUNIT) $(INTRINSICSUNIT)
 ASTARGET=--64
 ASTARGET=--64
 endif
 endif
+ifeq ($(ARCH),powerpc64)
+ASTARGET=-a64
+endif
 ifeq ($(ARCH),aarch64)
 ifeq ($(ARCH),aarch64)
 CPU_UNITS=$(INTRINSICSUNIT)
 CPU_UNITS=$(INTRINSICSUNIT)
 endif
 endif
@@ -956,6 +959,9 @@ endif
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 override TARGET_UNITS+=$(SYSTEMUNIT) $(UUCHARUNIT) $(UNIXTYPEUNIT) $(CTYPESUNIT) $(OBJPASUNIT) $(MACPASUNIT) $(ISO7185UNIT) $(EXTPASUNIT) $(STRINGSUNIT) $(SYSCALLUNIT) $(SYSCTLUNIT) $(BASEUNIXUNIT) $(UNIXUTILUNIT) $(CHARACTERUNIT) $(UNIXUNIT) $(RTLCONSTSUNIT) $(INITCUNIT) $(CMEMUNIT) $(DLUNIT) $(TERMIOUNIT) $(SYSUTILSUNIT) $(MATHUNIT) $(TYPINFOUNIT) $(TYPESUNIT) $(SORTBASEUNIT) $(CLASSESUNIT) $(FGLUNIT) $(DYNLIBSUNIT) $(CPU_UNITS) $(CHARSETUNIT) $(CPALLUNIT) $(GETOPTSUNIT) $(HEAPTRCUNIT) $(LNFODWRFUNIT) $(LINEINFOUNIT) $(ERRORSUNIT) $(BSDUNIT) $(FREEBSDUNIT) $(CONSOLEUNIT) $(SYSCONSTUNIT) $(CTHREADSUNIT) $(DOSUNIT) $(CWSTRINGUNIT) $(FPINTRESUNIT) $(UNIXCPUNIT) $(FPWIDESTRINGUNIT) $(SOFTFPUUNIT) $(SFPUX80UNIT) $(UFLOATX80UNIT) $(SFPU128UNIT) $(UFLOAT128UNIT) $(UNIX98PTYUNIT)
 override TARGET_UNITS+=$(SYSTEMUNIT) $(UUCHARUNIT) $(UNIXTYPEUNIT) $(CTYPESUNIT) $(OBJPASUNIT) $(MACPASUNIT) $(ISO7185UNIT) $(EXTPASUNIT) $(STRINGSUNIT) $(SYSCALLUNIT) $(SYSCTLUNIT) $(BASEUNIXUNIT) $(UNIXUTILUNIT) $(CHARACTERUNIT) $(UNIXUNIT) $(RTLCONSTSUNIT) $(INITCUNIT) $(CMEMUNIT) $(DLUNIT) $(TERMIOUNIT) $(SYSUTILSUNIT) $(MATHUNIT) $(TYPINFOUNIT) $(TYPESUNIT) $(SORTBASEUNIT) $(CLASSESUNIT) $(FGLUNIT) $(DYNLIBSUNIT) $(CPU_UNITS) $(CHARSETUNIT) $(CPALLUNIT) $(GETOPTSUNIT) $(HEAPTRCUNIT) $(LNFODWRFUNIT) $(LINEINFOUNIT) $(ERRORSUNIT) $(BSDUNIT) $(FREEBSDUNIT) $(CONSOLEUNIT) $(SYSCONSTUNIT) $(CTHREADSUNIT) $(DOSUNIT) $(CWSTRINGUNIT) $(FPINTRESUNIT) $(UNIXCPUNIT) $(FPWIDESTRINGUNIT) $(SOFTFPUUNIT) $(SFPUX80UNIT) $(UFLOATX80UNIT) $(SFPU128UNIT) $(UFLOAT128UNIT) $(UNIX98PTYUNIT)
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override TARGET_UNITS+=$(SYSTEMUNIT) $(UUCHARUNIT) $(UNIXTYPEUNIT) $(CTYPESUNIT) $(OBJPASUNIT) $(MACPASUNIT) $(ISO7185UNIT) $(EXTPASUNIT) $(STRINGSUNIT) $(SYSCALLUNIT) $(SYSCTLUNIT) $(BASEUNIXUNIT) $(UNIXUTILUNIT) $(CHARACTERUNIT) $(UNIXUNIT) $(RTLCONSTSUNIT) $(INITCUNIT) $(CMEMUNIT) $(DLUNIT) $(TERMIOUNIT) $(SYSUTILSUNIT) $(MATHUNIT) $(TYPINFOUNIT) $(TYPESUNIT) $(SORTBASEUNIT) $(CLASSESUNIT) $(FGLUNIT) $(DYNLIBSUNIT) $(CPU_UNITS) $(CHARSETUNIT) $(CPALLUNIT) $(GETOPTSUNIT) $(HEAPTRCUNIT) $(LNFODWRFUNIT) $(LINEINFOUNIT) $(ERRORSUNIT) $(BSDUNIT) $(FREEBSDUNIT) $(CONSOLEUNIT) $(SYSCONSTUNIT) $(CTHREADSUNIT) $(DOSUNIT) $(CWSTRINGUNIT) $(FPINTRESUNIT) $(UNIXCPUNIT) $(FPWIDESTRINGUNIT) $(SOFTFPUUNIT) $(SFPUX80UNIT) $(UFLOATX80UNIT) $(SFPU128UNIT) $(UFLOAT128UNIT) $(UNIX98PTYUNIT)
+endif
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 override TARGET_IMPLICITUNITS+=$(EXEINFOUNIT) $(CP_UNITS) $(UNICODEDATAUNIT)
 override TARGET_IMPLICITUNITS+=$(EXEINFOUNIT) $(CP_UNITS) $(UNICODEDATAUNIT)
 endif
 endif
@@ -965,6 +971,9 @@ endif
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 override TARGET_IMPLICITUNITS+=$(EXEINFOUNIT) $(CP_UNITS) $(UNICODEDATAUNIT)
 override TARGET_IMPLICITUNITS+=$(EXEINFOUNIT) $(CP_UNITS) $(UNICODEDATAUNIT)
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override TARGET_IMPLICITUNITS+=$(EXEINFOUNIT) $(CP_UNITS) $(UNICODEDATAUNIT)
+endif
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 override TARGET_LOADERS+=prt0 cprt0 gprt0 dllprt0
 override TARGET_LOADERS+=prt0 cprt0 gprt0 dllprt0
 endif
 endif
@@ -974,6 +983,9 @@ endif
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 override TARGET_LOADERS+=prt0 cprt0 gprt0 dllprt0
 override TARGET_LOADERS+=prt0 cprt0 gprt0 dllprt0
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override TARGET_LOADERS+=prt0 cprt0 gprt0 dllprt0
+endif
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 override TARGET_RSTS+=$(MATHUNIT) $(TYPINFOUNIT) $(CLASSESUNIT) $(SYSCONSTUNIT) $(UNICODEDATAUNIT)
 override TARGET_RSTS+=$(MATHUNIT) $(TYPINFOUNIT) $(CLASSESUNIT) $(SYSCONSTUNIT) $(UNICODEDATAUNIT)
 endif
 endif
@@ -983,6 +995,9 @@ endif
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 override TARGET_RSTS+=$(MATHUNIT) $(TYPINFOUNIT) $(CLASSESUNIT) $(SYSCONSTUNIT) $(UNICODEDATAUNIT)
 override TARGET_RSTS+=$(MATHUNIT) $(TYPINFOUNIT) $(CLASSESUNIT) $(SYSCONSTUNIT) $(UNICODEDATAUNIT)
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override TARGET_RSTS+=$(MATHUNIT) $(TYPINFOUNIT) $(CLASSESUNIT) $(SYSCONSTUNIT) $(UNICODEDATAUNIT)
+endif
 override INSTALL_FPCPACKAGE=y y y
 override INSTALL_FPCPACKAGE=y y y
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 override COMPILER_INCLUDEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(BSDPROCINC) $(OSPROCINC)
 override COMPILER_INCLUDEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(BSDPROCINC) $(OSPROCINC)
@@ -993,6 +1008,9 @@ endif
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 override COMPILER_INCLUDEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(BSDPROCINC) $(OSPROCINC)
 override COMPILER_INCLUDEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(BSDPROCINC) $(OSPROCINC)
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override COMPILER_INCLUDEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(BSDPROCINC) $(OSPROCINC)
+endif
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 ifeq ($(CPU_OS_TARGET),i386-freebsd)
 override COMPILER_SOURCEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(COMMON)
 override COMPILER_SOURCEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(COMMON)
 endif
 endif
@@ -1002,6 +1020,9 @@ endif
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 ifeq ($(CPU_OS_TARGET),aarch64-freebsd)
 override COMPILER_SOURCEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(COMMON)
 override COMPILER_SOURCEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(COMMON)
 endif
 endif
+ifeq ($(CPU_OS_TARGET),powerpc64-freebsd)
+override COMPILER_SOURCEDIR+=$(INC) $(PROCINC) $(UNIXINC) $(BSDINC) $(COMMON)
+endif
 ifdef REQUIRE_UNITSDIR
 ifdef REQUIRE_UNITSDIR
 override UNITSDIR+=$(REQUIRE_UNITSDIR)
 override UNITSDIR+=$(REQUIRE_UNITSDIR)
 endif
 endif

+ 0 - 5
rtl/freebsd/Makefile.fpc

@@ -84,15 +84,10 @@ ifeq ($(ARCH),x86_64)
 CPU_UNITS=$(X86UNIT) $(PORTSUNIT) $(CPUUNIT) $(INTRINSICSUNIT)
 CPU_UNITS=$(X86UNIT) $(PORTSUNIT) $(CPUUNIT) $(INTRINSICSUNIT)
 ASTARGET=--64
 ASTARGET=--64
 endif
 endif
-
 ifeq ($(ARCH),aarch64)
 ifeq ($(ARCH),aarch64)
 CPU_UNITS=$(INTRINSICSUNIT)
 CPU_UNITS=$(INTRINSICSUNIT)
 endif
 endif
 
 
-ifeq ($(ARCH),powerpc64)
-CPU_UNITS=$(INTRINSICSUNIT)
-endif
-
 # Paths
 # Paths
 OBJPASDIR=$(RTL)/objpas
 OBJPASDIR=$(RTL)/objpas
 
 

+ 2 - 1
rtl/freebsd/powerpc64/cprt0.as

@@ -6,7 +6,8 @@
         /* FreeBSD/ppc64: avoid @toc@ha/@toc@l relocations (not supported by some assemblers).
         /* FreeBSD/ppc64: avoid @toc@ha/@toc@l relocations (not supported by some assemblers).
            Use full 64-bit absolute address materialization for data symbols. */
            Use full 64-bit absolute address materialization for data symbols. */
         .macro  LOAD_64BIT_ADDR ra, sym
         .macro  LOAD_64BIT_ADDR ra, sym
-		ld	\ra,(\sym)@got(2)
+		addis     \ra,2,\sym@toc@ha
+		ld        \ra,\sym@toc@l(\ra)
         .endm
         .endm
 
 
         .section .rodata
         .section .rodata

+ 1 - 0
rtl/inc/generic.inc

@@ -14,6 +14,7 @@
 
 
  **********************************************************************}
  **********************************************************************}
 
 
+
 function align(addr : PtrUInt;alignment : PtrUInt) : PtrUInt;{$ifdef SYSTEMINLINE}inline;{$endif}
 function align(addr : PtrUInt;alignment : PtrUInt) : PtrUInt;{$ifdef SYSTEMINLINE}inline;{$endif}
   var
   var
     tmp,am1 : PtrUInt;
     tmp,am1 : PtrUInt;

+ 0 - 3
rtl/unix/classes.pp

@@ -41,9 +41,6 @@ unit Classes;
 
 
 interface
 interface
 
 
-{$ifdef NO_FPC_USE_INTRINSICS}
-  {$undef FPC_USE_INTRINSICS}
-{$endif}
 {$IFDEF FPC_DOTTEDUNITS}
 {$IFDEF FPC_DOTTEDUNITS}
 uses
 uses
   System.SysUtils,
   System.SysUtils,

+ 1 - 1
utils/fpcm/fpcmmain.pp

@@ -172,7 +172,7 @@ interface
         { amiga }   ( false, false, true,  true,  false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { amiga }   ( false, false, true,  true,  false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { atari }   ( false, false, true,  false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { atari }   ( false, false, true,  false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { solaris } ( false, true,  false, false, true,  true,  false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { solaris } ( false, true,  false, false, true,  true,  false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
-        { qnx }     ( false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
+        { qnx }     ( false, true,  false, false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { netware } ( false, true,  false, false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { netware } ( false, true,  false, false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { openbsd } ( false, true,  false, false, false, true,  false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { openbsd } ( false, true,  false, false, false, true,  false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { wdosx }   ( false, true,  false, false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),
         { wdosx }   ( false, true,  false, false, false, false, false, false, false, false, false, false, false, false, false,   false, false, false,  false, false, false,  false,  false, false, false),