Skip to content

Commit

Permalink
Reorganized stuff and finished declaring and using XObject resources.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmeaster30 committed Jul 22, 2023
1 parent acb7720 commit 9d4d948
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 34 deletions.
8 changes: 8 additions & 0 deletions BlastPDF/Builder/Graphics/Drawing/PdfImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BlastPDF.Builder.Graphics.Drawing;

public static class PdfImageExtensions {
public static PdfGraphicsObject Image(this PdfGraphicsObject graphics, string resource) {
graphics.SubObjects.Add(new PdfXObject { Resource = resource });
return graphics;
}
}
7 changes: 6 additions & 1 deletion BlastPDF/Builder/Graphics/Drawing/PdfInlineImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public class PdfInlineImage : PdfGraphicsObject
public static PdfInlineImage FromFile(string filename, FileFormat format, PdfColorSpace colorSpace, PdfFilter[] filters)
{
var image = IImage.Decode(filename, format);
return FromImage(image, colorSpace, filters);
}

public static PdfInlineImage FromImage(IImage image, PdfColorSpace colorSpace, PdfFilter[] filters)
{
var result = new PdfInlineImage {
Width = image.Width(),
Height = image.Height(),
Expand All @@ -38,7 +43,7 @@ public static PdfInlineImage FromFile(string filename, FileFormat format, PdfCol
}
}

public static class PdfImageExtensions
public static class PdfInlineImageExtensions
{
public static PdfGraphicsObject InlineImage(this PdfGraphicsObject graphics, string filename, FileFormat format, PdfColorSpace colorSpace = PdfColorSpace.DeviceRGB, PdfFilter[] filters = null)
{
Expand Down
1 change: 1 addition & 0 deletions BlastPDF/Builder/PdfDocument.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using BlastPDF.Builder.Resources;
using BlastPDF.Builder.Resources.Font;

namespace BlastPDF.Builder;

Expand Down
1 change: 1 addition & 0 deletions BlastPDF/Builder/PdfPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using BlastPDF.Builder.Graphics;
using BlastPDF.Builder.Resources;
using BlastPDF.Builder.Resources.Font;

namespace BlastPDF.Builder;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace BlastPDF.Builder.Resources;
namespace BlastPDF.Builder.Resources.Font;

public enum PdfFontType
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Collections.Generic;

namespace BlastPDF.Builder.Resources;
namespace BlastPDF.Builder.Resources.Font;

public class PdfFontType1 : PdfFontResource
{
Expand Down
78 changes: 78 additions & 0 deletions BlastPDF/Builder/Resources/Image/PdfImageResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using BlastPDF.Builder.Graphics;
using BlastPDF.Filter;
using SharperImage;
using SharperImage.Formats;

namespace BlastPDF.Builder.Resources.Image;

public class PdfImageResource : PdfObject
{
public uint Width { get; set; }
public uint Height { get; set; }
public PdfColorSpace ColorSpace { get; set; }
public int BitsPerComponent { get; set; }
public PdfFilter[] Filters { get; set; }

public IImage ImageData { get; set; }

public static PdfImageResource FromFile(string filename, FileFormat format, PdfColorSpace colorSpace, PdfFilter[] filters)
{
var image = IImage.Decode(filename, format);
return FromImage(image, colorSpace, filters);
}

public static PdfImageResource FromImage(IImage image, PdfColorSpace colorSpace, PdfFilter[] filters)
{
var result = new PdfImageResource {
Width = image.Width(),
Height = image.Height(),
ColorSpace = colorSpace,
BitsPerComponent = 8, // TODO I would like this to be figured out from the pixel format in the image
ImageData = image,
Filters = filters,
};

if (filters is null)
{
result.Filters = new[] {PdfFilter.AsciiHex};
}

return result;
}
}

public static class PdfImageResourceExtensions
{
public static PdfPage UseImage(this PdfPage page, string resourceName, string filename, FileFormat format, PdfColorSpace colorSpace = PdfColorSpace.DeviceRGB, PdfFilter[] filters = null)
{
if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName));
if (page.Resources.ContainsKey(resourceName)) throw new ArgumentException($"Resource '{resourceName}' already exists as a resource for this page :(");
page.Resources.Add(resourceName, PdfImageResource.FromFile(filename, format, colorSpace, filters));
return page;
}

public static PdfDocument UseImage(this PdfDocument doc, string resourceName, string filename, FileFormat format, PdfColorSpace colorSpace = PdfColorSpace.DeviceRGB, PdfFilter[] filters = null)
{
if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName));
if (doc.Resources.ContainsKey(resourceName)) throw new ArgumentException($"Resource '{resourceName}' already exists as a resource for this document :(");
doc.Resources.Add(resourceName, PdfImageResource.FromFile(filename, format, colorSpace, filters));
return doc;
}

public static PdfPage UseImage(this PdfPage page, string resourceName, IImage image, PdfColorSpace colorSpace = PdfColorSpace.DeviceRGB, PdfFilter[] filters = null)
{
if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName));
if (page.Resources.ContainsKey(resourceName)) throw new ArgumentException($"Resource '{resourceName}' already exists as a resource for this page :(");
page.Resources.Add(resourceName, PdfImageResource.FromImage(image, colorSpace, filters));
return page;
}

