我正在根据Adobe this document中的信息创建一个编写简单PDF文档的例程。为文本和形状创建流已经证明是直截了当的,但我仍然坚持插入图像。
有人能提供一个简单的解释,说明如何将文件(任何图像格式,如gif,bmp,jpg等)转换为PDF流吗?请注意,我不想创建整个PDF文件,只是文件中的流。
使用我可用的应用程序,无法查看它在其他地方是如何完成的,因为整个流都是从头到尾编码的,而这正是我想要解决的编码方法。
虽然我不想重新发明整个创建PDF文件轮,但我想了解它的这个特定部分是如何工作的,所以不想使用库(因此没有提到语言的原因我我正在使用。)
答案 0 :(得分:10)
您需要在内容流中使用Do
运算符。 E.g。
.... /Im1 Do .......
Im1
引用页面资源字典中的XObject资源
例如,
In the page dictionary ...
<<
...
/Contents 1 0 R
/Resources << /XObject << /Im1 2 0 R >> >>
...
>>
对象2 0 R将是图像XObject:
2 0 obj << /Type /XObject /Subtype /Image /Width 100 /Height 100 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length 10000 /Filter /DCTDecode >>
stream
JPEG DATA HERE
endstream
endobj
一些注意事项:
- 要定位和缩放图像,必须使用cm
运算符设置当前图形矩阵。例如
150 0 0 150 100 100 cm
会将图片定位在(100,100)
,并使图片150
宽,150
高。
您不仅限于JPEG - 您可以使用JPEG2000s(使用/ Filter = / JPXDecode)或位图像素数据(省略过滤器)
包含所有这些内容的规范部分是8.9
我没有尝试过LZW解码 - 我猜这可能适用于GIF
通常在显示图像时将图形状态推入堆栈。 e.g。
q a b c d e f cm /Im1 Do Q
q和Q运算符按下并弹出图形状态(重要的是cm运算符!)
答案 1 :(得分:5)
可以在此处找到基于上述内容从jpg创建pdf的简单C#程序。
请注意“stream”和实际jpg-stream一词必须用\ n(或\ r \ n)分隔的详细信息!!!
最诚挚的问候 埃斯克拉恩
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
namespace ConsoleApplication1
{
class Program
{
static void WriStr(FileStream Out, string s)
{
Out.Write(System.Text.Encoding.ASCII.GetBytes(s), 0, s.Length);
}
static void Main(string[] args)
{
string InJpg = @"InFile.JPG";
string OutPdf = @"OutFile.pdf";
byte[] buffer = new byte[8192];
var stream = File.OpenRead(InJpg); // The easiest way to get the metadata is to temporaryly load it as a BMP
Bitmap bmp = (Bitmap)Bitmap.FromStream(stream);
int w = bmp.Width; String wf = (w * 72 / bmp.HorizontalResolution).ToString().Replace(",", ".");
int h = bmp.Height; ; string hf = (h * 72 / bmp.VerticalResolution).ToString().Replace(",", ".");
stream.Close();
FileStream Out = File.Create(OutPdf);
var lens = new List<long>();
WriStr(Out, "%PDF-1.5\r\n");
lens.Add(Out.Position);
WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Type /Catalog\r\n/Pages 2 0 R>>\r\nendobj\r\n");
lens.Add(Out.Position);
WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Count 1/Kids [ <<\r\n" +
"/Type /Page\r\n" +
"/Parent 2 0 R\r\n" +
"/MediaBox [0 0 " + wf + " " + hf + "]\r\n" +
"/Resources<< /ProcSet [/PDF /ImageC]\r\n /XObject <</Im1 4 0 R >> >>\r\n" +
"/Contents 3 0 R\r\n" +
">>\r\n ]\r\n" +
">>\r\nendobj\r\n");
string X = "\r\n" +
"q\r\n" +
"" + wf + " 0 0 " + hf + " 0 0 cm\r\n" +
"/Im1 Do\r\n" +
"Q\r\n";
lens.Add(Out.Position);
WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Length " + X.Length.ToString() + ">>" +
"stream" + X + "endstream\r\n" +
"endobj\r\n");
lens.Add(Out.Position);
WriStr(Out, lens.Count.ToString() + " 0 obj " + "<</Name /Im1" +
"/Type /XObject\r\n" +
"/Subtype /Image\r\n" +
"/Width " + w.ToString() +
"/Height " + h.ToString() +
"/Length 5 0 R\r\n" +
"/Filter /DCTDecode\r\n" +
"/ColorSpace /DeviceRGB\r\n" +
"/BitsPerComponent 8\r\n" +
">> stream\r\n");
long Siz = Out.Position;
var in1 = File.OpenRead(InJpg);
while (true)
{
var len = in1.Read(buffer, 0, buffer.Length);
if (len != 0) Out.Write(buffer, 0, len); else break;
}
in1.Close();
Siz = Out.Position - Siz;
WriStr(Out, "\r\nendstream\r\n" +
"endobj\r\n");
lens.Add(Out.Position);
WriStr(Out, lens.Count.ToString() + " 0 obj " + Siz.ToString() + " endobj\r\n");
long startxref = Out.Position;
WriStr(Out, "xref\r\n" +
"0 " + (lens.Count + 1).ToString() + "\r\n" +
"0000000000 65535 f\r\n");
foreach (var L in lens)
WriStr(Out, (10000000000 + L).ToString().Substring(1) + " 00000 n\r\n");
WriStr(Out, "trailer\r\n" +
"<<\r\n" +
" /Size " + (lens.Count + 1).ToString() + "\r\n" +
" /Root 1 0 R\r\n" +
">>\r\n" +
"startxref\r\n" +
startxref.ToString() + "\r\n%%EOF");
Out.Close();
}
}
}
ADD 2016-04-07:
这是一个带有注释的更高版本,支持缩放和多JPG页面以及一个完整的程序主包装器(附加功能很容易添加,遗漏了很遗憾...)
using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
namespace Jpg2Pdfdir
{
class Program
{
static void WriStr(FileStream Out, string s, params object[] args)
{
s = string.Format(s, args);
Out.Write(System.Text.Encoding.ASCII.GetBytes(s), 0, s.Length);
}
//Combined from http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf
/// <summary>
/// Create a pdf from a list of jpgs, optionally stretching&compressing them. (Note the scaling is a display&print thing only, the jpg_stream itself is included unchanged)
/// </summary>
/// <param name="InJpgs">List of Jpg (full)names</param>
/// <param name="OutPdf">Name of the pdf to create</param>
/// <param name="StretchWs">For each jpg the width-scaling factor, fall back to the last given, and if none to 1.0</param>
/// <param name="StretchHs">For each jpg the height scalling, none positive or missing value is replaced with the width scale value (to keep aspect ratio)</param>
static void JpgToPdf(List<string> InJpgs, string OutPdf, List<Double> StretchWs, List<Double> StretchHs)
{
if (StretchWs==null || StretchWs.Count==0)StretchWs=new List<double>{1.0}; //default to unchanged
if (StretchHs==null)StretchHs=new List<double>{}; //Default to all with same aspect ratio
byte[] buffer = new byte[8192];
int[] ws = new int[InJpgs.Count];
int[] hs = new int[InJpgs.Count];
string[] wfs = new string[InJpgs.Count];
string[] hfs = new string[InJpgs.Count];
for (int i=0;i<InJpgs.Count;i++) {
double StretchW=i<StretchWs.Count?StretchWs[i]:StretchWs[StretchWs.Count-1]; // Fall back to the last
double StretchH=i<StretchHs.Count && 0<StretchHs[i]?StretchHs[i]:StretchW; //Fall back to same X-Y scale.
System.IO.FileStream stream = File.OpenRead(InJpgs[i]);
// The easiest way to get the metadata is to temporaryly load the file, ignoring the ImageData!
using (Image Img = Image.FromStream(stream,false, false)) { //Last parameter: vaildateImageData=FALSE
ws[i] = Img.Width ; wfs[i] = (ws[i] * StretchW * 72 / Img.HorizontalResolution).ToString(System.Globalization.CultureInfo.InvariantCulture);
hs[i] = Img.Height; hfs[i] = (hs[i] * StretchH * 72 / Img.VerticalResolution ).ToString(System.Globalization.CultureInfo.InvariantCulture);
}
stream.Close();
}
FileStream Out = File.Create(OutPdf);
//Holds the object-positions (Or lengths before)
var lens = new List<long>();
//Must have header
WriStr(Out, "%PDF-1.5\r\n");
//Obj 1 The catalog, pointing to the pages in object 2
lens.Add(Out.Position);
WriStr(Out, "{0} 0 obj " + "<</Type /Catalog\r\n/Pages 2 0 R>>\r\nendobj\r\n", lens.Count);
//Obj 2 The pageS, with inline object for the Kids object of type Page
//Note the size in the MediaBox, The resource for the image in object 4 (Streams can not be inline objects)
//And the Contents in object 3, and that the Parent of the Page points back to object 2 self.
lens.Add(Out.Position);
String Pages = "";
for (int i = 0; i < InJpgs.Count; i++) {
Pages+= "<<\r\n"+
"/Type /Page\r\n" +
"/Parent 2 0 R\r\n" +
"/MediaBox [0 0 " + wfs[i] + " " + hfs[i] + "]\r\n" +
"/Resources << /XObject <</Im"+(1+i).ToString()+" "+(4+3*i).ToString()+" 0 R >> >>\r\n" +
"/Contents "+(3+3*i).ToString()+" 0 R\r\n" +
">>\r\n";
}
WriStr(Out, "{0} 0 obj <</Type /Pages /Count {1} /Kids [{2}]\r\n" +
">>\r\nendobj\r\n", lens.Count, InJpgs.Count, Pages);
for (int i = 0; i < InJpgs.Count; i++) {
// Obj 3+3i. The command stream to do the image Im# in a string, so the length can be evaluated. Note this is WITHOUT the leading and trailing CRLF
string X =
"q\r\n" +
"" + wfs[i] + " 0 0 " + hfs[i] + " 0 0 cm\r\n" +
"/Im"+(1+i).ToString()+" Do\r\n" +
"Q";
lens.Add(Out.Position);
WriStr(Out, lens.Count.ToString() + " 0 obj <</Length {0}>> stream\r\n" +
"{1}\r\n" +
"endstream\r\n" +
"endobj\r\n", X.Length, X);
// Obj 4+3i of type XObject containing the jpg-stream, and with a reference to the length that will be stored in object 5 when known
lens.Add(Out.Position);
WriStr(Out, "{0} 0 obj <</Name /Im{1}" +
"/Type /XObject\r\n" +
"/Subtype /Image\r\n" +
"/Width {2}"+
"/Height {3}"+
"/Length {4} 0 R\r\n" +
"/Filter /DCTDecode\r\n" +
"/ColorSpace /DeviceRGB\r\n" +
"/BitsPerComponent 8\r\n" +
">> stream\r\n", lens.Count, 1+i, ws[i], hs[i], 5+3*i);
long Siz = Out.Position;
var in1 = File.OpenRead(InJpgs[i]);
while (true)
{
var len = in1.Read(buffer, 0, buffer.Length);
if (len != 0) Out.Write(buffer, 0, len); else break;
}
in1.Close();
Siz = Out.Position - Siz; // The difference is the stream-length
WriStr(Out, "\r\nendstream\r\n" +
"endobj\r\n");
// Obj 5+3i the stream length (not known at the time of the begining of object 4
lens.Add(Out.Position);
WriStr(Out, "{0} 0 obj {1} endobj\r\n",lens.Count ,Siz);
}
//Pointer for XREF-table saved
long startxref = Out.Position;
//The XREF table, note the zero'th object, it is the free-object-list not used here
WriStr(Out, "xref\r\n" +
"0 {0}\r\n" +
"0000000000 65535 f\r\n", lens.Count+1);
//Position of each object saved entered in the XREF
foreach (var L in lens)
WriStr(Out, (10000000000 + L).ToString().Substring(1) + " 00000 n\r\n");
//The trailer, pointing to object 1 as the Root
//and the saved startxref last, judt before the %%EOF marker
WriStr(Out, "trailer\r\n" +
"<<\r\n" +
" /Size {0}\r\n" +
" /Root 1 0 R\r\n" +
">>\r\n" +
"startxref\r\n", lens.Count+1);
WriStr(Out, startxref.ToString() + "\r\n" +
"%%EOF");
Out.Close();
}
static void Main(string[] args)
{
if (0==args.Length) {
Console.WriteLine("Call with {JpgName [ScaleXY | ScaleW ScaleH] } [OutputName] , OutputName defaults to first .jpg -> .pdf");
return;
}
List<string> basejpgs = new List<string>();
double WrkDouble;
List<double> ScaFacWs = new List<double>();
List<double> ScaFacHs = new List<double>();
int i = 0;
while(i<args.Length && System.IO.File.Exists(args[i]) && System.IO.Path.GetExtension(args[i]).ToLower()==".jpg") {
basejpgs.Add(args[i]);
i++;
if (i<args.Length && Double.TryParse(args[i], out WrkDouble)) {
i++;
} else {
WrkDouble=1.0; //Default to 1x
}
ScaFacWs.Add(WrkDouble);
if (i < args.Length && Double.TryParse(args[i], out WrkDouble))
{
i++;
} else {
WrkDouble=-1; //Default to same x-y scale
}
ScaFacHs.Add(WrkDouble);
}
//if (basejpgs.Count==0) basejpgs.Add("Red16x16.jPg"); //####DEBUG####
string destpdf = basejpgs[0];
if (i<args.Length && (System.IO.Path.GetExtension(args[i]).ToLower()==".pdf" || System.IO.Path.GetExtension(args[i])=="")) {
destpdf=args[i];
i++;
}
if (i<args.Length) {
Console.WriteLine("Too many arguments, or could not decode???");
}
destpdf = System.IO.Path.ChangeExtension(destpdf, ".PDF");
JpgToPdf(basejpgs, destpdf, ScaFacWs, ScaFacHs);
}
}
}