Sign in Join
Wiki copytemporary

spelling: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: spelling

OldNewLine
1+## Using Hunspell with Lazarus
2+
3+> **Note:** This page is current as of August, 2018 but things change...
4+
5+This page is about using the Hunspell library with Lazarus. It outlines a model that does, sort of, work. You will almost certainly need to make some changes for your specific purposes but hopefully, this page will give you a good start.
6+
7+Firstly, in the forum, there are several references to some code that will work with the Hunspell library. The hunspell.pas unit is based heavily on these blocks of code. Most have no license information and an assumption is being made that they are "common knowledge" and thus free of any constraint. I have added a bit that manages the problem of finding the library and dictionary files. And established a reasonable interface.
8+
9+Further, user **rvk** from the forum has build a windows 64 bit DLL as there seemed little alternative for Windows users.
10+
11+## About Hunspell
12+
13+Hunspell is an active open source project, distributed under the Mozilla Public License. The hunspell library is used in products like Libra Office, Open Office and Firefox. Its can be made work on Windows, Linux and Mac (and probably heaps of other platforms). See the platform Specific pages below. Dictionaries for Hunspell are readily available and may already be installed on many machines. Even if you cannot access another application's library, you can use its dictionary.
14+
15+Dictionaries for Hunspell come as a *.dic and *.aff file pair. For example, the Australian dictionary consists of en_AU.dic and en_AU.aff . The 'en' indicates its english and the 'AU' says its specifically for Australia. As an English speaker, I note that the en_US dictionaries always seem to be installed and I add the Australian ones. I don't know how widespread this pattern is in non English speaking systems. You can find dictionaries here <https://github.com/LibreOffice/dictionaries> and also some information related to it here <https://wiki.documentfoundation.org/Development/Dictionaries>
16+
17+## Note about dictionaries
18+
19+In some cases in order to display the suggested words correctly the dictionaries must be encoded in UTF-8. If you have notice that some words are not shown correctly--for example, 'apple' in Polish is 'jabłko' but you get 'jab�ko'--it means that dictionary pl_PL.aff and pl_PL.dic must be converted in UTF-8. To convert dictionaries into UTF-8 in Linux Debian (it should work in any Linux, converted dictionaries can be used in Windows, check also [this forum thread](http://forum.lazarus.freepascal.org/index.php/topic,44298.0.html)) follow below steps:
20+
21+1\. Start terminal as root
22+
23+`  su`
24+
25+2\. If not yet installed, install hunspell (hunspell-pl is for Polish dictionary--more at [Debian Hunspell package](https://packages.debian.org/search?keywords=hunspell))
26+
27+`  apt-get install hunspell`\
28+`  apt-get install hunspell-pl`
29+
30+3\. Go to /usr/share/hunspell/ where dictionaries are kept and create folder dic
31+
32+`  cd /usr/share/hunspell/`\
33+`  mkdir dic`
34+
35+4\. Convert for example dictionary pl_PL.aff and pl_PL.dic. Use [ISO-8859-2](https://en.wikipedia.org/wiki/ISO/IEC_8859-2) (for Eastern Europe), [ISO-8859-1](https://en.wikipedia.org/wiki/ISO/IEC_8859-1) (or ISO-8859-15) for Western Europe, ISO-8859-5 for (Russian?), etc.
36+
37+`  iconv -f ISO-8859-2 -t UTF-8 /usr/share/hunspell/pl_PL.aff | sed 's/^SET ISO8859-2$/SET UTF-8/g' > dic/pl_PL.aff`\
38+`  iconv -f ISO-8859-2 -t UTF-8 /usr/share/hunspell/pl_PL.dic > dic/pl_PL.dic`
39+
40+5\. Copy your 'dic' folder or dictionaries from 'dic' folder to your application.
41+
42+## Platform Specific
43+
44+### Linux
45+
46+Many Linux distributions will have Hunspell installed by default along with the appropriate language dictionaries. If not, its probably just a case of using the distribution's package manager. If all else fails, grab the source from the hunspell GitHub site and build it yourself. Linux users are like that.
47+
48+To see if you do in fact have a hunspell library installed, try this command.
49+
50+` ldconfig -p | grep hunspell`
51+
52+Similarly, you can probably find some dictionaries with
53+
54+` ls -l /usr/share/hunspell`
55+
56+If that does not work, try
57+
58+` find /usr -name *.aff`
59+
60+It will take a bit longer...
61+
62+### Windows
63+
64+Installing the hunspell library on Windows is more of an issue. There is apparently no pre compiled 'kit' available and most Windows apps that use Hunspell appear to bind it staticly thus no hunspell.dll left lying around for you to use. But just to be sure, try searching for `*hunspell*.dll`. The Hunspell GitHub site lists a recipe to build one but it involves installing MSYS2 and is quite involved. The resulting DLL also needs a couple of GCC DLLs along as well.
65+
66+Fortunately user **rvk** on the Lazarus Forum has built us a nice statically (ie stand alone) linked DLL using Microsoft Visual Studio Community 2015. As such, you can use and distribute this DLL with your program subject to the Mozilla Public License.
67+
68+You will find this DLL bundled with its license file in <https://github.com/davidbannon/hunspell4pas>, click 'DLL', click the dll file, and you will get a 'Download' button. Don't forget to also get the license file, it should be distributed, one way or another with your app.
69+
70+### macOS
71+
72+The author's Mac appears to have had the Hunspell library installed when Sierra was installed. But maybe, just maybe, it came along with Firefox. I'd like some feed back .... \[already installed on Mojave and Catalina\]
73+
74+> **Note:** See <https://opensource.apple.com/tarballs/hunspell/> for source download for v1.2.8.
75+
76+To see if you aleady have a hunspell library installed, try this command
77+
78+` find / 2>&1 | grep "\hunspell"`
79+
80+It will run for some time, depending on how many files on your system. It will likely find several files including some in your XCode directory. However, end users will probably not have XCode installed. The one particularly interesting file to me was
81+
82+` /usr/lib/libhunspell-1.2.dylib`
83+
84+Version 1.2 is a bit older than elsewhere but it worked fine. If you don't find a usable library, I suggest you install one using a package manager like MacPorts, Fink or brew. Next issue issue is you will need some dictionaries. Similar command,
85+
86+` find / 2>&1 | grep "\.aff"`
87+
88+again, slow, it searches your whole disk. I found
89+
90+` /Applications/Firefox.app/Contents/Resources/dictionaries/en-US.aff`
91+
92+And a quick 'ls' assured me that there was a matching 'en-US.dic' files so all good. A more experienced Mac user might like to suggest better search strategies. Please !
93+
94+## The Hunspell Unit
95+
96+When this page was initially created (2017 ?) I made a serious mistake and used some code that did not have a clear license or history. It has been suggested that by doing so, I breached the Hunspell license as has anyone who has used it since then.
97+
98+To correct this situation, I have now released a new version of the Hunspell Wrapper that has a quite small part, just the function definitions, with the Hunspell (License: MPL 1.1/GPL 2.0/LGPL 2.1) and the Hunspell Unit (License: The Clear BSD License) in seperate files. From a coding point of view, its almost identical to what used to be here.
99+
100+Please see <https://github.com/davidbannon/hunspell4pas>
101+
102+I have removed the hunspell wrapper that used to live on this page. The one on Github above is a drop in replacement, you need hunspell.pas and hunspell.inc. The remainder of the file make a basic test/demo command line Lazarus project that replaces Demo 1.
103+
104+### Demo 2 in a Full GUI
105+
106+A Lazarus GUI demo makes a lot more sense and has been tested on Linux, Mac and Windows. But it is a bit harder to copy and paste.
107+
108+For this demo, you'll need a form with two TMemos, Memo1 and MemoMsg. A button, ButtonSpell, a Tlistbox, Listbox1. Make the following event handlers : FormCreate for the main form; a double click for the listbox and click on the Button.
109+
110+First you must create the hunspell object and see if it found a library its own way, here is an example in the FormCreate() method ....
111+
112+``` pascal
113+uses hunspell; // get hunspell.pas and hunspell.inc from https://github.com/davidbannon/hunspell4pas
114+var
115+ Form1: TForm1;
116+ Sp: THunspell;
117+ DictPath : AnsiString;
118+
119+procedure TForm1.FormCreate(Sender: TObject);
120+begin
121+ SetDefaultDicPath();
122+ Sp := THunspell.Create(True);
123+ if Sp.ErrorMessage = '' then begin
124+ MemoMsg.append('Library Loaded =' + Sp.LibraryFullName);
125+ ButtonSpell.enabled := CheckForDict();
126+ end else
127+ MemoMsg.append(SP.ErrorMessage);
128+end;
129+```
130+
131+In this example, we are writing some status message to MemoMsg, its an easy way to see whats happening. The ButtonSpell is NOT enabled until the dictionaries have been set. Wait for it ....
132+
133+We now need two methods, one reads a nominated directory looking for any likely dictionary files, the other manages the decisions. If we find just one dictionary set, use it, if we find none, complain. But if we find several, and thats most likely, we have to ask the user which dictionary (ie language) they wish to use.
134+
135+``` pascal
136+function TForm1.FindDictionary(const Dict : TStrings; const DPath : AnsiString) : boolean;
137+var
138+ Info : TSearchRec;
139+begin
140+ Dict.Clear;
141+ if FindFirst(AppendPathDelim(DPath) + '*.dic', faAnyFile and faDirectory, Info)=0 then begin
142+ repeat
143+ Dict.Add(Info.Name);
144+ until FindNext(Info) <> 0;
145+ end;
146+ FindClose(Info);
147+ Result := Dict.Count >= 1;
148+end;
149+
150+function TForm1.CheckForDict() : boolean;
151+begin
152+ Result := False;
153+ EditDictPath.Caption := DictPathAlt;
154+ if not FindDictionary(ListBox1.Items, DictPath) then
155+ MemoMsg.Append('ERROR - no dictionaries found in ' + DictPath);
156+ if ListBox1.Items.Count = 1 then begin // Exactly one returned.
157+ if not Sp.SetDictionary( AppendPathDelim(DictPath) +
158+ ListBox1.Items.Strings[0])
159+ then
160+ MemoMsg.Append('ERROR ' + SP.ErrorMessage)
161+ else
162+ MemoMsg.Append('Dictionary set to ' + DictPath +
163+ ListBox1.Items.Strings[0]);
164+ end;
165+ Result := SP.GoodToGo; // only true if count was exactly one or FindDict failed and nothing changed
166+end;
167+```
168+
169+Ah, you say, but where are we looking for the dictionaries ? Sadly, I don't have a good solution for that. Here is where I found mine -
170+
171+``` pascal
172+procedure TForm1.SetDefaultDicPath();
173+begin
174+ {$ifdef LINUX}
175+ DictPath := '/usr/share/hunspell/';
176+ {$ENDIF}
177+ {$ifdef WINDOWS}
178+ DictPath := ExtractFilePath(Application.ExeName);
179+ //DictPath := 'C:\Program Files\LibreOffice 5\share\extensions\dict-en\';
180+ {$ENDIF}
181+ {$ifdef DARWIN}
182+ DictPath := '/Applications/Firefox.app/Contents/Resources/dictionaries/';
183+ //DictPathAlt := ExtractFilePath(Application.ExeName);
184+ {$endif}
185+end;
186+```
187+
188+Maybe, if other users contribute where they found usable hunspell dictionaries, we can build a list for each platform. Or just take the easy way out and tell user to get some dictionaries and put them into the application directory on Windows and Mac. Your thoughts very welcome....
189+
190+So far, if there is exactly one dictionary set in the indicated directory, all good. But what if there are several ? Our list box has a list of them, if the user double clicks one, they trigger this method -
191+
192+``` pascal
193+procedure TForm1.ListBox1DblClick(Sender: TObject);
194+begin
195+ if ListBox1.ItemIndex > -1 then
196+ ButtonSpell.enabled := Sp.SetDictionary( AppendPathDelim(DictPath) +
197+ ListBox1.Items.Strings[ListBox1.ItemIndex]);
198+ if SP.ErrorMessage = '' then begin
199+ MemoMsg.Append('Good To Go =' + booltostr(Sp.GoodToGo, True));
200+ MemoMsg.Append('Dictionary set to ' + AppendPathDelim(DictPath) +
201+ ListBox1.Items.Strings[ListBox1.ItemIndex]);
202+ end else
203+ MemoMsg.append('ERROR ' + SP.ErrorMessage);
204+end;
205+```
206+
207+Assuming we now have everything "good to go" we can press the Spell button and trigger this -
208+
209+``` pascal
210+procedure TForm1.ButtonSpellClick(Sender: TObject);
211+begin
212+ if not Sp.Spell(Edit1.text) then begin
213+ Memo1.Lines.BeginUpdate;
214+ Sp.Suggest('badspeller', Memo1.lines);
215+ Memo1.Lines.EndUpdate;
216+ end else
217+ Memo1.Lines.Clear;
218+end;
219+```
220+
221+Memo1 now contains some suggestions for better ways to spell badspeller !
222+
223+Important ! Don't forget to free up our hunspeller object, memory leaks are evil !
224+
225+``` pascal
226+procedure TForm1.FormDestroy(Sender: TObject);
227+begin
228+ Sp.free;
229+ Sp := nil;
230+end;
231+```
232+
233+This module will do considerably more but its presented here in its most stripped down form for readability.
234+
235+*A note to people new to Lazarus, methods with "(Sender: TObject)" shown above cannot just be pasted into your source, use the Form's Object Inspector to create the events first, then paste my sample code into the method.*
236+
237+### Demo 3 - Lazspell - TMemo
238+
239+In this example TMemo component was used.
240+
241+![](/wiki-images/b/b3/Lazspell.gif)
242+
243+Lazspell - an example of spelling checker - from <https://github.com/Raf20076/Lazspell>
244+
245+### Demo 4 - Lazspell 2 - TRichMemo
246+
247+In this example TRichMemo component was used.
248+
249+![](/wiki-images/8/8c/Lazspell-ver2.gif)
250+
251+Lazspell 2 version 2 - an example of spelling checker - from <https://github.com/Raf20076/Lazspell-2-version-2>
252+
253+### Demo 5 - Simple spelling checker
254+
255+1\. Start Lazarus IDE
256+
257+2\. Click Project -\> New Project -\> Choose -\> Application
258+
259+`  You have just created a new application. Now save it `
260+
261+3\. Click File -\> Save as
262+
263+`  Choose a folder where your application will be saved`\
264+`  First, project1.lpi will be saved, then unit1.pas`
265+
266+4\. Place hunspell.pas and hunspell.inc (from <https://github.com/davidbannon/hunspell4pas>) in your folder.
267+
268+5\. Place libhunspell.dll in your application folder (you can download it from here <https://github.com/Raf20076/Lazspell> or <https://github.com/davidbannon/hunspell4pas>) any options or compile by yourself.
269+
270+6\. Place dictionary in your application folder, (two files) like pl_PL.aff, pl_PL.dic. Dictionaries must be encoded in UTF8. You can download them from here <https://github.com/Raf20076/Lazspell/tree/master/dict>
271+
272+7\. On Form1 place component TButton (Button1) from Standard Tab
273+
274+8\. On Form1 place component TMemo (Memo1) from Standard Tab
275+
276+9\. On Form1 place component TListbox (ListBox1) from Standard Tab (here misspell words will be shown)
277+
278+10\. Click once Button1 on Form1 then go to Object Inspector, click Events tab, and double click next to OnClick event: it creates OnClick event and paste code (see the code of the whole application) starts from
279+
280+``` pascal
281+
282+{Check spelling}
283+ var
284+ i : Integer;
285+ MAX : Integer;
286+ FillInArrayWithWords:TStringArray;
287+ FillInString1: String;
288+ FillInString2: String;
289+```
290+
291+till
292+
293+``` pascal
294+
295+//If word is not in dictionary show it in Listbox as an error
296+```
297+
298+11\. Go to Object Inspector and click Form1, then click Events tab then double click next to OnCreate it creates OnCreate event and paste code (see the code of the whole application)
299+
300+``` pascal
301+ SpellCheck := THunspell.Create(True);
302+ SpellCheck.SetDictionary('pl_PL.dic');//Load dictionary
303+ SpellCheck.GoodToGo := True;
304+```
305+
306+12\. Still in ObjectInspector click Form1,then click Events tab then double click next to OnDestroy it creates OnDestroy event and paste code (see the code of the whole application)
307+
308+` SpellCheck.free;`\
309+` SpellCheck := nil;`
310+
311+13\. Then in your code place functions like ArrayValueCount etc
312+
313+See Below the code of the whole application to compare with your one.
314+
315+``` pascal
316+//SpellChecker by Raf20076, Poland 2019
317+
318+unit Unit1;
319+
320+{$mode objfpc}{$H+}
321+
322+interface
323+
324+uses
325+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
326+ LazFileUtils, LCLProc, LazUtils, LazUtf8;
327+
328+type
329+ TForm1 = class(TForm)
330+ Button1: TButton;
331+ Label1: TLabel;
332+ Label2: TLabel;
333+ ListBox1: TListBox;
334+ ListBox2: TListBox;
335+ Memo1: TMemo;
336+ procedure Button1Click(Sender: TObject);
337+ procedure FormCreate(Sender: TObject);
338+ procedure FormDestroy(Sender: TObject);
339+ end;
340+
341+var
342+ Form1: TForm1;
343+
344+implementation
345+
346+uses hunspell;//place hunspell.pas and hunspell .inc in your application folder
347+ //from https://github.com/davidbannon/hunspell4pas
348+
349+var
350+ SpellCheck: THunspell;
351+
352+{$R *.lfm}
353+
354+procedure TForm1.FormCreate(Sender: TObject);
355+begin
356+ SpellCheck := THunspell.Create(True);
357+ SpellCheck.SetDictionary('pl_PL.dic');//Load dictionary
358+ SpellCheck.GoodToGo := True;
359+end;
360+
361+{Extract words from string: non characters, spaces, carriagereturn aware}
362+
363+function ArrayValueCount(const InputArray: Array of string): Integer;
364+{Count elements in array}
365+var
366+ i:Integer;
367+begin
368+ result := 0;
369+ for i := low(InputArray) to high(InputArray) do
370+ if InputArray[i] <> ' ' then // 'between them one space'
371+ inc(result);
372+end;
373+
374+function StripOffNonCharacter(const aString: string): string;
375+{Remove non characters from string}
376+var
377+ a: Char;
378+begin
379+ Result := '';
380+ for a in aString do begin //below punctuation marks, numbers to remove from string
381+ if not CharInSet( a, ['.', ',', ';', ':', '!', '/',
382+ '?', '@', '#', '$', '%', '&', '*', '(', ')', '{',
383+ '}', '[', ']', '-', '\', '|', '<', '>', '''', '"', '^',
384+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_', '+',
385+ '=', '~']) then //'„', '”'])these marks make error: Ordinal expression expected, need to be fixed
386+ begin
387+ Result := Result + a;
388+ end;
389+ end;
390+end;
391+
392+function ReplaceCarriageReturn(s: string) : string;
393+{Replace carriagereturn with one space}
394+var
395+ i: Integer;
396+begin
397+ Result:=s;
398+ for i := 1 to Length(Result) do
399+ if Result[i] in [#3..#13] then
400+ Result[i] := ' ';//'Between them one space'
401+end;
402+
403+procedure TForm1.Button1Click(Sender: TObject);
404+{Check spelling}
405+var
406+ i : Integer;
407+ MAX : Integer;
408+ FillInArrayWithWords:TStringArray;
409+ FillInString1: String;
410+ FillInString2: String;
411+begin
412+ ListBox1.clear;
413+ ListBox1.Items.Clear;
414+
415+ FillInString1 := ReplaceCarriageReturn(Memo1.Lines.Text);//take text from Memo1; replace carriage return
416+ //with one space (using ReplaceCarriageReturn function) and put into FillInString1
417+
418+ FillInString2 := StripOffNonCharacter(FillInString1);//remove all non characters from FillInString1
419+ //(using StripOffNonCharacter function) and put string without non charachters into FillInString2
420+
421+ FillInArrayWithWords := FillInString2.split(' '); //Split string into words with ' ' one space
422+ //(using .split) and put separate words into array FillInArrayWithWords
423+
424+ MAX := ArrayValueCount(FillInArrayWithWords); //Give how many elements are in array
425+ //(using function ArrayValueCount)
426+
427+ for i := 0 to MAX -1 do
428+ if not SpellCheck.Spell(FillInArrayWithWords[i]) then
429+ ListBox1.Items.add(FillInArrayWithWords[i]);
430+ //Take word from array and check in dictionary through hunspell (using SpellCheck.Spell function)
431+ //If word is not in dictionary show it in Listbox as an error
432+end;
433+
434+procedure TForm1.FormDestroy(Sender: TObject);
435+begin
436+ SpellCheck.free;
437+ SpellCheck := nil;
438+end;
439+
440+end.
441+```
442+
443+When you run your application and type some text in Memo and then press the button, the application will check if any errors are found, if found then shows them in ListBox. The main problem was how to remove carriage return and non characters and then how to split text into separate words. It's not an easy task. Therefore there are three functions to do this:ReplaceCarriageReturn function, StripOffNonCharacter function and FillInString2.split , function. The FillInString2.split function actually uses .split function from SysUtils. Hopefully the code of the whole application is self explanatory.
444+
445+## Further Reading and Links
446+
447+- <https://github.com/hunspell/hunspell>
448+- <https://github.com/davidbannon/hunspell4pas> - the (correctly licensed) hunspell object pascal unit.
449+- <https://github.com/Homebrew> - probably sensible way to get hunspell on your Mac if it's not already there.
450+- <https://github.com/tomboy-notes/tomboy-ng/releases> - Contains The 64bit Windows DLL in a tomboy-ng_win64_\<ver\>.zip
451+- <https://github.com/cutec-chris/hunspell>
452+- <https://forum.lazarus.freepascal.org/index.php/topic,50474.0.html> - FastHighliter code to use for highlighting misspelt words
453+- <https://github.com/Raf20076> - Lazspell is an example of simple spelling checker application written in Lazarus IDE.
454+- <https://forum.lazarus.freepascal.org/index.php/topic,46233.0.html>
455+- <https://forum.lazarus.freepascal.org/index.php/topic,51569.0.html>
456+- <https://forum.lazarus.freepascal.org/index.php?topic=44298.0>
457+
458+---
459+*From the Free Pascal wiki, page "spelling", as it stood on 2022-10-04 after a change by Alextp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history