(* Name: UnicodeData Copyright: Copyright (C) SIL International. Documentation: Description: Create Date: 1 Aug 2006 Modified Date: 10 Jun 2014 Authors: mcdurdin Related Files: Dependencies: Bugs: Todo: Notes: History: 01 Aug 2006 - mcdurdin - Rework for Keyman Developer 7 Character map 23 Aug 2006 - mcdurdin - Add ShortName property and FindDataByName function 23 Aug 2006 - mcdurdin - Add GetUnicodeRangeFromFilter function 14 Sep 2006 - mcdurdin - Moved to new location 06 Oct 2006 - mcdurdin - Add font ranges 06 Oct 2006 - mcdurdin - Fix CRLF issue with importing text files 06 Oct 2006 - mcdurdin - Optimize cached characters in database 04 Jan 2007 - mcdurdin - Don't change space to underscore when filtering block names 23 Aug 2007 - mcdurdin - I1014 - Fix character map database rebuild 12 Mar 2010 - mcdurdin - I1891 - Improve character search 12 Mar 2010 - mcdurdin - I2210 - Filter should support numeric ranges better 18 Mar 2011 - mcdurdin - I2794 - Fix memory leaks 18 Mar 2011 - mcdurdin - I2476 - Keyman Developer 8.0 cannot build character map database 18 Mar 2011 - mcdurdin - I2299 - Keyman Developer tries to write to UnicodeData.mdb which is stored in Program Files 25 Mar 2011 - mcdurdin - I2845 - After fixing Developer Unicode data references, BuildUniData fails due to path overrides 04 May 2012 - mcdurdin - I3308 - V9.0 - Start to move towards Delphi namespaces 04 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls 04 May 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes 08 Jun 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes 26 Jun 2012 - mcdurdin - I3379 - KM9 - Remove old Winapi references now in Delphi libraries 08 Oct 2012 - mcdurdin - I3465 - V9.0 - Filtering is not working in character map 10 Jun 2014 - mcdurdin - I4257 - V9.0 - Add ethnologue language codes to unicodedata.mdb *) unit UnicodeData; // I3308 // I3306 interface uses System.Classes, System.Contnrs, System.SysUtils, Winapi.Windows, ADODB_TLB, ADOX_TLB, TTInfo; //UfrmUnicodeDataStatus; type TUnicodeCharacter = record CodeValue: Integer; // 0 means record not found CharacterName: WideString; { GeneralCategory: string; CanonicalCombiningClasses: Integer; BidirectionalCategory: string; CharacterDecompositionMapping: string; // '' is not specified DecimalDigitValue: Integer; // -1 is not specified DigitValue: Integer; // -1 is not specified NumericValue: string; // -1 is not specified Mirrored: Boolean; Unicode10Name: string; // '' is not specified _10646Comment: string; // '' is not specified UpperCaseMapping: Integer; // 0 is not specified LowerCaseMapping: Integer; // 0 is not specified TitleCaseMapping: Integer; // 0 is not specified } end; {TUnicodeChar = record Char: DWord; //array[0..2] of WideChar; end;} //TUnicodeCharArray = array[0..1000000] of TUnicodeChar; //PUnicodeCharArray = ^TUnicodeCharArray;// TIntegerArray = array[0..1000000] of Integer; PIntegerArray = ^TIntegerArray; TUnicodeBlock = class private FName: WideString; FBlockID: Integer; FEndChar: Integer; FCacheCharData: PIntegerArray; FTag: Integer; FCacheCharCount: Integer; FStartChar: Integer; FShortName: WideString; public destructor Destroy; override; property BlockID: Integer read FBlockID; property Name: WideString read FName; property ShortName: WideString read FShortName; property StartChar: Integer read FStartChar; property EndChar: Integer read FEndChar; property CacheCharCount: Integer read FCacheCharCount; property CacheCharData: PIntegerArray read FCacheCharData; property Tag: Integer read FTag write FTag; end; TUnicodeBlockList = class(TObjectList) private FName: WideString; FShortName: WideString; function GetItem(Index: Integer): TUnicodeBlock; procedure SetItem(Index: Integer; const Value: TUnicodeBlock); public property Name: WideString read FName; property ShortName: WideString read FShortName; property Items[Index: Integer]: TUnicodeBlock read GetItem write SetItem; default; end; TUnicodeDataError = ( udeCouldNotDeleteDatabaseForRebuild, udeCouldNotCreateDatabase); TUnicodeData = class; IUnicodeDataUIManager = interface procedure UDUI_Error(Sender: TUnicodeData; Error: TUnicodeDataError; const Details: WideString); procedure UDUI_UpdateStatus(const Msg: WideString; Pos, Max: Integer); function UDUI_StartRebuild(Callback: TNotifyEvent; AskFirst: Boolean): Boolean; function UDUI_ShouldStartRebuildOnError(const Msg: WideString): Boolean; end; TUnicodeData = class private FBlocks: TUnicodeBlockList; FUnicodeSourcePath: WideString; FRefs: Integer; FDatabase: ADODB_TLB.Connection; // DaoDatabase; FDisabled: Boolean; FRebuilt: Boolean; FUnicodeDataUIManager: IUnicodeDataUIManager; FADBPath: string; FFontRanges: TTTCMapTable; FFontName: WideString; FForceDBPathToAppDataOnBuild: Boolean; // I2845 procedure DoError(ErrorCode: TUnicodeDataError; const Details: WideString = ''); function ShouldBuildDatabase: Boolean; procedure CallbackBuildDatabase(Sender: TObject); procedure ImportBlocks; procedure ImportChars; procedure LoadBlocks; function IsHangulCode(code: Integer; var ch: TUnicodeCharacter): Boolean; function IsCJKCode(code: Integer; var ch: TUnicodeCharacter): Boolean; function IsHangulName(aname: string; var ch: TUnicodeCharacter): Boolean; function IsCJKName(aname: string; var ch: TUnicodeCharacter): Boolean; procedure LoadDatabase; procedure CloseDatabase; function IsSpecialChar(ch: Integer): Boolean; procedure CleanupFilter(var filter: WideString; FBlock: Boolean); function UpdateBlockAndGetID(ch: Integer): Integer; procedure ImportUniHan; procedure BuildFontCharTable(const AFontName: WideString); procedure SetFontName(const Value: WideString); procedure SetDBPathToAppData; public constructor Create(const SourcePath: WideString; AUnicodeDataUIManager: IUnicodeDataUIManager; const ADBPath: string = ''; AForceDBPathToAppDataOnBuild: Boolean = True); // I2845 destructor Destroy; override; function DBPath: string; procedure BuildDatabase(const ASourcePath: WideString = ''); function FindDataByCode(code: Integer): TUnicodeCharacter; function FindDataByName(aname: WideString): TUnicodeCharacter; function GetSearchBlocks(filter: WideString): TUnicodeBlockList; procedure RefreshOptions; property Rebuilt: Boolean read FRebuilt; property Blocks: TUnicodeBlockList read FBlocks; property FontName: WideString read FFontName write SetFontName; property UnicodeDataUIManager: IUnicodeDataUIManager read FUnicodeDataUIManager; end; TUnicodeDataFormat = class { class function GeneralCategory(GC: string): string; class function CanonicalCombiningClass(CCC: Integer): string; class function BidirectionalCategory(BC: string): string; class function CharacterDecompositionMapping(CDM: string): string; class function DecimalDigitValue(DDV: Integer): string; class function DigitValue(DV: Integer): string; class function NumericValue(NV: string): string; class function Mirrored(M: Boolean): string; class function Unicode10Name(U1N: string): string; class function _10646Comment(_1C: string): string; class function UpperCaseMapping(UCM: Integer): string; class function LowerCaseMapping(LCM: Integer): string; class function TitleCaseMapping(LCM: Integer): string; } class function CleanCharacterName(nm: AnsiString): AnsiString; // I3310 end; const SKRebuildDatabase = 'Keyman Developer has a Unicode character database, for use in the Character Map, '+ 'that needs to be built. This may take up to a minute to complete.'#13#10#13#10+ 'Do you want to do this now?'; var FUnicodeData: TUnicodeData = nil; procedure CreateUnicodeData(const SourcePath: WideString; AUnicodeDataUIManager: IUnicodeDataUIManager; const ADBPath: string = ''); procedure FreeUnicodeData; function GetUnicodeRangeFromFilter(filter: WideString; var RangeStart, RangeStop: Integer): Boolean; const UnicodeDataTxtName = 'UnicodeData.txt'; UnicodeDataMdbName = 'unicodedata.mdb'; implementation uses System.AnsiStrings, System.StrUtils, System.Variants, Winapi.ShlObj, RegistryKeys, Unicode, utilstr, utilsystem; var _: Variant; procedure CreateUnicodeData(const SourcePath: WideString; AUnicodeDataUIManager: IUnicodeDataUIManager; const ADBPath: string = ''); begin if not Assigned(FUnicodeData) then FUnicodeData := TUnicodeData.Create(SourcePath, AUnicodeDataUIManager, ADBPath); Inc(FUnicodeData.FRefs); end; procedure FreeUnicodeData; begin if not Assigned(FUnicodeData) then Exit; Dec(FUnicodeData.FRefs); if FUnicodeData.FRefs = 0 then FreeAndNil(FUnicodeData); end; function StrTok(var str: ansistring; const tok: AnsiChar; var val: ansistring): Boolean; // I3310 var n: Integer; begin if str = '' then Result := False else begin Result := True; n := Pos(tok, str); if n = 0 then begin val := str; str := ''; end else begin val := Copy(str, 1, n-1); Delete(str, 1, n); end; end; end; {------------------------------------------------------------------------------- - TUnicodeData: Read character properties from database or thru algorithm - ------------------------------------------------------------------------------} procedure TUnicodeData.BuildDatabase(const ASourcePath: WideString = ''); var OldSourcePath: WideString; begin OldSourcePath := FUnicodeSourcePath; if ASourcePath <> '' then FUnicodeSourcePath := ASourcePath; if FUnicodeDataUIManager.UDUI_StartRebuild(CallbackBuildDatabase, False) then FRebuilt := True; if ASourcePath <> '' then FUnicodeSourcePath := OldSourcePath; end; procedure ReadStreamLine(fs: TFileStream; var s: ansistring); // I3310 var ch: array[0..100] of ansiChar; str: shortstring; {nCR,} iCR, iLF, n: Integer; const chcr: shortstring = #13; chlf: shortstring = #10; begin s := ''; while fs.Position < fs.Size do begin n := fs.Read(ch, 100); str := Copy(ch, 1, n); //nCR := 0; iCR := Pos(chcr, str); // I3310 //if iCR > 0 then //begin //Delete(str, iCR, 1); //Inc(nCR); //end; iLF := Pos(chlf, str); // I3310 if iLF > 0 then begin s := s + Copy(str, 1, iLF-1); if iCR = iLF then Inc(iLF); fs.Seek(-(Length(str)-iLF), soFromCurrent); s := System.AnsiStrings.AnsiReplaceText(s, #13, ''); // I3310 // I3310 Exit; end; s := s + str; end; end; procedure TUnicodeData.CallbackBuildDatabase(Sender: TObject); const SQL_Create_eBlock = 'CREATE TABLE eBlock (' + ' BlockID INT IDENTITY(1,1) CONSTRAINT PrimaryKey PRIMARY KEY,' + ' StartChar INT NOT NULL,' + ' EndChar INT NOT NULL,' + ' Name VARCHAR(240) NOT NULL,' + ' CacheCharCount INT NULL,' + ' CacheCharData IMAGE NULL)'; SQL_Create_eCharacter = 'CREATE TABLE eCharacter ('+ ' CharacterID INT IDENTITY(1,1) CONSTRAINT PrimaryKey PRIMARY KEY,'+ ' BlockID INT NOT NULL,'+ ' CodeValue INT NOT NULL,'+ ' NameIsDescription BIT,'+ ' CharacterName VARCHAR(240) NOT NULL,'+ { ' GeneralCategory VARCHAR(2),'+ ' CanonicalCombiningClasses INT NOT NULL,'+ ' BidirectionalCategory VARCHAR(3) NOT NULL,'+ ' CharacterDecompositionMapping VARCHAR(255),'+ ' DecimalDigitValue INT,'+ ' DigitValue INT,'+ ' NumericValue VARCHAR(20),'+ ' Mirrored BIT,'+ ' Unicode10Name VARCHAR(240),'+ ' A10646Comment VARCHAR(255),'+ ' UpperCaseMapping INT,'+ ' LowerCaseMapping INT,'+ ' TitleCaseMapping INT,' +} ' CONSTRAINT FK_Character_Block FOREIGN KEY (BlockID) REFERENCES eBlock (BlockID))'; SQL_Create_ByCodeValue = 'CREATE UNIQUE INDEX ByCodeValue ON eCharacter (CodeValue) WITH DISALLOW NULL'; SQL_Create_ByCharacterName = 'CREATE INDEX ByCharacterName ON eCharacter (CharacterName) WITH DISALLOW NULL'; SQL_Create_qCharacterByCode = 'CREATE PROCEDURE qCharacterByCode (prmCode INT) AS SELECT * FROM eCharacter WHERE CodeValue = prmCode;'; SQL_Create_qCharacterByName = 'CREATE PROCEDURE qCharacterByName (prmName VARCHAR(240)) AS SELECT * FROM eCharacter WHERE CharacterName Like prmName;'; SQL_Create_qBlocks = 'CREATE PROCEDURE qBlocks AS SELECT * FROM eBlock ORDER BY StartChar;'; SQL_Create_qFilterCharacters = 'CREATE PROCEDURE qFilterCharacters (prmFilter VARCHAR(240)) AS '+ ' SELECT CodeValue FROM eCharacter WHERE CharacterName LIKE prmFilter ORDER BY CodeValue'; SQL_Create_qFilterCharactersBlock = 'CREATE PROCEDURE qFilterCharactersBlock (prmFilter VARCHAR(240)) AS '+ ' SELECT CodeValue FROM eCharacter INNER JOIN eBlock ON eCharacter.BlockID = eBlock.BlockID WHERE eBlock.Name LIKE prmFilter ORDER BY CodeValue'; SQL_Create_qFilterCharactersRange = 'CREATE PROCEDURE qFilterCharactersRange (prmStart INT, prmStop INT) AS '+ ' SELECT CodeValue FROM eCharacter WHERE (CodeValue >= prmStart AND CodeValue <= prmStop) ORDER BY CodeValue'; SQL_Create_qFilterCharactersSingle = 'CREATE PROCEDURE qFilterCharactersSingle (prmCode INT) AS '+ ' SELECT CodeValue FROM eCharacter WHERE CodeValue = prmCode'; // Language table // I4257 SQL_Create_eLanguage = 'CREATE TABLE eLanguage ( '+ ' LanguageCode CHAR(3) NOT NULL CONSTRAINT LanguagePrimaryKey PRIMARY KEY, ' + ' Name VARCHAR(128) NOT NULL, ' + ' Country CHAR(2) NOT NULL, '+ ' Status CHAR NOT NULL)'; SQL_Create_LanguageByCodeValue = 'CREATE UNIQUE INDEX LanguageByCodeValue ON eLanguage (LanguageCode) WITH DISALLOW NULL'; SQL_Create_LanguageByName = 'CREATE INDEX LanguageByName ON eLanguage (Name) WITH DISALLOW NULL'; SQL_Create_qLanguageByCode = 'CREATE PROCEDURE qLanguageByCode (prmCode CHAR(3)) AS SELECT * FROM eLanguage WHERE LanguageCode = prmCode;'; SQL_Create_qLanguagesByName = 'CREATE PROCEDURE qLanguagesByName (prmName VARCHAR(128)) AS SELECT * FROM eLanguage WHERE Name Like prmName;'; var //s: string; //val: array[0..14] of string; //i: Integer; vRecords: OleVariant; FCatalog: ADOX_TLB.Catalog; begin CloseDatabase; if FForceDBPathToAppDataOnBuild then SetDBPathToAppData; // I2476, I2299, I2845 if FileExists(DBPath+UnicodeDataMdbName) then if not System.SysUtils.DeleteFile(DBPath+UnicodeDataMdbName) then begin DoError(udeCouldNotDeleteDatabaseForRebuild); //WideShowMessage('Could not delete database '''+DBPath+UnicodeDataMdbName+''': '+SysErrorMessage(GetLastError)); Exit; end; { Create the database } FUnicodeDataUIManager.UDUI_UpdateStatus('Creating database', 0, 0); try FCatalog := CoCatalog.Create; FCatalog.Create('Provider=Microsoft.Jet.OLEDB.4.0;Data Source='+DBPath+UnicodeDataMdbName); FCatalog := nil; FDatabase := CoConnection.Create; {$R-} {$WARNINGS OFF} // Constant expr violates subrange bounds for adConnectUnspecified FDatabase.Open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source='+DBPath+UnicodeDataMdbName, '', '', adConnectUnspecified); {$R+} {$WARNINGS ON} // Constant expr violates subrange bounds for adConnectUnspecified FDatabase.Execute(SQL_Create_eBlock, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_eCharacter, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_ByCodeValue, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_ByCharacterName, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qCharacterByCode, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qCharacterByName, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qBlocks, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qFilterCharacters, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qFilterCharactersBlock, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qFilterCharactersRange, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_qFilterCharactersSingle, vRecords, adExecuteNoRecords); FDatabase.Execute(SQL_Create_eLanguage, vRecords, adExecuteNoRecords); // I4257 FDatabase.Execute(SQL_Create_LanguageByCodeValue, vRecords, adExecuteNoRecords); // I4257 FDatabase.Execute(SQL_Create_LanguageByName, vRecords, adExecuteNoRecords); // I4257 FDatabase.Execute(SQL_Create_qLanguageByCode, vRecords, adExecuteNoRecords); // I4257 FDatabase.Execute(SQL_Create_qLanguagesByName, vRecords, adExecuteNoRecords); // I4257 except on E:Exception do begin try FDatabase.Close; except ; end; FDatabase := nil; System.SysUtils.DeleteFile(DBPath+UnicodeDataMdbName); DoError(udeCouldNotCreateDatabase); FDisabled := True; Exit; end; end; FUnicodeDataUIManager.UDUI_UpdateStatus('Filling database', 0, 0); { Fill the database } //oldpos := 0; try ImportBlocks; ImportChars; ImportUnihan; except on E:Exception do begin DoError(udeCouldNotCreateDatabase, E.Message); FDisabled := True; end; end; end; procedure TUnicodeData.SetFontName(const Value: WideString); begin FFontName := Value; BuildFontCharTable(FFontName); end; function TUnicodeData.ShouldBuildDatabase: Boolean; var txtdate, datdate: TDateTime; begin Result := False; if not FileAge(FUnicodeSourcePath+UnicodeDataTxtName, txtdate) then Exit; // UnicodeData.txt does not exist if FileAge(DBPath+UnicodeDataMdbName, datdate) then if datdate >= txtdate then Exit; Result := True; // The txt file is newer than the dat file, or the dat file does not exist end; constructor TUnicodeData.Create(const SourcePath: WideString; AUnicodeDataUIManager: IUnicodeDataUIManager; const ADBPath: string = ''; AForceDBPathToAppDataOnBuild: Boolean = True); // I2845 begin inherited Create; FADBPath := ADBPath; FForceDBPathToAppDataOnBuild := AForceDBPathToAppDataOnBuild; // I2845 FUnicodeDataUIManager := AUnicodeDataUIManager; FUnicodeSourcePath := IncludeTrailingPathDelimiter(SourcePath); FBlocks := TUnicodeBlockList.Create; //if not CanUseDatabases then Exit; RefreshOptions; end; procedure TUnicodeData.ImportBlocks; var fs: TFileStream; s: ansistring; range, rname: ansistring; FUnicodeSourceFile: string; //ur: TUnicodeBlock; n: Integer; eblock: ADODB_TLB.Recordset; begin FUnicodeSourceFile := FUnicodeSourcePath + 'blocks.txt'; fs := TFileStream.Create(FUnicodeSourceFile, fmOpenRead); //DBPath+UnicodeDataTxtName, fmOpenRead); with fs do try eblock := CoRecordset.Create; eblock.Open('eBlock', FDatabase, adOpenForwardOnly, adLockOptimistic, adCmdTable);// Execute('eCharacter', vRecords, adCmdTable); try while Position < Size do begin ReadStreamLine(fs, s); n := System.AnsiStrings.PosEx('#', s); // I3310 // I3310 if n > 0 then Delete(s,n,Length(s)); if System.AnsiStrings.Trim(s) = '' then Continue; // I3310 // I3310 if not StrTok(s, ';', range) then Continue; if not StrTok(s, ';', rname) then Continue; range := System.AnsiStrings.Trim(range); // I3310 // I3310 rname := System.AnsiStrings.Trim(rname); // I3310 // I3310 n := System.AnsiStrings.PosEx('..', range); if n = 0 then Continue; // I3310 // I3310 eblock.AddNew(_, _); eblock.Collect['StartChar'] := StrToInt(String_AtoU('$'+Copy(range,1,n-1))); // I3310 // I3310 eblock.Collect['EndChar'] := StrToInt(String_AtoU('$'+Copy(range,n+2,Length(range)))); // I3310 // I3310 eblock.Collect['Name'] := String_AtoU(rname); // I3310 // I3310 eblock.Update(_, _); end; finally eblock := nil; end; finally fs.Free; end; LoadBlocks; end; function TUnicodeData.IsSpecialChar(ch: Integer): Boolean; begin Result := ({(ch >= $0000) and} (ch <= $001F)) or // Controls 15.1 ((ch >= $0080) and (ch <= $009F)) or // Controls 15.1 ((ch >= $D800) and (ch <= $DFFF)) or // Surrogates 15.5 ((ch >= $FE00) and (ch <= $FE0F)) or // Variation selectors 15.x ((ch >= $E0100) and (ch <= $E01EF)) or // Variation selectors 15.x (((ch and $FFFF) = $FFFE) or ((ch and $FFFF) = $FFFF)) or // Non-characters U+[??]FFFE/FFFF 15.8 ((ch >= $FDD0) and (ch <= $FDEE)) or // non-characters within Arabic Presentations Forms A block 15.8 ((ch >= $FFF0) and (ch <= $FFFF)) or // Specials 15.9 (ch = $FEFF) or // BOM reversed 15.9 ((ch >= $E0000) and (ch <= $E007F)); // TAG characters 15.10 end; function TUnicodeData.UpdateBlockAndGetID(ch: Integer): Integer; var i: Integer; begin for i := 0 to FBlocks.Count - 1 do if (FBlocks[i].StartChar <= ch) and (ch <= FBlocks[i].EndChar) then begin Result := FBlocks[i].BlockID; if (FBlocks[i].FCacheCharCount mod 1000) = 0 then begin ReallocMem(FBlocks[i].FCacheCharData, (FBlocks[i].FCacheCharCount + 1000) * SizeOf(Integer)); end; //SetLength(FBlocks[i].FCacheCharData, FBlocks[i].FCacheCharCount + 1000); Inc(FBlocks[i].FCacheCharCount); FBlocks[i].FCacheCharData[FBlocks[i].CacheCharCount-1] := ch; Exit; end; Result := 0; end; procedure TUnicodeData.ImportChars; var fs: TFileStream; echar, eblocks: ADODB_TLB.RecordSet; s: ansistring; val: array[0..14] of ansistring; oldpos, ch, i: Integer; b: Variant; p: PByte; begin fs := TFileStream.Create(FUnicodeSourcePath + 'unicodedata.txt', fmOpenRead); //DBPath+UnicodeDataTxtName, fmOpenRead); with fs do try { Import the charaters } oldpos := 0; echar := CoRecordset.Create; echar.Open('eCharacter', FDatabase, adOpenForwardOnly, adLockOptimistic, adCmdTable);// Execute('eCharacter', vRecords, adCmdTable); try while Position < Size do begin ReadStreamLine(fs, s); for i := 0 to 14 do StrTok(s, ';', val[i]); if Copy(val[1], 1, 1) = '<' then Continue; // not a code point, control or similar ch := StrToInt(String_AtoU('$'+val[0])); // I3310 // I3310 if IsSpecialChar(ch) then Continue; echar.AddNew(_, _); echar.Collect['CodeValue'] := ch; echar.Collect['BlockID'] := UpdateBlockAndGetID(ch); echar.Collect['CharacterName'] := String_AtoU(TUnicodeDataFormat.CleanCharacterName(Copy(val[1], 1, 240))); // I3310 // I3310 echar.Collect['NameIsDescription'] := False; { echar.Collect['GeneralCategory'] := Copy(val[2], 1, 2); echar.Collect['CanonicalCombiningClasses'] := StrToInt(val[3]); echar.Collect['BidirectionalCategory'] := Copy(val[4], 1, 3); if val[5] <> '' then echar.Collect['CharacterDecompositionMapping'] := Copy(val[5], 1, 255); if val[6] <> '' then echar.Collect['DecimalDigitValue'] := StrToInt(val[6]); if val[7] <> '' then echar.Collect['DigitValue'] := StrToInt(val[7]); if val[8] <> '' then echar.Collect['NumericValue'] := Copy(val[8], 1, 20); if val[9] = '' then val[9] := 'N'; echar.Collect['Mirrored'] := UpCase(val[9][1]) = 'Y'; if val[10] <> '' then echar.Collect['Unicode10Name'] := Copy(val[10], 1, 240); if val[11] <> '' then echar.Collect['A10646Comment'] := Copy(val[11], 1, 255); if val[12] <> '' then echar.Collect['UpperCaseMapping'] := StrToInt('$'+val[12]); if val[13] <> '' then echar.Collect['LowerCaseMapping'] := StrToInt('$'+val[13]); if val[14] <> '' then echar.Collect['TitleCaseMapping'] := StrToInt('$'+val[14]); } echar.Update(_, _); if Position - oldpos > Size div 200 then begin FUnicodeDataUIManager.UDUI_UpdateStatus('Filling database (U+'+String_AtoU(val[0])+')', Position, Size); // I3310 // I3310 oldpos := Position; end; end; finally echar := nil; end; { Save the block cache data } eblocks := CoRecordset.Create; eblocks.Open('eBlock', FDatabase, adOpenForwardOnly, adLockOptimistic, adCmdTable); try while not eblocks.EOF do begin for i := 0 to FBlocks.Count - 1 do if FBlocks[i].BlockID = eblocks.Collect['BlockID'] then begin eblocks.Collect['CacheCharCount'] := FBlocks[i].CacheCharCount; {aADOStream := CoStream.Create; aADOStream.Type_ := adTypeBinary; aADOStream.Open(EmptyParam, adModeUnknown, adOpenStreamUnspecified, '', '');} b := VarArrayCreate([0,(FBlocks[i].CacheCharCount)*SizeOf(Integer)-1], varByte); p := PByte(VarArrayLock(b)); CopyMemory(p, FBlocks[i].CacheCharData, (FBlocks[i].CacheCharCount)*SizeOf(Integer)); VarArrayUnlock(b); eblocks.Fields['CacheCharData'].AppendChunk(b);// AppendChunk(b); //eblocks.Collect['CacheCharData'] := IntArrayToStr(FBlocks[i].CacheCharData); Break; end; eblocks.MoveNext; end; finally eblocks := nil; end; finally fs.Free; end; end; procedure TUnicodeData.ImportUniHan; var fs: TFileStream; echar, eblocks: ADODB_TLB.RecordSet; t, s: ansistring; val: array[0..14] of ansistring; oldpos, ch, i: Integer; curch: Integer; ft, fdesc: WideString; fch: WideChar; fdefinition: WideString; b: Variant; p: PByte; begin curch := 0; if not FileExists(FUnicodeSourcePath + 'unihan.txt') then Exit; { Unihan is optional } fs := TFileStream.Create(FUnicodeSourcePath + 'unihan.txt', fmOpenRead); //DBPath+UnicodeDataTxtName, fmOpenRead); with fs do try { Import the charaters } oldpos := 0; echar := CoRecordset.Create; echar.Open('eCharacter', FDatabase, adOpenForwardOnly, adLockOptimistic, adCmdTable);// Execute('eCharacter', vRecords, adCmdTable); try while Position < Size do begin ReadStreamLine(fs, s); if (Copy(s,1,1) = '#') or (s = '') then Continue; t := s; for i := 0 to 2 do StrTok(s, #9, val[i]); if Copy(val[0],1,2) <> 'U+' then begin writeln('Invalid Line: '+t); ExitCode := 3; Exit; end; { File format is UTF-8 } Delete(val[0], 1, 2); ch := StrToInt('$'+String_AtoU(val[0])); // I3310 // I3310 if curch <> ch then begin if curch <> 0 then begin if ft <> '' then fdesc := ft + '; ' + fdefinition else fdesc := fdefinition; if Length(fdesc) > 237 then echar.Collect['CharacterName'] := Copy(fdesc,1,237)+'...' else echar.Collect['CharacterName'] := fdesc; try echar.Update(_, _); except on E:Exception do begin echar.CancelUpdate; writeln('Error adding U+'+IntToHex(curch,4)+': '+E.Message); end; end; end; echar.AddNew(_, _); echar.Collect['CodeValue'] := ch; echar.Collect['BlockID'] := UpdateBlockAndGetID(ch); echar.Collect['NameIsDescription'] := True; { Disables the drag and drop of character name } curch := ch; fdefinition := ''; fdesc := ''; ft := ''; end; fch := #0; if val[1] = 'kCantonese' then fch := 'C' else if val[1] = 'kDefinition' then fdefinition := UTF8ToString(val[2]) // I3310 else if val[1] = 'kHangul' then fch := 'H' else if val[1] = 'kJapaneseKun' then fch := 'J' else if val[1] = 'kKorean' then fch := 'K' else if val[1] = 'kMandarin' then fch := 'M' else if val[1] = 'kVietnamese' then fch := 'V'; if fch <> #0 then begin if ft <> '' then ft := ft + ' '; ft := ft + fch + ':'+UTF8ToString(val[2]); // I3310 end; if Position - oldpos > Size div 1000 then begin FUnicodeDataUIManager.UDUI_UpdateStatus('Filling database from Unihan (U+'+String_AtoU(val[0])+')', Position, Size); // I3310 // I3310 oldpos := Position; end; end; finally echar := nil; end; { Save the block cache data } eblocks := CoRecordset.Create; eblocks.Open('eBlock', FDatabase, adOpenForwardOnly, adLockOptimistic, adCmdTable); try while not eblocks.EOF do begin for i := 0 to FBlocks.Count - 1 do if FBlocks[i].BlockID = eblocks.Collect['BlockID'] then begin eblocks.Collect['CacheCharCount'] := FBlocks[i].CacheCharCount; b := VarArrayCreate([0,(FBlocks[i].CacheCharCount)*SizeOf(Integer)-1], varByte); p := PByte(VarArrayLock(b)); CopyMemory(p, @FBlocks[i].CacheCharData, (FBlocks[i].CacheCharCount)*SizeOf(Integer)); VarArrayUnlock(b); eblocks.Fields['CacheCharData'].AppendChunk(b);// AppendChunk(b); //eblocks.Collect['CacheCharData'] := IntArrayToStr(FBlocks[i].CacheCharData); Break; end; eblocks.MoveNext; end; finally eblocks := nil; end; finally fs.Free; end; end; (* function TUnicodeData.IntArrayToStr(v: TIntegerArray): Variant; var i: Integer; s: string; begin s := ''; for i := 0 to High(v) do begin s := s + IntToHex(v[i], 8); end; Result := s; { TVarData(Result).VType := varInteger; TVarData(Result).VAny Result := VarArrayCreate([Low(v)+1, High(v)+1], varInteger); for i := Low(v) to High(v) do Result[i+1] := v[i];} end; function TUnicodeData.VarStrToIntArray(v: Variant): TIntegerArray; var i: Integer; s: string; begin s := v; SetLength(Result, Length(s) div 8); for i := 0 to (Length(s) div 8) - 1 do begin Result[i] := StrToInt('$'+Copy(s,i*8+1,8)); end; { SetLength(Result, VarArrayHighBound(v, 1) - VarArrayLowBound(v, 1) + 1); for i := Low(Result) to High(Result) do Result[i] := v[i+1];} end; *) procedure TUnicodeData.LoadBlocks; var rec: ADODB_TLB.Recordset; vRecords: OleVariant; ub: TUnicodeBlock; b: OleVariant; p: PByte; begin if not Assigned(FDatabase) then Exit; FBlocks.Clear; rec := FDatabase.Execute('qBlocks', vRecords, adCmdStoredProc); try while not rec.EOF do begin ub := TUnicodeBlock.Create; ub.FBlockID := rec.Collect['BlockID']; ub.FName := rec.Collect['Name']; ub.FShortName := ub.FName; ub.FStartChar := rec.Collect['StartChar']; ub.FEndChar := rec.Collect['EndChar']; if not VarIsNull(rec.Collect['CacheCharCount']) then begin ub.FCacheCharCount := rec.Collect['CacheCharCount']; if ub.FCacheCharCount > 0 then begin b := rec.Fields['CacheCharData'].GetChunk(ub.FCacheCharCount*SizeOf(Integer)); p := PByte(VarArrayLock(b)); ub.FCacheCharData := AllocMem(ub.CacheCharCount * SizeOf(Integer)); //SetLength(ub.FCacheCharData, ub.FCacheCharCount); CopyMemory(ub.FCacheCharData, p, ub.FCacheCharCount*SizeOf(Integer)); VarArrayUnlock(b); end else ub.FCacheCharData := nil; //SetLength(ub.FCacheCharData, 0); //ub.FCacheCharData := VarStrToIntArray(rec.Collect['CacheCharData']); //SetLength(ub.CacheCharData, ub.CacheCharCount); end; FBlocks.Add(ub); rec.MoveNext; end; finally rec := nil; end; end; procedure TUnicodeData.LoadDatabase; begin CloseDatabase; if ShouldBuildDatabase then begin UnicodeDataUIManager.UDUI_StartRebuild(CallbackBuildDatabase, True); Exit; end; if FileExists(DBPath+UnicodeDataMdbName) then begin try FDatabase := CoConnection.Create; {$R-} {$WARNINGS OFF} // Constant expr violates subrange bounds for adConnectUnspecified FDatabase.Open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source='+DBPath+UnicodeDataMdbName, '', '', adConnectUnspecified); {$R+} {$WARNINGS ON} // Constant expr violates subrange bounds for adConnectUnspecified //FDatabase := dbEngine.OpenDatabase(DBPath+UnicodeDataMdbName, _, False, _); LoadBlocks; except on E:Exception do begin FDatabase := nil; FBlocks.Clear; if FUnicodeDataUIManager.UDUI_ShouldStartRebuildOnError(E.Message) then BuildDatabase; end; end; end; end; procedure TUnicodeData.RefreshOptions; begin {with TKeymanDeveloperOptions.Create do try Read; FDisabled := CharMapDisableDatabaseLookups; finally Free; end;} FDisabled := False; if FDisabled then CloseDatabase else LoadDatabase; end; procedure TUnicodeData.CloseDatabase; begin if Assigned(FDatabase) then FDatabase.Close; FDatabase := nil; end; destructor TUnicodeData.Destroy; begin try CloseDatabase; except ; end; FBlocks.Free; FFontRanges.Free; inherited Destroy; end; procedure TUnicodeData.DoError(ErrorCode: TUnicodeDataError; const Details: WideString = ''); begin FUnicodeDataUIManager.UDUI_Error(Self, ErrorCode, Details); end; procedure TUnicodeData.SetDBPathToAppData; // I2476, I2299 begin FADBPath := GetFolderPath(CSIDL_APPDATA) + SFolderSharedDatabases + '\'; ForceDirectories(FADBPath); end; function TUnicodeData.DBPath: string; var FAppDataPath: string; begin FAppDataPath := GetFolderPath(CSIDL_APPDATA) + SFolderSharedDatabases + '\'; // I2476, I2299 if FADBPath <> '' then Result := FADBPath else if FileExists(FAppDataPath+UnicodeDataMdbName) then // I2476, I2299 Result := FAppDataPath else if FileExists(FUnicodeSourcePath+UnicodeDataMdbName) then Result := FUnicodeSourcePath else begin Result := GetFolderPath(CSIDL_APPDATA) + SFolderSharedDatabases + '\'; ForceDirectories(Result); end; end; function FillData(rec: ADODB_TLB.Recordset): TUnicodeCharacter; //rec: DaoRecordset): TUnicodeCharacter; begin Result.CodeValue := rec.Collect['CodeValue']; Result.CharacterName := rec.Collect['CharacterName']; { Result.GeneralCategory := rec.Collect['GeneralCategory']; Result.CanonicalCombiningClasses := rec.Collect['CanonicalCombiningClasses']; Result.BidirectionalCategory := rec.Collect['BidirectionalCategory']; if not VarIsNull(rec.Collect['CharacterDecompositionMapping']) then Result.CharacterDecompositionMapping := rec.Collect['CharacterDecompositionMapping'] else Result.CharacterDecompositionMapping := ''; if not VarIsNull(rec.Collect['DecimalDigitValue']) then Result.DecimalDigitValue := rec.Collect['DecimalDigitValue'] else Result.DecimalDigitValue := -1; if not VarIsNull(rec.Collect['DigitValue']) then Result.DigitValue := rec.Collect['DigitValue'] else Result.DigitValue := -1; if not VarIsNull(rec.Collect['NumericValue']) then Result.NumericValue := rec.Collect['NumericValue'] else Result.NumericValue := ''; Result.Mirrored := rec.Collect['Mirrored']; if not VarIsNull(rec.Collect['Unicode10Name']) then Result.Unicode10Name := rec.Collect['Unicode10Name'] else Result.Unicode10Name := ''; if not VarIsNull(rec.Collect['A10646Comment']) then Result._10646Comment := rec.Collect['A10646Comment'] else Result._10646Comment := ''; if not VarIsNull(rec.Collect['UpperCaseMapping']) then Result.UpperCaseMapping := rec.Collect['UpperCaseMapping'] else Result.UpperCaseMapping := 0; if not VarIsNull(rec.Collect['LowerCaseMapping']) then Result.LowerCaseMapping := rec.Collect['LowerCaseMapping'] else Result.LowerCaseMapping := 0; if not VarIsNull(rec.Collect['TitleCaseMapping']) then Result.TitleCaseMapping := rec.Collect['TitleCaseMapping'] else Result.TitleCaseMapping := 0; } end; function TUnicodeData.FindDataByCode(code: Integer): TUnicodeCharacter; var //rec: DaoRecordset; rec: ADODB_TLB.Recordset; vRecords: OleVariant; begin Result.CodeValue := 0; if FDisabled then Exit; if IsHangulCode(code, Result) then Exit; if not Assigned(FDatabase) then Exit; rec := FDatabase.Execute('EXECUTE qCharacterByCode '+IntToStr(code), vRecords, adCmdText); try if not rec.EOF then Result := FillData(rec) else if IsCJKCode(code, Result) then Exit else begin Result.CodeValue := code; Result.CharacterName := ''; end; finally rec := nil; end; end; function TUnicodeData.FindDataByName(aname: WideString): TUnicodeCharacter; var rec: ADODB_TLB.Recordset; vRecords: OleVariant; begin Result.CodeValue := 0; CleanupFilter(aname, False); if FDisabled then Exit; if IsHangulName(aname, Result) then Exit; if IsCJKName(aname, Result) then Exit; if not Assigned(FDatabase) then Exit; rec := FDatabase.Execute('qCharacterByName '+QuotedStr(aname+'%'), vRecords, adCmdStoredProc); // I3310 // I3465 try if not rec.EOF then Result := FillData(rec); finally rec := nil; end; end; procedure TUnicodeData.CleanupFilter(var filter: WideString; FBlock: Boolean); var I: Integer; begin { Replace *, # and ? with appropriate search characters } I := 1; while I <= length(filter) do begin if filter[i] = '*' then filter[i] := '%' else if (filter[i] = '_') then begin if FBlock then filter[I] := ' ' else begin filter[I] := '['; Insert('_]', filter, I+1); Inc(I, 2); end; end else if (filter[i] = ' ') then begin if not FBlock then filter[i] := '%' end else if (filter[i] = '#') or (filter[i] = '?') then filter[i] := '_'; Inc(I); end; { Detect end-of-line delimiter } I := Pos('$', filter); if I > 0 then Delete(filter, I, Length(filter)) else filter := filter + '%'; { Detect beginning-of-line delimiter } if Copy(filter, 1, 1) = '^' then Delete(filter, 1, 1) else filter := '%' + filter; end; function GetUnicodeRangeFromFilter(filter: WideString; var RangeStart, RangeStop: Integer): Boolean; // I2210 function UnicodeCharToValue(v: WideString): Integer; begin if Copy(v, 1, 2) = 'U+' then Result := StrToIntDef('$'+Copy(v,3,10), 0) else if Length(v) < 2 then Result := 0 else Result := StrToIntDef('$'+v, 0); end; var filterStart, filterStop: WideString; n: Integer; FRange: Boolean; begin filter := Trim(UpperCase(filter)); n := Pos('-', filter); if n > 0 then begin //[U+]xxxx-[U+]yyyy filterStart := Trim(Copy(filter, 1, n-1)); filterStop := Trim(Copy(filter, n+1, Length(filter))); FRange := True; end else begin //[U+]xxxx filterStart := filter; filterStop := filter; FRange := False; end; RangeStart := UnicodeCharToValue(filterStart); RangeStop := UnicodeCharToValue(filterStop); if FRange and (RangeStop > 0) and (filterStart = '') then RangeStart := $20 else if FRange and (filterStop = '') and (RangeStart > 0) then RangeStop := $10FFFF; Result := (RangeStart > 0); end; function TUnicodeData.GetSearchBlocks(filter: WideString): TUnicodeBlockList; var //rec: DaoRecordset; rec: ADODB_TLB.Recordset; vRecords: OleVariant; cmd: WideString; FontCmapIndex, RangeStart, RangeStop: Integer; FCurrentFont, FRange, FBlock: Boolean; origfilter: WideString; CodeValue: Integer; ub: TUnicodeBlock; nBlock: Integer; begin Result := nil; if FDisabled then Exit; if not Assigned(FDatabase) then Exit; filter := Trim(filter); FBlock := False; FRange := False; FCurrentFont := False; RangeStart := 0; RangeStop := 0; ub := nil; if Copy(Filter, 1, 1) = '>' then begin Delete(filter, 1, 1); FCurrentFont := Assigned(FFontRanges); if FCurrentFont and (filter = '') then begin { All characters from current font - no database search required } Result := TUnicodeBlockList.Create; Result.FName := FFontName+', all characters'; Result.FShortName := '>'; FontCmapIndex := 0; if FontCmapIndex >= FFontRanges.Count then Exit; nBlock := 0; while FontCmapIndex < FFontRanges.Count do begin for CodeValue := FFontRanges.Item[FontCmapIndex].StartCode to FFontRanges.Item[FontCmapIndex].EndCode do begin if not Assigned(ub) or (ub.EndChar < CodeValue) then begin while (nBlock < FBlocks.Count) and (FBlocks[nBlock].EndChar < CodeValue) do Inc(nBlock); if nBlock = FBlocks.Count then Break; ub := TUnicodeBlock.Create; ub.FName := FBlocks[nBlock].Name; ub.FShortName := FBlocks[nBlock].ShortName; ub.FBlockID := FBlocks[nBlock].BlockID; ub.FEndChar := FBlocks[nBlock].EndChar; ub.FStartChar := FBlocks[nBlock].StartChar; ub.FCacheCharCount := 0; Result.Add(ub); end; if (ub.FCacheCharCount mod 100) = 0 then begin ReallocMem(ub.FCacheCharData, (ub.FCacheCharCount+100) * SizeOf(Integer)); end; ub.FCacheCharData[ub.FCacheCharCount] := CodeValue; Inc(ub.FCacheCharCount); end; Inc(FontCmapIndex); end; Exit; end; end; if GetUnicodeRangeFromFilter(filter, RangeStart, RangeStop) then begin FRange := True; if RangeStart = 0 then origfilter := '(invalid range)' else begin origfilter := 'U+'+IntToHex(RangeStart, 4); if RangeStop <> 0 then origfilter := origfilter+' - U+'+IntToHex(RangeStop,4); end; end else begin if Copy(filter, 1, 1) = '<' then begin Delete(filter,1,1); FBlock := True; end; origfilter := filter; CleanupFilter(filter, FBlock); end; if FBlock then cmd := WideFormat('qFilterCharactersBlock %s', [QuotedStr(filter)]) // I3310 // I3465 else if FRange then begin if RangeStop = 0 then cmd := WideFormat('qFilterCharactersRange %d, %d', [RangeStart, RangeStart]) else if RangeStart = 0 then Exit else cmd := WideFormat('qFilterCharactersRange %d, %d', [RangeStart, RangeStop]) end else cmd := WideFormat('qFilterCharacters %s', [QuotedStr(filter)]); // I3310 // I3465 try rec := FDatabase.Execute(cmd, vRecords, adCmdStoredProc or adAsyncFetch); // or adAsyncExecute); except Exit; { ignore invalid filters for now } end; try Result := TUnicodeBlockList.Create; if FBlock then Result.FName := 'Blocks matching '+origfilter else if FRange then Result.FName := 'Unicode range '+origfilter else Result.FName := 'Characters matching '+origfilter; if FBlock then Result.FShortName := '<'+origfilter else Result.FShortName := origfilter; if FCurrentFont then begin if origfilter = '' then Result.FName := FFontName+', all characters' else if FBlock then Result.FName := FFontName+', blocks matching '+origfilter else if FRange then Result.FName := FFontName+', unicode range '+origfilter else Result.FName := FFontName+', characters matching '+origfilter; Result.FShortName := '>' + Result.FShortName; end; FontCmapIndex := 0; if FCurrentFont and (FontCmapIndex >= FFontRanges.Count) then Exit; nBlock := 0; //Result.FBlockID := 0; //Result.FCacheCharCount := 0; while not rec.EOF do begin CodeValue := rec.Collect['CodeValue']; if FCurrentFont then begin { Filter by current font } while (CodeValue > FFontRanges.Item[FontCmapIndex].EndCode) do begin Inc(FontCmapIndex); if FontCmapIndex >= FFontRanges.Count then Exit; { No more characters in current font, so just exit } end; if CodeValue < FFontRanges.Item[FontCmapIndex].StartCode then begin rec.MoveNext; Continue; end; end; if not Assigned(ub) or (ub.EndChar < CodeValue) then begin while (nBlock < FBlocks.Count) and (FBlocks[nBlock].EndChar < CodeValue) do Inc(nBlock); if nBlock = FBlocks.Count then Break; ub := TUnicodeBlock.Create; ub.FName := FBlocks[nBlock].Name; ub.FShortName := FBlocks[nBlock].ShortName; ub.FBlockID := FBlocks[nBlock].BlockID; ub.FEndChar := FBlocks[nBlock].EndChar; ub.FStartChar := FBlocks[nBlock].StartChar; ub.FCacheCharCount := 0; Result.Add(ub); end; if (ub.FCacheCharCount mod 100) = 0 then begin ReallocMem(ub.FCacheCharData, (ub.FCacheCharCount+100) * SizeOf(Integer)); end; ub.FCacheCharData[ub.FCacheCharCount] := CodeValue; Inc(ub.FCacheCharCount); rec.MoveNext; end; finally rec := nil; end; end; {------------------------------------------------------------------------------- - Algorithms for finding CJK and Hangul character properties - ------------------------------------------------------------------------------} { CJK Unified Ideographs } procedure FillCJK(code: Integer; var ch: TUnicodeCharacter); begin ch.CodeValue := code; ch.CharacterName := 'CJK_UNIFIED_IDEOGRAPH_' + IntToHex(code, 4); { ch.GeneralCategory := 'Lo'; ch.CanonicalCombiningClasses := 0; ch.BidirectionalCategory := 'L'; ch.CharacterDecompositionMapping := ''; ch.DecimalDigitValue := -1; ch.DigitValue := -1; ch.NumericValue := ''; ch.Mirrored := False; ch.Unicode10Name := ''; ch._10646Comment := 'Details generated by algorithm'; ch.UpperCaseMapping := 0; ch.LowerCaseMapping := 0; ch.TitleCaseMapping := 0;} end; const CJKUnifiedBase = $4E00; CJKUnifiedFinal = $BFFF; function TUnicodeData.IsCJKCode(code: Integer; var ch: TUnicodeCharacter): Boolean; begin Result := (code >= CJKUnifiedBase) and (code <= CJKUnifiedFinal); if Result then FillCJK(code, ch); end; function TUnicodeData.IsCJKName(aname: string; var ch: TUnicodeCharacter): Boolean; begin Result := False; if UpperCase(Copy(aname, 1, 22)) <> 'CJK_UNIFIED_IDEOGRAPH_' then Exit; Delete(aname, 1, 22); if Length(aname) <> 4 then Exit; ch.CodeValue := StrToIntDef('$'+aname, 0); if ch.CodeValue = 0 then Exit; FillCJK(ch.CodeValue, ch); Result := True; end; { Hangul } const HangulSBase = $AC00; HangulLBase = $1100; HangulVBase = $1161; HangulTBase = $11A7; HangulLCount = 19; HangulVCount = 21; HangulTCount = 28; HangulNCount = HangulVCount * HangulTCount; // 588 HangulSCount = HangulLCount * HangulNCount; // 11172 const Hangul_JAMO_L_TABLE: array[0..HangulLCount-1] of string = ( 'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '', 'J', 'JJ', 'C', 'K', 'T', 'P', 'H'); Hangul_JAMO_V_TABLE: array[0..HangulVCount-1] of string = ( 'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA', 'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'); Hangul_JAMO_T_TABLE: array[0..HangulTCount-1] of string = ( '', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM', 'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG', 'J', 'C', 'K', 'T', 'P', 'H'); function HangulDecompose(s: Integer): string; var SIndex, L, V, T: Integer; begin SIndex := s - HangulSBase; if (SIndex < 0) or (SIndex >= HangulSCount) then begin Result := ''; Exit; end; L := HangulLBase + SIndex div HangulNCount; V := HangulVBase + (SIndex mod HangulNCount) div HangulTCount; T := HangulTBase + SIndex mod HangulTCount; Result := IntToHex(L, 4) + ' ' + IntToHex(V, 4); if T <> HangulTBase then Result := Result + IntToHex(T, 4); end; function HangulName(s: Integer): string; var SIndex, LIndex, VIndex, TIndex: Integer; begin SIndex := s - HangulSBase; if (SIndex < 0) or (SIndex >= HangulSCount) then begin Result := ''; Exit; end; LIndex := SIndex div HangulNCount; VIndex := (SIndex mod HangulNCount) div HangulTCount; TIndex := SIndex mod HangulTCount; Result := 'HANGUL_SYLLABLE_' + Hangul_JAMO_L_TABLE[LIndex] + Hangul_JAMO_V_TABLE[VIndex] + Hangul_JAMO_T_TABLE[TIndex]; end; procedure FillHangul(code: Integer; var ch: TUnicodeCharacter); begin ch.CodeValue := code; ch.CharacterName := HangulName(code); { ch.GeneralCategory := 'Lo'; ch.CanonicalCombiningClasses := 0; ch.BidirectionalCategory := 'L'; ch.CharacterDecompositionMapping := HangulDecompose(code); ch.DecimalDigitValue := -1; ch.DigitValue := -1; ch.NumericValue := ''; ch.Mirrored := False; ch.Unicode10Name := ''; ch._10646Comment := 'Details generated by algorithm'; ch.UpperCaseMapping := 0; ch.LowerCaseMapping := 0; ch.TitleCaseMapping := 0;} end; function TUnicodeData.IsHangulCode(code: Integer; var ch: TUnicodeCharacter): Boolean; begin Result := (code >= HangulSBase) and (code < HangulSBase + HangulSCount); if Result then begin FillHangul(code, ch); end; end; function TUnicodeData.IsHangulName(aname: string; var ch: TUnicodeCharacter): Boolean; var code, i, LIndex, VIndex, TIndex: Integer; L, V, T: string; begin Result := False; if Copy(UpperCase(aname), 1, 16) = 'HANGUL_SYLLABLE_' then begin Delete(aname, 1, 16); if Length(aname) < 1 then Exit; { Find initial } L := Copy(aname, 1, 2); if Length(L) > 1 then if L[1] <> L[2] then Delete(L,2,1); // Only doubles allowed if Pos(L[1],'GNDRMBSJCKTPH') = 0 then LIndex := 11 else begin LIndex := -1; for i := 0 to High(Hangul_JAMO_L_TABLE) do if Hangul_JAMO_L_TABLE[i] = L then begin LIndex := i; Break; end; Delete(aname, 1, Length(L)); end; if LIndex = -1 then Exit; { Find vowel } V := Copy(aname, 1, 3); if Length(V) >= 3 then if Pos(V[3], 'AEIOUWY') = 0 then Delete(V,3,1); if Length(V) >= 2 then if Pos(V[2], 'AEIOUWY') = 0 then Delete(V,2,1); VIndex := -1; for i := 0 to High(Hangul_JAMO_V_TABLE) do if Hangul_JAMO_V_TABLE[i] = V then begin VIndex := i; Break; end; if VIndex = -1 then Exit; Delete(aname, 1, Length(V)); { Find final } TIndex := -1; T := aname; for i := 0 to High(Hangul_JAMO_T_TABLE) do if Hangul_JAMO_T_TABLE[i] = T then begin TIndex := i; Break; end; if TIndex = -1 then Exit; { Composition } code := (HangulSBase + (LIndex * HangulVCount + VIndex) * HangulTCount) + TIndex; Result := True; FillHangul(code, ch); end; end; { Font Data Filling } procedure TUnicodeData.BuildFontCharTable(const AFontName: WideString); begin if AFontName = '' then FreeAndNil(FFontRanges) else with TTTInfoFromFont.Create(AFontName) do try if not Assigned(CMapTable) then begin { We need to build a new ansi character map from the current character set - get char set info from font } FFontRanges := TTTCMapTable.Create; //FFontRanges.BuildFromCodePage(CP_ACP); end else begin FFontRanges := TTTCMapTable.Create; FFontRanges.Assign(CMapTable); end; finally Free; end; end; {------------------------------------------------------------------------------- - TUnicodeDataFormat: Formatting functions for Unicode data - ------------------------------------------------------------------------------} class function TUnicodeDataFormat.CleanCharacterName(nm: AnsiString): AnsiString; // I3310 var i: Integer; begin for i := 1 to Length(nm) do begin case nm[i] of 'a'..'z': nm[i] := Upcase(nm[i]); 'A'..'Z', '0'..'9', '-', '_': ; else nm[i] := '_'; end; end; Result := nm; end; (*class function TUnicodeDataFormat.GeneralCategory(GC: string): string; const GeneralCategories: array[0..30, 0..1] of string = ( ('Lu', 'Letter, Uppercase'), ('Ll', 'Letter, Lowercase'), ('Lt', 'Letter, Titlecase'), ('Lm', 'Letter, Modifier'), ('Lo', 'Letter, Other'), ('Mn', 'Mark, Non-Spacing'), ('Mc', 'Mark, Spacing Combining'), ('Me', 'Mark, Enclosing'), ('Nd', 'Number, Decimal Digit'), ('Nl', 'Number, Letter'), ('No', 'Number, Other'), ('Pc', 'Punctuation, Connector'), ('Pd', 'Punctuation, Dash'), ('Ps', 'Punctuation, Open'), ('Pe', 'Punctuation, Close'), ('Pi', 'Punctuation, Initial quote'), ('Pf', 'Punctuation, Final quote'), ('Po', 'Punctuation, Other'), ('Sm', 'Symbol, Math'), ('Sc', 'Symbol, Currency'), ('Sk', 'Symbol, Modifier'), ('So', 'Symbol, Other'), ('Zs', 'Separator, Space'), ('Zl', 'Separator, Line'), ('Zp', 'Separator, Paragraph'), ('Cc', 'Other, Control'), ('Cf', 'Other, Format'), ('Cs', 'Other, Surrogate'), ('Co', 'Other, Private Use'), ('Cn', 'Other, Not Assigned'), ('L&', 'Letter, Uppercase, Lowercase or Titlecase')); var i: Integer; begin for i := 0 to High(GeneralCategories) do if GeneralCategories[i, 0] = GC then begin Result := GeneralCategories[i, 1]; Exit; end; Result := 'Unknown ('+GC+')'; end; class function TUnicodeDataFormat.CanonicalCombiningClass(CCC: Integer): string; begin case CCC of 0: Result := 'Spacing, split, enclosing, reordrant, and Tibetan subjoined'; 1: Result := 'Overlays and interior'; 7: Result := 'Nuktas'; 8: Result := 'Hiragana/Katakana voicing marks'; 9: Result := 'Viramas'; 10: Result := 'Start of fixed position classes'; 199: Result := 'End of fixed position classes'; 200: Result := 'Below left attached'; 202: Result := 'Below attached'; 204: Result := 'Below right attached'; 208: Result := 'Left attached (reordrant around single base character)'; 210: Result := 'Right attached'; 212: Result := 'Above left attached'; 214: Result := 'Above attached'; 216: Result := 'Above right attached'; 218: Result := 'Below left'; 220: Result := 'Below'; 222: Result := 'Below right'; 224: Result := 'Left (reordrant around single base character)'; 226: Result := 'Right'; 228: Result := 'Above left'; 230: Result := 'Above'; 232: Result := 'Above right'; 233: Result := 'Double below'; 234: Result := 'Double above'; 240: Result := 'Below (iota subscript)'; else Result := 'Unknown ('+IntToStr(CCC)+')'; end; end; class function TUnicodeDataFormat.BidirectionalCategory(BC: string): string; const BidiCats: array[0..18, 0..1] of string = ( ('L', 'Left-to-Right'), ('LRE', 'Left-to-Right Embedding'), ('LRO', 'Left-to-Right Override'), ('R', 'Right-to-Left'), ('AL', 'Right-to-Left Arabic'), ('RLE', 'Right-to-Left Embedding'), ('RLO', 'Right-to-Left Override'), ('PDF', 'Pop Directional Format'), ('EN', 'European Number'), ('ES', 'European Number Separator'), ('ET', 'European Number Terminator'), ('AN', 'Arabic Number'), ('CS', 'Common Number Separator'), ('NSM', 'Non-Spacing Mark'), ('BN', 'Boundary Neutral'), ('B', 'Paragraph Separator'), ('S', 'Segment Separator'), ('WS', 'Whitespace'), ('ON', 'Other Neutrals')); var i: Integer; begin for i := 0 to High(BiDiCats) do if BiDiCats[i, 0] = BC then begin Result := BiDiCats[i, 1]; Exit; end; Result := 'Unknown ('+BC+')'; end; class function TUnicodeDataFormat.CharacterDecompositionMapping(CDM: string): string; begin Result := CDM; end; class function TUnicodeDataFormat.DecimalDigitValue(DDV: Integer): string; begin if DDV >= 0 then Result := IntToStr(DDV) else Result := ''; end; class function TUnicodeDataFormat.DigitValue(DV: Integer): string; begin if DV >= 0 then Result := IntToStr(DV) else Result := ''; end; class function TUnicodeDataFormat.NumericValue(NV: string): string; begin Result := Trim(NV); end; class function TUnicodeDataFormat.Mirrored(M: Boolean): string; begin if M then Result := 'Yes' else Result := 'No'; end; class function TUnicodeDataFormat._10646Comment(_1C: string): string; begin Result := Trim(_1C); end; class function TUnicodeDataFormat.Unicode10Name(U1N: string): string; begin Result := Trim(U1N); end; class function TUnicodeDataFormat.UpperCaseMapping(UCM: Integer): string; begin if UCM > 0 then Result := 'U+'+IntToHex(UCM,4) else Result := ''; end; class function TUnicodeDataFormat.LowerCaseMapping(LCM: Integer): string; begin if LCM > 0 then Result := 'U+'+IntToHex(LCM,4) else Result := ''; end; class function TUnicodeDataFormat.TitleCaseMapping(LCM: Integer): string; begin if LCM > 0 then Result := 'U+'+IntToHex(LCM,4) else Result := ''; end;*) { TUnicodeBlockList } function TUnicodeBlockList.GetItem(Index: Integer): TUnicodeBlock; begin Result := inherited GetItem(Index) as TUnicodeBlock; end; procedure TUnicodeBlockList.SetItem(Index: Integer; const Value: TUnicodeBlock); begin inherited SetItem(Index, Value); end; { TUnicodeBlock } destructor TUnicodeBlock.Destroy; begin if Assigned(FCacheCharData) then // I2794 FreeMem(FCacheCharData); FCacheCharData := nil; inherited Destroy; end; initialization with TVarData(_) do begin vType:= varError; vError:= Integer($80020004); {DISP_E_PARAMNOTFOUND} end; end.