2015-02-20 19:43:42 +01:00

3432 lines
116 KiB
Plaintext
Executable File

CREATE OR REPLACE package body as_pdf3
is
--
type tp_pls_tab is table of pls_integer index by pls_integer;
type tp_objects_tab is table of number(10) index by pls_integer;
type tp_pages_tab is table of blob index by pls_integer;
type tp_settings is record
( page_width number
, page_height number
, margin_left number
, margin_right number
, margin_top number
, margin_bottom number
);
type tp_font is record
( standard boolean
, family varchar2(100)
, style varchar2(2) -- N Normal
-- I Italic
-- B Bold
-- BI Bold Italic
, subtype varchar2(15)
, name varchar2(100)
, fontname varchar2(100)
, char_width_tab tp_pls_tab
, encoding varchar2(100) , charset varchar2(1000)
, compress_font boolean := true
, fontsize number
, unit_norm number
, bb_xmin pls_integer
, bb_ymin pls_integer
, bb_xmax pls_integer
, bb_ymax pls_integer
, flags pls_integer
, first_char pls_integer
, last_char pls_integer
, italic_angle number
, ascent pls_integer
, descent pls_integer
, capheight pls_integer
, stemv pls_integer
, diff varchar2(32767)
, cid boolean := false
, fontfile2 blob
, ttf_offset pls_integer
, used_chars tp_pls_tab
, numGlyphs pls_integer
, indexToLocFormat pls_integer
, loca tp_pls_tab
, code2glyph tp_pls_tab
, hmetrics tp_pls_tab
);
type tp_font_tab is table of tp_font index by pls_integer;
type tp_img is record
( adler32 varchar2(8)
, width pls_integer
, height pls_integer
, color_res pls_integer
, color_tab raw(768)
, greyscale boolean
, pixels blob
, type varchar2(5)
, nr_colors pls_integer
, transparancy_index pls_integer
);
type tp_img_tab is table of tp_img index by pls_integer;
type tp_info is record
( title varchar2(1024)
, author varchar2(1024)
, subject varchar2(1024)
, keywords varchar2(32767)
);
type tp_page_prcs is table of clob index by pls_integer;
--
-- globals
g_pdf_doc blob; -- the PDF-document being constructed
g_objects tp_objects_tab;
g_pages tp_pages_tab;
g_settings tp_settings;
g_fonts tp_font_tab;
g_used_fonts tp_pls_tab;
g_current_font pls_integer;
g_images tp_img_tab;
g_x number; -- current x-location of the "cursor"
g_y number; -- current y-location of the "cursor"
g_info tp_info;
g_page_nr pls_integer;
g_page_prcs tp_page_prcs;
--
-- constants
c_nl constant varchar2(2) := chr(13) || chr(10);
--
function num2raw( p_value number )
return raw
is
begin
return hextoraw( to_char( p_value, 'FM0XXXXXXX' ) );
end;
--
function raw2num( p_value raw )
return number
is
begin
return to_number( rawtohex( p_value ), 'XXXXXXXX' );
end;
--
function raw2num( p_value raw, p_pos pls_integer, p_len pls_integer )
return pls_integer
is
begin
return to_number( rawtohex( utl_raw.substr( p_value, p_pos, p_len ) ), 'XXXXXXXX' );
end;
--
function to_short( p_val raw, p_factor number := 1 )
return number
is
t_rv number;
begin
t_rv := to_number( rawtohex( p_val ), 'XXXXXXXXXX' );
if t_rv > 32767
then
t_rv := t_rv - 65536;
end if;
return t_rv * p_factor;
end;
--
function blob2num( p_blob blob, p_len integer, p_pos integer )
return number
is
begin
return to_number( rawtohex( dbms_lob.substr( p_blob, p_len, p_pos ) ), 'xxxxxxxx' );
end;
--
function file2blob( p_dir varchar2, p_file_name varchar2 )
return blob
is
t_raw raw(32767);
t_blob blob;
fh utl_file.file_type;
begin
fh := utl_file.fopen( p_dir, p_file_name, 'rb' );
dbms_lob.createtemporary( t_blob, true );
loop
begin
utl_file.get_raw( fh, t_raw );
dbms_lob.append( t_blob, t_raw );
exception
when no_data_found
then
exit;
end;
end loop;
utl_file.fclose( fh );
return t_blob;
exception
when others
then
if utl_file.is_open( fh )
then
utl_file.fclose( fh );
end if;
raise;
end;
--
procedure init_core_fonts
is
function uncompress_withs( p_compressed_tab varchar2 )
return tp_pls_tab
is
t_rv tp_pls_tab;
t_tmp raw(32767);
begin
if p_compressed_tab is not null
then
t_tmp := utl_compress.lz_uncompress
( utl_encode.base64_decode( utl_raw.cast_to_raw( p_compressed_tab ) ) );
for i in 0 .. 255
loop
t_rv( i ) := to_number( utl_raw.substr( t_tmp, i * 4 + 1, 4 ), '0xxxxxxx' );
end loop;
end if;
return t_rv;
end;
--
procedure init_core_font
( p_ind pls_integer
, p_family varchar2
, p_style varchar2
, p_name varchar2
, p_compressed_tab varchar2
)
is
begin
g_fonts( p_ind ).family := p_family;
g_fonts( p_ind ).style := p_style;
g_fonts( p_ind ).name := p_name;
g_fonts( p_ind ).fontname := p_name;
g_fonts( p_ind ).standard := true;
g_fonts( p_ind ).encoding := 'WE8MSWIN1252';
g_fonts( p_ind ).charset := sys_context( 'userenv', 'LANGUAGE' );
g_fonts( p_ind ).charset := substr( g_fonts( p_ind ).charset
, 1
, instr( g_fonts( p_ind ).charset, '.' )
) || g_fonts( p_ind ).encoding;
g_fonts( p_ind ).char_width_tab := uncompress_withs( p_compressed_tab );
end;
begin
init_core_font( 1, 'helvetica', 'N', 'Helvetica'
, 'H4sIAAAAAAAAC81Tuw3CMBC94FQMgMQOLAGVGzNCGtc0dAxAT+8lsgE7RKJFomOA'
|| 'SLT4frHjBEFJ8XSX87372C8A1Qr+Ax5gsWGYU7QBAK4x7gTnGLOS6xJPOd8w5NsM'
|| '2OvFvQidAP04j1nyN3F7iSNny3E6DylPeeqbNqvti31vMpfLZuzH86oPdwaeo6X+'
|| '5X6Oz5VHtTqJKfYRNVu6y0ZyG66rdcxzXJe+Q/KJ59kql+bTt5K6lKucXvxWeHKf'
|| '+p6Tfersfh7RHuXMZjHsdUkxBeWtM60gDjLTLoHeKsyDdu6m8VK3qhnUQAmca9BG'
|| 'Dq3nP+sV/4FcD6WOf9K/ne+hdav+DTuNLeYABAAA' );
--
init_core_font( 2, 'helvetica', 'I', 'Helvetica-Oblique'
, 'H4sIAAAAAAAAC81Tuw3CMBC94FQMgMQOLAGVGzNCGtc0dAxAT+8lsgE7RKJFomOA'
|| 'SLT4frHjBEFJ8XSX87372C8A1Qr+Ax5gsWGYU7QBAK4x7gTnGLOS6xJPOd8w5NsM'
|| '2OvFvQidAP04j1nyN3F7iSNny3E6DylPeeqbNqvti31vMpfLZuzH86oPdwaeo6X+'
|| '5X6Oz5VHtTqJKfYRNVu6y0ZyG66rdcxzXJe+Q/KJ59kql+bTt5K6lKucXvxWeHKf'
|| '+p6Tfersfh7RHuXMZjHsdUkxBeWtM60gDjLTLoHeKsyDdu6m8VK3qhnUQAmca9BG'
|| 'Dq3nP+sV/4FcD6WOf9K/ne+hdav+DTuNLeYABAAA' );
--
init_core_font( 3, 'helvetica', 'B', 'Helvetica-Bold'
, 'H4sIAAAAAAAAC8VSsRHCMAx0SJcBcgyRJaBKkxXSqKahYwB6+iyRTbhLSUdHRZUB'
|| 'sOWXLF8SKCn+ZL/0kizZuaJ2/0fn8XBu10SUF28n59wbvoCr51oTD61ofkHyhBwK'
|| '8rXusVaGAb4q3rXOBP4Qz+wfUpzo5FyO4MBr39IH+uLclFvmCTrz1mB5PpSD52N1'
|| 'DfqS988xptibWfbw9Sa/jytf+dz4PqQz6wi63uxxBpCXY7uUj88jNDNy1mYGdl97'
|| '856nt2f4WsOFed4SpzumNCvlT+jpmKC7WgH3PJn9DaZfA42vlgh96d+wkHy0/V95'
|| 'xyv8oj59QbvBN2I/iAuqEAAEAAA=' );
--
init_core_font( 4, 'helvetica', 'BI', 'Helvetica-BoldOblique'
, 'H4sIAAAAAAAAC8VSsRHCMAx0SJcBcgyRJaBKkxXSqKahYwB6+iyRTbhLSUdHRZUB'
|| 'sOWXLF8SKCn+ZL/0kizZuaJ2/0fn8XBu10SUF28n59wbvoCr51oTD61ofkHyhBwK'
|| '8rXusVaGAb4q3rXOBP4Qz+wfUpzo5FyO4MBr39IH+uLclFvmCTrz1mB5PpSD52N1'
|| 'DfqS988xptibWfbw9Sa/jytf+dz4PqQz6wi63uxxBpCXY7uUj88jNDNy1mYGdl97'
|| '856nt2f4WsOFed4SpzumNCvlT+jpmKC7WgH3PJn9DaZfA42vlgh96d+wkHy0/V95'
|| 'xyv8oj59QbvBN2I/iAuqEAAEAAA=' );
--
init_core_font( 5, 'times', 'N', 'Times-Roman'
, 'H4sIAAAAAAAAC8WSKxLCQAyG+3Bopo4bVHbwHGCvUNNT9AB4JEwvgUBimUF3wCNR'
|| 'qAoGRZL9twlQikR8kzTvZBtF0SP6O7Ej1kTnSRfEhHw7+Jy3J4XGi8w05yeZh2sE'
|| '4j312ZDeEg1gvSJy6C36L9WX1urr4xrolfrSrYmrUCeDPGMu5+cQ3Ur3OXvQ+TYf'
|| '+2FGexOZvTM1L3S3o5fJjGQJX2n68U2ur3X5m3cTvfbxsk9pcsMee60rdTjnhNkc'
|| 'Zip9HOv9+7/tI3Oif3InOdV/oLdx3gq2HIRaB1Ob7XPk35QwwxDyxg3e09Dv6nSf'
|| 'rxQjvty8ywDce9CXvdF9R+4y4o+7J1P/I9sABAAA' );
--
init_core_font( 6, 'times', 'I', 'Times-Italic'
, 'H4sIAAAAAAAAC8WSPQ6CQBCFF+i01NB5g63tPcBegYZTeAB6SxNLjLUH4BTEeAYr'
|| 'Kwpj5ezsW2YgoKXFl2Hnb9+wY4x5m7+TOOJMdIFsRywodkfMBX9aSz7bXGp+gj6+'
|| 'R4TvOtJ3CU5Eq85tgGsbxG3QN8iFZY1WzpxXwkckFTR7e1G6osZGWT1bDuBnTeP5'
|| 'KtW/E71c0yB2IFbBphuyBXIL9Y/9fPvhf8se6vsa8nmeQtU6NSf6ch9fc8P9DpqK'
|| 'cPa5/I7VxDwruTN9kV3LDvQ+h1m8z4I4x9LIbnn/Fv6nwOdyGq+d33jk7/cxztyq'
|| 'XRhTz/it7Mscg7fT5CO+9ahnYk20Hww5IrwABAAA' );
--
init_core_font( 7, 'times', 'B', 'Times-Bold'
, 'H4sIAAAAAAAAC8VSuw3CQAy9XBqUAVKxAZkgHQUNEiukySxpqOjTMQEDZIrUDICE'
|| 'RHUVVfy9c0IQJcWTfbafv+ece7u/Izs553cgAyN/APagl+wjgN3XKZ5kmTg/IXkw'
|| 'h4JqXUEfAb1I1VvwFYysk9iCffmN4+gtccSr5nlwDpuTepCZ/MH0FZibDUnO7MoR'
|| 'HXdDuvgjpzNxgevG+dF/hr3dWfoNyEZ8Taqn+7d7ozmqpGM8zdMYruFrXopVjvY2'
|| 'in9gXe+5vBf1KfX9E6TOVBsb8i5iqwQyv9+a3Gg/Cv+VoDtaQ7xdPwfNYRDji09g'
|| 'X/FvLNGmO62B9jSsoFwgfM+jf1z/SPwrkTMBOkCTBQAEAAA=' );
--
init_core_font( 8, 'times', 'BI', 'Times-BoldItalic'
, 'H4sIAAAAAAAAC8WSuw2DMBCGHegYwEuECajIAGwQ0TBFBnCfPktkAKagzgCRIqWi'
|| 'oso9fr+Qo5RB+nT2ve+wMWYzf+fgjKmOJFelPhENnS0xANJXHfwHSBtjfoI8nMMj'
|| 'tXo63xKW/Cx9ONRn3US6C/wWvYeYNr+LH2IY6cHGPkJfvsc5kX7mFjF+Vqs9iT6d'
|| 'zwEL26y1Qz62nWlvD5VSf4R9zPuon/ne+C45+XxXf5lnTGLTOZCXPx8v9Qfdjdid'
|| '5vD/f/+/pE/Ur14kG+xjTHRc84pZWsC2Hjk2+Hgbx78j4Z8W4DlL+rBnEN5Bie6L'
|| 'fsL+1u/InuYCdsdaeAs+RxftKfGdfQDlDF/kAAQAAA==' );
--
init_core_font( 9, 'courier', 'N', 'Courier', null );
for i in 0 .. 255
loop
g_fonts( 9 ).char_width_tab( i ) := 600;
end loop;
--
init_core_font( 10, 'courier', 'I', 'Courier-Oblique', null );
g_fonts( 10 ).char_width_tab := g_fonts( 9 ).char_width_tab;
--
init_core_font( 11, 'courier', 'B', 'Courier-Bold', null );
g_fonts( 11 ).char_width_tab := g_fonts( 9 ).char_width_tab;
--
init_core_font( 12, 'courier', 'BI', 'Courier-BoldOblique', null );
g_fonts( 12 ).char_width_tab := g_fonts( 9 ).char_width_tab;
--
init_core_font( 13, 'symbol', 'N', 'Symbol'
, 'H4sIAAAAAAAAC82SIU8DQRCFZ28xIE+cqcbha4tENKk/gQCJJ6AweIK9H1CHqKnp'
|| 'D2gTFBaDIcFwCQkJSTG83fem7SU0qYNLvry5nZ25t7NnZkv7c8LQrFhAP6GHZvEY'
|| 'HOB9ylxGubTfNVRc34mKpFonzBQ/gUZ6Ds7AN6i5lv1dKv8Ab1eKQYSV4hUcgZFq'
|| 'J/Sec7fQHtdTn3iqfvdrb7m3e2pZW+xDG3oIJ/Li3gfMr949rlU74DyT1/AuTX1f'
|| 'YGhOzTP8B0/RggsEX/I03vgXPrrslZjfM8/pGu40t2ZjHgud97F7337mXP/GO4h9'
|| '3WmPPaOJ/jrOs9yC52MlrtUzfWupfTX51X/L+13Vl/J/s4W2S3pSfSh5DmeXerMf'
|| '+LXhWQAEAAA=' );
--
init_core_font( 14, 'zapfdingbats', 'N', 'ZapfDingbats'
, 'H4sIAAAAAAAAC83ROy9EQRjG8TkzjdJl163SSHR0EpdsVkSi2UahFhUljUKUIgoq'
|| 'CrvJCtFQyG6EbSSERGxhC0ofQAQFxbIi8T/7PoUPIOEkvzxzzsycdy7O/fUTtToX'
|| 'bnCuvHPOV8gk4r423ovkGQ5od5OTWMeesmBz/RuZIWv4wCAY4z/xjipeqflC9qAD'
|| 'aRwxrxkJievSFzrRh36tZ1zttL6nkGX+A27xrLnttE/IBji9x7UvcIl9nPJ9AL36'
|| 'd1L9hyihoDW10L62cwhNyhntryZVExYl3kMj+zym+CrJv6M8VozPmfr5L8uwJORL'
|| 'tox7NFHG/Obj79FlwhqZ1X292xn6CbAXP/fjjv6rJYyBtUdl1vxEO6fcRB7bMmJ3'
|| 'GYZsTN0GdrDL/Ao5j1GZNr5kwqydX5z1syoiYEq5gCtlSrXi+mVbi3PfVAuhoQAE'
|| 'AAA=' );
--
end;
--
function to_char_round
( p_value number
, p_precision pls_integer := 2
)
return varchar2
is
begin
return to_char( round( p_value, p_precision ), 'TM9', 'NLS_NUMERIC_CHARACTERS=.,' );
end;
--
procedure raw2pdfdoc( p_raw blob )
is
begin
dbms_lob.append( g_pdf_doc, p_raw );
end;
--
procedure txt2pdfdoc( p_txt varchar2 )
is
begin
raw2pdfdoc( utl_raw.cast_to_raw( p_txt || c_nl ) );
end;
--
function add_object( p_txt varchar2 := null )
return number
is
t_self number(10);
begin
t_self := g_objects.count( );
g_objects( t_self ) := dbms_lob.getlength( g_pdf_doc );
--
if p_txt is null
then
txt2pdfdoc( t_self || ' 0 obj' );
else
txt2pdfdoc( t_self || ' 0 obj' || c_nl || '<<' || p_txt || '>>' || c_nl || 'endobj' );
end if;
--
return t_self;
end;
--
procedure add_object( p_txt varchar2 := null )
is
t_dummy number(10) := add_object( p_txt );
begin
null;
end;
--
function adler32( p_src in blob )
return varchar2
is
s1 pls_integer := 1;
s2 pls_integer := 0;
n pls_integer;
step_size number;
tmp varchar2(32766);
c65521 constant pls_integer := 65521;
begin
step_size := trunc( 16383 / dbms_lob.getchunksize( p_src ) ) * dbms_lob.getchunksize( p_src );
for j in 0 .. trunc( ( dbms_lob.getlength( p_src ) - 1 ) / step_size )
loop
tmp := rawtohex( dbms_lob.substr( p_src, step_size, j * step_size + 1 ) );
for i in 1 .. length( tmp ) / 2
loop
n := to_number( substr( tmp, i * 2 - 1, 2 ), 'xx' );
s1 := s1 + n;
if s1 >= c65521
then
s1 := s1 - c65521;
end if;
s2 := s2 + s1;
if s2 >= c65521
then
s2 := s2 - c65521;
end if;
end loop;
end loop;
return to_char( s2, 'fm0XXX' ) || to_char( s1, 'fm0XXX' );
end;
--
function flate_encode( p_val blob )
return blob
is
t_blob blob;
begin
t_blob := hextoraw( '789C' );
dbms_lob.copy( t_blob
, utl_compress.lz_compress( p_val )
, dbms_lob.lobmaxsize
, 3
, 11
);
dbms_lob.trim( t_blob, dbms_lob.getlength( t_blob ) - 8 );
dbms_lob.append( t_blob, hextoraw( adler32( p_val ) ) );
return t_blob;
end;
--
procedure put_stream
( p_stream blob
, p_compress boolean := true
, p_extra varchar2 := ''
, p_tag boolean := true
)
is
t_blob blob;
t_compress boolean := false;
begin
if p_compress and nvl( dbms_lob.getlength( p_stream ), 0 ) > 0
then
t_compress := true;
t_blob := flate_encode( p_stream );
else
t_blob := p_stream;
end if;
txt2pdfdoc( case when p_tag then '<<' end
|| case when t_compress then '/Filter /FlateDecode ' end
|| '/Length ' || nvl( length( t_blob ), 0 )
|| p_extra
|| '>>' );
txt2pdfdoc( 'stream' );
raw2pdfdoc( t_blob );
txt2pdfdoc( 'endstream' );
if dbms_lob.istemporary( t_blob ) = 1
then
dbms_lob.freetemporary( t_blob );
end if;
end;
--
function add_stream
( p_stream blob
, p_extra varchar2 := ''
, p_compress boolean := true
)
return number
is
t_self number(10);
begin
t_self := add_object;
put_stream( p_stream
, p_compress
, p_extra
);
txt2pdfdoc( 'endobj' );
return t_self;
end;
--
function subset_font( p_index pls_integer )
return blob
is
t_tmp blob;
t_header blob;
t_tables blob;
t_len pls_integer;
t_code pls_integer;
t_glyph pls_integer;
t_offset pls_integer;
t_factor pls_integer;
t_unicode pls_integer;
t_used_glyphs tp_pls_tab;
t_fmt varchar2(10);
t_utf16_charset varchar2(1000);
t_raw raw(32767);
t_v varchar2(32767);
t_table_records raw(32767);
begin
if g_fonts( p_index ).cid
then
t_used_glyphs := g_fonts( p_index ).used_chars;
t_used_glyphs( 0 ) := 0;
else
t_utf16_charset := substr( g_fonts( p_index ).charset, 1, instr( g_fonts( p_index ).charset, '.' ) ) || 'AL16UTF16';
t_used_glyphs( 0 ) := 0;
t_code := g_fonts( p_index ).used_chars.first;
while t_code is not null
loop
t_unicode := to_number( rawtohex( utl_raw.convert( hextoraw( to_char( t_code, 'fm0x' ) )
, t_utf16_charset
, g_fonts( p_index ).charset -- ???? database characterset ?????
)
), 'XXXXXXXX' );
if g_fonts( p_index ).flags = 4 -- a symbolic font
then
-- assume code 32, space maps to the first code from the font
t_used_glyphs( g_fonts( p_index ).code2glyph( g_fonts( p_index ).code2glyph.first + t_unicode - 32 ) ) := 0;
else
t_used_glyphs( g_fonts( p_index ).code2glyph( t_unicode ) ) := 0;
end if;
t_code := g_fonts( p_index ).used_chars.next( t_code );
end loop;
end if;
--
dbms_lob.createtemporary( t_tables, true );
t_header := utl_raw.concat( hextoraw( '00010000' )
, dbms_lob.substr( g_fonts( p_index ).fontfile2, 8, g_fonts( p_index ).ttf_offset + 4 )
);
t_offset := 12 + blob2num( g_fonts( p_index ).fontfile2, 2, g_fonts( p_index ).ttf_offset + 4 ) * 16;
t_table_records := dbms_lob.substr( g_fonts( p_index ).fontfile2
, blob2num( g_fonts( p_index ).fontfile2, 2, g_fonts( p_index ).ttf_offset + 4 ) * 16
, g_fonts( p_index ).ttf_offset + 12
);
for i in 1 .. blob2num( g_fonts( p_index ).fontfile2, 2, g_fonts( p_index ).ttf_offset + 4 )
loop
case utl_raw.cast_to_varchar2( utl_raw.substr( t_table_records, i * 16 - 15, 4 ) )
when 'post'
then
dbms_lob.append( t_header
, utl_raw.concat( utl_raw.substr( t_table_records, i * 16 - 15, 4 ) -- tag
, hextoraw( '00000000' ) -- checksum
, num2raw( t_offset + dbms_lob.getlength( t_tables ) ) -- offset
, num2raw( 32 ) -- length
)
);
dbms_lob.append( t_tables
, utl_raw.concat( hextoraw( '00030000' )
, dbms_lob.substr( g_fonts( p_index ).fontfile2
, 28
, raw2num( t_table_records, i * 16 - 7, 4 ) + 5
)
)
);
when 'loca'
then
if g_fonts( p_index ).indexToLocFormat = 0
then
t_fmt := 'fm0XXX';
else
t_fmt := 'fm0XXXXXXX';
end if;
t_raw := null;
dbms_lob.createtemporary( t_tmp, true );
t_len := 0;
for g in 0 .. g_fonts( p_index ).numGlyphs - 1
loop
t_raw := utl_raw.concat( t_raw, hextoraw( to_char( t_len, t_fmt ) ) );
if utl_raw.length( t_raw ) > 32770
then
dbms_lob.append( t_tmp, t_raw );
t_raw := null;
end if;
if t_used_glyphs.exists( g )
then
t_len := t_len + g_fonts( p_index ).loca( g + 1 ) - g_fonts( p_index ).loca( g );
end if;
end loop;
t_raw := utl_raw.concat( t_raw, hextoraw( to_char( t_len, t_fmt ) ) );
dbms_lob.append( t_tmp, t_raw );
dbms_lob.append( t_header
, utl_raw.concat( utl_raw.substr( t_table_records, i * 16 - 15, 4 ) -- tag
, hextoraw( '00000000' ) -- checksum
, num2raw( t_offset + dbms_lob.getlength( t_tables ) ) -- offset
, num2raw( dbms_lob.getlength( t_tmp ) ) -- length
)
);
dbms_lob.append( t_tables, t_tmp );
dbms_lob.freetemporary( t_tmp );
when 'glyf'
then
if g_fonts( p_index ).indexToLocFormat = 0
then
t_factor := 2;
else
t_factor := 1;
end if;
t_raw := null;
dbms_lob.createtemporary( t_tmp, true );
for g in 0 .. g_fonts( p_index ).numGlyphs - 1
loop
if ( t_used_glyphs.exists( g )
and g_fonts( p_index ).loca( g + 1 ) > g_fonts( p_index ).loca( g )
)
then
t_raw := utl_raw.concat( t_raw
, dbms_lob.substr( g_fonts( p_index ).fontfile2
, ( g_fonts( p_index ).loca( g + 1 ) - g_fonts( p_index ).loca( g ) ) * t_factor
, g_fonts( p_index ).loca( g ) * t_factor + raw2num( t_table_records, i * 16 - 7, 4 ) + 1
)
);
if utl_raw.length( t_raw ) > 32778
then
dbms_lob.append( t_tmp, t_raw );
t_raw := null;
end if;
end if;
end loop;
if utl_raw.length( t_raw ) > 0
then
dbms_lob.append( t_tmp, t_raw );
end if;
dbms_lob.append( t_header
, utl_raw.concat( utl_raw.substr( t_table_records, i * 16 - 15, 4 ) -- tag
, hextoraw( '00000000' ) -- checksum
, num2raw( t_offset + dbms_lob.getlength( t_tables ) ) -- offset
, num2raw( dbms_lob.getlength( t_tmp ) ) -- length
)
);
dbms_lob.append( t_tables, t_tmp );
dbms_lob.freetemporary( t_tmp );
else
dbms_lob.append( t_header
, utl_raw.concat( utl_raw.substr( t_table_records, i * 16 - 15, 4 ) -- tag
, utl_raw.substr( t_table_records, i * 16 - 11, 4 ) -- checksum
, num2raw( t_offset + dbms_lob.getlength( t_tables ) ) -- offset
, utl_raw.substr( t_table_records, i * 16 - 3, 4 ) -- length
)
);
dbms_lob.copy( t_tables
, g_fonts( p_index ).fontfile2
, raw2num( t_table_records, i * 16 - 3, 4 )
, dbms_lob.getlength( t_tables ) + 1
, raw2num( t_table_records, i * 16 - 7, 4 ) + 1
);
end case;
end loop;
dbms_lob.append( t_header, t_tables );
dbms_lob.freetemporary( t_tables );
return t_header;
end;
--
function add_font( p_index pls_integer )
return number
is
t_self number(10);
t_fontfile number(10);
t_font_subset blob;
t_used pls_integer;
t_used_glyphs tp_pls_tab;
t_w varchar2(32767);
t_unicode pls_integer;
t_utf16_charset varchar2(1000);
t_width number;
begin
if g_fonts( p_index ).standard
then
return add_object( '/Type/Font'
|| '/Subtype/Type1'
|| '/BaseFont/' || g_fonts( p_index ).name
|| '/Encoding/WinAnsiEncoding' -- code page 1252
);
end if;
--
if g_fonts( p_index ).cid
then
t_self := add_object;
txt2pdfdoc( '<</Type/Font/Subtype/Type0/Encoding/Identity-H'
|| '/BaseFont/' || g_fonts( p_index ).name
|| '/DescendantFonts ' || to_char( t_self + 1 ) || ' 0 R'
|| '/ToUnicode ' || to_char( t_self + 8 ) || ' 0 R'
|| '>>' );
txt2pdfdoc( 'endobj' );
add_object;
txt2pdfdoc( '[' || to_char( t_self + 2 ) || ' 0 R]' );
txt2pdfdoc( 'endobj' );
add_object( '/Type/Font/Subtype/CIDFontType2/CIDToGIDMap/Identity/DW 1000'
|| '/BaseFont/' || g_fonts( p_index ).name
|| '/CIDSystemInfo ' || to_char( t_self + 3 ) || ' 0 R'
|| '/W ' || to_char( t_self + 4 ) || ' 0 R'
|| '/FontDescriptor ' || to_char( t_self + 5 ) || ' 0 R' );
add_object( '/Ordering(Identity) /Registry(Adobe) /Supplement 0' );
--
t_utf16_charset := substr( g_fonts( p_index ).charset, 1, instr( g_fonts( p_index ).charset, '.' ) ) || 'AL16UTF16';
t_used_glyphs := g_fonts( p_index ).used_chars;
t_used_glyphs( 0 ) := 0;
t_used := t_used_glyphs.first();
while t_used is not null
loop
if g_fonts( p_index ).hmetrics.exists( t_used )
then
t_width := g_fonts( p_index ).hmetrics( t_used );
else
t_width := g_fonts( p_index ).hmetrics( g_fonts( p_index ).hmetrics.last() );
end if;
t_width := trunc( t_width * g_fonts( p_index ).unit_norm );
if t_used_glyphs.prior( t_used ) = t_used - 1
then
t_w := t_w || ' ' || t_width;
else
t_w := t_w || '] ' || t_used || ' [' || t_width;
end if;
t_used := t_used_glyphs.next( t_used );
end loop;
t_w := '[' || ltrim( t_w, '] ' ) || ']]';
add_object;
txt2pdfdoc( t_w );
txt2pdfdoc( 'endobj' );
add_object
( '/Type/FontDescriptor'
|| '/FontName/' || g_fonts( p_index ).name
|| '/Flags ' || g_fonts( p_index ).flags
|| '/FontBBox [' || g_fonts( p_index ).bb_xmin
|| ' ' || g_fonts( p_index ).bb_ymin
|| ' ' || g_fonts( p_index ).bb_xmax
|| ' ' || g_fonts( p_index ).bb_ymax
|| ']'
|| '/ItalicAngle ' || to_char_round( g_fonts( p_index ).italic_angle )
|| '/Ascent ' || g_fonts( p_index ).ascent
|| '/Descent ' || g_fonts( p_index ).descent
|| '/CapHeight ' || g_fonts( p_index ).capheight
|| '/StemV ' || g_fonts( p_index ).stemv
|| '/FontFile2 ' || to_char( t_self + 6 ) || ' 0 R' );
t_fontfile := add_stream( g_fonts( p_index ).fontfile2
, '/Length1 ' || dbms_lob.getlength( g_fonts( p_index ).fontfile2 )
, g_fonts( p_index ).compress_font
);
t_font_subset := subset_font( p_index );
t_fontfile := add_stream( t_font_subset
, '/Length1 ' || dbms_lob.getlength( t_font_subset )
, g_fonts( p_index ).compress_font
);
declare
t_g2c tp_pls_tab;
t_code pls_integer;
t_c_start pls_integer;
t_map varchar2(32767);
t_cmap varchar2(32767);
t_cor pls_integer;
t_cnt pls_integer;
begin
t_code := g_fonts( p_index ).code2glyph.first;
if g_fonts( p_index ).flags = 4 -- a symbolic font
then
-- assume code 32, space maps to the first code from the font
t_cor := t_code - 32;
else
t_cor := 0;
end if;
while t_code is not null
loop
t_g2c( g_fonts( p_index ).code2glyph( t_code ) ) := t_code - t_cor;
t_code := g_fonts( p_index ).code2glyph.next( t_code );
end loop;
t_cnt := 0;
t_used_glyphs := g_fonts( p_index ).used_chars;
t_used := t_used_glyphs.first();
while t_used is not null
loop
t_map := t_map || '<' || to_char( t_used, 'FM0XXX' )
|| '> <' || to_char( t_g2c( t_used ), 'FM0XXX' )
|| '>' || chr( 10 );
if t_cnt = 99
then
t_cnt := 0;
t_cmap := t_cmap || chr( 10 ) || '100 beginbfchar' || chr( 10 ) || t_map || 'endbfchar';
t_map := '';
else
t_cnt := t_cnt + 1;
end if;
t_used := t_used_glyphs.next( t_used );
end loop;
if t_cnt > 0
then
t_cmap := t_cnt || ' beginbfchar' || chr( 10 ) || t_map || 'endbfchar';
end if;
t_fontfile := add_stream( utl_raw.cast_to_raw(
'/CIDInit /ProcSet findresource begin 12 dict begin
begincmap
/CIDSystemInfo
<< /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def
/CMapName /Adobe-Identity-UCS def /CMapType 2 def
1 begincodespacerange
<0000> <FFFF>
endcodespacerange
' || t_cmap || '
endcmap
CMapName currentdict /CMap defineresource pop
end
end' ) );
end;
return t_self;
end if;
--
g_fonts( p_index ).first_char := g_fonts( p_index ).used_chars.first();
g_fonts( p_index ).last_char := g_fonts( p_index ).used_chars.last();
t_self := add_object;
txt2pdfdoc( '<</Type /Font '
|| '/Subtype /' || g_fonts( p_index ).subtype
|| ' /BaseFont /' || g_fonts( p_index ).name
|| ' /FirstChar ' || g_fonts( p_index ).first_char
|| ' /LastChar ' || g_fonts( p_index ).last_char
|| ' /Widths ' || to_char( t_self + 1 ) || ' 0 R'
|| ' /FontDescriptor ' || to_char( t_self + 2 ) || ' 0 R'
|| ' /Encoding ' || to_char( t_self + 3 ) || ' 0 R'
|| ' >>' );
txt2pdfdoc( 'endobj' );
add_object;
txt2pdfdoc( '[' );
begin
for i in g_fonts( p_index ).first_char .. g_fonts( p_index ).last_char
loop
txt2pdfdoc( g_fonts( p_index ).char_width_tab( i ) );
end loop;
exception
when others
then
dbms_output.put_line( '**** ' || g_fonts( p_index ).name );
end;
txt2pdfdoc( ']' );
txt2pdfdoc( 'endobj' );
add_object
( '/Type /FontDescriptor'
|| ' /FontName /' || g_fonts( p_index ).name
|| ' /Flags ' || g_fonts( p_index ).flags
|| ' /FontBBox [' || g_fonts( p_index ).bb_xmin
|| ' ' || g_fonts( p_index ).bb_ymin
|| ' ' || g_fonts( p_index ).bb_xmax
|| ' ' || g_fonts( p_index ).bb_ymax
|| ']'
|| ' /ItalicAngle ' || to_char_round( g_fonts( p_index ).italic_angle )
|| ' /Ascent ' || g_fonts( p_index ).ascent
|| ' /Descent ' || g_fonts( p_index ).descent
|| ' /CapHeight ' || g_fonts( p_index ).capheight
|| ' /StemV ' || g_fonts( p_index ).stemv
|| case
when g_fonts( p_index ).fontfile2 is not null
then ' /FontFile2 ' || to_char( t_self + 4 ) || ' 0 R'
end );
add_object( '/Type /Encoding /BaseEncoding /WinAnsiEncoding '
|| g_fonts( p_index ).diff
|| ' ' );
if g_fonts( p_index ).fontfile2 is not null
then
t_font_subset := subset_font( p_index );
t_fontfile :=
add_stream( t_font_subset
, '/Length1 ' || dbms_lob.getlength( t_font_subset )
, g_fonts( p_index ).compress_font
);
end if;
return t_self;
end;
--
procedure add_image( p_img tp_img )
is
t_pallet number(10);
begin
if p_img.color_tab is not null
then
t_pallet := add_stream( p_img.color_tab );
else
t_pallet := add_object; -- add an empty object
txt2pdfdoc( 'endobj' );
end if;
add_object;
txt2pdfdoc( '<</Type /XObject /Subtype /Image'
|| ' /Width ' || to_char( p_img.width )
|| ' /Height ' || to_char( p_img.height )
|| ' /BitsPerComponent ' || to_char( p_img.color_res )
);
--
if p_img.transparancy_index is not null
then
txt2pdfdoc( '/Mask [' || p_img.transparancy_index || ' ' || p_img.transparancy_index || ']' );
end if;
if p_img.color_tab is null
then
if p_img.greyscale
then
txt2pdfdoc( '/ColorSpace /DeviceGray' );
else
txt2pdfdoc( '/ColorSpace /DeviceRGB' );
end if;
else
txt2pdfdoc( '/ColorSpace [/Indexed /DeviceRGB '
|| to_char( utl_raw.length( p_img.color_tab ) / 3 - 1 )
|| ' ' || to_char( t_pallet ) || ' 0 R]'
);
end if;
--
if p_img.type = 'jpg'
then
put_stream( p_img.pixels, false, '/Filter /DCTDecode', false );
elsif p_img.type = 'png'
then
put_stream( p_img.pixels, false
, ' /Filter /FlateDecode /DecodeParms <</Predictor 15 '
|| '/Colors ' || p_img.nr_colors
|| '/BitsPerComponent ' || p_img.color_res
|| ' /Columns ' || p_img.width
|| ' >> '
, false );
else
put_stream( p_img.pixels, p_tag => false );
end if;
txt2pdfdoc( 'endobj' );
end;
--
function add_resources
return number
is
t_ind pls_integer;
t_self number(10);
t_fonts tp_objects_tab;
begin
--
t_ind := g_used_fonts.first;
while t_ind is not null
loop
t_fonts( t_ind ) := add_font( t_ind );
t_ind := g_used_fonts.next( t_ind );
end loop;
--
t_self := add_object;
txt2pdfdoc( '<</ProcSet [/PDF /Text]' );
--
if g_used_fonts.count() > 0
then
txt2pdfdoc( '/Font <<' );
t_ind := g_used_fonts.first;
while t_ind is not null
loop
txt2pdfdoc( '/F'|| to_char( t_ind ) || ' '
|| to_char( t_fonts( t_ind ) ) || ' 0 R'
);
t_ind := g_used_fonts.next( t_ind );
end loop;
txt2pdfdoc( '>>' );
end if;
--
if g_images.count( ) > 0
then
txt2pdfdoc( '/XObject <<' );
for i in g_images.first .. g_images.last
loop
txt2pdfdoc( '/I' || to_char( i ) || ' ' || to_char( t_self + 2 * i ) || ' 0 R' );
end loop;
txt2pdfdoc( '>>' );
end if;
--
txt2pdfdoc( '>>' );
txt2pdfdoc( 'endobj' );
--
if g_images.count( ) > 0
then
for i in g_images.first .. g_images.last
loop
add_image( g_images( i ) );
end loop;
end if;
return t_self;
end;
--
procedure add_page
( p_page_ind pls_integer
, p_parent number
, p_resources number
)
is
t_content number(10);
begin
t_content := add_stream( g_pages( p_page_ind ) );
add_object;
txt2pdfdoc( '<< /Type /Page' );
txt2pdfdoc( '/Parent ' || to_char( p_parent ) || ' 0 R' );
txt2pdfdoc( '/Contents ' || to_char( t_content ) || ' 0 R' );
txt2pdfdoc( '/Resources ' || to_char( p_resources ) || ' 0 R' );
txt2pdfdoc( '>>' );
txt2pdfdoc( 'endobj' );
end;
--
function add_pages
return number
is
t_self number(10);
t_resources number(10);
begin
t_resources := add_resources;
t_self := add_object;
txt2pdfdoc( '<</Type/Pages/Kids [' );
--
for i in g_pages.first .. g_pages.last
loop
txt2pdfdoc( to_char( t_self + i * 2 + 2 ) || ' 0 R' );
end loop;
--
txt2pdfdoc( ']' );
txt2pdfdoc( '/Count ' || g_pages.count() );
txt2pdfdoc( '/MediaBox [0 0 '
|| to_char_round( g_settings.page_width
, 0
)
|| ' '
|| to_char_round( g_settings.page_height
, 0
)
|| ']' );
txt2pdfdoc( '>>' );
txt2pdfdoc( 'endobj' );
--
if g_pages.count() > 0
then
for i in g_pages.first .. g_pages.last
loop
add_page( i, t_self, t_resources );
end loop;
end if;
--
return t_self;
end;
--
function add_catalogue
return number
is
begin
return add_object( '/Type/Catalog'
|| '/Pages ' || to_char( add_pages ) || ' 0 R'
|| '/OpenAction [0 /XYZ null null 0.77]'
);
end;
--
function add_info
return number
is
t_banner varchar2( 1000 );
begin
begin
select 'running on '
|| replace( replace( replace( substr( banner
, 1
, 950
)
, '\'
, '\\'
)
, '('
, '\('
)
, ')'
, '\)'
)
into t_banner
from v$version
where instr( upper( banner )
, 'DATABASE'
) > 0;
t_banner := '/Producer (' || t_banner || ')';
exception
when others
then
null;
end;
--
return add_object( to_char( sysdate, '"/CreationDate (D:"YYYYMMDDhh24miss")"' )
|| '/Creator (AS-PDF 0.3.0 by Anton Scheffer)'
|| t_banner
|| '/Title <FEFF' || utl_i18n.string_to_raw( g_info.title, 'AL16UTF16' ) || '>'
|| '/Author <FEFF' || utl_i18n.string_to_raw( g_info.author, 'AL16UTF16' ) || '>'
|| '/Subject <FEFF' || utl_i18n.string_to_raw( g_info.subject, 'AL16UTF16' ) || '>'
|| '/Keywords <FEFF' || utl_i18n.string_to_raw( g_info.keywords, 'AL16UTF16' ) || '>'
);
end;
--
procedure finish_pdf
is
t_xref number;
t_info number(10);
t_catalogue number(10);
begin
if g_pages.count = 0
then
new_page;
end if;
if g_page_prcs.count > 0
then
for i in g_pages.first .. g_pages.last
loop
g_page_nr := i;
for p in g_page_prcs.first .. g_page_prcs.last
loop
begin
execute immediate replace( replace( g_page_prcs( p ), '#PAGE_NR#', i + 1 ), '"PAGE_COUNT#', g_pages.count );
exception
when others then null;
end;
end loop;
end loop;
end if;
dbms_lob.createtemporary( g_pdf_doc, true );
txt2pdfdoc( '%PDF-1.3' );
raw2pdfdoc( hextoraw( '25E2E3CFD30D0A' ) ); -- add a hex comment
t_info := add_info;
t_catalogue := add_catalogue;
t_xref := dbms_lob.getlength( g_pdf_doc );
txt2pdfdoc( 'xref' );
txt2pdfdoc( '0 ' || to_char( g_objects.count() ) );
txt2pdfdoc( '0000000000 65535 f ' );
for i in 1 .. g_objects.count( ) - 1
loop
txt2pdfdoc( to_char( g_objects( i ), 'fm0000000000' ) || ' 00000 n' );
-- this line should be exactly 20 bytes, including EOL
end loop;
txt2pdfdoc( 'trailer' );
txt2pdfdoc( '<< /Root ' || to_char( t_catalogue ) || ' 0 R' );
txt2pdfdoc( '/Info ' || to_char( t_info ) || ' 0 R' );
txt2pdfdoc( '/Size ' || to_char( g_objects.count() ) );
txt2pdfdoc( '>>' );
txt2pdfdoc( 'startxref' );
txt2pdfdoc( to_char( t_xref ) );
txt2pdfdoc( '%%EOF' );
--
g_objects.delete;
for i in g_pages.first .. g_pages.last
loop
dbms_lob.freetemporary( g_pages( i ) );
end loop;
g_objects.delete;
g_pages.delete;
g_fonts.delete;
g_used_fonts.delete;
g_page_prcs.delete;
if g_images.count() > 0
then
for i in g_images.first .. g_images.last
loop
if dbms_lob.istemporary( g_images( i ).pixels ) = 1
then
dbms_lob.freetemporary( g_images( i ).pixels );
end if;
end loop;
g_images.delete;
end if;
end;
--
function conv2uu( p_value number, p_unit varchar2 )
return number
is
c_inch constant number := 25.40025;
begin
return round( case lower( p_unit )
when 'mm' then p_value * 72 / c_inch
when 'cm' then p_value * 720 / c_inch
when 'pt' then p_value -- also point
when 'point' then p_value
when 'inch' then p_value * 72
when 'in' then p_value * 72 -- also inch
when 'pica' then p_value * 12
when 'p' then p_value * 12 -- also pica
when 'pc' then p_value * 12 -- also pica
when 'em' then p_value * 12 -- also pica
when 'px' then p_value -- pixel voorlopig op point zetten
when 'px' then p_value * 0.8 -- pixel
else null
end
, 3
);
end;
--
procedure set_page_size
( p_width number
, p_height number
, p_unit varchar2 := 'cm'
)
is
begin
g_settings.page_width := conv2uu( p_width, p_unit );
g_settings.page_height := conv2uu( p_height, p_unit );
end;
--
procedure set_page_format( p_format varchar2 := 'A4' )
is
begin
case upper( p_format )
when 'A3'
then
set_page_size( 420, 297, 'mm' );
when 'A4'
then
set_page_size( 297, 210, 'mm' );
when 'A5'
then
set_page_size( 210, 148, 'mm' );
when 'A6'
then
set_page_size( 148, 105, 'mm' );
when 'LEGAL'
then
set_page_size( 14, 8.5, 'in' );
when 'LETTER'
then
set_page_size( 11, 8.5, 'in' );
when 'QUARTO'
then
set_page_size( 11, 9, 'in' );
when 'EXECUTIVE'
then
set_page_size( 10.5, 7.25, 'in' );
else
null;
end case;
end;
--
procedure set_page_orientation( p_orientation varchar2 := 'PORTRAIT' )
is
t_tmp number;
begin
if ( ( upper( p_orientation ) in ( 'L', 'LANDSCAPE' )
and g_settings.page_height > g_settings.page_width
)
or ( upper( p_orientation ) in( 'P', 'PORTRAIT' )
and g_settings.page_height < g_settings.page_width
)
)
then
t_tmp := g_settings.page_width;
g_settings.page_width := g_settings.page_height;
g_settings.page_height := t_tmp;
end if;
end;
--
procedure set_margins
( p_top number := null
, p_left number := null
, p_bottom number := null
, p_right number := null
, p_unit varchar2 := 'cm'
)
is
t_tmp number;
begin
t_tmp := nvl( conv2uu( p_top, p_unit ), -1 );
if t_tmp < 0 or t_tmp > g_settings.page_height
then
t_tmp := conv2uu( 3, 'cm' );
end if;
g_settings.margin_top := t_tmp;
t_tmp := nvl( conv2uu( p_bottom, p_unit ), -1 );
if t_tmp < 0 or t_tmp > g_settings.page_height
then
t_tmp := conv2uu( 4, 'cm' );
end if;
g_settings.margin_bottom := t_tmp;
t_tmp := nvl( conv2uu( p_left, p_unit ), -1 );
if t_tmp < 0 or t_tmp > g_settings.page_width
then
t_tmp := conv2uu( 1, 'cm' );
end if;
g_settings.margin_left := t_tmp;
t_tmp := nvl( conv2uu( p_right, p_unit ), -1 );
if t_tmp < 0 or t_tmp > g_settings.page_width
then
t_tmp := conv2uu( 1, 'cm' );
end if;
g_settings.margin_right := t_tmp;
--
if g_settings.margin_top + g_settings.margin_bottom + conv2uu( 1, 'cm' )> g_settings.page_height
then
g_settings.margin_top := 0;
g_settings.margin_bottom := 0;
end if;
if g_settings.margin_left + g_settings.margin_right + conv2uu( 1, 'cm' )> g_settings.page_width
then
g_settings.margin_left := 0;
g_settings.margin_right := 0;
end if;
end;
--
procedure set_info
( p_title varchar2 := null
, p_author varchar2 := null
, p_subject varchar2 := null
, p_keywords varchar2 := null
)
is
begin
g_info.title := substr( p_title, 1, 1024 );
g_info.author := substr( p_author, 1, 1024 );
g_info.subject := substr( p_subject, 1, 1024 );
g_info.keywords := substr( p_keywords, 1, 16383 );
end;
--
procedure init
is
begin
g_objects.delete;
g_pages.delete;
g_fonts.delete;
g_used_fonts.delete;
g_page_prcs.delete;
g_images.delete;
g_settings := null;
g_current_font := null;
g_x := null;
g_y := null;
g_info := null;
g_page_nr := null;
g_objects( 0 ) := 0;
init_core_fonts;
set_page_format;
set_page_orientation;
set_margins;
end;
--
function get_pdf
return blob
is
begin
finish_pdf;
return g_pdf_doc;
end;
--
procedure save_pdf
( p_dir varchar2 := 'MY_DIR'
, p_filename varchar2 := 'my.pdf'
, p_freeblob boolean := true
)
is
t_fh utl_file.file_type;
t_len pls_integer := 32767;
begin
finish_pdf;
t_fh := utl_file.fopen( p_dir, p_filename, 'wb' );
for i in 0 .. trunc( ( dbms_lob.getlength( g_pdf_doc ) - 1 ) / t_len )
loop
utl_file.put_raw( t_fh
, dbms_lob.substr( g_pdf_doc
, t_len
, i * t_len + 1
)
);
end loop;
utl_file.fclose( t_fh );
if p_freeblob
then
dbms_lob.freetemporary( g_pdf_doc );
end if;
end;
--
procedure raw2page( p_txt blob )
is
begin
if g_pages.count() = 0
then
new_page;
end if;
dbms_lob.append( g_pages( coalesce( g_page_nr, g_pages.count( ) - 1 ) )
, utl_raw.concat( p_txt, hextoraw( '0D0A' ) )
);
end;
--
procedure txt2page( p_txt varchar2 )
is
begin
raw2page( utl_raw.cast_to_raw( p_txt ) );
end;
--
procedure output_font_to_doc( p_output_to_doc boolean )
is
begin
if p_output_to_doc
then
txt2page( 'BT /F' || g_current_font || ' '
|| to_char_round( g_fonts( g_current_font ).fontsize ) || ' Tf ET'
);
end if;
end;
--
procedure set_font
( p_index pls_integer
, p_fontsize_pt number
, p_output_to_doc boolean := true
)
is
begin
if p_index is not null
then
g_used_fonts( p_index ) := 0;
g_current_font := p_index;
g_fonts( p_index ).fontsize := p_fontsize_pt;
output_font_to_doc( p_output_to_doc );
end if;
end;
--
function set_font
( p_fontname varchar2
, p_fontsize_pt number
, p_output_to_doc boolean := true
)
return pls_integer
is
t_fontname varchar2(100);
begin
if p_fontname is null
then
if ( g_current_font is not null
and p_fontsize_pt != g_fonts( g_current_font ).fontsize
)
then
g_fonts( g_current_font ).fontsize := p_fontsize_pt;
output_font_to_doc( p_output_to_doc );
end if;
return g_current_font;
end if;
--
t_fontname := lower( p_fontname );
for i in g_fonts.first .. g_fonts.last
loop
if lower( g_fonts( i ).fontname ) = t_fontname
then
exit when g_current_font = i and g_fonts( i ).fontsize = p_fontsize_pt and g_page_nr is null;
g_fonts( i ).fontsize := coalesce( p_fontsize_pt
, g_fonts( nvl( g_current_font, i ) ).fontsize
, 12
);
g_current_font := i;
g_used_fonts( i ) := 0;
output_font_to_doc( p_output_to_doc );
return g_current_font;
end if;
end loop;
return null;
end;
--
procedure set_font
( p_fontname varchar2
, p_fontsize_pt number
, p_output_to_doc boolean := true
)
is
t_dummy pls_integer;
begin
t_dummy := set_font( p_fontname, p_fontsize_pt, p_output_to_doc );
end;
--
function set_font
( p_family varchar2
, p_style varchar2 := 'N'
, p_fontsize_pt number := null
, p_output_to_doc boolean := true
)
return pls_integer
is
t_family varchar2(100);
t_style varchar2(100);
begin
if p_family is null and g_current_font is null
then
return null;
end if;
if p_family is null and p_style is null and p_fontsize_pt is null
then
return null;
end if;
t_family := coalesce( lower( p_family )
, g_fonts( g_current_font ).family
);
t_style := upper( p_style );
t_style := case t_style
when 'NORMAL' then 'N'
when 'REGULAR' then 'N'
when 'BOLD' then 'B'
when 'ITALIC' then 'I'
when 'OBLIQUE' then 'I'
else t_style
end;
t_style := coalesce( t_style
, case when g_current_font is null then 'N' else g_fonts( g_current_font ).style end
);
--
for i in g_fonts.first .. g_fonts.last
loop
if ( g_fonts( i ).family = t_family
and g_fonts( i ).style = t_style
)
then
return set_font( g_fonts( i ).fontname, p_fontsize_pt, p_output_to_doc );
end if;
end loop;
return null;
end;
--
procedure set_font
( p_family varchar2
, p_style varchar2 := 'N'
, p_fontsize_pt number := null
, p_output_to_doc boolean := true
)
is
t_dummy pls_integer;
begin
t_dummy := set_font( p_family, p_style, p_fontsize_pt, p_output_to_doc );
end;
--
procedure new_page
is
begin
g_pages( g_pages.count() ) := null;
dbms_lob.createtemporary( g_pages( g_pages.count() - 1 ), true );
if g_current_font is not null and g_pages.count() > 0
then
txt2page( 'BT /F' || g_current_font || ' '
|| to_char_round( g_fonts( g_current_font ).fontsize )
|| ' Tf ET'
);
end if;
g_x := null;
g_y := null;
end;
--
function pdf_string( p_txt in blob )
return blob
is
t_rv blob;
t_ind integer;
type tp_tab_raw is table of raw(1);
tab_raw tp_tab_raw
:= tp_tab_raw( utl_raw.cast_to_raw( '\' )
, utl_raw.cast_to_raw( '(' )
, utl_raw.cast_to_raw( ')' )
);
begin
t_rv := p_txt;
for i in tab_raw.first .. tab_raw.last
loop
t_ind := -1;
loop
t_ind := dbms_lob.instr( t_rv
, tab_raw( i )
, t_ind + 2
);
exit when t_ind <= 0;
dbms_lob.copy( t_rv
, t_rv
, dbms_lob.lobmaxsize
, t_ind + 1
, t_ind
);
dbms_lob.copy( t_rv
, utl_raw.cast_to_raw( '\' )
, 1
, t_ind
, 1
);
end loop;
end loop;
return t_rv;
end;
--
function txt2raw( p_txt varchar2 )
return raw
is
t_rv raw(32767);
t_unicode pls_integer;
begin
if g_current_font is null
then
set_font( 'helvetica' );
end if;
if g_fonts( g_current_font ).cid
then
for i in 1 .. length( p_txt )
loop
t_unicode := utl_raw.cast_to_binary_integer( utl_raw.convert( utl_raw.cast_to_raw( substr( p_txt, i, 1 ) )
, 'AMERICAN_AMERICA.AL16UTF16'
, sys_context( 'userenv', 'LANGUAGE' ) -- ???? font characterset ?????
)
);
if g_fonts( g_current_font ).flags = 4 -- a symbolic font
then
-- assume code 32, space maps to the first code from the font
t_unicode := g_fonts( g_current_font ).code2glyph.first + t_unicode - 32;
end if;
if g_fonts( g_current_font ).code2glyph.exists( t_unicode )
then
g_fonts( g_current_font ).used_chars( g_fonts( g_current_font ).code2glyph( t_unicode ) ) := 0;
t_rv := utl_raw.concat( t_rv
, utl_raw.cast_to_raw( to_char( g_fonts( g_current_font ).code2glyph( t_unicode ), 'FM0XXX' ) )
);
else
t_rv := utl_raw.concat( t_rv, utl_raw.cast_to_raw( '0000' ) );
end if;
end loop;
t_rv := utl_raw.concat( utl_raw.cast_to_raw( '<' )
, t_rv
, utl_raw.cast_to_raw( '>' )
);
else
t_rv := utl_raw.convert( utl_raw.cast_to_raw( p_txt )
, g_fonts( g_current_font ).charset
, sys_context( 'userenv', 'LANGUAGE' )
);
for i in 1 .. utl_raw.length( t_rv )
loop
g_fonts( g_current_font ).used_chars( raw2num( t_rv, i, 1 ) ) := 0;
end loop;
t_rv := utl_raw.concat( utl_raw.cast_to_raw( '(' )
, pdf_string( t_rv )
, utl_raw.cast_to_raw( ')' )
);
end if;
return t_rv;
end;
--
procedure put_raw( p_x number, p_y number, p_txt raw, p_degrees_rotation number := null )
is
c_pi constant number := 3.14159265358979323846264338327950288419716939937510;
t_tmp varchar2(32767);
t_sin number;
t_cos number;
begin
t_tmp := to_char_round( p_x ) || ' ' || to_char_round( p_y );
if p_degrees_rotation is null
then
t_tmp := t_tmp || ' Td ';
else
t_sin := sin( p_degrees_rotation / 180 * c_pi );
t_cos := cos( p_degrees_rotation / 180 * c_pi );
t_tmp := to_char_round( t_cos, 5 ) || ' ' || t_tmp;
t_tmp := to_char_round( - t_sin, 5 ) || ' ' || t_tmp;
t_tmp := to_char_round( t_sin, 5 ) || ' ' || t_tmp;
t_tmp := to_char_round( t_cos, 5 ) || ' ' || t_tmp;
t_tmp := t_tmp || ' Tm ';
end if;
raw2page( utl_raw.concat( utl_raw.cast_to_raw( 'BT ' || t_tmp )
, p_txt
, utl_raw.cast_to_raw( ' Tj ET' )
)
);
end;
--
procedure put_txt( p_x number, p_y number, p_txt varchar2, p_degrees_rotation number := null )
is
begin
if p_txt is not null
then
put_raw( p_x, p_y, txt2raw( p_txt ), p_degrees_rotation );
end if;
end;
--
function str_len( p_txt in varchar2 )
return number
is
t_width number;
t_char pls_integer;
t_rtxt raw(32767);
t_tmp number;
t_font tp_font;
begin
if p_txt is null
then
return 0;
end if;
--
t_width := 0;
t_font := g_fonts( g_current_font );
if t_font.cid
then
t_rtxt := utl_raw.convert( utl_raw.cast_to_raw( p_txt )
, 'AMERICAN_AMERICA.AL16UTF16' -- 16 bit font => 2 bytes per char
, sys_context( 'userenv', 'LANGUAGE' ) -- ???? font characterset ?????
);
for i in 1 .. utl_raw.length( t_rtxt ) / 2
loop
t_char := to_number( utl_raw.substr( t_rtxt, i * 2 - 1, 2 ), 'xxxx' );
if t_font.flags = 4 -- a symbolic font
then
-- assume code 32, space maps to the first code from the font
t_char := t_font.code2glyph.first + t_char - 32;
end if;
if ( t_font.code2glyph.exists( t_char )
and t_font.hmetrics.exists( t_font.code2glyph( t_char ) )
)
then
t_tmp := t_font.hmetrics( t_font.code2glyph( t_char ) );
else
t_tmp := t_font.hmetrics( t_font.hmetrics.last() );
end if;
t_width := t_width + t_tmp;
end loop;
t_width := t_width * t_font.unit_norm;
t_width := t_width * t_font.fontsize / 1000;
else
t_rtxt := utl_raw.convert( utl_raw.cast_to_raw( p_txt )
, t_font.charset -- should be an 8 bit font
, sys_context( 'userenv', 'LANGUAGE' )
);
for i in 1 .. utl_raw.length( t_rtxt )
loop
t_char := to_number( utl_raw.substr( t_rtxt, i, 1 ), 'xx' );
t_width := t_width + t_font.char_width_tab( t_char );
end loop;
t_width := t_width * t_font.fontsize / 1000;
end if;
return t_width;
end;
--
procedure write
( p_txt in varchar2
, p_x in number := null
, p_y in number := null
, p_line_height in number := null
, p_start in number := null -- left side of the available text box
, p_width in number := null -- width of the available text box
, p_alignment in varchar2 := null
)
is
t_line_height number;
t_x number;
t_y number;
t_start number;
t_width number;
t_len number;
t_cnt pls_integer;
t_ind pls_integer;
t_alignment varchar2(100);
begin
if p_txt is null
then
return;
end if;
--
if g_current_font is null
then
set_font( 'helvetica' );
end if;
--
t_line_height := nvl( p_line_height, g_fonts( g_current_font ).fontsize );
if ( t_line_height < g_fonts( g_current_font ).fontsize
or t_line_height > ( g_settings.page_height - g_settings.margin_top - t_line_height ) / 4
)
then
t_line_height := g_fonts( g_current_font ).fontsize;
end if;
t_start := nvl( p_start, g_settings.margin_left );
if ( t_start < g_settings.margin_left
or t_start > g_settings.page_width - g_settings.margin_right - g_settings.margin_left
)
then
t_start := g_settings.margin_left;
end if;
t_width := nvl( p_width
, g_settings.page_width - g_settings.margin_right - g_settings.margin_left
);
if ( t_width < str_len( ' ' )
or t_width > g_settings.page_width - g_settings.margin_right - g_settings.margin_left
)
then
t_width := g_settings.page_width - g_settings.margin_right - g_settings.margin_left;
end if;
t_x := coalesce( p_x, g_x, g_settings.margin_left );
t_y := coalesce( p_y
, g_y
, g_settings.page_height - g_settings.margin_top - t_line_height
);
if t_y < 0
then
t_y := coalesce( g_y
, g_settings.page_height - g_settings.margin_top - t_line_height
) - t_line_height;
end if;
if t_x > t_start + t_width
then
t_x := t_start;
t_y := t_y - t_line_height;
elsif t_x < t_start
then
t_x := t_start;
end if;
if t_y < g_settings.margin_bottom
then
new_page;
t_x := t_start;
t_y := g_settings.page_height - g_settings.margin_top - t_line_height;
end if;
--
t_ind := instr( p_txt, chr(10) );
if t_ind > 0
then
g_x := t_x;
g_y := t_y;
write( rtrim( substr( p_txt, 1, t_ind - 1 ), chr(13) ), t_x, t_y, t_line_height, t_start, t_width, p_alignment );
t_y := g_y - t_line_height;
if t_y < g_settings.margin_bottom
then
new_page;
t_y := g_settings.page_height - g_settings.margin_top - t_line_height;
end if;
g_x := t_start;
g_y := t_y;
write( substr( p_txt, t_ind + 1 ), t_start, t_y, t_line_height, t_start, t_width, p_alignment );
return;
end if;
--
t_len := str_len( p_txt );
if t_len <= t_width - t_x + t_start
then
t_alignment := lower( substr( p_alignment, 1, 100 ) );
if instr( t_alignment, 'right' ) > 0 or instr( t_alignment, 'end' ) > 0
then
t_x := t_start + t_width - t_len;
elsif instr( t_alignment, 'center' ) > 0
then
t_x := ( t_width + t_x + t_start - t_len ) / 2;
end if;
put_txt( t_x, t_y, p_txt );
g_x := t_x + t_len + str_len( ' ' );
g_y := t_y;
return;
end if;
--
t_cnt := 0;
while ( instr( p_txt, ' ', 1, t_cnt + 1 ) > 0
and str_len( substr( p_txt, 1, instr( p_txt, ' ', 1, t_cnt + 1 ) - 1 ) ) <= t_width - t_x + t_start
)
loop
t_cnt := t_cnt + 1;
end loop;
if t_cnt > 0
then
t_ind := instr( p_txt, ' ', 1, t_cnt );
write( substr( p_txt, 1, t_ind - 1 ), t_x, t_y, t_line_height, t_start, t_width, p_alignment );
t_y := t_y - t_line_height;
if t_y < g_settings.margin_bottom
then
new_page;
t_y := g_settings.page_height - g_settings.margin_top - t_line_height;
end if;
write( substr( p_txt, t_ind + 1 ), t_start, t_y, t_line_height, t_start, t_width, p_alignment );
return;
end if;
--
if t_x > t_start and t_len < t_width
then
t_y := t_y - t_line_height;
if t_y < g_settings.margin_bottom
then
new_page;
t_y := g_settings.page_height - g_settings.margin_top - t_line_height;
end if;
write( p_txt, t_start, t_y, t_line_height, t_start, t_width, p_alignment );
else
if length( p_txt ) = 1
then
if t_x > t_start
then
t_y := t_y - t_line_height;
if t_y < g_settings.margin_bottom
then
new_page;
t_y := g_settings.page_height - g_settings.margin_top - t_line_height;
end if;
end if;
write( p_txt, t_x, t_y, t_line_height, t_start, t_len );
else
t_ind := 2; -- start with 2 to make sure we get amaller string!
while str_len( substr( p_txt, 1, t_ind ) ) <= t_width - t_x + t_start
loop
t_ind := t_ind + 1;
end loop;
write( substr( p_txt, 1, t_ind - 1 ), t_x, t_y, t_line_height, t_start, t_width, p_alignment );
t_y := t_y - t_line_height;
if t_y < g_settings.margin_bottom
then
new_page;
t_y := g_settings.page_height - g_settings.margin_top - t_line_height;
end if;
write( substr( p_txt, t_ind ), t_start, t_y, t_line_height, t_start, t_width, p_alignment );
end if;
end if;
end;
--
function load_ttf_font
( p_font blob
, p_encoding varchar2 := 'WINDOWS-1252'
, p_embed boolean := false
, p_compress boolean := true
, p_offset number := 1
)
return pls_integer
is
this_font tp_font;
type tp_font_table is record
( offset pls_integer
, length pls_integer
);
type tp_tables is table of tp_font_table index by varchar2(4);
t_tables tp_tables;
t_tag varchar2(4);
t_blob blob;
t_offset pls_integer;
nr_hmetrics pls_integer;
subtype tp_glyphname is varchar2(250);
type tp_glyphnames is table of tp_glyphname index by pls_integer;
t_glyphnames tp_glyphnames;
t_glyph2name tp_pls_tab;
t_font_ind pls_integer;
begin
if dbms_lob.substr( p_font, 4, p_offset ) != hextoraw( '00010000' ) -- OpenType Font
then
return null;
end if;
for i in 1 .. blob2num( p_font, 2, p_offset + 4 )
loop
t_tag :=
utl_raw.cast_to_varchar2( dbms_lob.substr( p_font, 4, p_offset - 4 + i * 16 ) );
t_tables( t_tag ).offset := blob2num( p_font, 4, p_offset + 4 + i * 16 ) + 1;
t_tables( t_tag ).length := blob2num( p_font, 4, p_offset + 8 + i * 16 );
end loop;
--
if ( not t_tables.exists( 'cmap' )
or not t_tables.exists( 'glyf' )
or not t_tables.exists( 'head' )
or not t_tables.exists( 'hhea' )
or not t_tables.exists( 'hmtx' )
or not t_tables.exists( 'loca' )
or not t_tables.exists( 'maxp' )
or not t_tables.exists( 'name' )
or not t_tables.exists( 'post' )
)
then
return null;
end if;
--
dbms_lob.createtemporary( t_blob, true );
dbms_lob.copy( t_blob, p_font, t_tables( 'maxp' ).length, 1, t_tables( 'maxp' ).offset );
this_font.numGlyphs := blob2num( t_blob, 2, 5 );
--
dbms_lob.copy( t_blob, p_font, t_tables( 'cmap' ).length, 1, t_tables( 'cmap' ).offset );
for i in 0 .. blob2num( t_blob, 2, 3 ) - 1
loop
if ( dbms_lob.substr( t_blob, 2, 5 + i * 8 ) = hextoraw( '0003' ) -- Windows
and dbms_lob.substr( t_blob, 2, 5 + i * 8 + 2 )
in ( hextoraw( '0000' ) -- Symbol
, hextoraw( '0001' ) -- Unicode BMP (UCS-2)
)
)
then
if dbms_lob.substr( t_blob, 2, 5 + i * 8 + 2 ) = hextoraw( '0000' ) -- Symbol
then
this_font.flags := 4; -- symbolic
else
this_font.flags := 32; -- non-symbolic
end if;
t_offset := blob2num( t_blob, 4, 5 + i * 8 + 4 ) + 1;
if dbms_lob.substr( t_blob, 2, t_offset ) != hextoraw( '0004' )
then
return null;
end if;
declare
t_seg_cnt pls_integer;
t_end_offs pls_integer;
t_start_offs pls_integer;
t_idDelta_offs pls_integer;
t_idRangeOffset_offs pls_integer;
t_tmp pls_integer;
t_start pls_integer;
begin
t_seg_cnt := blob2num( t_blob, 2, t_offset + 6 ) / 2;
t_end_offs := t_offset + 14;
t_start_offs := t_end_offs + t_seg_cnt * 2 + 2;
t_idDelta_offs := t_start_offs + t_seg_cnt * 2;
t_idRangeOffset_offs := t_idDelta_offs + t_seg_cnt * 2;
for seg in 0 .. t_seg_cnt - 1
loop
t_tmp := blob2num( t_blob, 2, t_idRangeOffset_offs + seg * 2 );
if t_tmp = 0
then
t_tmp := blob2num( t_blob, 2, t_idDelta_offs + seg * 2 );
for c in blob2num( t_blob, 2, t_start_offs + seg * 2 )
.. blob2num( t_blob, 2, t_end_offs + seg * 2 )
loop
this_font.code2glyph( c ) := mod( c + t_tmp, 65536 );
end loop;
else
t_start := blob2num( t_blob, 2, t_start_offs + seg * 2 );
for c in t_start .. blob2num( t_blob, 2, t_end_offs + seg * 2 )
loop
this_font.code2glyph( c ) := blob2num( t_blob, 2, t_idRangeOffset_offs + t_tmp + ( seg + c - t_start ) * 2 );
end loop;
end if;
end loop;
end;
exit;
end if;
end loop;
--
t_glyphnames( 0 ) := '.notdef';
t_glyphnames( 1 ) := '.null';
t_glyphnames( 2 ) := 'nonmarkingreturn';
t_glyphnames( 3 ) := 'space';
t_glyphnames( 4 ) := 'exclam';
t_glyphnames( 5 ) := 'quotedbl';
t_glyphnames( 6 ) := 'numbersign';
t_glyphnames( 7 ) := 'dollar';
t_glyphnames( 8 ) := 'percent';
t_glyphnames( 9 ) := 'ampersand';
t_glyphnames( 10 ) := 'quotesingle';
t_glyphnames( 11 ) := 'parenleft';
t_glyphnames( 12 ) := 'parenright';
t_glyphnames( 13 ) := 'asterisk';
t_glyphnames( 14 ) := 'plus';
t_glyphnames( 15 ) := 'comma';
t_glyphnames( 16 ) := 'hyphen';
t_glyphnames( 17 ) := 'period';
t_glyphnames( 18 ) := 'slash';
t_glyphnames( 19 ) := 'zero';
t_glyphnames( 20 ) := 'one';
t_glyphnames( 21 ) := 'two';
t_glyphnames( 22 ) := 'three';
t_glyphnames( 23 ) := 'four';
t_glyphnames( 24 ) := 'five';
t_glyphnames( 25 ) := 'six';
t_glyphnames( 26 ) := 'seven';
t_glyphnames( 27 ) := 'eight';
t_glyphnames( 28 ) := 'nine';
t_glyphnames( 29 ) := 'colon';
t_glyphnames( 30 ) := 'semicolon';
t_glyphnames( 31 ) := 'less';
t_glyphnames( 32 ) := 'equal';
t_glyphnames( 33 ) := 'greater';
t_glyphnames( 34 ) := 'question';
t_glyphnames( 35 ) := 'at';
t_glyphnames( 36 ) := 'A';
t_glyphnames( 37 ) := 'B';
t_glyphnames( 38 ) := 'C';
t_glyphnames( 39 ) := 'D';
t_glyphnames( 40 ) := 'E';
t_glyphnames( 41 ) := 'F';
t_glyphnames( 42 ) := 'G';
t_glyphnames( 43 ) := 'H';
t_glyphnames( 44 ) := 'I';
t_glyphnames( 45 ) := 'J';
t_glyphnames( 46 ) := 'K';
t_glyphnames( 47 ) := 'L';
t_glyphnames( 48 ) := 'M';
t_glyphnames( 49 ) := 'N';
t_glyphnames( 50 ) := 'O';
t_glyphnames( 51 ) := 'P';
t_glyphnames( 52 ) := 'Q';
t_glyphnames( 53 ) := 'R';
t_glyphnames( 54 ) := 'S';
t_glyphnames( 55 ) := 'T';
t_glyphnames( 56 ) := 'U';
t_glyphnames( 57 ) := 'V';
t_glyphnames( 58 ) := 'W';
t_glyphnames( 59 ) := 'X';
t_glyphnames( 60 ) := 'Y';
t_glyphnames( 61 ) := 'Z';
t_glyphnames( 62 ) := 'bracketleft';
t_glyphnames( 63 ) := 'backslash';
t_glyphnames( 64 ) := 'bracketright';
t_glyphnames( 65 ) := 'asciicircum';
t_glyphnames( 66 ) := 'underscore';
t_glyphnames( 67 ) := 'grave';
t_glyphnames( 68 ) := 'a';
t_glyphnames( 69 ) := 'b';
t_glyphnames( 70 ) := 'c';
t_glyphnames( 71 ) := 'd';
t_glyphnames( 72 ) := 'e';
t_glyphnames( 73 ) := 'f';
t_glyphnames( 74 ) := 'g';
t_glyphnames( 75 ) := 'h';
t_glyphnames( 76 ) := 'i';
t_glyphnames( 77 ) := 'j';
t_glyphnames( 78 ) := 'k';
t_glyphnames( 79 ) := 'l';
t_glyphnames( 80 ) := 'm';
t_glyphnames( 81 ) := 'n';
t_glyphnames( 82 ) := 'o';
t_glyphnames( 83 ) := 'p';
t_glyphnames( 84 ) := 'q';
t_glyphnames( 85 ) := 'r';
t_glyphnames( 86 ) := 's';
t_glyphnames( 87 ) := 't';
t_glyphnames( 88 ) := 'u';
t_glyphnames( 89 ) := 'v';
t_glyphnames( 90 ) := 'w';
t_glyphnames( 91 ) := 'x';
t_glyphnames( 92 ) := 'y';
t_glyphnames( 93 ) := 'z';
t_glyphnames( 94 ) := 'braceleft';
t_glyphnames( 95 ) := 'bar';
t_glyphnames( 96 ) := 'braceright';
t_glyphnames( 97 ) := 'asciitilde';
t_glyphnames( 98 ) := 'Adieresis';
t_glyphnames( 99 ) := 'Aring';
t_glyphnames( 100 ) := 'Ccedilla';
t_glyphnames( 101 ) := 'Eacute';
t_glyphnames( 102 ) := 'Ntilde';
t_glyphnames( 103 ) := 'Odieresis';
t_glyphnames( 104 ) := 'Udieresis';
t_glyphnames( 105 ) := 'aacute';
t_glyphnames( 106 ) := 'agrave';
t_glyphnames( 107 ) := 'acircumflex';
t_glyphnames( 108 ) := 'adieresis';
t_glyphnames( 109 ) := 'atilde';
t_glyphnames( 110 ) := 'aring';
t_glyphnames( 111 ) := 'ccedilla';
t_glyphnames( 112 ) := 'eacute';
t_glyphnames( 113 ) := 'egrave';
t_glyphnames( 114 ) := 'ecircumflex';
t_glyphnames( 115 ) := 'edieresis';
t_glyphnames( 116 ) := 'iacute';
t_glyphnames( 117 ) := 'igrave';
t_glyphnames( 118 ) := 'icircumflex';
t_glyphnames( 119 ) := 'idieresis';
t_glyphnames( 120 ) := 'ntilde';
t_glyphnames( 121 ) := 'oacute';
t_glyphnames( 122 ) := 'ograve';
t_glyphnames( 123 ) := 'ocircumflex';
t_glyphnames( 124 ) := 'odieresis';
t_glyphnames( 125 ) := 'otilde';
t_glyphnames( 126 ) := 'uacute';
t_glyphnames( 127 ) := 'ugrave';
t_glyphnames( 128 ) := 'ucircumflex';
t_glyphnames( 129 ) := 'udieresis';
t_glyphnames( 130 ) := 'dagger';
t_glyphnames( 131 ) := 'degree';
t_glyphnames( 132 ) := 'cent';
t_glyphnames( 133 ) := 'sterling';
t_glyphnames( 134 ) := 'section';
t_glyphnames( 135 ) := 'bullet';
t_glyphnames( 136 ) := 'paragraph';
t_glyphnames( 137 ) := 'germandbls';
t_glyphnames( 138 ) := 'registered';
t_glyphnames( 139 ) := 'copyright';
t_glyphnames( 140 ) := 'trademark';
t_glyphnames( 141 ) := 'acute';
t_glyphnames( 142 ) := 'dieresis';
t_glyphnames( 143 ) := 'notequal';
t_glyphnames( 144 ) := 'AE';
t_glyphnames( 145 ) := 'Oslash';
t_glyphnames( 146 ) := 'infinity';
t_glyphnames( 147 ) := 'plusminus';
t_glyphnames( 148 ) := 'lessequal';
t_glyphnames( 149 ) := 'greaterequal';
t_glyphnames( 150 ) := 'yen';
t_glyphnames( 151 ) := 'mu';
t_glyphnames( 152 ) := 'partialdiff';
t_glyphnames( 153 ) := 'summation';
t_glyphnames( 154 ) := 'product';
t_glyphnames( 155 ) := 'pi';
t_glyphnames( 156 ) := 'integral';
t_glyphnames( 157 ) := 'ordfeminine';
t_glyphnames( 158 ) := 'ordmasculine';
t_glyphnames( 159 ) := 'Omega';
t_glyphnames( 160 ) := 'ae';
t_glyphnames( 161 ) := 'oslash';
t_glyphnames( 162 ) := 'questiondown';
t_glyphnames( 163 ) := 'exclamdown';
t_glyphnames( 164 ) := 'logicalnot';
t_glyphnames( 165 ) := 'radical';
t_glyphnames( 166 ) := 'florin';
t_glyphnames( 167 ) := 'approxequal';
t_glyphnames( 168 ) := 'Delta';
t_glyphnames( 169 ) := 'guillemotleft';
t_glyphnames( 170 ) := 'guillemotright';
t_glyphnames( 171 ) := 'ellipsis';
t_glyphnames( 172 ) := 'nonbreakingspace';
t_glyphnames( 173 ) := 'Agrave';
t_glyphnames( 174 ) := 'Atilde';
t_glyphnames( 175 ) := 'Otilde';
t_glyphnames( 176 ) := 'OE';
t_glyphnames( 177 ) := 'oe';
t_glyphnames( 178 ) := 'endash';
t_glyphnames( 179 ) := 'emdash';
t_glyphnames( 180 ) := 'quotedblleft';
t_glyphnames( 181 ) := 'quotedblright';
t_glyphnames( 182 ) := 'quoteleft';
t_glyphnames( 183 ) := 'quoteright';
t_glyphnames( 184 ) := 'divide';
t_glyphnames( 185 ) := 'lozenge';
t_glyphnames( 186 ) := 'ydieresis';
t_glyphnames( 187 ) := 'Ydieresis';
t_glyphnames( 188 ) := 'fraction';
t_glyphnames( 189 ) := 'currency';
t_glyphnames( 190 ) := 'guilsinglleft';
t_glyphnames( 191 ) := 'guilsinglright';
t_glyphnames( 192 ) := 'fi';
t_glyphnames( 193 ) := 'fl';
t_glyphnames( 194 ) := 'daggerdbl';
t_glyphnames( 195 ) := 'periodcentered';
t_glyphnames( 196 ) := 'quotesinglbase';
t_glyphnames( 197 ) := 'quotedblbase';
t_glyphnames( 198 ) := 'perthousand';
t_glyphnames( 199 ) := 'Acircumflex';
t_glyphnames( 200 ) := 'Ecircumflex';
t_glyphnames( 201 ) := 'Aacute';
t_glyphnames( 202 ) := 'Edieresis';
t_glyphnames( 203 ) := 'Egrave';
t_glyphnames( 204 ) := 'Iacute';
t_glyphnames( 205 ) := 'Icircumflex';
t_glyphnames( 206 ) := 'Idieresis';
t_glyphnames( 207 ) := 'Igrave';
t_glyphnames( 208 ) := 'Oacute';
t_glyphnames( 209 ) := 'Ocircumflex';
t_glyphnames( 210 ) := 'apple';
t_glyphnames( 211 ) := 'Ograve';
t_glyphnames( 212 ) := 'Uacute';
t_glyphnames( 213 ) := 'Ucircumflex';
t_glyphnames( 214 ) := 'Ugrave';
t_glyphnames( 215 ) := 'dotlessi';
t_glyphnames( 216 ) := 'circumflex';
t_glyphnames( 217 ) := 'tilde';
t_glyphnames( 218 ) := 'macron';
t_glyphnames( 219 ) := 'breve';
t_glyphnames( 220 ) := 'dotaccent';
t_glyphnames( 221 ) := 'ring';
t_glyphnames( 222 ) := 'cedilla';
t_glyphnames( 223 ) := 'hungarumlaut';
t_glyphnames( 224 ) := 'ogonek';
t_glyphnames( 225 ) := 'caron';
t_glyphnames( 226 ) := 'Lslash';
t_glyphnames( 227 ) := 'lslash';
t_glyphnames( 228 ) := 'Scaron';
t_glyphnames( 229 ) := 'scaron';
t_glyphnames( 230 ) := 'Zcaron';
t_glyphnames( 231 ) := 'zcaron';
t_glyphnames( 232 ) := 'brokenbar';
t_glyphnames( 233 ) := 'Eth';
t_glyphnames( 234 ) := 'eth';
t_glyphnames( 235 ) := 'Yacute';
t_glyphnames( 236 ) := 'yacute';
t_glyphnames( 237 ) := 'Thorn';
t_glyphnames( 238 ) := 'thorn';
t_glyphnames( 239 ) := 'minus';
t_glyphnames( 240 ) := 'multiply';
t_glyphnames( 241 ) := 'onesuperior';
t_glyphnames( 242 ) := 'twosuperior';
t_glyphnames( 243 ) := 'threesuperior';
t_glyphnames( 244 ) := 'onehalf';
t_glyphnames( 245 ) := 'onequarter';
t_glyphnames( 246 ) := 'threequarters';
t_glyphnames( 247 ) := 'franc';
t_glyphnames( 248 ) := 'Gbreve';
t_glyphnames( 249 ) := 'gbreve';
t_glyphnames( 250 ) := 'Idotaccent';
t_glyphnames( 251 ) := 'Scedilla';
t_glyphnames( 252 ) := 'scedilla';
t_glyphnames( 253 ) := 'Cacute';
t_glyphnames( 254 ) := 'cacute';
t_glyphnames( 255 ) := 'Ccaron';
t_glyphnames( 256 ) := 'ccaron';
t_glyphnames( 257 ) := 'dcroat';
--
dbms_lob.copy( t_blob, p_font, t_tables( 'post' ).length, 1, t_tables( 'post' ).offset );
this_font.italic_angle := to_short( dbms_lob.substr( t_blob, 2, 5 ) )
+ to_short( dbms_lob.substr( t_blob, 2, 7 ) ) / 65536;
case rawtohex( dbms_lob.substr( t_blob, 4, 1 ) )
when '00010000'
then
for g in 0 .. 257
loop
t_glyph2name( g ) := g;
end loop;
when '00020000'
then
t_offset := blob2num( t_blob, 2, 33 ) * 2 + 35;
while nvl( blob2num( t_blob, 1, t_offset ), 0 ) > 0
loop
t_glyphnames( t_glyphnames.count ) := utl_raw.cast_to_varchar2( dbms_lob.substr( t_blob, blob2num( t_blob, 1, t_offset ), t_offset + 1 ) );
t_offset := t_offset + blob2num( t_blob, 1, t_offset ) + 1;
end loop;
for g in 0 .. blob2num( t_blob, 2, 33 ) - 1
loop
t_glyph2name( g ) := blob2num( t_blob, 2, 35 + 2 * g );
end loop;
when '00025000'
then
for g in 0 .. blob2num( t_blob, 2, 33 ) - 1
loop
t_offset := blob2num( t_blob, 1, 35 + g );
if t_offset > 127
then
t_glyph2name( g ) := g - t_offset;
else
t_glyph2name( g ) := g + t_offset;
end if;
end loop;
when '00030000'
then
t_glyphnames.delete;
else
dbms_output.put_line( 'no post ' || dbms_lob.substr( t_blob, 4, 1 ) );
end case;
--
dbms_lob.copy( t_blob, p_font, t_tables( 'head' ).length, 1, t_tables( 'head' ).offset );
if dbms_lob.substr( t_blob, 4, 13 ) = hextoraw( '5F0F3CF5' ) -- magic
then
declare
t_tmp pls_integer := blob2num( t_blob, 2, 45 );
begin
if bitand( t_tmp, 1 ) = 1
then
this_font.style := 'B';
end if;
if bitand( t_tmp, 2 ) = 2
then
this_font.style := this_font.style || 'I';
this_font.flags := this_font.flags + 64;
end if;
this_font.style := nvl( this_font.style, 'N' );
this_font.unit_norm := 1000 / blob2num( t_blob, 2, 19 );
this_font.bb_xmin := to_short( dbms_lob.substr( t_blob, 2, 37 ), this_font.unit_norm );
this_font.bb_ymin := to_short( dbms_lob.substr( t_blob, 2, 39 ), this_font.unit_norm );
this_font.bb_xmax := to_short( dbms_lob.substr( t_blob, 2, 41 ), this_font.unit_norm );
this_font.bb_ymax := to_short( dbms_lob.substr( t_blob, 2, 43 ), this_font.unit_norm );
this_font.indexToLocFormat := blob2num( t_blob, 2, 51 ); -- 0 for short offsets, 1 for long
end;
end if;
--
dbms_lob.copy( t_blob, p_font, t_tables( 'hhea' ).length, 1, t_tables( 'hhea' ).offset );
if dbms_lob.substr( t_blob, 4, 1 ) = hextoraw( '00010000' ) -- version 1.0
then
this_font.ascent := to_short( dbms_lob.substr( t_blob, 2, 5 ), this_font.unit_norm );
this_font.descent := to_short( dbms_lob.substr( t_blob, 2, 7 ), this_font.unit_norm );
this_font.capheight := this_font.ascent;
nr_hmetrics := blob2num( t_blob, 2, 35 );
end if;
--
dbms_lob.copy( t_blob, p_font, t_tables( 'hmtx' ).length, 1, t_tables( 'hmtx' ).offset );
for j in 0 .. nr_hmetrics - 1
loop
this_font.hmetrics( j ) := blob2num( t_blob, 2, 1 + 4 * j );
end loop;
--
dbms_lob.copy( t_blob, p_font, t_tables( 'name' ).length, 1, t_tables( 'name' ).offset );
if dbms_lob.substr( t_blob, 2, 1 ) = hextoraw( '0000' ) -- format 0
then
t_offset := blob2num( t_blob, 2, 5 ) + 1;
for j in 0 .. blob2num( t_blob, 2, 3 ) - 1
loop
if ( dbms_lob.substr( t_blob, 2, 7 + j * 12 ) = hextoraw( '0003' ) -- Windows
and dbms_lob.substr( t_blob, 2, 11 + j * 12 ) = hextoraw( '0409' ) -- English United States
)
then
case rawtohex( dbms_lob.substr( t_blob, 2, 13 + j * 12 ) )
when '0001'
then
this_font.family := utl_i18n.raw_to_char( dbms_lob.substr( t_blob, blob2num( t_blob, 2, 15 + j * 12 ), t_offset + blob2num( t_blob, 2, 17 + j * 12 ) ), 'AL16UTF16' );
when '0006'
then
this_font.name := utl_i18n.raw_to_char( dbms_lob.substr( t_blob, blob2num( t_blob, 2, 15 + j * 12 ), t_offset + blob2num( t_blob, 2, 17 + j * 12 ) ), 'AL16UTF16' );
else
null;
end case;
end if;
end loop;
end if;
--
if this_font.italic_angle != 0
then
this_font.flags := this_font.flags + 64;
end if;
this_font.subtype := 'TrueType';
this_font.stemv := 50;
this_font.family := lower( this_font.family );
this_font.encoding := utl_i18n.map_charset( p_encoding
, utl_i18n.generic_context
, utl_i18n.iana_to_oracle
);
this_font.encoding := nvl( this_font.encoding, upper( p_encoding ) );
this_font.charset := sys_context( 'userenv', 'LANGUAGE' );
this_font.charset := substr( this_font.charset
, 1
, instr( this_font.charset, '.' )
) || this_font.encoding;
this_font.cid := upper( p_encoding ) in ( 'CID', 'AL16UTF16', 'UTF', 'UNICODE' );
this_font.fontname := this_font.name;
this_font.compress_font := p_compress;
--
if ( p_embed or this_font.cid ) and t_tables.exists( 'OS/2' )
then
dbms_lob.copy( t_blob, p_font, t_tables( 'OS/2' ).length, 1, t_tables( 'OS/2' ).offset );
if blob2num( t_blob, 2, 9 ) != 2
then
this_font.fontfile2 := p_font;
this_font.ttf_offset := p_offset;
this_font.name := dbms_random.string( 'u', 6 ) || '+' || this_font.name;
--
t_blob := dbms_lob.substr( p_font, t_tables( 'loca' ).length, t_tables( 'loca' ).offset );
declare
t_size pls_integer := 2 + this_font.indexToLocFormat * 2; -- 0 for short offsets, 1 for long
begin
for i in 0 .. this_font.numGlyphs
loop
this_font.loca( i ) := blob2num( t_blob, t_size, 1 + i * t_size );
end loop;
end;
end if;
end if;
--
if not this_font.cid
then
if this_font.flags = 4 -- a symbolic font
then
declare
t_real pls_integer;
begin
for t_code in 32 .. 255
loop
t_real := this_font.code2glyph.first + t_code - 32; -- assume code 32, space maps to the first code from the font
if this_font.code2glyph.exists( t_real )
then
this_font.first_char := least( nvl( this_font.first_char, 255 ), t_code );
this_font.last_char := t_code;
if this_font.hmetrics.exists( this_font.code2glyph( t_real ) )
then
this_font.char_width_tab( t_code ) := trunc( this_font.hmetrics( this_font.code2glyph( t_real ) ) * this_font.unit_norm );
else
this_font.char_width_tab( t_code ) := trunc( this_font.hmetrics( this_font.hmetrics.last() ) * this_font.unit_norm );
end if;
else
this_font.char_width_tab( t_code ) := trunc( this_font.hmetrics( 0 ) * this_font.unit_norm );
end if;
end loop;
end;
else
declare
t_unicode pls_integer;
t_prv_diff pls_integer;
t_utf16_charset varchar2(1000);
t_winansi_charset varchar2(1000);
t_glyphname tp_glyphname;
begin
t_prv_diff := -1;
t_utf16_charset := substr( this_font.charset, 1, instr( this_font.charset, '.' ) ) || 'AL16UTF16';
t_winansi_charset := substr( this_font.charset, 1, instr( this_font.charset, '.' ) ) || 'WE8MSWIN1252';
for t_code in 32 .. 255
loop
t_unicode := utl_raw.cast_to_binary_integer( utl_raw.convert( hextoraw( to_char( t_code, 'fm0x' ) )
, t_utf16_charset
, this_font.charset
)
);
t_glyphname := '';
this_font.char_width_tab( t_code ) := trunc( this_font.hmetrics( this_font.hmetrics.last() ) * this_font.unit_norm );
if this_font.code2glyph.exists( t_unicode )
then
this_font.first_char := least( nvl( this_font.first_char, 255 ), t_code );
this_font.last_char := t_code;
if this_font.hmetrics.exists( this_font.code2glyph( t_unicode ) )
then
this_font.char_width_tab( t_code ) := trunc( this_font.hmetrics( this_font.code2glyph( t_unicode ) ) * this_font.unit_norm );
end if;
if t_glyph2name.exists( this_font.code2glyph( t_unicode ) )
then
if t_glyphnames.exists( t_glyph2name( this_font.code2glyph( t_unicode ) ) )
then
t_glyphname := t_glyphnames( t_glyph2name( this_font.code2glyph( t_unicode ) ) );
end if;
end if;
end if;
--
if ( t_glyphname is not null
and t_unicode != utl_raw.cast_to_binary_integer( utl_raw.convert( hextoraw( to_char( t_code, 'fm0x' ) )
, t_winansi_charset
, this_font.charset
)
)
)
then
this_font.diff := this_font.diff || case when t_prv_diff != t_code - 1 then ' ' || t_code end || ' /' || t_glyphname;
t_prv_diff := t_code;
end if;
end loop;
end;
if this_font.diff is not null
then
this_font.diff := '/Differences [' || this_font.diff || ']';
end if;
end if;
end if;
--
t_font_ind := g_fonts.count( ) + 1;
g_fonts( t_font_ind ) := this_font;
/*
--
dbms_output.put_line( this_font.fontname || ' ' || this_font.family || ' ' || this_font.style
|| ' ' || this_font.flags
|| ' ' || this_font.code2glyph.first
|| ' ' || this_font.code2glyph.prior( this_font.code2glyph.last )
|| ' ' || this_font.code2glyph.last
|| ' nr glyphs: ' || this_font.numGlyphs
); */
--
return t_font_ind;
end;
--
procedure load_ttf_font
( p_font blob
, p_encoding varchar2 := 'WINDOWS-1252'
, p_embed boolean := false
, p_compress boolean := true
, p_offset number := 1
)
is
t_tmp pls_integer;
begin
t_tmp := load_ttf_font( p_font, p_encoding, p_embed, p_compress );
end;
--
function load_ttf_font
( p_dir varchar2 := 'MY_FONTS'
, p_filename varchar2 := 'BAUHS93.TTF'
, p_encoding varchar2 := 'WINDOWS-1252'
, p_embed boolean := false
, p_compress boolean := true
)
return pls_integer
is
begin
return load_ttf_font( file2blob( p_dir, p_filename ), p_encoding, p_embed, p_compress );
end;
--
procedure load_ttf_font
( p_dir varchar2 := 'MY_FONTS'
, p_filename varchar2 := 'BAUHS93.TTF'
, p_encoding varchar2 := 'WINDOWS-1252'
, p_embed boolean := false
, p_compress boolean := true
)
is
begin
load_ttf_font( file2blob( p_dir, p_filename ), p_encoding, p_embed, p_compress );
end;
--
procedure load_ttc_fonts
( p_ttc blob
, p_encoding varchar2 := 'WINDOWS-1252'
, p_embed boolean := false
, p_compress boolean := true
)
is
type tp_font_table is record
( offset pls_integer
, length pls_integer
);
type tp_tables is table of tp_font_table index by varchar2(4);
t_tables tp_tables;
t_tag varchar2(4);
t_blob blob;
t_offset pls_integer;
t_font_ind pls_integer;
begin
if utl_raw.cast_to_varchar2( dbms_lob.substr( p_ttc, 4, 1 ) ) != 'ttcf'
then
return;
end if;
for f in 0 .. blob2num( p_ttc, 4, 9 ) - 1
loop
t_font_ind := load_ttf_font( p_ttc, p_encoding, p_embed, p_compress, blob2num( p_ttc, 4, 13 + f * 4 ) + 1 );
dbms_output.put_line( t_font_ind || ' ' || g_fonts( t_font_ind ).fontname || ' ' || g_fonts( t_font_ind ).family || ' ' || g_fonts( t_font_ind ).style );
end loop;
end;
--
procedure load_ttc_fonts
( p_dir varchar2 := 'MY_FONTS'
, p_filename varchar2 := 'CAMBRIA.TTC'
, p_encoding varchar2 := 'WINDOWS-1252'
, p_embed boolean := false
, p_compress boolean := true
)
is
begin
load_ttc_fonts( file2blob( p_dir, p_filename ), p_encoding, p_embed, p_compress );
end;
--
function rgb( p_hex_rgb varchar2 )
return varchar2
is
begin
return to_char_round( nvl( to_number( substr( ltrim( p_hex_rgb, '#' )
, 1, 2 )
, 'xx' ) / 255
, 0 )
, 5 ) || ' '
|| to_char_round( nvl( to_number( substr( ltrim( p_hex_rgb, '#' )
, 3, 2 )
, 'xx' ) / 255
, 0 )
, 5 ) || ' '
|| to_char_round( nvl( to_number( substr( ltrim( p_hex_rgb, '#' )
, 5, 2 )
, 'xx' ) / 255
, 0 )
, 5 ) || ' ';
end;
--
procedure set_color( p_rgb varchar2 := '000000', p_backgr boolean )
is
begin
txt2page( rgb( p_rgb ) || case when p_backgr then 'RG' else 'rg' end );
end;
--
procedure set_color( p_rgb varchar2 := '000000' )
is
begin
set_color( p_rgb, false );
end;
--
procedure set_color
( p_red number := 0
, p_green number := 0
, p_blue number := 0
)
is
begin
if ( p_red between 0 and 255
and p_blue between 0 and 255
and p_green between 0 and 255
)
then
set_color( to_char( p_red, 'fm0x' )
|| to_char( p_green, 'fm0x' )
|| to_char( p_blue, 'fm0x' )
, false
);
end if;
end;
--
procedure set_bk_color( p_rgb varchar2 := 'ffffff' )
is
begin
set_color( p_rgb, true );
end;
--
procedure set_bk_color
( p_red number := 0
, p_green number := 0
, p_blue number := 0
)
is
begin
if ( p_red between 0 and 255
and p_blue between 0 and 255
and p_green between 0 and 255
)
then
set_color( to_char( p_red, 'fm0x' )
|| to_char( p_green, 'fm0x' )
|| to_char( p_blue, 'fm0x' )
, true
);
end if;
end;
--
procedure horizontal_line
( p_x number
, p_y number
, p_width number
, p_line_width number := 0.5
, p_line_color varchar2 := '000000'
)
is
t_use_color boolean;
begin
txt2page( 'q ' || to_char_round( p_line_width, 5 ) || ' w' );
t_use_color := substr( p_line_color
, -6
) != '000000';
if t_use_color
then
set_color( p_line_color );
set_bk_color( p_line_color );
else
txt2page( '0 g' );
end if;
txt2page( to_char_round( p_x, 5 ) || ' '
|| to_char_round( p_y, 5 ) || ' m '
|| to_char_round( p_x + p_width, 5 ) || ' '
|| to_char_round( p_y, 5 ) || ' l b'
);
txt2page( 'Q' );
end;
--
procedure vertical_line
( p_x number
, p_y number
, p_height number
, p_line_width number := 0.5
, p_line_color varchar2 := '000000'
)
is
t_use_color boolean;
begin
txt2page( 'q ' || to_char_round( p_line_width, 5 ) || ' w' );
t_use_color := substr( p_line_color
, -6
) != '000000';
if t_use_color
then
set_color( p_line_color );
set_bk_color( p_line_color );
else
txt2page( '0 g' );
end if;
txt2page( to_char_round( p_x, 5 ) || ' '
|| to_char_round( p_y, 5 ) || ' m '
|| to_char_round( p_x, 5 ) || ' '
|| to_char_round( p_y + p_height, 5 ) || ' l b'
);
txt2page( 'Q' );
end;
--
procedure rect
( p_x number
, p_y number
, p_width number
, p_height number
, p_line_color varchar2 := null
, p_fill_color varchar2 := null
, p_line_width number := 0.5
)
is
begin
txt2page( 'q' );
if substr( p_line_color, -6 ) != substr( p_fill_color, -6 )
then
txt2page( to_char_round( p_line_width, 5 ) || ' w' );
end if;
if substr( p_line_color, -6 ) != '000000'
then
set_bk_color( p_line_color );
else
txt2page( '0 g' );
end if;
if p_fill_color is not null
then
set_color( p_fill_color );
end if;
txt2page( to_char_round( p_x, 5 ) || ' ' || to_char_round( p_y, 5 ) || ' '
|| to_char_round( p_width, 5 ) || ' ' || to_char_round( p_height, 5 ) || ' re '
|| case
when p_fill_color is null
then 'S'
else case when p_line_color is null then 'f' else 'b' end
end
);
txt2page( 'Q' );
end;
--
function get( p_what pls_integer )
return number
is
begin
return case p_what
when c_get_page_width then g_settings.page_width
when c_get_page_height then g_settings.page_height
when c_get_margin_top then g_settings.margin_top
when c_get_margin_right then g_settings.margin_right
when c_get_margin_bottom then g_settings.margin_bottom
when c_get_margin_left then g_settings.margin_left
when c_get_x then g_x
when c_get_y then g_y
when c_get_fontsize then g_fonts( g_current_font ).fontsize
when c_get_current_font then g_current_font
end;
end;
--
function parse_jpg( p_img_blob blob )
return tp_img
is
buf raw(4);
t_img tp_img;
t_ind integer;
begin
if ( dbms_lob.substr( p_img_blob, 2, 1 ) != hextoraw( 'FFD8' ) -- SOI Start of Image
or dbms_lob.substr( p_img_blob, 2, dbms_lob.getlength( p_img_blob ) - 1 ) != hextoraw( 'FFD9' ) -- EOI End of Image
)
then -- this is not a jpg I can handle
return null;
end if;
--
t_img.pixels := p_img_blob;
t_img.type := 'jpg';
if dbms_lob.substr( t_img.pixels, 2, 3 ) in ( hextoraw( 'FFE0' ) -- a APP0 jpg
, hextoraw( 'FFE1' ) -- a APP1 jpg
)
then
t_img.color_res := 8;
t_img.height := 1;
t_img.width := 1;
--
t_ind := 3;
t_ind := t_ind + 2 + blob2num( t_img.pixels, 2, t_ind + 2 );
loop
buf := dbms_lob.substr( t_img.pixels, 2, t_ind );
exit when buf = hextoraw( 'FFDA' ); -- SOS Start of Scan
exit when buf = hextoraw( 'FFD9' ); -- EOI End Of Image
exit when substr( rawtohex( buf ), 1, 2 ) != 'FF';
if rawtohex( buf ) in ( 'FFD0' -- RSTn
, 'FFD1', 'FFD2', 'FFD3', 'FFD4', 'FFD5', 'FFD6', 'FFD7', 'FF01' -- TEM
)
then
t_ind := t_ind + 2;
else
if buf = hextoraw( 'FFC0' ) -- SOF0 (Start Of Frame 0) marker
then
t_img.color_res := blob2num( t_img.pixels, 1, t_ind + 4 );
t_img.height := blob2num( t_img.pixels, 2, t_ind + 5 );
t_img.width := blob2num( t_img.pixels, 2, t_ind + 7 );
end if;
t_ind := t_ind + 2 + blob2num( t_img.pixels, 2, t_ind + 2 );
end if;
end loop;
end if;
--
return t_img;
end;
--
function parse_png( p_img_blob blob )
return tp_img
is
t_img tp_img;
buf raw(32767);
len integer;
ind integer;
color_type pls_integer;
begin
if rawtohex( dbms_lob.substr( p_img_blob, 8, 1 ) ) != '89504E470D0A1A0A' -- not the right signature
then
return null;
end if;
dbms_lob.createtemporary( t_img.pixels, true );
ind := 9;
loop
len := blob2num( p_img_blob, 4, ind ); -- length
exit when len is null or ind > dbms_lob.getlength( p_img_blob );
case utl_raw.cast_to_varchar2( dbms_lob.substr( p_img_blob, 4, ind + 4 ) ) -- Chunk type
when 'IHDR'
then
t_img.width := blob2num( p_img_blob, 4, ind + 8 );
t_img.height := blob2num( p_img_blob, 4, ind + 12 );
t_img.color_res := blob2num( p_img_blob, 1, ind + 16 );
color_type := blob2num( p_img_blob, 1, ind + 17 );
t_img.greyscale := color_type in ( 0, 4 );
when 'PLTE'
then
t_img.color_tab := dbms_lob.substr( p_img_blob, len, ind + 8 );
when 'IDAT'
then
dbms_lob.copy( t_img.pixels, p_img_blob, len, dbms_lob.getlength( t_img.pixels ) + 1, ind + 8 );
when 'IEND'
then
exit;
else
null;
end case;
ind := ind + 4 + 4 + len + 4; -- Length + Chunk type + Chunk data + CRC
end loop;
--
t_img.type := 'png';
t_img.nr_colors := case color_type
when 0 then 1
when 2 then 3
when 3 then 1
when 4 then 2
else 4
end;
--
return t_img;
end;
--
function lzw_decompress
( p_blob blob
, p_bits pls_integer
)
return blob
is
powers tp_pls_tab;
--
g_lzw_ind pls_integer;
g_lzw_bits pls_integer;
g_lzw_buffer pls_integer;
g_lzw_bits_used pls_integer;
--
type tp_lzw_dict is table of raw(1000) index by pls_integer;
t_lzw_dict tp_lzw_dict;
t_clr_code pls_integer;
t_nxt_code pls_integer;
t_new_code pls_integer;
t_old_code pls_integer;
t_blob blob;
--
function get_lzw_code
return pls_integer
is
t_rv pls_integer;
begin
while g_lzw_bits_used < g_lzw_bits
loop
g_lzw_ind := g_lzw_ind + 1;
g_lzw_buffer := blob2num( p_blob, 1, g_lzw_ind ) * powers( g_lzw_bits_used ) + g_lzw_buffer;
g_lzw_bits_used := g_lzw_bits_used + 8;
end loop;
t_rv := bitand( g_lzw_buffer, powers( g_lzw_bits ) - 1 );
g_lzw_bits_used := g_lzw_bits_used - g_lzw_bits;
g_lzw_buffer := trunc( g_lzw_buffer / powers( g_lzw_bits ) );
return t_rv;
end;
--
begin
for i in 0 .. 30
loop
powers( i ) := power( 2, i );
end loop;
--
t_clr_code := powers( p_bits - 1 );
t_nxt_code := t_clr_code + 2;
for i in 0 .. least( t_clr_code - 1, 255 )
loop
t_lzw_dict( i ) := hextoraw( to_char( i, 'fm0X' ) );
end loop;
dbms_lob.createtemporary( t_blob, true );
g_lzw_ind := 0;
g_lzw_bits := p_bits;
g_lzw_buffer := 0;
g_lzw_bits_used := 0;
--
t_old_code := null;
t_new_code := get_lzw_code( );
loop
case nvl( t_new_code, t_clr_code + 1 )
when t_clr_code + 1
then
exit;
when t_clr_code
then
t_new_code := null;
g_lzw_bits := p_bits;
t_nxt_code := t_clr_code + 2;
else
if t_new_code = t_nxt_code
then
t_lzw_dict( t_nxt_code ) :=
utl_raw.concat( t_lzw_dict( t_old_code )
, utl_raw.substr( t_lzw_dict( t_old_code ), 1, 1 )
);
dbms_lob.append( t_blob, t_lzw_dict( t_nxt_code ) );
t_nxt_code := t_nxt_code + 1;
elsif t_new_code > t_nxt_code
then
exit;
else
dbms_lob.append( t_blob, t_lzw_dict( t_new_code ) );
if t_old_code is not null
then
t_lzw_dict( t_nxt_code ) := utl_raw.concat( t_lzw_dict( t_old_code )
, utl_raw.substr( t_lzw_dict( t_new_code ), 1, 1 )
);
t_nxt_code := t_nxt_code + 1;
end if;
end if;
if bitand( t_nxt_code, powers( g_lzw_bits ) - 1 ) = 0
and g_lzw_bits < 12
then
g_lzw_bits := g_lzw_bits + 1;
end if;
end case;
t_old_code := t_new_code;
t_new_code := get_lzw_code( );
end loop;
t_lzw_dict.delete;
--
return t_blob;
end;
--
function parse_gif( p_img_blob blob )
return tp_img
is
img tp_img;
buf raw(4000);
ind integer;
t_len pls_integer;
begin
if dbms_lob.substr( p_img_blob, 3, 1 ) != utl_raw.cast_to_raw( 'GIF' )
then
return null;
end if;
ind := 7;
buf := dbms_lob.substr( p_img_blob, 7, 7 ); -- Logical Screen Descriptor
ind := ind + 7;
img.color_res := raw2num( utl_raw.bit_and( utl_raw.substr( buf, 5, 1 ), hextoraw( '70' ) ) ) / 16 + 1;
img.color_res := 8;
if raw2num( buf, 5, 1 ) > 127
then
t_len := 3 * power( 2, raw2num( utl_raw.bit_and( utl_raw.substr( buf, 5, 1 ), hextoraw( '07' ) ) ) + 1 );
img.color_tab := dbms_lob.substr( p_img_blob, t_len, ind ); -- Global Color Table
ind := ind + t_len;
end if;
--
loop
case dbms_lob.substr( p_img_blob, 1, ind )
when hextoraw( '3B' ) -- trailer
then
exit;
when hextoraw( '21' ) -- extension
then
if dbms_lob.substr( p_img_blob, 1, ind + 1 ) = hextoraw( 'F9' )
then -- Graphic Control Extension
if utl_raw.bit_and( dbms_lob.substr( p_img_blob, 1, ind + 3 ), hextoraw( '01' ) ) = hextoraw( '01' )
then -- Transparent Color Flag set
img.transparancy_index := blob2num( p_img_blob, 1, ind + 6 );
end if;
end if;
ind := ind + 2; -- skip sentinel + label
loop
t_len := blob2num( p_img_blob, 1, ind ); -- Block Size
exit when t_len = 0;
ind := ind + 1 + t_len; -- skip Block Size + Data Sub-block
end loop;
ind := ind + 1; -- skip last Block Size
when hextoraw( '2C' ) -- image
then
declare
img_blob blob;
min_code_size pls_integer;
code_size pls_integer;
flags raw(1);
begin
img.width := utl_raw.cast_to_binary_integer( dbms_lob.substr( p_img_blob, 2, ind + 5 )
, utl_raw.little_endian
);
img.height := utl_raw.cast_to_binary_integer( dbms_lob.substr( p_img_blob, 2, ind + 7 )
, utl_raw.little_endian
);
img.greyscale := false;
ind := ind + 1 + 8; -- skip sentinel + img sizes
flags := dbms_lob.substr( p_img_blob, 1, ind );
if utl_raw.bit_and( flags, hextoraw( '80' ) ) = hextoraw( '80' )
then
t_len := 3 * power( 2, raw2num( utl_raw.bit_and( flags, hextoraw( '07' ) ) ) + 1 );
img.color_tab := dbms_lob.substr( p_img_blob, t_len, ind + 1 ); -- Local Color Table
end if;
ind := ind + 1; -- skip image Flags
min_code_size := blob2num( p_img_blob, 1, ind );
ind := ind + 1; -- skip LZW Minimum Code Size
dbms_lob.createtemporary( img_blob, true );
loop
t_len := blob2num( p_img_blob, 1, ind ); -- Block Size
exit when t_len = 0;
dbms_lob.append( img_blob, dbms_lob.substr( p_img_blob, t_len, ind + 1 ) ); -- Data Sub-block
ind := ind + 1 + t_len; -- skip Block Size + Data Sub-block
end loop;
ind := ind + 1; -- skip last Block Size
img.pixels := lzw_decompress( img_blob, min_code_size + 1 );
--
if utl_raw.bit_and( flags, hextoraw( '40' ) ) = hextoraw( '40' )
then -- interlaced
declare
pass pls_integer;
pass_ind tp_pls_tab;
l_mod number;
begin
dbms_lob.createtemporary( img_blob, true );
pass_ind( 1 ) := 1;
pass_ind( 2 ) := trunc( ( img.height - 1 ) / 8 ) + 1;
pass_ind( 3 ) := pass_ind( 2 ) + trunc( ( img.height + 3 ) / 8 );
pass_ind( 4 ) := pass_ind( 3 ) + trunc( ( img.height + 1 ) / 4 );
pass_ind( 2 ) := pass_ind( 2 ) * img.width + 1;
pass_ind( 3 ) := pass_ind( 3 ) * img.width + 1;
pass_ind( 4 ) := pass_ind( 4 ) * img.width + 1;
for i in 0 .. img.height - 1
loop
l_mod := mod( i, 8 );
pass := case l_mod
when 0 then 1
when 4 then 2
when 2 then 3
when 6 then 3
else 4
end;
dbms_lob.append( img_blob, dbms_lob.substr( img.pixels, img.width, pass_ind( pass ) ) );
pass_ind( pass ) := pass_ind( pass ) + img.width;
end loop;
img.pixels := img_blob;
end;
end if;
--
dbms_lob.freetemporary( img_blob );
end;
else
exit;
end case;
end loop;
--
img.type := 'gif';
return img;
end;
--
function parse_img
( p_blob in blob
, p_adler32 in varchar2 := null
, p_type in varchar2 := null
)
return tp_img
is
t_img tp_img;
begin
t_img.type := p_type;
if t_img.type is null
then
if rawtohex( dbms_lob.substr( p_blob, 8, 1 ) ) = '89504E470D0A1A0A'
then
t_img.type := 'png';
elsif dbms_lob.substr( p_blob , 3, 1 ) = utl_raw.cast_to_raw( 'GIF' )
then
t_img.type := 'gif';
else
t_img.type := 'jpg';
end if;
end if;
--
t_img := case lower( t_img.type )
when 'gif' then parse_gif( p_blob )
when 'png' then parse_png( p_blob )
when 'jpg' then parse_jpg( p_blob )
else null
end;
--
if t_img.type is not null
then
t_img.adler32 := coalesce( p_adler32, adler32( p_blob ) );
end if;
return t_img;
end;
--
procedure put_image
( p_img blob
, p_x number
, p_y number
, p_width number := null
, p_height number := null
, p_align varchar2 := 'center'
, p_valign varchar2 := 'top'
)
is
t_x number;
t_y number;
t_img tp_img;
t_ind pls_integer;
t_adler32 varchar2(8);
begin
if p_img is null
then
return;
end if;
t_adler32 := adler32( p_img );
t_ind := g_images.first;
while t_ind is not null
loop
exit when g_images( t_ind ).adler32 = t_adler32;
t_ind := g_images.next( t_ind );
end loop;
--
if t_ind is null
then
t_img := parse_img( p_img, t_adler32 );
if t_img.adler32 is null
then
return;
end if;
t_ind := g_images.count( ) + 1;
g_images( t_ind ) := t_img;
end if;
--
t_x := case substr( upper( p_align ), 1, 1 )
when 'L' then p_x -- left
when 'S' then p_x -- start
when 'R' then p_x + nvl( p_width, 0 ) - g_images( t_ind ).width -- right
when 'E' then p_x + nvl( p_width, 0 ) - g_images( t_ind ).width -- end
else ( p_x + nvl( p_width, 0 ) - g_images( t_ind ).width ) / 2 -- center
end;
t_y := case substr( upper( p_valign ), 1, 1 )
when 'C' then ( p_y - nvl( p_height, 0 ) + g_images( t_ind ).height ) / 2 -- center
when 'B' then p_y - nvl( p_height, 0 ) + g_images( t_ind ).height -- bottom
else p_y -- top
end;
--
txt2page( 'q ' || to_char_round( least( nvl( p_width, g_images( t_ind ).width ), g_images( t_ind ).width ) )
|| ' 0 0 ' || to_char_round( least( nvl( p_height, g_images( t_ind ).height ), g_images( t_ind ).height ) )
|| ' ' || to_char_round( t_x ) || ' ' || to_char_round( t_y )
|| ' cm /I' || to_char( t_ind ) || ' Do Q'
);
end;
--
procedure put_image
( p_dir varchar2
, p_file_name varchar2
, p_x number
, p_y number
, p_width number := null
, p_height number := null
, p_align varchar2 := 'center'
, p_valign varchar2 := 'top'
)
is
t_blob blob;
begin
t_blob := file2blob( p_dir
, p_file_name
);
put_image( t_blob
, p_x
, p_y
, p_width
, p_height
, p_align
, p_valign
);
dbms_lob.freetemporary( t_blob );
end;
--
procedure put_image
( p_url varchar2
, p_x number
, p_y number
, p_width number := null
, p_height number := null
, p_align varchar2 := 'center'
, p_valign varchar2 := 'top'
)
is
t_blob blob;
begin
t_blob := httpuritype( p_url ).getblob( );
put_image( t_blob
, p_x
, p_y
, p_width
, p_height
, p_align
, p_valign
);
dbms_lob.freetemporary( t_blob );
end;
--
procedure set_page_proc( p_src clob )
is
begin
g_page_prcs( g_page_prcs.count ) := p_src;
end;
--
procedure cursor2table
( p_c integer
, p_widths tp_col_widths := null
, p_headers tp_headers := null
)
is
t_col_cnt integer;
$IF DBMS_DB_VERSION.VER_LE_10 $THEN
t_desc_tab dbms_sql.desc_tab2;
$ELSE
t_desc_tab dbms_sql.desc_tab3;
$END
d_tab dbms_sql.date_table;
n_tab dbms_sql.number_table;
v_tab dbms_sql.varchar2_table;
t_bulk_size pls_integer := 200;
t_r integer;
t_cur_row pls_integer;
type tp_integer_tab is table of integer;
t_chars tp_integer_tab := tp_integer_tab( 1, 8, 9, 96, 112 );
t_dates tp_integer_tab := tp_integer_tab( 12, 178, 179, 180, 181 , 231 );
t_numerics tp_integer_tab := tp_integer_tab( 2, 100, 101 );
t_widths tp_col_widths;
t_tmp number;
t_x number;
t_y number;
t_start_x number;
t_lineheight number;
t_padding number := 2;
t_num_format varchar2(100) := 'tm9';
t_date_format varchar2(100) := 'dd.mm.yyyy';
t_txt varchar2(32767);
c_rf number := 0.2; -- raise factor of text above cell bottom
--
procedure show_header
is
begin
if p_headers is not null and p_headers.count > 0
then
t_x := t_start_x;
for c in 1 .. t_col_cnt
loop
rect( t_x, t_y, t_widths( c ), t_lineheight );
if c <= p_headers.count
then
put_txt( t_x + t_padding, t_y + c_rf * t_lineheight, p_headers( c ) );
end if;
t_x := t_x + t_widths( c );
end loop;
t_y := t_y - t_lineheight;
end if;
end;
--
begin
$IF DBMS_DB_VERSION.VER_LE_10 $THEN
dbms_sql.describe_columns2( p_c, t_col_cnt, t_desc_tab );
$ELSE
dbms_sql.describe_columns3( p_c, t_col_cnt, t_desc_tab );
$END
if p_widths is null or p_widths.count < t_col_cnt
then
t_tmp := get( c_get_page_width ) - get( c_get_margin_left ) - get( c_get_margin_right );
t_widths := tp_col_widths();
t_widths.extend( t_col_cnt );
for c in 1 .. t_col_cnt
loop
t_widths( c ) := round( t_tmp / t_col_cnt, 1 );
end loop;
else
t_widths := p_widths;
end if;
--
if get( c_get_current_font ) is null
then
set_font( 'helvetica', 12 );
end if;
--
for c in 1 .. t_col_cnt
loop
case
when t_desc_tab( c ).col_type member of t_numerics
then
dbms_sql.define_array( p_c, c, n_tab, t_bulk_size, 1 );
when t_desc_tab( c ).col_type member of t_dates
then
dbms_sql.define_array( p_c, c, d_tab, t_bulk_size, 1 );
when t_desc_tab( c ).col_type member of t_chars
then
dbms_sql.define_array( p_c, c, v_tab, t_bulk_size, 1 );
else
null;
end case;
end loop;
--
t_start_x := get( c_get_margin_left );
t_lineheight := get( c_get_fontsize ) * 1.2;
t_y := coalesce( get( c_get_y ) - t_lineheight, get( c_get_page_height ) - get( c_get_margin_top ) ) - t_lineheight;
--
show_header;
--
loop
t_r := dbms_sql.fetch_rows( p_c );
for i in 0 .. t_r - 1
loop
if t_y < get( c_get_margin_bottom )
then
new_page;
t_y := get( c_get_page_height ) - get( c_get_margin_top ) - t_lineheight;
show_header;
end if;
t_x := t_start_x;
for c in 1 .. t_col_cnt
loop
case
when t_desc_tab( c ).col_type member of t_numerics
then
n_tab.delete;
dbms_sql.column_value( p_c, c, n_tab );
rect( t_x, t_y, t_widths( c ), t_lineheight );
t_txt := to_char( n_tab( i + n_tab.first() ), t_num_format );
if t_txt is not null
then
put_txt( t_x + t_widths( c ) - t_padding - str_len( t_txt ), t_y + c_rf * t_lineheight, t_txt );
end if;
t_x := t_x + t_widths( c );
when t_desc_tab( c ).col_type member of t_dates
then
d_tab.delete;
dbms_sql.column_value( p_c, c, d_tab );
rect( t_x, t_y, t_widths( c ), t_lineheight );
t_txt := to_char( d_tab( i + d_tab.first() ), t_date_format );
if t_txt is not null
then
put_txt( t_x + t_padding, t_y + c_rf * t_lineheight, t_txt );
end if;
t_x := t_x + t_widths( c );
when t_desc_tab( c ).col_type member of t_chars
then
v_tab.delete;
dbms_sql.column_value( p_c, c, v_tab );
rect( t_x, t_y, t_widths( c ), t_lineheight );
t_txt := v_tab( i + v_tab.first() );
if t_txt is not null
then
put_txt( t_x + t_padding, t_y + c_rf * t_lineheight, t_txt );
end if;
t_x := t_x + t_widths( c );
else
null;
end case;
end loop;
t_y := t_y - t_lineheight;
end loop;
exit when t_r != t_bulk_size;
end loop;
g_y := t_y;
end;
--
procedure query2table
( p_query varchar2
, p_widths tp_col_widths := null
, p_headers tp_headers := null
)
is
t_cx integer;
t_dummy integer;
begin
t_cx := dbms_sql.open_cursor;
dbms_sql.parse( t_cx, p_query, dbms_sql.native );
t_dummy := dbms_sql.execute( t_cx );
cursor2table( t_cx, p_widths, p_headers );
dbms_sql.close_cursor( t_cx );
end;
$IF not DBMS_DB_VERSION.VER_LE_10 $THEN
--
procedure refcursor2table
( p_rc sys_refcursor
, p_widths tp_col_widths := null
, p_headers tp_headers := null
)
is
t_cx integer;
t_rc sys_refcursor;
begin
t_rc := p_rc;
t_cx := dbms_sql.to_cursor_number( t_rc );
cursor2table( t_cx, p_widths, p_headers );
dbms_sql.close_cursor( t_cx );
end;
$END
end as_pdf3;
/