Recent

Author Topic: Another Hunspell Wrapper question.  (Read 1649 times)

Raf20076

  • Full Member
  • ***
  • Posts: 159
    • https://github.com/Raf20076
Another Hunspell Wrapper question.
« on: June 10, 2023, 06:50:00 am »
Hi Guys I have found another Hunspell wrapper on Russian website and translate it in google, And I am just thinking is it possible to put it in wiki about spelling. I didn't contact the author, even I don't know how but What do you think?

Code: Pascal  [Select][+][-]
  1. {
  2.   ZHunSpellers.pas a Hunspell wrapper
  3.  
  4.   License
  5.  
  6.   Interface module with Hunspell dynamic library.
  7.   Based on code found in many places on the Lazarus forum and Github.
  8.   It is intended to be freely used by anyone for any purpose.
  9.   OlegZ 2020
  10.  
  11.   Extract from the article.
  12.  
  13.   The ZHunSpellers module contains the code of the THunSpeller class , through the methods of which access
  14.   to the functionality of the hunspell DLL library is provided , the attachment of which will be performed
  15.   automatically when the first instance of the class is created.
  16.  
  17.   By default, the DLL file name is libhunspell-1.7_32.dll or libhunspell-1.7_64.dll ,
  18.   and the path to the library is defined according to standard Windows rules .
  19.   If you need to change the default, you should define the DefaultDLLPath and
  20.   DefaultHunspellLibName variables before creating
  21.   the first instance of the THunSpeller class .
  22.  
  23.   In order not to be tied to the DLLs I found, I decided to try compiling hunspell myself .
  24.   To work with C++, in which the libraries are written, I already had MSYS + MinGW GCC v9.2.0 installed,
  25.   as well as IDE Code::Blocks 20.03. After that, after downloading the hunspell source code from the project site ,
  26.   I created a Code::Blocks DLL project based on the files in the src/hunspell directory from the project's master branch on Github.
  27.   After that, it only remained to compile the project, not forgetting to define BUILDING_LIBHUNSPELL ,
  28.   and when linking, set the -static-libgcc and -static-libstdc++ flagsto exclude dynamic linking of GCC DLLs.
  29.   By following these procedures on 32-bit and 64-bit systems, I got the necessary DLLs based on the current version
  30.   of hunspell 1.7 - libhunspell-1.7_32.dll and libhunspell-1.7_64.dll , respectively.
  31.  
  32.  
  33.   Machine automatic translation from Russian to English
  34.  
  35.   Source of unit and article in Russian;
  36.   http://electroanalysis.ucoz.net/publ/zametki/proverka_orfografii_spell_checking_v_vashej_programme/3-1-0-13
  37. }
  38. unit ZHunSpellers;//ZHunSpellers.pas
  39.  
  40. {$mode objfpc}{$H+}
  41.  
  42. interface
  43.  
  44. uses
  45.   windows,
  46.   Classes, SysUtils,
  47.   LazUTF8;
  48.  
  49. {$Inline on}
  50.  
  51. //----------------------------------------------------------------------------------------
  52. const
  53.   // Default DLL file name
  54.   {$IfDef WIN32}
  55.   cDefaultHunspellLibName = 'libhunspell-1.7_32.dll';//Name of hunspell DLL 32bit
  56.   {$Else}
  57.   {$IfDef WIN64}
  58.   cDefaultHunspellLibName = 'libhunspell-1.7_64.dll';//Name of hunspell DLL 64bit
  59.   {$Else}
  60.   {$Error Bad target OS}
  61.   {$EndIf}
  62.   {$EndIf}
  63.  
  64. //----------------------------------------------------------------------------------------
  65. // Exception handler for module errors
  66. type
  67.   EHunSpellerError = class(Exception)
  68.   end;
  69.  
  70. //----------------------------------------------------------------------------------------
  71. type
  72.  
  73.   { THunSpeller }
  74.  
  75.   THunSpeller = class
  76.     private
  77.      FDictionaryFullName: string; // The full name of the dic file of the loaded dictionary
  78.      FSpeller: Pointer; // Pointer to speller instance - result of Hunspell_create
  79.      // Returns True if the speller is ready to go
  80.      function GetReady: boolean; inline;
  81.      // Returns the encoding of the loaded dictionary
  82.      function GetDictEncoding: string;
  83.      // Internal procedure for executing Hunspell_suggest, Hunspell_analyze and Hunspell_stem functions
  84.      procedure iProcWithList(aFunc: pointer; const aWord: string; aList: TStrings);
  85.  
  86.     public
  87.      // Creating a speller without loading a dictionary
  88.      constructor Create;
  89.      // Creating a speller loading a dictionary whose file is named aFullDictName
  90.      // The affix file (aff) is considered to be in the same directory
  91.      constructor Create(const aFullDictName: string);
  92.      destructor Destroy; override;
  93.  
  94.      // Loading dictionary from file aFullDictName, the previous dictionary is closed
  95.      procedure OpenDictionary(const aFullDictName: string);
  96.      // Closing the loaded dictionary
  97.      procedure CloseDictionary;
  98.  
  99.      //----------- DLL function wrappers -----------
  100.      // Checking the word aWord, returns True if the word is correct
  101.      function Spell(const aWord: string): boolean;
  102.      // List returns variants of the wrong word aWard
  103.      // If there are no options, then an empty list is returned
  104.      procedure Suggest(const aWord: string; aList: TStrings);
  105.      // In the list aList, the results of the morphological analysis of the word aWard are returned
  106.      procedure Analyze(const aWord: string; aList: TStrings);
  107.      // in the list aList return variants of the stem of the word aWard
  108.      procedure Stem(const aWord: string; aList: TStrings);
  109.      // The word aWord is added to the loaded (run-time) dictionary
  110.      procedure Add(const aWord: string);
  111.      // The word aWard is added to the loaded (run-time) dictionary
  112.      // Inflected forms of the word aWard are formed similarly to the dictionary word aExample
  113.      procedure AddWithAffix(const aWord, aExample: string);
  114.      // The word aWord is removed from the loaded (run-time) dictionary
  115.      procedure Remove(const aWord: string);
  116.  
  117.      property Ready: boolean read GetReady;
  118.      property DictionaryFullName: string read FDictionaryFullName;
  119.      property DictEncoding: string read GetDictEncoding;
  120.   end;
  121.  
  122. //----------------------------------------------------------------------------------------
  123. var
  124.   DefaultDLLPath: string = ''; // DLL file search path
  125.   DefaultHunspellLibName: string = cDefaultHunspellLibName; // DLL file name
  126.  
  127. // Returns the full filename of the loaded DLL
  128. function GetDLLFullName: string;
  129.  
  130. property DLLFullName: string read GetDLLFullName;
  131.  
  132. //****************************************************************************************
  133. //****************************************************************************************
  134. implementation
  135.  
  136. //----------------------------------------------------------------------------------------
  137. // DLL Library control
  138. //----------------------------------------------------------------------------------------
  139. var
  140.   FDLLFullName: string; // Fully qualified filename of the loaded DLL
  141.   FLibHandle: THandle = NilHandle; // Descriptor DLL-library
  142.   FAllLibProcLoaded: boolean = false; // = True if the addresses of all used functions were successfully loaded
  143.  
  144.   // addresses of required DLL functions
  145.   HS_create: pointer;
  146.   HS_destroy: pointer;
  147.   HS_spell: pointer;
  148.   HS_suggest: pointer;
  149.   HS_analyze: pointer;
  150.   HS_stem: pointer;
  151.   HS_free_list: pointer;
  152.   HS_add: pointer;
  153.   HS_add_with_affix: pointer;
  154.   HS_remove: pointer;
  155.   HS_get_dic_encoding: pointer;
  156.  
  157.   FSpellersCount: integer = 0; // counter of active spellers
  158.  
  159. //----------------------------------------------------------------------------------------
  160. //----------------------------------------------------------------------------------------
  161. //----------------------------------------------------------------------------------------
  162.  
  163. // Returns the full filename of the loaded DLL
  164. function GetDLLFullName: string;
  165. begin
  166.  Result := FDLLFullName;
  167. end;
  168.  
  169. // Internal function to get the address of the exported function by name
  170. // An EHunSpellerError trap is generated on error
  171. function iGetProcAddr(const aFuncName: string): pointer;
  172. begin
  173.   Result := system.GetProcAddress(FLibHandle, aFuncName);
  174.   if not Assigned(Result) then
  175.     raise EHunSpellerError.CreateFmt('Failed to find functions "%s" in "%s"',
  176.      [aFuncName, FDLLFullName]);
  177. end;
  178.  
  179. // Getting addresses of all used DLL functions
  180. procedure SetupFuncVars;
  181. begin
  182.   HS_create := iGetProcAddr('Hunspell_create');
  183.   HS_destroy := iGetProcAddr('Hunspell_destroy');
  184.   HS_spell := iGetProcAddr('Hunspell_spell');
  185.   HS_suggest := iGetProcAddr('Hunspell_suggest');
  186.   HS_analyze := iGetProcAddr('Hunspell_analyze');
  187.   HS_stem := iGetProcAddr('Hunspell_stem');
  188.   HS_free_list := iGetProcAddr('Hunspell_free_list');
  189.   HS_add := iGetProcAddr('Hunspell_add');
  190.   HS_add_with_affix := iGetProcAddr('Hunspell_add_with_affix');
  191.   HS_remove := iGetProcAddr('Hunspell_remove');
  192.   HS_get_dic_encoding := iGetProcAddr('Hunspell_get_dic_encoding');
  193.   FAllLibProcLoaded := true;
  194. end;
  195.  
  196. // DLL loading
  197. procedure OpenLib;
  198. const
  199.   cBufLen = 1024;
  200. var
  201.   s: string;
  202. begin
  203.   s := DefaultDLLPath + DefaultHunspellLibName;
  204.   FLibHandle := system.LoadLibrary(s);
  205.   if FLibHandle = NilHandle then
  206.     raise EHunSpellerError.CreateFmt('Failed to load library: %s', [FDLLFullName]);
  207.  
  208.   SetLength(s, cBufLen);
  209.   GetModuleFileName(FLibHandle, @s[1], cBufLen);
  210.   SetLength(s, strlen(@s[1]));
  211.   FDLLFullName := WinCPToUTF8(s);
  212.   SetupFuncVars;
  213. end;
  214.  
  215. // DLL unloading
  216. procedure CloseLib;
  217. begin
  218.   if (FLibHandle <> NilHandle) then begin
  219.     system.FreeLibrary(FLibHandle);
  220.     FLibHandle := NilHandle;
  221.     FAllLibProcLoaded := false;
  222.   end;
  223. end;
  224.  
  225. //----------------------------------------------------------------------------------------
  226. //----------------------------------------------------------------------------------------
  227. //----------------------------------------------------------------------------------------
  228.  
  229. { THunSpeller }
  230.  
  231. // Returns True if the speller instance is ready to go
  232. function THunSpeller.GetReady: boolean;
  233. begin
  234.   Result := (FLibHandle <> NilHandle) // DLL loaded
  235.        and FAllLibProcLoaded // All used functions loaded
  236.        and (FSpeller <> nil); // Speller created
  237. end;
  238.  
  239. //----------------------------------------------------------------------------------------
  240. // Returns the encoding type of the dictionary file
  241. function THunSpeller.GetDictEncoding: string;
  242. //hunspell.h -> char* Hunspell_get_dic_encoding(Hunhandle* pHunspell);
  243. type
  244.   THSFunc_get_dic_encoding = function(spell: Pointer): PChar; cdecl;
  245. var
  246.   p: pchar;
  247. begin
  248.   if not Ready then exit('Speller is not ready');
  249.  
  250.   p := THSFunc_get_dic_encoding(HS_get_dic_encoding)(FSpeller);
  251.   if p = nil then exit('Encoding string not found');
  252.  
  253.   SetLength(Result, strlen(p));
  254.   Move(p^, Result[1], strlen(p));
  255. end;
  256.  
  257. //----------------------------------------------------------------------------------------
  258. // creating a speller instance without opening a dictionary
  259. constructor THunSpeller.Create;
  260. begin
  261.   if FLibHandle = NilHandle then
  262.     OpenLib;
  263. end;
  264.  
  265. //----------------------------------------------------------------------------------------
  266. // Creating a speller instance with dictionary opening
  267. constructor THunSpeller.Create(const aFullDictName: string);
  268. begin
  269.   if FLibHandle = NilHandle then
  270.    OpenLib;
  271.   OpenDictionary(aFullDictName);
  272. end;
  273.  
  274. //----------------------------------------------------------------------------------------
  275. destructor THunSpeller.Destroy;
  276. begin
  277.   CloseDictionary;
  278.   inherited Destroy;
  279. end;
  280.  
  281. //----------------------------------------------------------------------------------------
  282. // Opening a dictionary from aFullDictName file,
  283. // previously opened dictionary is closed beforehand
  284. procedure THunSpeller.OpenDictionary(const aFullDictName: string);
  285. //hunspell.h -> Hunhandle* Hunspell_create(const char* affpath, const char* dpath);
  286. type
  287.   THSFunc_create = function(aff_file: PChar; dict_file: PChar): Pointer; cdecl;
  288. var
  289.   FullAffName : string;
  290. begin
  291.   CloseDictionary;
  292.   FDictionaryFullName := aFullDictName;
  293.   FullAffName := UTF8Copy(aFullDictName, 1, UTF8Length(aFullDictName) - 3) + 'aff';
  294.   FSpeller := THSFunc_create(HS_create)(PChar(UTF8ToWinCP(FullAffName)), PChar(UTF8ToWinCP(aFullDictName)));
  295.   if FSpeller = nil then
  296.     raise EHunSpellerError.CreateFmt('Failed to open Dictionary "%s"', [aFullDictName]);
  297.  
  298.   inc(FSpellersCount);
  299. end;
  300.  
  301. //----------------------------------------------------------------------------------------
  302. // Dictionary closure
  303. procedure THunSpeller.CloseDictionary;
  304. //hunspell.h -> Hunspell_destroy(Hunhandle* pHunspell);
  305. type
  306.   THSFunc_destroy = procedure(spell: Pointer); cdecl;
  307. begin
  308.   if Ready then begin
  309.     THSFunc_destroy(HS_destroy)(FSpeller);
  310.     FSpeller := nil;
  311.     dec(FSpellersCount);
  312.   end;
  313. end;
  314.  
  315. //----------------------------------------------------------------------------------------
  316. // Checking the word aWord, returns True if the word is correct
  317. function THunSpeller.Spell(const aWord: string): boolean;
  318. //hunspell.h -> int Hunspell_spell(Hunhandle* pHunspell, const char* );
  319. type
  320.   THSFunc_spell = function(spell: Pointer; word: PChar): Boolean; cdecl;
  321. begin
  322.   if Ready then
  323.     Result := THSFunc_spell(HS_spell)(FSpeller, PChar(aWord))
  324.   else
  325.     Result := false;
  326. end;
  327.  
  328. //----------------------------------------------------------------------------------------
  329. // Private procedure for calling DLL functions that return a result as a list of words:
  330. // Hunspell_suggest, Hunspell_analyze and Hunspell_stem
  331. procedure THunSpeller.iProcWithList(aFunc: pointer; const aWord: string; aList: TStrings);
  332. type
  333.   THSFuncWithList = function(spell: Pointer; out slst: PPChar; word: PChar): Integer; cdecl;
  334.   //hunspell.h -> void Hunspell_free_list(Hunhandle* pHunspell, char*** slst, int n);
  335.   THSFunc_free_list = procedure(spell: Pointer; var slst: PPChar; n: integer); cdecl;
  336. var
  337.   i, len: Integer;
  338.   SugList, Words: PPChar;
  339. begin
  340.   aList.Clear;
  341.   if not Ready then
  342.     exit;
  343.  
  344.   try
  345.     len := THSFuncWithList(aFunc)(FSpeller, SugList, PChar(aWord));
  346.     Words := SugList;
  347.     for i := 1 to len do begin
  348.       aList.Add(PChar(Words^));
  349.       Inc(Words);
  350.     end;
  351.   finally
  352.     // Freeing the list of words
  353.     THSFunc_free_list(HS_free_list)(FSpeller, SugList, len);
  354.   end;
  355. end;
  356.  
  357. //----------------------------------------------------------------------------------------
  358. // List returns variants of the wrong word aWard
  359. // If there are no options, then an empty list is returned
  360. procedure THunSpeller.Suggest(const aWord: string; aList: TStrings);
  361. //hunspell.h -> int Hunspell_suggest(Hunhandle* pHunspell, char*** slst, const char* word);
  362. begin
  363.   iProcWithList(HS_suggest, aWord, aList);
  364. end;
  365.  
  366. //----------------------------------------------------------------------------------------
  367. // In the list aList, the results of the morphological analysis of the word aWard are returned
  368. procedure THunSpeller.Analyze(const aWord: string; aList: TStrings);
  369. //hunspell.h -> int Hunspell_analyze(Hunhandle* pHunspell, char*** slst, const char* word);
  370. begin
  371.   iProcWithList(HS_analyze, aWord, aList);
  372. end;
  373.  
  374. //----------------------------------------------------------------------------------------
  375. // in the list aList return variants of the stem of the word aWard
  376. procedure THunSpeller.Stem(const aWord: string; aList: TStrings);
  377. //hunspell.h -> int Hunspell_stem(Hunhandle* pHunspell, char*** slst, const char* word);
  378. begin
  379.   iProcWithList(HS_stem, aWord, aList);
  380. end;
  381.  
  382. //----------------------------------------------------------------------------------------
  383. // The word aWord is added to the loaded (run-time) dictionary
  384. procedure THunSpeller.Add(const aWord: string);
  385. //hunspell.h -> int Hunspell_add(Hunhandle* pHunspell, const char* word);
  386. type
  387.   THSFunc_add = function(spell: Pointer; word: PChar): Integer; cdecl;
  388. begin
  389.   if Ready then
  390.     THSFunc_add(HS_add)(FSpeller, Pchar(aWord));
  391. end;
  392.  
  393. //----------------------------------------------------------------------------------------
  394. // The word aWord is added to the loaded (run-time) dictionary
  395. // Inflected forms of the word aWard are formed similarly to the dictionary word Example
  396. procedure THunSpeller.AddWithAffix(const aWord, aExample: string);
  397. //hunspell.h -> int Hunspell_add_with_affix(Hunhandle* pHunspell, const char* word, const char* example);
  398. type
  399.   THSFunc_add_with_affix = function(spell: Pointer; word: PChar; example: PChar): Integer; cdecl;
  400. begin
  401.   if Ready then
  402.     THSFunc_add_with_affix(HS_add_with_affix)(FSpeller, PChar(aWord), PChar(aExample));
  403. end;
  404.  
  405. //----------------------------------------------------------------------------------------
  406. // The word aWord is removed from the loaded (run-time) dictionary
  407. procedure THunSpeller.Remove(const aWord: string);
  408. //hunspell.h -> int Hunspell_remove(Hunhandle* pHunspell, const char* word);
  409. type
  410.   THSFunc_remove = function(spell: Pointer; word: PChar): Integer; cdecl;
  411. begin
  412.   if Ready then
  413.     THSFunc_remove(HS_remove)(FSpeller, Pchar(aWord));
  414. end;
  415.  
  416. //----------------------------------------------------------------------------------------
  417. //----------------------------------------------------------------------------------------
  418. //----------------------------------------------------------------------------------------
  419. finalization
  420.   // Unload DLL on program exit
  421.   CloseLib;
  422.   // Checking the completion of all speller instances
  423.   if FSpellersCount > 0 then
  424.     raise EHunSpellerError.CreateFmt('Not disposed spellers: %d', [FSpellersCount]);
  425. end.
  426.  
  427. {
  428.  
  429. Help for ZHunSpellers.pas
  430.  
  431.  
  432.  
  433. --------------------------
  434. EHunSpellerError
  435. --------------------------
  436. Declaration: Exception handler for module errors
  437. type EHunSpellerError = class(Exception)
  438. Inheritance
  439. EHunSpellerError
  440. Exception handler for module errors
  441. |
  442. Exception
  443. ?
  444. TObject
  445.  
  446.  
  447.  
  448.  
  449.  
  450. --------------------------
  451. THunSpeller
  452. --------------------------
  453. type THunSpeller = class
  454. public
  455.   constructor Create();Creating an instance of the THunSpeller class
  456.   destructor Destroy; override;Closes the opened dictionary and removes the class instance
  457.   procedure OpenDictionary();Loading a dictionary from a file
  458.   procedure CloseDictionary; Closing a loaded dictionary
  459.   function Spell();Performs a word check
  460.   procedure Suggest();Returns spellings of a word in a list
  461.   procedure Analyze();Performs morphological analysis of a word
  462.   procedure Stem();Returns word stem variants
  463.   procedure Add();Adding a word to the loaded (run-time) dictionary
  464.   procedure AddWithAffix();Adding a word to the loaded (run-time) dictionary
  465.   procedure Remove();Removes a word from the loaded (run-time) dictionary
  466.   property Ready: Boolean; [r] Speller ready to go
  467.   property DictionaryFullName: string; [r] Returns the fully qualified dic filename of the loaded dictionary
  468.   property DictEncoding: string; [r] Returns the encoding of the loaded dictionary
  469. end;
  470.  
  471.  
  472. Inheritance
  473. THunSpeller A class that implements the interface with the dynamic library HunSpell
  474. |
  475. TObject
  476.  
  477. Description
  478. The THunSpellers class instantiates a speller associated with a particular dictionary file and provides access to the Hunspell dynamic library functions.
  479.  
  480. Remark:    When the first instance of the class is created, the dynamic library is loaded.
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487. ------------------------
  488. GetDLLFullName
  489. ------------------------
  490. Returns the full filename of the loaded DLL
  491. Declaration
  492.  
  493. function GetDLLFullName: string;
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506. }
  507.  