public static PdfDocument UseImage(this PdfDocument doc, string resourceName, IImage image, PdfColorSpace colorSpace = PdfColorSpace.DeviceRGB, PdfFilter[] filters = null)
{
if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName));
if (doc.Resources.ContainsKey(resourceName)) throw new ArgumentException($"Resource '{resourceName}' already exists as a resource for this document :(");
doc.Resources.Add(resourceName, PdfImageResource.FromImage(image, colorSpace, filters));
return doc;
}
}
22 changes: 22 additions & 0 deletions BlastPDF/Builder/Resources/PdfResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace BlastPDF.Builder.Resources;

public static class PdfResource
{
public static PdfPage UseObject(this PdfPage page, string resourceName, PdfObject resource)
{
if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName));
if (page.Resources.ContainsKey(resourceName)) throw new ArgumentException($"Resource '{resourceName}' already exists as a resource for this page :(");
page.Resources.Add(resourceName, resource);
return page;
}

public static PdfDocument UseObject(this PdfDocument doc, string resourceName, PdfObject resource)
{
if (string.IsNullOrEmpty(resourceName)) throw new ArgumentNullException(nameof(resourceName));
if (doc.Resources.ContainsKey(resourceName)) throw new ArgumentException($"Resource '{resourceName}' already exists as a resource for this document :(");
doc.Resources.Add(resourceName, resource);
return doc;
}
}
57 changes: 50 additions & 7 deletions BlastPDF/Exporter/Basic/BasicExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using BlastPDF.Builder.Graphics;
using BlastPDF.Builder.Graphics.Drawing;
using BlastPDF.Builder.Resources;
using BlastPDF.Builder.Resources.Font;
using BlastPDF.Builder.Resources.Image;
using BlastPDF.Filter;
using BlastSharp.Lists;
using SharperImage.Enumerators;
Expand Down Expand Up @@ -110,9 +112,16 @@ private static PdfExporterResults Export(this PdfPage page, Stream stream, int o
List<(string, int)> x_objects = new();
foreach (var res in page.Resources)
{
var startOffset = stream.Position;
crossReferences.Add((nextStart, stream.Position));
x_objects.Add((res.Key, nextStart));
res.Value.Export(stream, nextStart);
nextStart += 1;
var endOffset = stream.Position;
crossReferences.Add((nextStart + 1, stream.Position));
stream.Write($"{nextStart + 1} 0 obj\n".ToUTF8());
stream.Write($"({endOffset - startOffset})\n".ToUTF8());
stream.Write("endobj\n\n".ToUTF8());
nextStart += 2;
}
// build page resource dictionary
var pageResources = nextStart;
Expand Down Expand Up @@ -190,6 +199,7 @@ private static PdfExporterResults Export(this PdfObject pdfObject, Stream stream
return pdfObject switch
{
PdfGraphicsObject graphics => graphics.Export(stream, objectNumber),
PdfImageResource imageRes => imageRes.Export(stream, objectNumber),
_ => throw new Exception("Unhandled subtype of PdfObject")
};
}
Expand Down Expand Up @@ -220,18 +230,50 @@ private static PdfExporterResults Export(this PdfFontType1 pdfFontType1, Stream
return new PdfExporterResults();
}

/*private static PdfExporterResults Export(this PdfEmbeddedImage embed, Stream stream, int objectNumber)
private static PdfExporterResults Export(this PdfImageResource image, Stream stream, int objectNumber)
{
stream.Write($"{objectNumber} 0 obj\n".ToUTF8());
stream.Write("<<\n/Type /XObject\n/Subtype /Image\n".ToUTF8());
stream.Write($"/Width {embed.Width}\n/Height {embed.Height}".ToUTF8());
stream.Write($"/ColorSpace /{embed.ColorSpace}\n/BitsPerComponent {embed.BitsPerComponent}\n".ToUTF8());
stream.Write($"/Length {embed.ImageData.LongLength}\n".ToUTF8());
stream.Write($"/Width {image.Width}\n/Height {image.Height}\n".ToUTF8());
stream.Write($"/ColorSpace /{image.ColorSpace}\n/BitsPerComponent {image.BitsPerComponent}\n".ToUTF8());
var filterNames = image.Filters.Select(x => x switch
{
PdfFilter.Ascii85 => "/A85",
PdfFilter.AsciiHex => "/AHx",
PdfFilter.Lzw => "/LZW",
PdfFilter.RunLength => "/RL",
_ => throw new NotImplementedException()
}).Join(" ");
stream.Write($"/Length {objectNumber + 1} 0 R\n".ToUTF8());
stream.Write($"\t/F [{filterNames}]\n".ToUTF8());
stream.Write(">>\nstream\n".ToUTF8());
stream.Write(embed.ImageData);
var imageData = image.ImageData.ToRowRankPixelEnumerable().SelectMany(p =>
{
switch (image.ColorSpace)
{
case PdfColorSpace.DeviceGray:
return new[] { (byte)(0.299 * p.Red + 0.587 * p.Green + 0.114 * p.Blue) };
case PdfColorSpace.DeviceRGB:
return new[] { p.Red, p.Green, p.Blue };
case PdfColorSpace.DeviceCMYK:
var rPrime = p.Red / 255.0;
var gPrime = p.Green / 255.0;
var bPrime = p.Blue / 255.0;
var k = 1 - Math.Max(rPrime, Math.Max(gPrime, bPrime));
var c = (1 - rPrime - k) / (1 - k);
var m = (1 - gPrime - k) / (1 - k);
var y = (1 - bPrime - k) / (1 - k);
return new[] { (byte)(c * 255), (byte)(m * 255), (byte)(y * 255), (byte)(k * 255) };
default:
throw new NotImplementedException();
}
}).ToList();
imageData = image.Filters.Reverse().Aggregate(imageData, (current, filter) => filter.Encode(current).ToList());

stream.Write(imageData.ToArray());
stream.Write("\nendstream\nendobj\n".ToUTF8());
return new PdfExporterResults();
}*/
}

