using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Paroxe.PdfRenderer.Internal; using UnityEngine; using Paroxe.PdfRenderer.WebGL; using System.Collections; // For WebGL namespace Paroxe.PdfRenderer { /// /// This class allow the application to render pages into textures. /// public class PDFRenderer : IDisposable { private bool m_Disposed; #if !UNITY_WEBGL || UNITY_EDITOR private PDFBitmap m_Bitmap; private byte[] m_IntermediateBuffer; #endif public PDFRenderer() { PDFLibrary.AddRef("PDFRenderer"); } ~PDFRenderer() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!m_Disposed) { if (disposing) { #if !UNITY_WEBGL || UNITY_EDITOR m_Bitmap.Dispose(); m_Bitmap = null; #endif } PDFLibrary.RemoveRef("PDFRenderer"); m_Disposed = true; } } #if UNITY_WEBGL public class RenderPageParameters { public IntPtr pageHandle; public Texture2D existingTexture; public Vector2 newTextureSize; public RenderPageParameters(IntPtr pageHandle, Texture2D existingTexture, Vector2 newTextureSize) { this.pageHandle = pageHandle; this.existingTexture = existingTexture; this.newTextureSize = newTextureSize; } } #endif public static PDFJS_Promise RenderPageToExistingTextureAsync(PDFPage page, Texture2D tex) { PDFJS_Promise renderPromise = new PDFJS_Promise(); #if !UNITY_WEBGL || UNITY_EDITOR using (PDFRenderer renderer = new PDFRenderer()) { renderPromise.HasFinished = true; renderPromise.HasSucceeded = true; renderPromise.HasReceivedJSResponse = true; renderer.RenderPageToExistingTexture(page, tex); renderPromise.Result = tex; } #else RenderPageParameters parameters = new RenderPageParameters(page.NativePointer, tex, new Vector2(tex.width, tex.height)); PDFJS_Library.Instance.PreparePromiseCoroutine(RenderPageCoroutine, renderPromise, parameters).Start(); #endif return renderPromise; } public static PDFJS_Promise RenderPageToTextureAsync(PDFPage page, int width, int height) { return RenderPageToTextureAsync(page, new Vector2(width, height)); } public static PDFJS_Promise RenderPageToTextureAsync(PDFPage page, Vector2 size) { PDFJS_Promise renderPromise = new PDFJS_Promise(); #if !UNITY_WEBGL || UNITY_EDITOR using (PDFRenderer renderer = new PDFRenderer()) { renderPromise.HasFinished = true; renderPromise.HasSucceeded = true; renderPromise.HasReceivedJSResponse = true; renderPromise.Result = renderer.RenderPageToTexture(page, (int)size.x, (int)size.y); } #else RenderPageParameters parameters = new RenderPageParameters(page.NativePointer, null, size); PDFJS_Library.Instance.PreparePromiseCoroutine(RenderPageCoroutine, renderPromise, parameters).Start(); #endif return renderPromise; } public static PDFJS_Promise RenderPageToTextureAsync(PDFPage page, float scale = 1.0f) { PDFJS_Promise renderPromise = new PDFJS_Promise(); #if !UNITY_WEBGL || UNITY_EDITOR using (PDFRenderer renderer = new PDFRenderer()) { renderPromise.HasFinished = true; renderPromise.HasSucceeded = true; renderPromise.HasReceivedJSResponse = true; Vector2 size = page.GetPageSize(scale); renderPromise.Result = renderer.RenderPageToTexture(page, (int)size.x, (int)size.y); } #else RenderPageParameters parameters = new RenderPageParameters(page.NativePointer, null, page.GetPageSize(scale)); PDFJS_Library.Instance.PreparePromiseCoroutine(RenderPageCoroutine, renderPromise, parameters).Start(); #endif return renderPromise; } #if UNITY_WEBGL && !UNITY_EDITOR private static IEnumerator RenderPageCoroutine(PDFJS_PromiseCoroutine promiseCoroutine, IPDFJS_Promise promise, object parameters) { PDFJS_Promise renderToCanvasPromise = new PDFJS_Promise(); PDFJS_Library.Instance.PreparePromiseCoroutine(null, renderToCanvasPromise, null); IntPtr pageHandle = ((RenderPageParameters)parameters).pageHandle; Texture2D texture = ((RenderPageParameters)parameters).existingTexture; Vector2 newtextureSize = ((RenderPageParameters)parameters).newTextureSize; Vector2 pageSize = PDFPage.GetPageSize(pageHandle, 1.0f); float scale = 1.0f; float width = 0.0f; float height = 0.0f; if (texture != null) { float wf = pageSize.x / texture.width; float hf = pageSize.y / texture.height; width = texture.width; height = texture.height; scale = 1.0f / Mathf.Max(wf, hf); } else { float wf = pageSize.x / newtextureSize.x; float hf = pageSize.y / newtextureSize.y; width = newtextureSize.x; height = newtextureSize.y; scale = 1.0f / Mathf.Max(wf, hf); } NativeMethods.PDFJS_RenderPageIntoCanvas(renderToCanvasPromise.PromiseHandle, pageHandle.ToInt32(), scale, width, height); while (!renderToCanvasPromise.HasReceivedJSResponse) yield return null; if (renderToCanvasPromise.HasSucceeded) { int canvasHandle = int.Parse(renderToCanvasPromise.JSObjectHandle); using (PDFJS_WebGLCanvas canvas = new PDFJS_WebGLCanvas(new IntPtr(canvasHandle))) { PDFJS_Promise renderToTexturePromise = promise as PDFJS_Promise; if (texture == null) { texture = new Texture2D((int)newtextureSize.x, (int)newtextureSize.y, TextureFormat.ARGB32, false); texture.filterMode = FilterMode.Bilinear; texture.Apply(); } NativeMethods.PDFJS_RenderCanvasIntoTexture(canvasHandle, texture.GetNativeTexturePtr().ToInt32()); renderToTexturePromise.Result = texture; renderToTexturePromise.HasSucceeded = true; renderToTexturePromise.HasFinished = true; promiseCoroutine.ExecuteThenAction(true, texture); } } else { PDFJS_Promise renderToTexturePromise = promise as PDFJS_Promise; renderToTexturePromise.Result = null; renderToTexturePromise.HasSucceeded = false; renderToTexturePromise.HasFinished = true; promiseCoroutine.ExecuteThenAction(false, null); } } #endif #if !UNITY_WEBGL || UNITY_EDITOR /// /// Render page into a new byte array. /// /// /// #if !UNITY_WEBGL public #else private #endif byte[] RenderPageToByteArray(PDFPage page) { return RenderPageToByteArray(page, (int)page.GetPageSize().x, (int)page.GetPageSize().y, null, RenderSettings.defaultRenderSettings); } /// /// Render page into a new byte array. /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif byte[] RenderPageToByteArray(PDFPage page, int width, int height) { return RenderPageToByteArray(page, width, height, null, RenderSettings.defaultRenderSettings); } /// /// Render page into a new byte array. /// /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif byte[] RenderPageToByteArray(PDFPage page, int width, int height, IPDFColoredRectListProvider rectsProvider) { return RenderPageToByteArray(page, width, height, rectsProvider, RenderSettings.defaultRenderSettings); } /// /// Render page into a new byte array. /// /// /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif byte[] RenderPageToByteArray(PDFPage page, int width, int height, IPDFColoredRectListProvider rectsProvider, RenderSettings settings) { if (settings == null) settings = RenderSettings.defaultRenderSettings; if (m_Bitmap == null || m_Bitmap.UseAlphaChannel != settings.transparentBackground || !m_Bitmap.HasSameSize(width, height)) { if (m_Bitmap != null) m_Bitmap.Dispose(); m_Bitmap = new PDFBitmap(width, height, settings.transparentBackground); } m_Bitmap.FillRect(0, 0, width, height, settings.transparentBackground ? 0x00000000 : int.MaxValue); int flags = settings == null ? RenderSettings.defaultRenderSettings.ComputeRenderingFlags() : settings.ComputeRenderingFlags(); float scale = width / page.GetPageSize(1.0f).x; PDFRect clipping = new PDFRect(width, height); PDFMatrix matrix = PDFMatrix.Identity; matrix.Scale(scale, -scale); matrix.Translate(0.0f, height); NativeMethods.FPDF_RenderPageBitmapWithMatrix(m_Bitmap.NativePointer, page.NativePointer, ref matrix, ref clipping, flags); IntPtr bufferPtr = m_Bitmap.GetBuffer(); if (bufferPtr == IntPtr.Zero) return null; int length = width * height * 4; if (m_IntermediateBuffer == null || m_IntermediateBuffer.Length < length) m_IntermediateBuffer = new byte[width * height * 4]; Marshal.Copy(bufferPtr, m_IntermediateBuffer, 0, width * height * 4); #if !UNITY_WEBGL IList coloredRects = rectsProvider != null ? rectsProvider.GetBackgroundColoredRectList(page) : null; if (coloredRects != null && coloredRects.Count > 0) { foreach (PDFColoredRect coloredRect in coloredRects) { var r = (int)(coloredRect.Color.r * 255) & 0xFF; var g = (int)(coloredRect.Color.g * 255) & 0xFF; var b = (int)(coloredRect.Color.b * 255) & 0xFF; var a = (int)(coloredRect.Color.a * 255) & 0xFF; float alpha = (a / (float)255); float reverseAlpha = 1.0f - alpha; Rect deviceRect = page.ConvertPageRectToDeviceRect(coloredRect.PageRect, new Vector2(width, height)); if (deviceRect.x >= 0.0f && deviceRect.y >= 0.0f && deviceRect.x + deviceRect.width <= width && deviceRect.y + deviceRect.height <= height) { for (int y = 0; y < (int)deviceRect.height; ++y) { for (int x = 0; x < (int)deviceRect.width; ++x) { int s = (((height - (int)deviceRect.y) - y) * width + (int)deviceRect.x + x) * 4; var sr = m_IntermediateBuffer[s]; var sg = m_IntermediateBuffer[s + 1]; var sb = m_IntermediateBuffer[s + 2]; m_IntermediateBuffer[s] = (byte)Mathf.Clamp(alpha * r + (reverseAlpha * sr), 0, 255); m_IntermediateBuffer[s + 1] = (byte)Mathf.Clamp(alpha * g + (reverseAlpha * sg), 0, 255); m_IntermediateBuffer[s + 2] = (byte)Mathf.Clamp(alpha * b + (reverseAlpha * sb), 0, 255); m_IntermediateBuffer[s + 3] = 0xFF; } } } } } #endif return m_IntermediateBuffer; } [StructLayout(LayoutKind.Sequential)] public struct PDFMatrix { public static PDFMatrix Identity { get { return new PDFMatrix { a = 1, b = 0, c = 0, d = 1, e = 0, f = 0 }; } } public float a; public float b; public float c; public float d; public float e; public float f; public PDFMatrix(float[] n) { if (n == null) throw new ArgumentNullException("n"); if (n.Length != 6) throw new ArgumentException("n must have 6 elements", "n"); a = n[0]; b = n[1]; c = n[2]; d = n[3]; e = n[4]; f = n[5]; } public PDFMatrix(float a, float b, float c, float d, float e, float f) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; } public void SetIdentity() { a = 1; b = 0; c = 0; d = 1; e = 0; f = 0; } public void Scale(float sx, float sy, bool prepended = false) { a *= sx; d *= sy; if (prepended) { b *= sx; c *= sy; return; } b *= sy; c *= sx; e *= sx; f *= sy; } public void Translate(float x, float y, bool prepended = false) { if (prepended) { e += x * a + y * c; f += y * d + x * b; return; } e += x; f += y; } public void Rotate(float radian, bool prepended = false) { float cosValue = Mathf.Cos(radian); float sinValue = Mathf.Sin(radian); ConcatInternal(new PDFMatrix(cosValue, sinValue, -sinValue, cosValue, 0, 0), prepended); } public void RotateAt(float fRadian, float dx, float dy, bool prepended = false) { Translate(dx, dy, prepended); Rotate(fRadian, prepended); Translate(-dx, -dy, prepended); } private void ConcatInternal(PDFMatrix other, bool prepend) { PDFMatrix left; PDFMatrix right; if (prepend) { left = other; right = this; } else { left = this; right = other; } a = left.a * right.a + left.b * right.c; b = left.a * right.b + left.b * right.d; c = left.c * right.a + left.d * right.c; d = left.c * right.b + left.d * right.d; e = left.e * right.a + left.f * right.c + right.e; f = left.e * right.b + left.f * right.d + right.f; } public float this[int index] { get { float num; switch (index) { case 0: { num = a; break; } case 1: { num = b; break; } case 2: { num = c; break; } case 3: { num = d; break; } case 4: { num = e; break; } case 5: { num = f; break; } default: { throw new IndexOutOfRangeException(string.Format("Invalid PDFMatrix index addressed: {0}!", new object[] { index })); } } return num; } set { switch (index) { case 0: { a = value; break; } case 1: { b = value; break; } case 2: { c = value; break; } case 3: { d = value; break; } case 4: { e = value; break; } case 5: { f = value; break; } default: { throw new IndexOutOfRangeException(string.Format("Invalid PDFMatrix index addressed: {0}!", new object[] { index })); } } } } public override bool Equals(object other) { return (other is PDFMatrix ? this == (PDFMatrix)other : false); } public static bool operator ==(PDFMatrix lhs, PDFMatrix rhs) { return (lhs.a != rhs.a || lhs.b != rhs.b || lhs.c != rhs.c || lhs.d != rhs.d || lhs.e != rhs.e ? false : lhs.f == rhs.f); } public static bool operator !=(PDFMatrix lhs, PDFMatrix rhs) { return !(lhs == rhs); } public override int GetHashCode() { unchecked { int hashCode = this.a.GetHashCode(); hashCode = hashCode * 23 + b.GetHashCode(); hashCode = hashCode * 23 + c.GetHashCode(); hashCode = hashCode * 23 + d.GetHashCode(); hashCode = hashCode * 23 + e.GetHashCode(); hashCode = hashCode * 23 + f.GetHashCode(); return hashCode; } } } [StructLayout(LayoutKind.Sequential)] public struct PDFRect { public float left; public float top; public float right; public float bottom; public PDFRect(float left, float top, float right, float bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public PDFRect(float width, float height) { left = 0; top = 0; right = width; bottom = height; } public override bool Equals(object other) { return (other is PDFRect ? this == (PDFRect)other : false); } public static bool operator ==(PDFRect lhs, PDFRect rhs) { return (lhs.left != rhs.left || lhs.top != rhs.top || lhs.right != rhs.right ? false : lhs.bottom == rhs.bottom); } public static bool operator !=(PDFRect lhs, PDFRect rhs) { return !(lhs == rhs); } public override int GetHashCode() { unchecked { int hashCode = this.left.GetHashCode(); hashCode = hashCode * 23 + top.GetHashCode(); hashCode = hashCode * 23 + right.GetHashCode(); hashCode = hashCode * 23 + bottom.GetHashCode(); return hashCode; } } } /// /// Render page into a new Texture2D. /// /// /// #if !UNITY_WEBGL public #else private #endif Texture2D RenderPageToTexture(PDFPage page) { return RenderPageToTexture(page, (int)page.GetPageSize().x, (int)page.GetPageSize().y, null, RenderSettings.defaultRenderSettings); } /// /// Render page into a new Texture2D. /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif Texture2D RenderPageToTexture(PDFPage page, int width, int height) { return RenderPageToTexture(page, width, height, null, RenderSettings.defaultRenderSettings); } /// /// Render page into a new Texture2D. /// /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif Texture2D RenderPageToTexture(PDFPage page, int width, int height, IPDFColoredRectListProvider rectsProvider) { return RenderPageToTexture(page, width, height, rectsProvider, RenderSettings.defaultRenderSettings); } /// /// Render page into a new Texture2D. /// /// /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif Texture2D RenderPageToTexture(PDFPage page, int width, int height, IPDFColoredRectListProvider rectsProvider, RenderSettings settings) { Texture2D newTex = new Texture2D(width, height, TextureFormat.RGBA32, false); RenderPageToExistingTexture(page, newTex, rectsProvider, settings); return newTex; } /// /// Render page into an existing Texture2D. /// /// /// #if !UNITY_WEBGL public #else private #endif void RenderPageToExistingTexture(PDFPage page, Texture2D texture) { RenderPageToExistingTexture(page, texture, null, RenderSettings.defaultRenderSettings); } /// /// Render page into an existing Texture2D. /// /// /// /// #if !UNITY_WEBGL public #else private #endif void RenderPageToExistingTexture(PDFPage page, Texture2D texture, IPDFColoredRectListProvider rectsProvider) { RenderPageToExistingTexture(page, texture, rectsProvider, RenderSettings.defaultRenderSettings); } /// /// Render page into an existing Texture2D. /// /// /// /// /// #if !UNITY_WEBGL public #else private #endif void RenderPageToExistingTexture(PDFPage page, Texture2D texture, IPDFColoredRectListProvider rectsProvider, RenderSettings settings) { byte[] byteArray = RenderPageToByteArray(page, texture.width, texture.height, rectsProvider, settings); if (byteArray != null) { texture.wrapMode = TextureWrapMode.Clamp; if ((texture.format != TextureFormat.RGBA32 && texture.format != TextureFormat.ARGB32 && texture.format != TextureFormat.BGRA32 && texture.format != (TextureFormat)37) || texture.mipmapCount > 1) { Color32[] pixels = new Color32[texture.width * texture.height]; for (int i = 0; i < pixels.Length; ++i) pixels[i] = new Color32( byteArray[i * 4], byteArray[i * 4 + 1], byteArray[i * 4 + 2], byteArray[i * 4 + 3]); texture.SetPixels32(pixels); texture.Apply(); } else { texture.LoadRawTextureData(byteArray); texture.Apply(); } } } #endif /// /// Allows the application to specify render settings. /// [Serializable] public class RenderSettings { public bool disableSmoothPath = false; public bool disableSmoothText = false; public bool disableSmoothImage = false; public bool grayscale = false; public bool optimizeTextForLCDDisplay = false; public bool renderAnnotations = false; public bool renderForPrinting = false; public bool transparentBackground = false; public static RenderSettings defaultRenderSettings { get { return new RenderSettings(); } } public int ComputeRenderingFlags() { int flags = 0x10; if (renderAnnotations) flags |= 0x01; if (optimizeTextForLCDDisplay) flags |= 0x02; if (grayscale) flags |= 0x08; if (renderForPrinting) flags |= 0x800; if (disableSmoothText) flags |= 0x1000; if (disableSmoothImage) flags |= 0x2000; if (disableSmoothPath) flags |= 0x4000; return flags; } } } }