// This file helps to generate a properly formed PDF document. // Available functions for external use: // pdfw.int.code: Make a PDF function from a K character vector. // pdfw.int.get: Convert a K object to a PDF string. (should be internal) // pdfw.rle: Run length encoding; good for packing simple (b&w) images. // pdfw.render: Renders a K structure to a PDF string. \d pdfw \l common/pdf/afmlib // Standard sizes. inch:72 cm:inch%2.54 paper.letter:0 0 8.5 11*inch paper.legal: 0 0 8.5 14*inch paper.a4: 0 0 595.25 841.9 \d int // Function: hexfromint // Used by: pdfw.int.nckc // Description: Convert an integer into a hex string with precision. hexlist: "0123456789ABCDEF" hexfromint: {(-y)#(y#"0"),hexlist[16 _vs x]} // Function: pdfw.int.code // Used by: pdfw.int.indref // Description: Package a K character vector into a block of literal PDF code // that pdfw.int.get will process correctly. code:{."{",(5:x),"}"} // Function: pdfw.int.indref // Used by: exported // Description: Generate a PDF indirect reference. indref:{code ($x)," ",($y)," R"} // Function: pdfw.int.ckc // Used by: pdfw.int.skcv // Description: Convert a K character into a PDF string-compliant character. // If character needs escaping, return it as a string. ckc:{[c] / pdfw.int.ckc // Check whether character needs escaping (ASCII range 0-31, 40, 41, 92, or // 127-255) n:_ic c :[(n>31)&(n<127)&(~c~"(")&(~c~")")&(~c~"\\"); c n~ 8; "\\b" n~ 9; "\\t" n~10; "\\n" n~12; "\\f" n~13; "\\r" c~"("; "\\(" c~")"; "\\)" c~"\\"; "\\\\" "\\" ,/ $ -3# 0 0, 8 _vs n]} // Function: pdfw.int.nckc // Used by: pdfw.int.nks // Description: Convert a K character into a PDF name-compliant character or // string. nckcn:"()<>[]{}/#" nckc:{[c] / pdfw.int.nckc // Check whether character needs escaping (ASCII range 1-32, 40, 41, 92, or // 127-255) n:_ic c :[(n>32)&(n<127)&((nckcn?c)~#nckcn); c "#",hexfromint[n;2]]} // Function: pdfw.int.skcv // Used by: pdfw.int.get // Description: Convert a K character vector into a PDF string. // Implementation: Apply pdfw.int.ckc[] over entire string and surround result // in '(' and ')'. skcv:{("(",/ckc'x),")"} // Function: pdfw.int.nks // Used by: pdfw.int.get // Description: Convert a K symbol into a PDF name. // Implementation: Convert symbol to string and prepend '/'. // Note: PDF names cannot have any of these: %()<>[]{}/# This // function will convert any instances of them, as well as // spaces and unprintables, to hex equivalents. In addition, // PDF names cannot have nulls, but neither can K symbols, so // no handling is necessary. nks:{"/",/nckc'$`$$x} // Function: pdfw.int.dkd // Used by: pdfw.int.get // Description: Convert a K dictionary into a PDF dictionary. If the // dictionary contains a 'STREAM' key, the result will be a PDF // stream ('Length' is added to the dictionary, and the 'STREAM' // is removed and appended to the end of it). // Note: This function recursively calls pdfw.int.get to parse its values. dkd:{ / pdfw.int.dkd if[`STREAM _in !x s:x.STREAM x:x _di `STREAM if[~-3=4:s;s:$s] x.Length:#s] d:"<<",/("\r\n ",/:({(get x[0])," ",(get x[1])}'(. x))),"\r\n>> " if[~_n~s d:d,"\r\nstream\r\n",s,"\r\nendstream"] d} // Function: pdfw.int.akl // Used by: pdfw.int.get // Description: Given a K list or vector, return a PDF array. // Note: This function recursively calls pdfw.int.get to parse its elements. akl:{"[ ",/((get'x),\:" "),"]"} // Function: pdfw.int.get // Used by: pdfw.int.dkd, pdfw.int.akl, pdfw.int.obj, exported // Given any K object, return a PDF representation. get:{[o] / pdfw.int.get t:4:o :[(t>0)&(t<4); $o // integer, float, or character atom t~-3; skcv[o] // char vector t~4; nks[o] // symbol atom t~5; dkd[o] // dictionary t~6; "null" // nil t~7; o[] // function (just execute it; should return a string) akl[o]]} // all lists (except character) // Function: pdfw.int.obj // Used by: pdfw.int.fkl // Description: Turn a K object into a PDF top-level object obj:{[n;o]($n)," 0 obj\r\n",get[o],"\r\nendobj\r\n\r\n"} // Function: pdfw.int.xref // Used by: pdfw.int.fkl // Description: Return a byte offset in xref format. Args are byte offset (or // next free), generation number, and type (character: 'f' or // 'n'). Returned string is 20 bytes. xref:{(-10#(9#"0"),$x)," ",(-5#(4#"0"),$y)," ",(1#z),"\r\n"} // Function: pdfw.int.fkl // Used by: pdfw.render // Description: Given an array of K objects that represent top-level PDF // objects, plus indexes to the info and root objects, return a // PDF file as a K character vector. fkl:{[oa;info;root] / pdfw.int.fkl file:"%PDF-1.4\r\n%\342\343\317\323\r\n" xr:xref[0;65535;"f"] objcount:0 do[#oa xr:xr,xref[#file;0;"n"] file,:obj[objcount+:1;oa[objcount]]] xr:"xref\r\n0 ",(get[objcount+:1]),"\r\n",xr trailer:"trailer\r\n" td:.((`Size;objcount) (`Root;indref[1+root;0])) if[~0N=info td[`Info]:indref[1+info;0]] trailer,:get[td] trailer,:"\r\nstartxref\r\n",($#file),"\r\n%%EOF\r\n" file,xr,trailer} tmpname:{ temp:.f.getTemp[] if[(*|temp)_in "/\\" temp:-1_ temp] files:!temp files:files[&_sm[files;"~kpdf*.pdf"]] files:.f.makePath[temp]'files .[.f.delete;;:]'files files:!temp name:"~kpdf",($*1 _draw 1000),".pdf" while[name _in files name:"~kpdf",($*1 _draw 1000),".pdf"] .f.makePath[temp;name]} \d ^ init:{[doc] / pdfw.init .[_d;doc;:;.()] page.init[doc] info.creator[doc;"K PDF Writer, by DFA Capital Management Inc."] } \d info author:{[doc;s] / pdfw.info.author .[_d;(doc;`info;`Author);:;s] } cdate:{[doc;d] / pdfw.info.cdate date:,/("D:"; ,/$_gtime d; "z") .[_d;(doc;`info;`CreationDate);:;date] } mdate:{[doc;d] / pdfw.info.mdate date:,/("D:"; ,/$_gtime d; "z") .[_d;(doc;`info;`ModDate);:;date] } creator:{[doc;s] / pdfw.info.creator .[_d;(doc;`info;`Creator);:;s] } producer:{[doc;s] / pdfw.info.producer .[_d;(doc;`info;`Producer);:;s] } title:{[doc;s] / pdfw.info.title .[_d;(doc;`info;`Title);:;s] } subject:{[doc;s] / pdfw.info.subject .[_d;(doc;`info;`Subject);:;s] } keywords:{[doc;s] / pdfw.info.keywords .[_d;(doc;`info;`Keywords);:;s] } \d ^ \d page init:{[doc] / pdfw.page.init / Initialize the page tree, including the first branch node. .[_d;(doc;`pages);:;()] .[_d;(doc;`curpage);:;0N] begin[doc] } begin:{[doc] / pdfw.page.begin / If the current page has contents, then end it before beginning a new one. curpage:_d[doc;`curpage] if[~0N=curpage if[`Contents _in !_d[doc;`pages;curpage] `0:,"Warning: Beginning a new page without ending the previous one." end[doc]]] / Install a new page, and increment the page number. newpage:#_d . doc,`pages curpage:_d[doc;`curpage] page:.,(`Parent;curpage) .[_d;doc,`pages;,;page] .[_d;doc,`curpage;:;newpage] / If creating a child node, mark this as a branch node. Also, add the child / object to the list. if[~0N=curpage .[_d;(doc;`pages;curpage;`Type);:;`Pages] if[~`Kids _in !_d[doc;`pages;curpage] .[_d;(doc;`pages;curpage;`Kids);:;!0]] .[_d;(doc;`pages;curpage;`Kids);,;newpage]] / If a font was set on a previous page, clear it. .[_d;doc,`curfont;:;_n] } end:{[doc] / pdfw.page.end / If this page isn't marked as a branch node, mark it as a child node. curpage:_d[doc;`curpage] if[~`Type _in !_d[doc;`pages;curpage] .[_d;(doc;`pages;curpage;`Type);:;`Page]] / Go up a step in the page tree. curpage:_d[doc;`pages;curpage;`Parent] .[_d;doc,`curpage;:;curpage] } mediabox:{[doc;rect] / pdfw.page.mediabox / Store the value in the page dictionary. curpage:_d[doc;`curpage] .[_d;(doc;`pages;curpage;`MediaBox);:;rect] } cropbox:{[doc;rect] / pdfw.page.cropbox / Store the value in the page dictionary. curpage:_d[doc;`curpage] .[_d;(doc;`pages;curpage;`CropBox);:;rect] } rotate:{[doc;degrees] / pdfw.page.rotate / Store the value in the page dictionary. curpage:_d[doc;`curpage] .[_d;(doc;`pages;curpage;`Rotate);:;degrees] } \d ^ int.pi:2*_asin 1 int.si:{:["e" _in s:$x;:["e" _in s:$0.0$0.7$x;0.7$x;s];s]} content.new:{[doc] / pdfw.content.new curpage:_d[doc;`curpage] / Create initial Contents entry, if necessary. if[~`Contents _in !_d[doc;`pages;curpage] .[_d;(doc;`pages;curpage;`curcon);:;-1] .[_d;(doc;`pages;curpage;`Contents);:;()]] oldcon:_d[doc;`pages;curpage;`curcon] .[_d;(doc;`pages;curpage;`curcon);:;#_d[doc;`pages;curpage;`Contents]] .[_d;(doc;`pages;curpage;`Contents);,;.,(`STREAM;"")] oldcon} content.select:{[doc;newcon] / pdfw.content.select curpage:_d[doc;`curpage] oldcon:_d[doc;`pages;curpage;`curcon] ncon:-1+#_d[doc;`pages;curpage;`Contents] if[(nconnewcon) '"Content index out of bounds"] .[_d;(doc;`pages;curpage;`curcon);:;newcon] oldcon} content.mark:{[doc;s] / pdfw.content.mark curpage:_d[doc;`curpage] if[~`Contents _in !_d[doc;`pages;curpage] content.new[doc]] curcon:_d[doc;`pages;curpage;`curcon] .[_d;(doc;`pages;curpage;`Contents;curcon;`STREAM);,;s," "] } gs.save:{[doc]content.mark[doc;"q"];} gs.restore:{[doc]content.mark[doc;"Q"];} egs.gsdict:{[doc;gd] / pdfw.egs.gsdict if[~`extgstateobjs _in !_d[doc] .[_d;(doc;`extgstateobjs);:;()]] gd[`Type]:`ExtGState egi:_d[doc;`extgstateobjs]?gd if[egi~#_d[doc;`extgstateobjs] .[_d;(doc;`extgstateobjs);,;gd]] egname:`$"EG",$egi curpage:_d[doc;`curpage] .[_d;(doc;`pages;curpage;`Resources;`ExtGState;egname);:;egi] content.mark[doc]'(int.get[egname];"gs") } ctm.matrix:{[doc;m]content.mark[doc]'int.si'[m],,"cm";} ctm.translate:{[doc;t]ctm.matrix[doc;1 0 0 1,t];} ctm.scale:{[doc;s]ctm.matrix[doc;(s 0;0;0;s 1;0;0)];} ctm.rotate:{[doc;a]a:int.pi*a%180;c:_cos a;s:_sin a;ctm.matrix[doc;(c;s;-s;c;0;0)];} ctm.skew:{[doc;a]a:int.pi*a%180;t:_tan a;ctm.matrix[doc;(1;t 0;t 1;1;0;0)];} ctm.rotate_rad:{[doc;a]c:_cos a;s:_sin a;ctm.matrix[doc;(c;s;-s;c;0;0)];} ctm.skew_rad:{[doc;a]t:_tan a;ctm.matrix[doc;(1;t 0;t 1;1;0;0)];} color.fill_gray:{[doc;g]content.mark[doc]'(int.si[g];,"g");} color.stroke_gray:{[doc;g]content.mark[doc]'(int.si[g];,"G");} color.fill_cmyk:{[doc;c]content.mark[doc]'int.si[c],,"k";} color.stroke_cmyk:{[doc;c]content.mark[doc]'int.si[c],,"K";} color.fill_rgb:{[doc;c]content.mark[doc]'int.si[c],,"rg";} color.stroke_rgb:{[doc;c]content.mark[doc]'int.si[c],,"RG";} color.fill_color:{[doc;c]content.mark[doc]'int.si[c],,"sc";} color.stroke_color:{[doc;c]content.mark[doc]'int.si[c],,"SC";} /color.fill_cs:{...} /color.stroke_cs:{...} path.moveto:{[doc;m]content.mark[doc]'int.si[m],,"m";} path.lineto:{[doc;l]content.mark[doc]'int.si[l],,"l";} path.curveto:{[doc;c1;c2;c3] / pdfw.path.curveto wn:_n~'(c1;c2;c3) :[0 0 0~wn;content.mark[doc]'int.si[c1,c2,c3],,"c" 1 0 0~wn;content.mark[doc]'int.si[c2,c3],,"v" 0 1 0~wn;content.mark[doc]'int.si[c1,c3],,"y" '"Bad coordinate list"] } path.rect:{[doc;r]content.mark[doc]'int.si[r],,"re";} path.closepath:{[doc]content.mark[doc;"h"];} path.clip:{[doc;rule]content.mark[doc;:[`eo=rule;"W*";"W"]];} path.paint:{[doc;params] / pdfw.path.paint if[@params;params:,params] newpath: `newpath _in params stroke: `stroke _in params fill: `fill _in params eo: `eo _in params closepath:`closepath _in params if[~newpath|stroke|fill '"At least one of `newpath, `stroke, or `fill must be selected"] op: :[newpath; "n" stroke&fill&eo&closepath;"b*" stroke&fill&closepath; "b" stroke&fill&eo; "B*" stroke&fill; "B" fill&eo; "f*" fill; "f" stroke&closepath; "s" stroke; "S" '"Bug: unhandled operator combination"] content.mark[doc;op] } line.flatness:{[doc;f]content.mark[doc]'(int.si[f];"i");} line.cap:{[doc;c]content.mark[doc]'(int.get[`butt`round`square?c];"J");} line.dash:{[doc;a;p]content.mark[doc]'(int.get[a];int.get[p];"d");} line.join:{[doc;j]content.mark[doc]'(int.get[`miter`round`bevel?j];"j");} line.width:{[doc;w]content.mark[doc]'(int.si[w];"w");} line.miter_limit:{[doc;m]content.mark[doc]'(int.si[m];"M");} font.add_type1:{[doc;base;encoding] / pdfw.font.add_type1 :[~`fonts _in !_d[doc] .[_d;(doc;`fonts);:;()] if[base _in _d[doc;`fonts;;`BaseFont] :_n]] font:.+(`Type`Subtype`BaseFont;`Font`Type1,base) if[~_n~encoding font[`Encoding]:encoding] .[_d;(doc;`fonts);,;font] } text.begin:{[doc].[_d;doc,`curfont;:;_n];content.mark[doc;"BT"];} text.end:{[doc]content.mark[doc;"ET"];} text.char_spacing:{[doc;s]content.mark[doc]'(int.si[s];"Tc");} text.word_spacing:{[doc;s]content.mark[doc]'(int.si[s];"Tw");} text.horiz_scale:{[doc;s]content.mark[doc]'(int.si[s];"Tz");} text.leading:{[doc;l]content.mark[doc]'(int.si[l];"TL");} text.font:{[doc;f;s] / pdfw.text.font fi:&f~'_d[doc;`fonts;;`BaseFont] if[fi~!0 `0:,"Warning: font has not been added; assuming Type 1 with default encoding." font.add_type1[doc;f;_n] fi:&f~'_d[doc;`fonts;;`BaseFont]] fi:*fi fname:`$"F",$fi curpage:_d[doc;`curpage] if[~(f;s)~_d[doc;`curfont] .[_d;doc,`curfont;:;(f;s)] .[_d;(doc;`pages;curpage;`Resources;`Font;fname);:;fi] content.mark[doc]'(int.get[fname];$s;"Tf")] } text.render_mode:{[doc;r] / pdfw.text.render_mode if[@r;r:,r] fill: `fill _in r stroke:`stroke _in r clip: `clip _in r r:(clip*4)+(-1+2 _sv (stroke;fill))!4 content.mark[doc]'($r;"Tr") } text.rise:{[doc;r]content.mark[doc]'int.si[r],,"Ts";} text.matrix:{[doc;m]content.mark[doc]'int.si[m],,"Tm";} text.translate:{[doc;t]text.matrix[doc;1 0 0 1,t];} text.scale:{[doc;s]text.matrix[doc;(s 0;0;0;s 1;0;0)];} text.rotate:{[doc;a]a:int.pi*a%180;c:_cos a;s:_sin a;text.matrix[doc;(c;s;-s;c;0;0)];} text.skew:{[doc;a]a:int.pi*a%180;t:_tan a;text.matrix[doc;(1;t 0;t 1;1;0;0)];} text.rotate_rad:{[doc;a]c:_cos a;s:_sin a;text.matrix[doc;(c;s;-s;c;0;0)];} text.skew_rad:{[doc;a]t:_tan a;text.matrix[doc;(1;t 0;t 1;1;0;0)];} text.move:{[doc;p]content.mark[doc]'int.si[p],,"Td";} text.nl:{[doc]content.mark[doc;"T*"];} text.leading_nl:{[doc;p]content.mark[doc]'int.si[p],,"TD";} text.show:{[doc;s]content.mark[doc]'(int.get[s];"Tj");} text.nl_show:{[doc;s]content.mark[doc]'(int.get[s];,"'");} text.nl_show_wc:{[doc;ws;cs;s]content.mark[doc]'(int.si[ws];int.si[cs];int.get[s];,"\"");} text.show_dyn:{[doc;l]content.mark[doc]'(int.get[l];"TJ");} xobj.placeimage:{[doc;w;h;cs;bpc;filter;pixmap] / pdfw.xobj.placeimage if[~`xobjs _in !_d[doc] .[_d;(doc;`xobjs);:;()]] img:.+(`Type`Subtype`Width`Height`ColorSpace`BitsPerComponent (`XObject;`Image;w;h;cs;bpc)) if[~_n~filter img[`Filter]:filter] img[`STREAM]:pixmap xi:#_d[doc;`xobjs] xname:`$"X",$xi .[_d;(doc;`xobjs);,;img] curpage:_d[doc;`curpage] .[_d;(doc;`pages;curpage;`Resources;`XObject;xname);:;xi] content.mark[doc]'(int.get[xname];"Do") } // Function: pdfw.rle // Used by: exported // Description: Create a run-length encoded version of a K character vector. rle:{[bytes] / pdfw.rle dups:0=':bytes pos:,/(0;(1+&<':dups);(&>':dups);#bytes) dups:_n pos:pos[