private static PdfExporterResults Export(this PdfGraphicsObject graphicsObject, Stream stream, int objectNumber) {
// TODO I have a feeling this could still be better
Expand All @@ -256,6 +298,7 @@ private static PdfExporterResults Export(this PdfGraphicsObject graphicsObject,
case PdfXObject xobj: return xobj.Export(stream, objectNumber);
case PdfInlineImage image: return image.Export(stream, objectNumber);
case PdfTextObject text: return text.Export(stream, objectNumber);

}

foreach (var obj in graphicsObject.SubObjects) {
Expand Down
42 changes: 20 additions & 22 deletions ShowCase/PdfBuilderExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using BlastPDF.Builder.Graphics;
using BlastPDF.Builder.Graphics.Drawing;
using BlastPDF.Builder.Resources;
using BlastPDF.Builder.Resources.Font;
using BlastPDF.Builder.Resources.Image;
using BlastPDF.Exporter.Basic;
using BlastPDF.Filter;
using BlastSharp.Dates;
Expand Down Expand Up @@ -42,10 +44,7 @@ public static void Run(string outputPdfName)
.UseHelvetica()
.UseCourierBold()
.UseTimesNewRomanItalic()
//.AddGraphics(PdfGraphicsObject.Create()
// .SetCMYK(0.0M, 0.0M, 1.0M, 0.0M)
// .Rect(0, 0, 1000, 1000)
// .Paint(PaintModeEnum.CloseFillStroke))
.UseImage("Cat", "../../../images/bmp/cat.bmp", FileFormat.BMP, PdfColorSpace.DeviceRGB, new []{PdfFilter.AsciiHex, PdfFilter.Lzw})
.AddGraphics(PdfTextObject.Create()
.TextLeading(12)
.SetFont("Helvetica", 24)
Expand Down Expand Up @@ -87,27 +86,26 @@ public static void Run(string outputPdfName)
.ShowText("too far")
.NextLineOffset(0, -6)
.ShowText("too far"))
/*.AddGraphics(PdfGraphicsObject.Create()
.Translate(250, 702)
.Scale(50.0M, 50.0M)
.InlineImage("../../../images/bmp/w3c_home.bmp", FileFormat.BMP, PdfColorSpace.DeviceRGB, new []{PdfFilter.ASCII85}))
.AddGraphics(PdfGraphicsObject.Create()
.Translate(300, 702)
.Scale(50.0M, 50.0M)
.InlineImage("../../../images/bmp/w3c_home.bmp", FileFormat.BMP)) // ASCIIHex
.AddGraphics(PdfGraphicsObject.Create()
.Translate(350, 702)
.Scale(50.0M, 50.0M)
.InlineImage("../../../images/bmp/w3c_home.bmp", FileFormat.BMP, PdfColorSpace.DeviceRGB, new []{PdfFilter.LZW}))
.AddGraphics(PdfGraphicsObject.Create()
.Translate(400, 702)
.Scale(50.0M, 50.0M)
.InlineImage("../../../images/bmp/w3c_home.bmp", FileFormat.BMP, PdfColorSpace.DeviceRGB, new []{PdfFilter.ASCII85, PdfFilter.LZW}))
*/
.AddGraphics(PdfGraphicsObject.Create()
.Translate(200, 200)
.Scale(600.0M, 600.0M)
.InlineImage("../../../images/bmp/cat.bmp", FileFormat.BMP, PdfColorSpace.DeviceRGB, new []{PdfFilter.AsciiHex, PdfFilter.Lzw}))
.Image("Cat")
.ResetState()
.Translate(250, 250)
.Scale(500.0M, 500.0M)
.Image("Cat")
.ResetState()
.Translate(300, 300)
.Scale(400.0M, 400.0M)
.Image("Cat")
.ResetState()
.Translate(350, 350)
.Scale(300.0M, 300.0M)
.Image("Cat")
.ResetState()
.Translate(400, 400)
.Scale(200.0M, 200.0M)
.Image("Cat"))
).Save(fs);

//if (File.Exists("template_test.pdf")) {
Expand Down

0 comments on commit 9d4d948

Please sign in to comment.