dbannon

  • Hero Member
  • *****
  • Posts: 2616
    • tomboy-ng, a rewrite of the classic Tomboy
Re: Another Hunspell Wrapper question.
« Reply #1 on: June 10, 2023, 01:57:31 pm »
The question might be the license. While its obvious the original writer intended it to be open, there is no way of knowing how much of it was copied from other, perhaps not quite so open sources. Especially as it mentions 'github' as a source.

I have found that, for example, Debian totally rejects anything that has an unclear license. I believe that Microsoft is quite careful with code submitted to its Winget service. In both cases, author's intention does not come into it, if it uses an existing, recognized license, thats good, if its a DIY set of words meaning the same thing, 'no', a lawyer would need to look at that and who's going to pay for that ?

I completely restructured the hunspell wrapper mentioned on the wiki page for exactly that reason, my first effort was based on both the hunspell.h file and things I found in this forum. Wrong ! With such a vague provenience, I could not stamp it with an existing license. So, I. broke it into two parts, a new, cleanroom write of the hunspell.h part, that retains hunspell's own license and mine that is under a BSD license. The FPC/Lazarus wiki assumes anyone posting has clear and freely given ownership....

Was it Karl Marx who said "first we must hang all the lawyers" ?

Davo

 
Lazarus 2, Linux (and reluctantly Win10, OSX)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

