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 }