пятница, 12 июня 2015 г.

LazReport и кириллица при экспорте в pdf

Предисловие


Шел 2015 год. Дело близилось к лету, и, следовательно, сдача дипломного проекта была не за горами. Мне нужно было создать приложение, одной из функций которого было формирование простенького отчета и сохранение его в pdf. В качестве IDE я использовал Lazarus 1.2.6, а для формирования отчета, соответственно, LazReport 0.9. Для его сохранения в pdf используется адаптированный для LazReport пакет powerpdf. Разработка уже близилась к завершению, как вдруг выяснилось, что полученный в pdf отчет не отображает кириллицу!




Ближе к делу


При экспорте адаптированный powerpdf использует кодировку Windows-1252. При этом символы кириллицы считаются "некорректными" и заменяются знаком "?".

function _UTF8ToWinAnsi(const value: string; InvalidChar:char='?'): string;
var
  W: widestring;
  i: Integer;
begin
  W := UTF8Decode(Value);
  result := '';
  for i:=1 to length(w) do
    result := result + CP1252(word(w[i]), InvalidChar);
end;

Поэтому первым делом я заменил реализацию функции _UTF8ToWinAnsi стандартной функцией UTF8ToAnsi




Теперь хотя бы пропали "?" и появились знакомые всем "кракозябры", которые при копировании из pdf в блокнот отображаются корректно. Уже лучше.

Далее сдвинуться с мертвой точки помог раздел на форуме и чтение спецификации. Руками поправив полученный pdf в соответствии с рекомендациями на форуме, получилось отобразить несколько символов кириллицы. Таким образом, я решил создать отдельный объект с кодировкой Windows-1251 следующего вида:

1 0 obj
<<
/Type /Encoding
/BaseEncoding /WinAnsiEncoding
/Differences [168 /afii10023 184 /afii10071 192 /afii10017 /afii10018 /afii10019 /afii10020 /afii10021 /afii10022 /afii10024 /afii10025 /afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 /afii10033 /afii10034 /afii10035 /afii10036 /afii10037 /afii10038 /afii10039 /afii10040 /afii10041 /afii10042 /afii10043 /afii10044 /afii10045 /afii10046 /afii10047 /afii10048 /afii10049 224 /afii10065 /afii10066 /afii10067 /afii10068 /afii10069 /afii10070 /afii10072 /afii10073 /afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 /afii10081 /afii10082 /afii10083 /afii10084 /afii10085 /afii10086 /afii10087 /afii10088 /afii10089 /afii10090 /afii10091 /afii10092 /afii10093 /afii10094 /afii10095 /afii10096 /afii10097 ]
>>
endobj

То есть, каждому коду кириллицы принудительно задаем соответствие отображаемого символа: 192 -> afii10017; 193 -> afii10018 и так далее. Теперь в качестве кодировки шрифта будем ссылаться на наш объект

8 0 obj
<<
/Type /Font
/Subtype /Type1
/FirstChar 32
/LastChar 255
/BaseFont /Helvetica
/Encoding 1 0 R
/Name /F0
>>
endobj

При просмотре видно, что символы кириллицы отображаются. Однако при этом они "наезжают" друг на друга. 





Первая мысль была о том, что нужно явно указать ширину каждого символа в объекте шрифта (ширину взял из документа, который получил с помощью виртуального pdf принтера):

8 0 obj
<<
/Type /Font
/Subtype /Type1
/FirstChar 32
/LastChar 255
/BaseFont /Helvetica
/Widths [278 278 355 556 556 889 667 191 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 333 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 0 556 0 222 556 333 1000 556 556 333 1000 667 333 1000 0 611 0 0 222 222 333 333 350 556 1000 333 1000 500 333 944 0 500 667 0 333 556 556 556 556 260 556 667 737 370 556 584 0 737 333 400 584 333 333 333 556 537 278 556 333 365 556 834 834 834 611 667 656 667 542 677 667 923 604 719 719 583 656 833 722 778 719 667 722 611 635 760 667 740 667 917 938 792 885 656 719 1010 722 556 573 531 365 583 556 669 458 559 559 438 583 688 552 556 542 556 500 458 500 823 500 573 521 802 823 625 719 521 510 750 542 ]
/Encoding 1 0 R
/Name /F0
>>
endobj

Ура, кажется, получилось!




Пора реализовать это в виде кода. Править будем пакет powerpdf. Создаем класс в PdfDoc.pas, который будет генерировать нам нашу кодировку:

TPdfWin1251Encoding = class(TPdfDictionary)
private
  FDifferences : TPdfArray;
  procedure FillDifferences(AStartAnsi, AStopAnsi, AStartAfii, ASkipAfii:integer);
public
  constructor CreateEncoding(AXref: TPdfXref);
end;

Далее создаем экземпляр этого класса и добавляем кодировку при создании шрифта:

function TPdfDoc.CreateFont(const FontName: string): TPdfFont;
begin
  {...}
  Result := PdfFont.Create(FXref, FontName);
  if Assigned(FFontEncoding) then begin
    Result.Data.AddItem('Encoding', FFontEncoding);
  end;
  {...}
end;

Для каждого из стандартных шрифтов в PdfFonts.pas заменяем ширину для символов кириллицы, а в методе SetData класса TPdfType1Font принудительно выводим ее в pdf.

Исправленный мною пакет powerpdf можно найти здесьВсе изменения регулируются объявлением/удалением директивы компиляции {$DEFINE POWERPDF_WIN1251}

Надеюсь, что данная информация оказалась полезной. Спасибо.


P.S. 


Моя радость длилась не долго, а именно до того момента, пока я не попросил товарища попробовать открыть у себя тестовую pdf. И вот, что он мне прислал (программа Adobe Reader)




Я попробовал открыть документ в STDU Viewer и Foxit Reader. В них документ отображался корректно. При этом шрифт Times-Roman (обычный и жирный) корректно отображался во всех трех программах. Поэтому на данном этапе для отчетов я использовал только их.

Думаю, что для решения этой проблемы все используемые шрифты необходимо также встроить в pdf. Возможно, позже у меня получится это сделать.

3 комментария: