uclipboard.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UClipboard;
  3. {$mode objfpc}{$H+}
  4. {$IFDEF DEBUG}
  5. { $DEFINE DEBUG_CLIPBOARD}
  6. {$ENDIF}
  7. {$DEFINE HTML_CLIPBOARD_FORMAT}
  8. {$IFDEF DARWIN}
  9. {$DEFINE TIFF_CLIPBOARD_FORMAT}
  10. {$ELSE}
  11. {$DEFINE BMP_CLIPBOARD_FORMAT}
  12. {$DEFINE PNG_CLIPBOARD_FORMAT}
  13. {$ENDIF}
  14. {$IFDEF WINDOWS}
  15. {$DEFINE PDN_CLIPBOARD_FORMAT}
  16. {$ENDIF}
  17. interface
  18. uses
  19. Classes, SysUtils, BGRABitmap;
  20. procedure CopyToClipboard(bmp: TBGRABitmap);
  21. function GetBitmapFromClipboard: TBGRABitmap;
  22. function ClipboardContainsBitmap: boolean;
  23. implementation
  24. uses Dialogs, BGRABitmapTypes, Clipbrd, Graphics, LCLIntf, LCLType, GraphType
  25. {$IFDEF PDN_CLIPBOARD_FORMAT}, math, BGRADNetDeserial{$ENDIF}
  26. {$IFDEF BMP_CLIPBOARD_FORMAT}, FPWriteBMP{$ENDIF}
  27. {$IFDEF HTML_CLIPBOARD_FORMAT}, fphttpclient{$ENDIF};
  28. {$IFDEF DEBUG_CLIPBOARD}
  29. const
  30. moreMimeTypes : array[0..6] of string =
  31. ('image/x-xbitmap','image/gif','image/jpeg','image/pjpeg','image/png','image/x-png','image/tiff');
  32. {$ENDIF}
  33. {$IFDEF TIFF_CLIPBOARD_FORMAT}
  34. var
  35. tiffClipboardFormat: TClipboardFormat;
  36. {$ENDIF}
  37. {$IFDEF PNG_CLIPBOARD_FORMAT}
  38. var
  39. pngClipboardFormat: TClipboardFormat;
  40. {$ENDIF}
  41. {$IFDEF PDN_CLIPBOARD_FORMAT}
  42. var
  43. pdnClipboardFormat: TClipboardFormat;
  44. function GetBitmapFromPaintDotNetMaskedSurface(deserial: TDotNetDeserialization): TBGRABitmap;
  45. var width,height: integer;
  46. dataObj: TSerializedArray;
  47. data: pointer;
  48. dataSize: longword;
  49. mainObj: TSerializedClass;
  50. segments,segment,poly: TSerializedArray;
  51. point: TSerializedClass;
  52. mask: TBGRABitmap;
  53. polyPts: array of TPointF;
  54. polyList: array of array of TPointF;
  55. i: Integer;
  56. j: Integer;
  57. k: Integer;
  58. minx,miny: double;
  59. offset: TPointF;
  60. begin
  61. result := nil;
  62. mainObj := deserial.FindClass('PaintDotNet.MaskedSurface');
  63. try
  64. width := StrToInt(deserial.GetSimpleField(mainObj,'surface\width'));
  65. height := StrToInt(deserial.GetSimpleField(mainObj,'surface\height'));
  66. dataObj := deserial.GetObjectField(mainObj, 'surface\scan0\chunk0') as TSerializedArray;
  67. if dataObj <> nil then
  68. begin
  69. data := dataObj.ItemPtr[0];
  70. dataSize := dataObj.ItemSize * dataObj.FieldCount;
  71. result := TBGRABitmap.Create(width,height);
  72. minx := 1e100;
  73. miny := 1e100;
  74. move(data^, result.Data^, min(dataSize, int64(result.NbPixels) * sizeof(TBGRAPixel)));
  75. if result.LineOrder = riloBottomToTop then result.VerticalFlip;
  76. mask := TBGRABitmap.Create(width,height, BGRABlack);
  77. polyList := nil;
  78. try
  79. segments := deserial.GetObjectField(mainObj,'geometryMask\polygonList\segments') as TSerializedArray;
  80. if segments <> nil then
  81. begin
  82. for i := 0 to segments.FieldCount-1 do
  83. begin
  84. segment := deserial.GetObjectField(segments,i) as TSerializedArray;
  85. if segment <> nil then
  86. for j := 0 to segment.FieldCount-1 do
  87. begin
  88. poly := deserial.GetObjectField(segment,j) as TSerializedArray;
  89. if poly <> nil then
  90. begin
  91. setlength(polyPts, poly.FieldCount);
  92. for k := 0 to high(polyPts) do
  93. begin
  94. point := deserial.GetObjectField(poly,k) as TSerializedClass;
  95. polyPts[k] := PointF(StrToFloat(point.FieldByNameAsString['_x']),
  96. StrToFloat(point.FieldByNameAsString['_y']));
  97. if polyPts[k].x < minx then minx := polyPts[k].x;
  98. if polyPts[k].y < miny then miny := polyPts[k].y;
  99. end;
  100. setlength(polyList, length(polyList)+1);
  101. polyList[high(polyList)] := polyPts;
  102. end;
  103. end;
  104. end;
  105. end;
  106. offset := PointF(floor(minx)+0.5,floor(miny)+0.5);
  107. for i := 0 to high(polyList) do
  108. begin
  109. polyPts := polyList[i];
  110. for j := 0 to high(polyPts) do
  111. polyPts[j] -= offset;
  112. mask.FillPolyAntialias(polyPts,BGRAWhite);
  113. end;
  114. result.ApplyMask(mask);
  115. finally
  116. mask.Free;
  117. end;
  118. end;
  119. except
  120. on ex:Exception do
  121. begin
  122. //nothing
  123. end;
  124. end;
  125. end;
  126. {$ENDIF}
  127. {$IFDEF HTML_CLIPBOARD_FORMAT}
  128. var
  129. htmlClipboardFormat: TClipboardFormat;
  130. function WideStringToStr(data: string): string;
  131. var
  132. i: integer;
  133. isWidestring: boolean;
  134. w: WideString;
  135. begin
  136. isWidestring := (length(data)>0) and ((length(data) and 1) = 0);
  137. i := 2;
  138. while (i <= length(data)) do
  139. begin
  140. if data[i] <> #0 then
  141. begin
  142. isWidestring := false;
  143. break;
  144. end;
  145. inc(i,2);
  146. end;
  147. if isWidestring then
  148. begin
  149. setlength(w, length(data) div 2);
  150. move(data[1],w[1],length(data));
  151. result := UTF8Encode(w);
  152. end
  153. else result := data;
  154. end;
  155. function HtmlEntitiesToText(data: string): string;
  156. var p,start: integer;
  157. entity: string;
  158. charcode,errpos: integer;
  159. begin
  160. p := 1;
  161. while p <= length(data)-1 do
  162. begin
  163. if (data[p]='&') and (data[p+1] in ['#','a'..'z','A'..'Z']) then
  164. begin
  165. start := p;
  166. inc(p);
  167. while (p < length(data)) and (data[p+1] in['0'..'9','a'..'z','A'..'Z']) do inc(p);
  168. entity := copy(data,start,p-start +1);
  169. if (p < length(data)) and (data[p+1] = ';') then inc(p);
  170. delete(data, start, p-start+1);
  171. p := start;
  172. case entity of
  173. '&nbsp': entity := #160;
  174. '&lt': entity := '<';
  175. '&gt': entity := '>';
  176. '&amp': entity := '&';
  177. else
  178. begin
  179. if copy(entity,1,2)='&#' then
  180. begin
  181. val(copy(entity,3,length(entity)-2),charcode,errpos);
  182. if (errpos = 0) and (charcode <= 127) then
  183. entity := char(charcode);
  184. end;
  185. end;
  186. end;
  187. insert(entity,data,p);
  188. inc(p,length(entity));
  189. continue;
  190. end;
  191. inc(p);
  192. end;
  193. result := data;
  194. end;
  195. function GetBitmapFromTag(tokens: TStringList): TBGRABitmap;
  196. var
  197. i: Integer;
  198. stream: TMemoryStream;
  199. url: string;
  200. begin
  201. if tokens.Count > 0 then
  202. begin
  203. if UpperCase(tokens[0]) = 'IMG' then
  204. begin
  205. for i := 1 to tokens.count-3 do
  206. if (UpperCase(tokens[i])='SRC')
  207. and (tokens[i+1]='=') and (tokens[i+2][1] in ['''','"']) then
  208. begin
  209. url := HtmlEntitiesToText(copy(tokens[i+2],2,length(tokens[i+2])-2));
  210. if copy(url,1,8) = 'https://' then
  211. delete(url,5,1);
  212. stream := TMemoryStream.Create;
  213. try
  214. TFPHttpClient.SimpleGet(url,stream);
  215. stream.Position:= 0;
  216. result := TBGRABitmap.Create(stream);
  217. except on ex: exception do begin
  218. ShowMessage(ex.Message);
  219. end;
  220. end;
  221. stream.Free;
  222. if result <> nil then exit;
  223. end;
  224. end;
  225. end;
  226. result := nil;
  227. end;
  228. function GetBitmapFromHtml(data: string): TBGRABitmap;
  229. var
  230. p: integer;
  231. inTag, inComment: boolean;
  232. tagTokens: TStringList;
  233. inStr1, inStr2, inId, inNum: integer;
  234. begin
  235. result := nil;
  236. data := WideStringToStr(data);
  237. inTag := false;
  238. inComment := false;
  239. inStr1 := 0;
  240. inStr2 := 0;
  241. inId := 0;
  242. inNum := 0;
  243. tagTokens := TStringList.Create;
  244. p := 1;
  245. while p <= length(data) do
  246. begin
  247. if inComment then
  248. begin
  249. if data[p] = '-' then
  250. begin
  251. if copy(data,p,3) = '-->' then
  252. begin
  253. p += 3;
  254. inComment:= false;
  255. continue;
  256. end;
  257. end;
  258. end else
  259. if inStr1<>0 then
  260. begin
  261. if data[p] = '''' then
  262. begin
  263. tagTokens.add(copy(data,inStr1,p-inStr1+1));
  264. inStr1 := 0;
  265. end;
  266. end else
  267. if inStr2<>0 then
  268. begin
  269. if data[p] = '"' then
  270. begin
  271. tagTokens.add(copy(data,inStr2,p-inStr2+1));
  272. inStr2 := 0;
  273. end;
  274. end else
  275. begin
  276. if inId<>0 then
  277. begin
  278. if not (data[p] in['A'..'Z','a'..'z',':','.']) then
  279. begin
  280. tagTokens.add(copy(data,inId,p-inId));
  281. inId := 0;
  282. end else
  283. begin
  284. inc(p);
  285. continue;
  286. end;
  287. end;
  288. if inNum<>0 then
  289. begin
  290. if not (data[p] in['0'..'9','.']) then
  291. begin
  292. tagTokens.add(copy(data,inNum,p-inNum));
  293. inNum := 0;
  294. end else
  295. begin
  296. inc(p);
  297. continue;
  298. end;
  299. end;
  300. if data[p]='<' then
  301. begin
  302. if copy(data,p,4) = '<!--' then
  303. begin
  304. p += 4;
  305. inComment := true;
  306. continue;
  307. end else
  308. inTag := true;
  309. end else
  310. if inTag then
  311. begin
  312. if data[p] = '''' then
  313. inStr1 := p
  314. else if data[p] = '"' then
  315. inStr2 := p
  316. else if data[p] in ['A'..'Z','a'..'z'] then
  317. inId := p
  318. else if data[p] in ['0'..'9','+','-'] then
  319. inNum := p
  320. else if data[p] = '>' then
  321. begin
  322. inTag := false;
  323. result := GetBitmapFromTag(tagTokens);
  324. tagTokens.clear;
  325. if result <> nil then exit;
  326. end else
  327. if data[p]>#32 then
  328. tagTokens.Add(data[p]);
  329. end;
  330. end;
  331. inc(p);
  332. end;
  333. tagTokens.Free;
  334. end;
  335. {$ENDIF}
  336. function GetBitmapFromClipboard: TBGRABitmap;
  337. var i: integer;
  338. Stream: TMemoryStream;
  339. data: string;
  340. {$IFDEF DEBUG_CLIPBOARD}
  341. j: integer;
  342. pcf: TPredefinedClipboardFormat;
  343. mime, str: string;
  344. c: char;
  345. prevCok: boolean;
  346. {$ENDIF}
  347. {$IFDEF PDN_CLIPBOARD_FORMAT}
  348. deserial: TDotNetDeserialization;
  349. {$ENDIF}
  350. begin
  351. result := nil;
  352. {$IFDEF DEBUG_CLIPBOARD}
  353. str := 'clipboard.FormatCount = '+inttostr(clipboard.FormatCount)+lineending;
  354. for i := 0 to clipboard.FormatCount-1 do
  355. begin
  356. if str <> '' then str += ', ';
  357. str := str + '#'+inttostr(clipboard.Formats[i])+'=';
  358. mime := ClipboardFormatToMimeType(clipboard.Formats[i]);
  359. if mime = '' then
  360. for pcf := low(TPredefinedClipboardFormat) to high(TPredefinedClipboardFormat) do
  361. if clipboard.Formats[i] = PredefinedClipboardFormat(pcf) then
  362. mime := PredefinedClipboardMimeTypes[pcf];
  363. if mime = '' then
  364. for j := low(moreMimeTypes) to high(moreMimeTypes) do
  365. if clipboard.Formats[j] = RegisterClipboardFormat(moreMimeTypes[j]) then
  366. mime := moreMimeTypes[j];
  367. str += mime;
  368. stream := TMemoryStream.Create;
  369. Clipboard.GetFormat(Clipboard.Formats[i],Stream);
  370. str += '('+inttostr(stream.Size)+' bytes)';
  371. if (mime = 'DataObject') or (mime = 'text/html') or (mime = 'HTML Format') or (mime = 'text/plain') then
  372. begin
  373. if stream.Size > 1024 then
  374. setlength(data,1024) else
  375. setlength(data,stream.size);
  376. stream.Position:= 0;
  377. stream.read(data[1],length(data));
  378. str += '=[';
  379. prevCok := false;
  380. for j := 1 to length(data) do
  381. begin
  382. c := data[j];
  383. if c in[#32..#126] then
  384. begin
  385. str+= c;
  386. prevCok := true
  387. end else
  388. begin
  389. if not (prevCOk and (c = #0)) then
  390. str += ' '+inttohex(ord(c),2)+' ';
  391. prevCok := false;
  392. end;
  393. end;
  394. str += ']'+lineending;
  395. end;
  396. stream.Free;
  397. end;
  398. ShowMessage(str);
  399. {$ENDIF}
  400. {$IFDEF PDN_CLIPBOARD_FORMAT}
  401. for i := 0 to clipboard.FormatCount-1 do
  402. if Clipboard.Formats[i] = pdnClipboardFormat then
  403. begin
  404. Stream := TMemoryStream.Create;
  405. Clipboard.GetFormat(Clipboard.Formats[i],Stream);
  406. stream.Position := 0;
  407. deserial := TDotNetDeserialization.Create;
  408. deserial.LoadFromStream(stream);
  409. Stream.Free;
  410. try
  411. result := GetBitmapFromPaintDotNetMaskedSurface(deserial);
  412. except
  413. end;
  414. deserial.Free;
  415. if result <> nil then exit;
  416. end;
  417. {$ENDIF}
  418. {$IFDEF TIFF_CLIPBOARD_FORMAT}
  419. for i := 0 to clipboard.FormatCount-1 do
  420. if Clipboard.Formats[i] = tiffClipboardFormat then
  421. begin
  422. Stream := TMemoryStream.Create;
  423. Clipboard.GetFormat(Clipboard.Formats[i],Stream);
  424. Stream.Position := 0;
  425. try
  426. result := TBGRABitmap.Create;
  427. result.LoadFromStream(Stream);
  428. if result.Empty then result.AlphaFill(255);
  429. except
  430. on ex:exception do
  431. begin
  432. result := nil;
  433. end;
  434. end;
  435. Stream.Free;
  436. if result <> nil then exit;
  437. end;
  438. {$ENDIF}
  439. {$IFDEF PNG_CLIPBOARD_FORMAT}
  440. for i := 0 to clipboard.FormatCount-1 do
  441. if Clipboard.Formats[i] = pngClipboardFormat then
  442. begin
  443. Stream := TMemoryStream.Create;
  444. Clipboard.GetFormat(Clipboard.Formats[i],Stream);
  445. Stream.Position := 0;
  446. try
  447. result := TBGRABitmap.Create;
  448. result.LoadFromStream(Stream);
  449. if result.Empty then result.AlphaFill(255);
  450. except
  451. on ex:exception do
  452. begin
  453. result := nil;
  454. end;
  455. end;
  456. Stream.Free;
  457. if result <> nil then exit;
  458. end;
  459. {$ENDIF}
  460. for i := 0 to clipboard.FormatCount-1 do
  461. if Clipboard.Formats[i] = htmlClipboardFormat then
  462. begin
  463. Stream := TMemoryStream.Create;
  464. Clipboard.GetFormat(Clipboard.Formats[i],Stream);
  465. if stream.Size > 65536 then
  466. setlength(data,65536) else
  467. setlength(data,stream.size);
  468. stream.Position:= 0;
  469. stream.read(data[1],length(data));
  470. Stream.Free;
  471. try
  472. result := GetBitmapFromHtml(data);
  473. except
  474. end;
  475. if result <> nil then exit;
  476. end;
  477. for i := 0 to clipboard.FormatCount-1 do
  478. if (Clipboard.Formats[i] = PredefinedClipboardFormat(pcfBitmap)) then
  479. begin
  480. Stream := TMemoryStream.Create;
  481. Clipboard.GetFormat(Clipboard.Formats[i],Stream);
  482. Stream.Position := 0;
  483. try
  484. result := TBGRABitmap.Create(Stream);
  485. if result.Empty then result.AlphaFill(255);
  486. except
  487. on ex:exception do
  488. begin
  489. result := nil;
  490. end;
  491. end;
  492. Stream.Free;
  493. if result <> nil then exit;
  494. end;
  495. end;
  496. function ClipboardContainsBitmap: boolean;
  497. var temp: TBGRABitmap;
  498. begin
  499. temp := GetBitmapFromClipboard;
  500. if (temp=nil) or (temp.Width = 0) or (temp.Height= 0) or temp.Empty then result := false
  501. else result := true;
  502. temp.Free;
  503. end;
  504. procedure CopyToClipboard(bmp: TBGRABitmap);
  505. var
  506. stream: TMemoryStream;
  507. {$IFDEF BMP_CLIPBOARD_FORMAT}
  508. bmpWriter: TFPWriterBMP;
  509. {$ENDIF}
  510. begin
  511. Clipboard.Clear;
  512. {$IFDEF BMP_CLIPBOARD_FORMAT}
  513. stream := TMemoryStream.Create;
  514. bmpWriter := TFPWriterBMP.Create;
  515. bmpWriter.BitsPerPixel := 32;
  516. bmp.SaveToStream(stream, bmpWriter);
  517. bmpWriter.Free;
  518. Clipboard.AddFormat(PredefinedClipboardFormat(pcfBitmap), stream);
  519. stream.Free;
  520. {$ENDIF}
  521. {$IFDEF TIFF_CLIPBOARD_FORMAT}
  522. stream := TMemoryStream.Create;
  523. bmp.SaveToStreamAs(stream, ifTiff);
  524. Clipboard.AddFormat(tiffClipboardFormat, stream);
  525. stream.Free;
  526. {$ENDIF}
  527. {$IFDEF PNG_CLIPBOARD_FORMAT}
  528. stream := TMemoryStream.Create;
  529. bmp.SaveToStreamAs(stream, ifPng);
  530. Clipboard.AddFormat(pngClipboardFormat, stream);
  531. stream.Free;
  532. {$ENDIF}
  533. end;
  534. initialization
  535. {$IFDEF TIFF_CLIPBOARD_FORMAT}
  536. tiffClipboardFormat := RegisterClipboardFormat({$IFDEF DARWIN}'public.tiff'{$ELSE}'image/tiff'{$ENDIF});
  537. {$ENDIF}
  538. {$IFDEF PNG_CLIPBOARD_FORMAT}
  539. pngClipboardFormat := RegisterClipboardFormat({$IFDEF DARWIN}'public.png'{$ELSE}{$IFDEF WINDOWS}'PNG'{$ELSE}'image/png'{$ENDIF}{$ENDIF});
  540. {$ENDIF}
  541. {$IFDEF HTML_CLIPBOARD_FORMAT}
  542. htmlClipboardFormat := RegisterClipboardFormat({$IFDEF DARWIN}'public.html'{$ELSE}'text/html'{$ENDIF});
  543. {$ENDIF}
  544. {$IFDEF PDN_CLIPBOARD_FORMAT}
  545. pdnClipboardFormat := RegisterClipboardFormat('PaintDotNet.MaskedSurface');
  546. {$ENDIF}
  547. end.