ufileextensions.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UFileExtensions;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, Dialogs, BGRABitmapTypes;
  7. Type
  8. TExtensionOptions = set of (eoReadable, eoWritable);
  9. var
  10. PictureFileExtensions: array of record
  11. name, extensionsWithoutDot: string;
  12. fileFormat: TBGRAImageFormat;
  13. filterForAllCases: string;
  14. options: TExtensionOptions;
  15. end;
  16. //returns the list of extensions for a specified filter index.
  17. //- if none is found, nil is returned.
  18. function GetSelectedFilterExtensions(const Filter: string; FilterIndex: integer; ARemoveLeadingDot: boolean): TStringList;
  19. //apply the extension of the selected filter index
  20. //- if the extension is already among the extensions for this filter index, the extension is not changed
  21. //- otherwise, the first extension is used
  22. //- it is not case sensitive
  23. function ApplySelectedFilterExtension(const FileName: string; const Filter: string; FilterIndex: integer): string;
  24. function GetExtensionFilter(AOption: TExtensionOptions; ADisplayPrefix: string = '*.'): string;
  25. function GetExtensionFilterIndex(AOption: TExtensionOptions; AExtensions: string): integer;
  26. function GetExtensionFilterByIndex(AOption: TExtensionOptions; AIndex: integer): string;
  27. procedure RegisterPicExt(AName: string; AExtensionsWithoutDot: string; AOptions: TExtensionOptions);
  28. function IsExtensionReadable(AFilename: string): boolean;
  29. function IsExtensionWritable(AFilename: string): boolean;
  30. function GetImageFormatName(AFormat: TBGRAImageFormat): string;
  31. implementation
  32. uses Masks, LazUTF8, UResourceStrings, BGRASVG,
  33. BGRALayerOriginal, BGRASVGOriginal, BGRAGradientOriginal,
  34. LCVectorOriginal, LCVectorShapes, URaw;
  35. function GetSelectedFilterExtensions(const Filter: string;
  36. FilterIndex: integer; ARemoveLeadingDot: boolean): TStringList;
  37. var
  38. ParsedFilter: TParseStringList;
  39. i: integer;
  40. ext: string;
  41. begin
  42. Result := nil;
  43. ParsedFilter := TParseStringList.Create(Filter, '|');
  44. try
  45. if (FilterIndex > 0) and (FilterIndex * 2 <= ParsedFilter.Count) then
  46. begin
  47. Result := TParseStringList.Create(ParsedFilter[FilterIndex * 2 - 1],';');
  48. //go backward because we may delete some entries
  49. for i := result.Count-1 downto 0 do
  50. begin
  51. ext := ExtractFileExt(result[i]);
  52. if ARemoveLeadingDot and (length(ext)>0) and (ext[1]='.') then Delete(ext,1,1);
  53. //ignore extensions with wildcards and empty extensions
  54. if (pos('*',ext)<>0) or (pos('?',ext)<>0) or (ext = '') then
  55. result.Delete(i)
  56. else
  57. result[i] := ext;
  58. end;
  59. end;
  60. finally
  61. ParsedFilter.Free;
  62. end;
  63. if (result <> nil) and (result.count = 0) then FreeAndNil(result);
  64. end;
  65. function ApplySelectedFilterExtension(const FileName: string;
  66. const Filter: string; FilterIndex: integer): string;
  67. var exts: TStringList;
  68. currentExt: string;
  69. i: integer;
  70. found: boolean;
  71. begin
  72. exts := GetSelectedFilterExtensions(Filter,FilterIndex,False);
  73. if exts = nil then
  74. begin
  75. result := FileName;
  76. exit;
  77. end;
  78. currentExt := ExtractFileExt(FileName);
  79. found := false;
  80. for i := 0 to exts.Count-1 do
  81. begin
  82. if UTF8CompareText(exts[i],currentExt) = 0 then
  83. begin
  84. found := true;
  85. break;
  86. end;
  87. end;
  88. if found or (exts.Count = 0) then
  89. result := FileName
  90. else
  91. result := ChangeFileExt(FileName, exts[0]);
  92. exts.Free;
  93. end;
  94. function GetExtensionFilter(AOption: TExtensionOptions; ADisplayPrefix: string = '*.'): string;
  95. var i: integer;
  96. extDescription, allExtWithoutDot, allExtFilter: string;
  97. begin
  98. result := '';
  99. allExtWithoutDot := '';
  100. allExtFilter := '';
  101. for i := 0 to high(PictureFileExtensions) do
  102. if (PictureFileExtensions[i].options * AOption = AOption) and
  103. (PictureFileExtensions[i].filterForAllCases <> '') then
  104. begin
  105. if result <> '' then result += '|';
  106. extDescription := ADisplayPrefix + StringReplace(PictureFileExtensions[i].extensionsWithoutDot,';',', ' +ADisplayPrefix,[rfReplaceAll]);
  107. result += PictureFileExtensions[i].name+' ('+extDescription+')|'+PictureFileExtensions[i].filterForAllCases;
  108. //do not repeat extensions in all-file-types
  109. if pos(', '+extDescription+', ', ', '+allExtWithoutDot+', ') = 0 then
  110. begin
  111. if allExtWithoutDot <> '' then allExtWithoutDot += ', ';
  112. allExtWithoutDot += extDescription;
  113. if allExtFilter <> '' then allExtFilter += ';';
  114. allExtFilter += PictureFileExtensions[i].filterForAllCases;
  115. end;
  116. end;
  117. if allExtWithoutDot = '' then
  118. begin
  119. allExtWithoutDot := ADisplayPrefix + '*';
  120. allExtFilter:= '*.*';
  121. end;
  122. if result <> '' then result := '|' + result;
  123. if length(allExtWithoutDot)>12 then
  124. result := rsAllSupportedFiletypes + ' (' + ADisplayPrefix+ '*)|' + allExtFilter + result
  125. else
  126. result := rsAllSupportedFiletypes + ' (' + allExtWithoutDot + ')|' + allExtFilter + result;
  127. end;
  128. function GetExtensionFilterIndex(AOption: TExtensionOptions; AExtensions: string): integer;
  129. var
  130. i: Integer;
  131. begin
  132. result := 2;
  133. for i := 0 to high(PictureFileExtensions) do
  134. if (PictureFileExtensions[i].options * AOption = AOption) and
  135. (PictureFileExtensions[i].filterForAllCases <> '') then
  136. begin
  137. if PictureFileExtensions[i].filterForAllCases = AExtensions then exit;
  138. inc(result);
  139. end;
  140. result := 1;
  141. end;
  142. function GetExtensionFilterByIndex(AOption: TExtensionOptions; AIndex: integer): string;
  143. var curIndex, i: integer;
  144. begin
  145. curIndex := 2;
  146. for i := 0 to high(PictureFileExtensions) do
  147. if (PictureFileExtensions[i].options * AOption = AOption) and
  148. (PictureFileExtensions[i].filterForAllCases <> '') then
  149. begin
  150. if curIndex = AIndex then exit(PictureFileExtensions[i].filterForAllCases);
  151. inc(curIndex);
  152. end;
  153. result := '*.*';
  154. end;
  155. function GetBit(Value: QWord; Index: Byte): Boolean;
  156. begin
  157. Result := ((Value shr Index) and 1) = 1;
  158. end;
  159. {(en) Converts AStrUtf8 to uppercase if AUppercase = true otherwise to lowercase}
  160. function ULCaseUtf8(AStrUtf8: string; AUppercase: Boolean): string;
  161. begin
  162. if AUppercase then Result:=UTF8UpperCase(AStrUtf8) else Result:= UTF8LowerCase(AStrUtf8);
  163. end;
  164. {(en) Generates various cases that may be encountered}
  165. function SingleExtAllCases (ASingleExtension: string; Delimiter: String=';'; Prefix: string=''; Suffix: String=''):string;
  166. var
  167. otherCase: String;
  168. begin
  169. Result := Prefix + ASingleExtension + Suffix;
  170. otherCase := UTF8LowerCase(ASingleExtension);
  171. if otherCase <> ASingleExtension then
  172. Result += Delimiter + Prefix + otherCase + Suffix;
  173. otherCase := UTF8UpperCase(ASingleExtension);
  174. if otherCase <> ASingleExtension then
  175. Result += Delimiter + Prefix + otherCase + Suffix;
  176. otherCase := UTF8UpperCase(UTF8Copy(ASingleExtension, 1, 1)) +
  177. UTF8LowerCase(UTF8Copy(ASingleExtension, 2, UTF8Length(ASingleExtension) - 1));
  178. if otherCase <> ASingleExtension then
  179. Result += Delimiter + Prefix + otherCase + Suffix;
  180. end;
  181. {(en) Generates various cases of file extensions}
  182. function ExtensionsAllCases (AllExtensions: String; ADelimiter: string = ';'; APrefix:string = '*.'): String;
  183. var
  184. ExtList: TStringList;
  185. i: integer;
  186. item: string;
  187. begin
  188. Result := '';
  189. ExtList:= TParseStringList.Create(AllExtensions,ADelimiter);
  190. for i:=0 to ExtList.Count -1 do
  191. begin
  192. item := SingleExtAllCases(ExtList[i],ADelimiter,APrefix,'');
  193. if item <> '' then
  194. begin
  195. if Result <> '' then result += ADelimiter;
  196. Result += item;
  197. end;
  198. end;
  199. ExtList.Free;
  200. end;
  201. //end All case extension subs
  202. procedure RegisterPicExt(AName: string; AExtensionsWithoutDot: string;
  203. AOptions: TExtensionOptions);
  204. var extList: TStringList;
  205. i: integer;
  206. suggested: TBGRAImageFormat;
  207. begin
  208. setlength(PictureFileExtensions, length(PictureFileExtensions)+1);
  209. with PictureFileExtensions[high(PictureFileExtensions)] do
  210. begin
  211. name := AName;
  212. extensionsWithoutDot := AExtensionsWithoutDot;
  213. filterForAllCases:= ExtensionsAllCases(extensionsWithoutDot, ';', '*.');
  214. fileFormat := ifUnknown;
  215. extList := TParseStringList.Create(extensionsWithoutDot,';');
  216. for i := 0 to extList.Count-1 do
  217. begin
  218. suggested := SuggestImageFormat(extList[i]);
  219. if suggested <> ifUnknown then
  220. begin
  221. if fileFormat = ifUnknown then
  222. fileFormat:= suggested
  223. else if fileFormat <> suggested then //contradiction
  224. begin
  225. fileFormat:= ifUnknown;
  226. break;
  227. end;
  228. end;
  229. end;
  230. extList.free;
  231. options := AOptions;
  232. end;
  233. end;
  234. function HasExtensionOptions(AFilename: string; AOptions: TExtensionOptions): boolean;
  235. var
  236. ext: string;
  237. i : integer;
  238. begin
  239. ext := UTF8LowerCase(ExtractFileExt(AFilename));
  240. if (ext<>'') and (ext[1]='.') then delete(ext,1,1);
  241. for i := 0 to high(PictureFileExtensions) do
  242. begin
  243. if pos(';'+ext+';', UTF8LowerCase(';'+PictureFileExtensions[i].extensionsWithoutDot+';'))<> 0 then
  244. begin
  245. if PictureFileExtensions[i].options * AOptions = AOptions then
  246. begin
  247. result := true;
  248. exit;
  249. end;
  250. end;
  251. end;
  252. result := false;
  253. end;
  254. function IsExtensionReadable(AFilename: string): boolean;
  255. begin
  256. result := HasExtensionOptions(AFilename,[eoReadable]);
  257. end;
  258. function IsExtensionWritable(AFilename: string): boolean;
  259. begin
  260. result := HasExtensionOptions(AFilename,[eoWritable]);
  261. end;
  262. function GetImageFormatName(AFormat: TBGRAImageFormat): string;
  263. var i: integer;
  264. begin
  265. if AFormat = ifUnknown then
  266. begin
  267. result := 'Unknown';
  268. exit;
  269. end;
  270. for i := 0 to high(PictureFileExtensions) do
  271. if PictureFileExtensions[i].fileFormat = AFormat then
  272. begin
  273. result := PictureFileExtensions[i].name;
  274. exit;
  275. end;
  276. result := 'Error';
  277. end;
  278. initialization
  279. RegisterPicExt(rsLayeredImage,'lzp;ora;pdn;oXo', [eoReadable]);
  280. RegisterPicExt(rsLayeredImage,'lzp;ora;oXo', [eoWritable]);
  281. RegisterPicExt(rsBitmap,'bmp', [eoReadable,eoWritable]);
  282. RegisterPicExt(rsAnimatedGIF,'gif', [eoReadable,eoWritable]);
  283. RegisterPicExt(rsIconOrCursor,'ico;cur', [eoReadable,eoWritable]);
  284. RegisterPicExt('JPEG','jpg;jpeg', [eoReadable,eoWritable]);
  285. RegisterPicExt(rsLazPaint,'lzp', [eoReadable,eoWritable]);
  286. RegisterPicExt(rsOpenRaster,'ora', [eoReadable,eoWritable]);
  287. RegisterPicExt('PC eXchange','pcx', [eoReadable,eoWritable]);
  288. RegisterPicExt('Paint.NET','pdn', [eoReadable]);
  289. RegisterPicExt('PhoXo','oXo', [eoReadable,eoWritable]);
  290. RegisterPicExt('Portable Network Graphic','png', [eoReadable,eoWritable]);
  291. RegisterPicExt(rsPhotoshop,'psd', [eoReadable]);
  292. BGRASVG.RegisterSvgFormat;
  293. RegisterPicExt('Scalable Vector Graphic','svg', [eoReadable]);
  294. RegisterPicExt('Targa','tga', [eoReadable,eoWritable]);
  295. RegisterPicExt('Tiff','tif;tiff', [eoReadable,eoWritable]);
  296. RegisterPicExt('WebP','webp', [eoReadable,eoWritable]);
  297. RegisterPicExt('X PixMap','xpm', [eoReadable,eoWritable]);
  298. RegisterPicExt('Portable Any Map', 'pbm;pgm;ppm', [eoReadable]);
  299. RegisterPicExt('X Window','xwd', [eoReadable]);
  300. RegisterPicExt('Raw',AllRawExtensions, [eoReadable]);
  301. end.