/ src / modules / AdvancedPaste / AdvancedPaste / Helpers / TransformHelpers.cs
TransformHelpers.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Globalization;
  7  using System.IO;
  8  using System.Threading;
  9  using System.Threading.Tasks;
 10  
 11  using AdvancedPaste.Models;
 12  using ManagedCommon;
 13  using Windows.ApplicationModel.DataTransfer;
 14  using Windows.Graphics.Imaging;
 15  using Windows.Storage.Streams;
 16  
 17  namespace AdvancedPaste.Helpers;
 18  
 19  public static class TransformHelpers
 20  {
 21      public static async Task<DataPackage> TransformAsync(PasteFormats format, DataPackageView clipboardData, CancellationToken cancellationToken, IProgress<double> progress)
 22      {
 23          return format switch
 24          {
 25              PasteFormats.PlainText => await ToPlainTextAsync(clipboardData),
 26              PasteFormats.Markdown => await ToMarkdownAsync(clipboardData),
 27              PasteFormats.Json => await ToJsonAsync(clipboardData),
 28              PasteFormats.ImageToText => await ImageToTextAsync(clipboardData, cancellationToken),
 29              PasteFormats.PasteAsTxtFile => await ToTxtFileAsync(clipboardData, cancellationToken),
 30              PasteFormats.PasteAsPngFile => await ToPngFileAsync(clipboardData, cancellationToken),
 31              PasteFormats.PasteAsHtmlFile => await ToHtmlFileAsync(clipboardData, cancellationToken),
 32              PasteFormats.TranscodeToMp3 => await TranscodeHelpers.TranscodeToMp3Async(clipboardData, cancellationToken, progress),
 33              PasteFormats.TranscodeToMp4 => await TranscodeHelpers.TranscodeToMp4Async(clipboardData, cancellationToken, progress),
 34              PasteFormats.KernelQuery => throw new ArgumentException($"Unsupported format {format}", nameof(format)),
 35              PasteFormats.CustomTextTransformation => throw new ArgumentException($"Unsupported format {format}", nameof(format)),
 36              _ => throw new ArgumentException($"Unknown value {format}", nameof(format)),
 37          };
 38      }
 39  
 40      private static async Task<DataPackage> ToPlainTextAsync(DataPackageView clipboardData)
 41      {
 42          Logger.LogTrace();
 43          return CreateDataPackageFromText(await clipboardData.GetTextOrEmptyAsync());
 44      }
 45  
 46      private static async Task<DataPackage> ToMarkdownAsync(DataPackageView clipboardData)
 47      {
 48          Logger.LogTrace();
 49          return CreateDataPackageFromText(await MarkdownHelper.ToMarkdownAsync(clipboardData));
 50      }
 51  
 52      private static async Task<DataPackage> ToJsonAsync(DataPackageView clipboardData)
 53      {
 54          Logger.LogTrace();
 55          return CreateDataPackageFromText(await JsonHelper.ToJsonFromXmlOrCsvAsync(clipboardData));
 56      }
 57  
 58      private static async Task<DataPackage> ImageToTextAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
 59      {
 60          Logger.LogTrace();
 61  
 62          var bitmap = await clipboardData.GetImageContentAsync() ?? throw new ArgumentException("No image content found in clipboard", nameof(clipboardData));
 63          var text = await OcrHelpers.ExtractTextAsync(bitmap, cancellationToken);
 64          return CreateDataPackageFromText(text);
 65      }
 66  
 67      private static async Task<DataPackage> ToPngFileAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
 68      {
 69          Logger.LogTrace();
 70  
 71          var clipboardBitmap = await clipboardData.GetImageContentAsync();
 72  
 73          using var pngStream = new InMemoryRandomAccessStream();
 74          var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, pngStream);
 75          encoder.SetSoftwareBitmap(clipboardBitmap);
 76          await encoder.FlushAsync();
 77  
 78          return await CreateDataPackageFromFileContentAsync(pngStream.AsStreamForRead(), "png", cancellationToken);
 79      }
 80  
 81      private static async Task<DataPackage> ToTxtFileAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
 82      {
 83          Logger.LogTrace();
 84  
 85          var text = await clipboardData.GetTextOrHtmlTextAsync();
 86          return await CreateDataPackageFromFileContentAsync(text, "txt", cancellationToken);
 87      }
 88  
 89      private static async Task<DataPackage> ToHtmlFileAsync(DataPackageView clipboardData, CancellationToken cancellationToken)
 90      {
 91          Logger.LogTrace();
 92  
 93          var cfHtml = await clipboardData.GetHtmlContentAsync();
 94          var html = RemoveHtmlMetadata(cfHtml);
 95  
 96          return await CreateDataPackageFromFileContentAsync(html, "html", cancellationToken);
 97      }
 98  
 99      /// <summary>
100      /// Removes leading CF_HTML metadata from HTML clipboard data.
101      /// See: https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
102      /// </summary>
103      private static string RemoveHtmlMetadata(string cfHtml)
104      {
105          int? GetIntTagValue(string tagName)
106          {
107              var tagNameWithColon = tagName + ":";
108              int tagStartPos = cfHtml.IndexOf(tagNameWithColon, StringComparison.InvariantCulture);
109  
110              const int tagValueLength = 10;
111              return tagStartPos != -1 && int.TryParse(cfHtml.AsSpan(tagStartPos + tagNameWithColon.Length, tagValueLength), CultureInfo.InvariantCulture, out int result) ? result : null;
112          }
113  
114          var startFragmentIndex = GetIntTagValue("StartFragment");
115          var endFragmentIndex = GetIntTagValue("EndFragment");
116  
117          return (startFragmentIndex == null || endFragmentIndex == null) ? cfHtml : cfHtml[startFragmentIndex.Value..endFragmentIndex.Value];
118      }
119  
120      private static async Task<DataPackage> CreateDataPackageFromFileContentAsync(string data, string fileExtension, CancellationToken cancellationToken)
121      {
122          if (string.IsNullOrEmpty(data))
123          {
124              throw new ArgumentException($"Empty value in {nameof(CreateDataPackageFromFileContentAsync)}", nameof(data));
125          }
126  
127          var path = GetPasteAsFileTempFilePath(fileExtension);
128  
129          await File.WriteAllTextAsync(path, data, cancellationToken);
130          return await DataPackageHelpers.CreateFromFileAsync(path);
131      }
132  
133      private static async Task<DataPackage> CreateDataPackageFromFileContentAsync(Stream stream, string fileExtension, CancellationToken cancellationToken)
134      {
135          var path = GetPasteAsFileTempFilePath(fileExtension);
136  
137          using var fileStream = File.Create(path);
138          await stream.CopyToAsync(fileStream, cancellationToken);
139  
140          return await DataPackageHelpers.CreateFromFileAsync(path);
141      }
142  
143      private static string GetPasteAsFileTempFilePath(string fileExtension)
144      {
145          var prefix = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsFile_FilePrefix");
146          var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture);
147  
148          return Path.Combine(Path.GetTempPath(), $"{prefix}{timestamp}.{fileExtension}");
149      }
150  
151      private static DataPackage CreateDataPackageFromText(string content) => DataPackageHelpers.CreateFromText(content);
152  }