Raf20076

  • Full Member
  • ***
  • Posts: 159
    • https://github.com/Raf20076
Re: Another Hunspell Wrapper question.
« Reply #2 on: June 11, 2023, 07:43:10 am »
My opinion is; these license are making mess in programming. There were hundreds cases in courts about a piece of codes, in 80s between Apple and Microsoft (about GUI) and other companies later and even now.

The conclusion is one, big companies uses any code they want not caring about licenses and if someone discovers it then it could end in the the court.  Why big companies, because they have money to pay.

The same with patents, in 19 century US didn't respect patents from outside of US, so US companies were stealing patents from Europe without any consequences to boost technology and industry, other countries did the same in Europe (stealing from each other).

See https://en.wikipedia.org/wiki/Patent_war , https://en.wikipedia.org/wiki/Apple_Computer,_Inc._v._Microsoft_Corp.#Court_case , https://en.wikipedia.org/wiki/Smartphone_patent_wars ,

avk

  • Hero Member
  • *****
  • Posts: 744
Re: Another Hunspell Wrapper question.
« Reply #3 on: June 11, 2023, 11:01:51 am »
...
Was it Karl Marx who said "first we must hang all the lawyers" ?
...

Was Shakespeare ever published under the pseudonym "Karl Marx"?

dsiders

  • Hero Member
  • *****
  • Posts: 949
Re: Another Hunspell Wrapper question.
« Reply #4 on: June 11, 2023, 04:47:46 pm »
...
Was it Karl Marx who said "first we must hang all the lawyers" ?
...

Was Shakespeare ever published under the pseudonym "Karl Marx"?

LOL!
Preview Lazarus 3.99 documentation at: https://dsiders.gitlab.io/lazdocsnext

dbannon

  • Hero Member
  • *****
  • Posts: 2616
    • tomboy-ng, a rewrite of the classic Tomboy
Re: Another Hunspell Wrapper question.
« Reply #5 on: June 12, 2023, 01:48:54 am »
Yep, Shakespeare, I cannot even find a record of Marx quoting Shakespeare !  :-[

Davo
Lazarus 2, Linux (and reluctantly Win10, OSX)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

 

TinyPortal © 2005-2018