Files
haiku-beta6/src/add-ons/print/drivers/pdf/source/PDFText.cpp
T
Michael Pfeiffer ceffc48db9 - updated to use PDFlib 5.0.0b2
- added basic support for stroke/fill opacity (transparency)


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@2991 a95241bf-73f2-0310-859d-f6bbb57e9c96
2003-03-22 08:47:20 +00:00

567 lines
14 KiB
C++

/*
PDF Writer printer driver.
Copyright (c) 2001, 2002 OpenBeOS.
Authors:
Philippe Houdoin
Simon Gauvin
Michael Pfeiffer
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <Debug.h>
#include <StorageKit.h>
#include <TranslationKit.h>
#include <support/UTF8.h>
#include "PDFWriter.h"
#include "Link.h"
#include "Bookmark.h"
#include "DrawShape.h"
#include "XReferences.h"
#include "Log.h"
#include "Report.h"
#include "pdflib.h"
typedef struct
{
uint16 from;
uint16 to;
int16 length;
uint16 *unicodes;
} unicode_to_encoding;
typedef struct
{
uint16 unicode;
uint16 cid;
} unicode_to_cid;
typedef struct
{
uint16 length;
unicode_to_cid *table;
} cid_table;
#ifdef UNICODE5_FROM
#error check code!
#endif
#define ELEMS(v, e) sizeof(v) / sizeof(e)
// Adobe Glyph List
#include "enc_range.h"
#include "unicode0.h"
#include "unicode1.h"
#include "unicode2.h"
#include "unicode3.h"
#include "unicode4.h"
static unicode_to_encoding encodings[] =
{
{UNICODE0_FROM, UNICODE0_TO, ELEMS(unicode0, uint16), unicode0},
{UNICODE1_FROM, UNICODE1_TO, ELEMS(unicode1, uint16), unicode1},
{UNICODE2_FROM, UNICODE2_TO, ELEMS(unicode2, uint16), unicode2},
{UNICODE3_FROM, UNICODE3_TO, ELEMS(unicode3, uint16), unicode3},
{UNICODE4_FROM, UNICODE4_TO, ELEMS(unicode4, uint16), unicode4}
};
// unicode to cid
#include "japanese.h"
#include "gb1.h"
#include "cns1.h"
#include "korean.h"
static cid_table cid_tables[] =
{
{ELEMS(japanese, unicode_to_cid), japanese},
{ELEMS(CNS1, unicode_to_cid), CNS1},
{ELEMS(GB1, unicode_to_cid), GB1},
{ELEMS(korean, unicode_to_cid), korean}
};
// --------------------------------------------------
static bool
find_encoding(uint16 unicode, uint8 &encoding, uint16 &index)
{
for (unsigned int i = 0; i < ELEMS(encodings, unicode_to_encoding); i++) {
if (encodings[i].from <= unicode && unicode <= encodings[i].to) {
int16 bottom = 0;
int16 top = encodings[i].length-1;
uint16* codes = encodings[i].unicodes;
while (top >= bottom) {
int16 m = (top + bottom) / 2;
if (unicode < codes[m]) {
top = m-1;
} else if (unicode > codes[m]) {
bottom = m+1;
} else {
index = m;
encoding = i;
return true;
}
}
return false;
}
}
return false;
}
// --------------------------------------------------
static bool
find_in_cid_tables(uint16 unicode, font_encoding &encoding, uint16 &index, font_encoding* order)
{
for (unsigned int i = 0; i < ELEMS(cid_tables, cid_table); i++) {
encoding = order[i];
if (encoding == invalid_encoding) break;
int index = encoding - first_cjk_encoding;
int32 bottom = 0;
int32 top = cid_tables[index].length-1;
unicode_to_cid *table = cid_tables[index].table;
while (top >= bottom) {
int32 m = (top + bottom) / 2;
if (unicode < table[m].unicode) {
top = m-1;
} else if (unicode > table[m].unicode) {
bottom = m+1;
} else {
index = table[m].cid;
return true;
}
}
}
return false;
}
// --------------------------------------------------
void
PDFWriter::GetFontName(BFont *font, char *fontname, bool &embed, font_encoding encoding)
{
font_family family;
font_style style;
font->GetFamilyAndStyle(&family, &style);
strcat(strcat(strcpy(fontname, family), "-"), style);
switch (encoding) {
case japanese_encoding:
strcpy(fontname, "HeiseiMin-W3"); return;
case chinese_cns1_encoding:
strcpy(fontname, "MHei-Medium"); return;
case chinese_gb1_encoding:
strcpy(fontname, "STSong-Light"); return;
case korean_encoding:
strcpy(fontname, "HYGoThic-Medium"); return;
return;
default:;
}
}
static const char* encoding_names[] =
{
"macroman",
// TrueType
"ttenc0",
"ttenc1",
"ttenc2",
"ttenc3",
"ttenc4",
// Type 1
"t1enc0",
"t1enc1",
"t1enc2",
"t1enc3",
"t1enc4",
// CJK
"UniJIS-UCS2-H",
"UniCNS-UCS2-H",
"UniGB-UCS2-H",
"UniKS-UCS2-H"
};
// --------------------------------------------------
int
PDFWriter::FindFont(char* fontName, bool embed, font_encoding encoding)
{
static Font* cache = NULL;
if (cache && cache->encoding == encoding && strcmp(cache->name.String(), fontName) == 0)
return cache->font;
REPORT(kDebug, fPage, "FindFont %s", fontName);
Font *f = NULL;
const int n = fFontCache.CountItems();
for (int i = 0; i < n; i++) {
f = fFontCache.ItemAt(i);
if (f->encoding == encoding && strcmp(f->name.String(), fontName) == 0) {
cache = f;
return f->font;
}
}
if (embed) embed = EmbedFont(fontName);
REPORT(kDebug, fPage, "Create new font");
int font = PDF_findfont(fPdf, fontName, encoding_names[encoding], embed);
if (font != -1) {
REPORT(kDebug, fPage, "font created");
cache = new Font(fontName, font, encoding);
fFontCache.AddItem(cache);
}
return font;
}
// --------------------------------------------------
void
PDFWriter::ToUtf8(uint32 encoding, const char *string, BString &utf8)
{
int32 len = strlen(string);
int32 srcLen = len, destLen = 255;
int32 state = 0;
char buffer[256];
int32 srcStart = 0;
do {
convert_to_utf8(encoding, &string[srcStart], &srcLen, buffer, &destLen, &state);
srcStart += srcLen;
len -= srcLen;
srcLen = len;
utf8.Append(buffer, destLen);
destLen = 255;
} while (len > 0);
};
// --------------------------------------------------
void
PDFWriter::ToUnicode(const char *string, BString &unicode)
{
int32 len = strlen(string);
int32 srcLen = len, destLen = 255;
int32 state = 0;
char buffer[256];
int32 srcStart = 0;
int i = 0;
unicode = "";
if (len == 0) return;
do {
convert_from_utf8(B_UNICODE_CONVERSION, &string[srcStart], &srcLen, buffer, &destLen, &state);
srcStart += srcLen;
len -= srcLen;
srcLen = len;
char *b = unicode.LockBuffer(i + destLen);
memcpy(&b[i], buffer, destLen);
unicode.UnlockBuffer(i + destLen);
i += destLen;
destLen = 255;
} while (len > 0);
}
// --------------------------------------------------
void
PDFWriter::ToPDFUnicode(const char *string, BString &unicode)
{
// PDFlib requires BOM at begin and two 0 at end of string
char marker[3] = { 0xfe, 0xff, 0}; // byte order marker
BString s;
ToUnicode(string, s);
unicode << marker;
int32 len = s.Length()+2;
char* buf = unicode.LockBuffer(len + 2); // reserve space for two additional '\0'
memcpy(&buf[2], s.String(), s.Length());
buf[len] = buf[len+1] = 0;
unicode.UnlockBuffer(len + 2);
}
// --------------------------------------------------
uint16
PDFWriter::CodePointSize(const char* s)
{
uint16 i = 1;
for (s++; !BeginsChar(*s); s++) i++;
return i;
}
void PDFWriter::RecordDests(const char* s) {
::RecordDests record(fXRefDests, fPage);
fXRefs->Matches(s, &record, true);
}
// --------------------------------------------------
void
PDFWriter::DrawChar(uint16 unicode, const char* utf8, int16 size)
{
// try to convert from utf8 to MacRoman encoding schema...
int32 srcLen = size;
int32 destLen = 1;
char dest[2] = "\0";
int32 state = 0;
bool embed = true;
font_encoding encoding = macroman_encoding;
if (convert_from_utf8(B_MAC_ROMAN_CONVERSION, utf8, &srcLen, dest, &destLen, &state, 0) != B_OK || dest[0] == 0 ) {
// could not convert to MacRoman
uint8 enc;
uint16 index;
font_encoding fenc;
// is code point in the Adobe Glyph List?
if (find_encoding(unicode, enc, index)) {
// LOG((fLog, "encoding for %x -> %d %d\n", unicode, (int)enc, (int)index));
// use one of the user defined encodings
if (fState->beFont.FileFormat() == B_TRUETYPE_WINDOWS) {
encoding = font_encoding(enc + tt_encoding0);
} else {
encoding = font_encoding(enc + t1_encoding0);
}
*dest = index;
} else if (find_in_cid_tables(unicode, fenc, index, fFontSearchOrder)) {
// LOG((fLog, "cid table %d index = %d\n", (int)enc, (int)index));
dest[0] = unicode / 256;
dest[1] = unicode % 256;
destLen = 2;
encoding = fenc;
embed = false;
} else {
// LOG((fLog, "encoding for %x not found!\n", unicode));
*dest = 0; // paint a box (is 0 a box in MacRoman) or
return; // simply skip character
}
}
// else LOG((fLog, "macroman srcLen=%d destLen=%d dest= %d %d!\n", srcLen, destLen, (int)dest[0], (int)dest[1]));
char fontName[B_FONT_FAMILY_LENGTH+B_FONT_STYLE_LENGTH+1];
int font;
GetFontName(&fState->beFont, fontName, embed, encoding);
font = FindFont(fontName, embed, encoding);
if (font < 0) {
REPORT(kWarning, fPage, "**** PDF_findfont(%s) failed, back to default font", fontName);
font = PDF_findfont(fPdf, "Helvetica", "macroman", 0);
}
fState->font = font;
uint16 face = fState->beFont.Face();
PDF_set_parameter(fPdf, "underline", (face & B_UNDERSCORE_FACE) != 0 ? "true" : "false");
PDF_set_parameter(fPdf, "strikeout", (face & B_STRIKEOUT_FACE) != 0 ? "true" : "false");
PDF_set_value(fPdf, "textrendering", (face & B_OUTLINED_FACE) != 0 ? 1 : 0);
PDF_setfont(fPdf, fState->font, scale(fState->beFont.Size()));
const float x = tx(fState->penX);
const float y = ty(fState->penY);
const float rotation = fState->beFont.Rotation();
const bool rotate = rotation != 0.0;
if (rotate) {
PDF_save(fPdf);
PDF_translate(fPdf, x, y);
PDF_rotate(fPdf, rotation);
PDF_set_text_pos(fPdf, 0, 0);
} else
PDF_set_text_pos(fPdf, x, y);
PDF_show2(fPdf, dest, destLen);
if (rotate) {
PDF_restore(fPdf);
}
}
// --------------------------------------------------
void
PDFWriter::ClipChar(BFont* font, const char* unicode, const char* utf8, int16 size, float width)
{
BShape glyph;
bool hasGlyph[1];
font->GetHasGlyphs(utf8, 1, hasGlyph);
if (hasGlyph[0]) {
BShape *glyphs[1];
glyphs[0] = &glyph;
font->GetGlyphShapes(utf8, 1, glyphs);
} else {
REPORT(kWarning, fPage, "glyph for %*.*s not found!", size, size, utf8);
// create a rectangle instead
font_height height;
fState->beFont.GetHeight(&height);
BRect r(0, 0, width, height.ascent);
float w = r.Width() < r.Height() ? r.Width()*0.1 : r.Height()*0.1;
BRect o = r; o.InsetBy(w, w);
w *= 2.0;
BRect i = r; i.InsetBy(w, w);
o.OffsetBy(0, -height.ascent);
i.OffsetBy(0, -height.ascent);
glyph.MoveTo(BPoint(o.left, o.top));
glyph.LineTo(BPoint(o.right, o.top));
glyph.LineTo(BPoint(o.right, o.bottom));
glyph.LineTo(BPoint(o.left, o.bottom));
glyph.Close();
glyph.MoveTo(BPoint(i.left, i.top));
glyph.LineTo(BPoint(i.left, i.bottom));
glyph.LineTo(BPoint(i.right, i.bottom));
glyph.LineTo(BPoint(i.right, i.top));
glyph.Close();
}
BPoint p(fState->penX, fState->penY);
PushInternalState(); SetOrigin(p);
{
DrawShape iterator(this, false);
iterator.Iterate(&glyph);
}
PopInternalState();
}
// --------------------------------------------------
void
PDFWriter::DrawString(char *string, float escapement_nospace, float escapement_space)
{
REPORT(kDebug, fPage, "DrawString string=\"%s\", escapement_nospace=%f, escapement_space=%f, at %f, %f", \
string, escapement_nospace, escapement_space, fState->penX, fState->penY);
if (IsDrawing()) {
// text color is always the high color and not the pattern!
SetColor(fState->foregroundColor);
}
// convert string to UTF8
BString utf8;
if (fState->beFont.Encoding() == B_UNICODE_UTF8) {
utf8 = string;
} else {
ToUtf8(fState->beFont.Encoding()-1, string, utf8);
}
// convert string in UTF8 to unicode UCS2
BString unicode;
ToUnicode(utf8.String(), unicode);
// need font object to calculate width of utf8 code point
BFont font = fState->beFont;
font.SetEncoding(B_UNICODE_UTF8);
// constants to calculate position of next character
const double rotation = DEGREE2RAD(fState->beFont.Rotation());
const bool rotate = rotation != 0.0;
const double cos1 = rotate ? cos(rotation) : 1;
const double sin1 = rotate ? -sin(rotation) : 0;
BPoint start(fState->penX, fState->penY);
BeginTransparency();
// If !MakesPDF() all the effort below just for the bounding box!
// draw each character
const char *c = utf8.String();
const unsigned char *u = (unsigned char*)unicode.String();
for (int i = 0; i < unicode.Length(); i += 2) {
int s = CodePointSize((char*)c);
float w = font.StringWidth(c, s);
if (MakesPDF()) {
if (IsClipping()) {
ClipChar(&font, (char*)u, c, s, w);
} else {
DrawChar(u[0]*256+u[1], c, s);
}
}
// position of next character
if (*(unsigned char*)c <= 0x20) { // should test if c is a white-space!
w += escapement_space;
} else {
w += escapement_nospace;
}
fState->penX += w * cos1;
fState->penY += w * sin1;
// next character
c += s; u += 2;
}
EndTransparency();
// text line processing (for non rotated text only!)
BPoint end(fState->penX, fState->penY);
BRect bounds;
font_height height;
font.GetHeight(&height);
bounds.left = start.x;
bounds.right = end.x;
bounds.top = start.y - height.ascent;
bounds.bottom = end.y + height.descent;
TextSegment* segment = new TextSegment(
utf8.String(), start,
escapement_space, escapement_nospace,
&bounds, &font, pdfSystem());
fTextLine.Add(segment);
if (IsDrawing()) {
}
}
// --------------------------------------------------
bool
PDFWriter::EmbedFont(const char* name)
{
static FontFile* cache = NULL;
if (cache && strcmp(cache->Name(), name) == 0) return cache->Embed();
const int n = fFonts->Length();
for (int i = 0; i < n; i++) {
FontFile* f = fFonts->At(i);
if (strcmp(f->Name(), name) == 0) {
cache = f;
return f->Embed(); // Size() < fEmbedMaxFontSize;
}
}
return false;
}