You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/ksvg/impl/libs/libtext2path/src/Converter.cpp

512 lines
16 KiB

/*
Copyright (C) 2003 Nikolas Zimmermann <wildfox@kde.org>
This file is part of the KDE project
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
aint with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <math.h>
#include "myboost/shared_ptr.h"
#include <fontconfig/fontconfig.h>
#include <fribidi/fribidi.h>
#ifdef FRIBIDI_NEW_FILENAME
#include <fribidi/fribidi-types.h>
#else
#include <fribidi/fribidi_types.h>
#endif
#include "Font.h"
#include "Glyph.h"
#include "Tools.h"
#include "Rectangle.h"
#include "Converter.h"
#include "QtUnicode.h"
#include "GlyphTracer.h"
// Macros
#define FT_TRUNC(x) ((x) >> 6)
#define FT_TOFLOAT(x) ((x) * (1.0 / 64.0))
#define FT_FROMFLOAT(x) ((int) floor ((x) * 64.0 + 0.5))
const double deg2rad = 0.017453292519943295769; // pi/180
using namespace T2P;
// Font engine. The core component.
Converter::Converter(GlyphTracer *tracer) : m_glyphTracer(tracer)
{
m_init = false;
m_kerning = true;
m_library = 0;
}
Converter::~Converter()
{
if(m_glyphTracer)
delete m_glyphTracer;
m_fontCache.clear();
m_glyphCache.clear();
// Close FreeType2 library
if(m_library)
FT_Done_FreeType(m_library);
}
void Converter::init()
{
// Initialize FreeType2
m_init = (FT_Init_FreeType(&m_library) == 0);
}
bool Converter::ready()
{
return m_init;
}
void Converter::setKerning(bool mode)
{
m_kerning = mode;
}
// Lookup font in cache or create new font
SharedFont Converter::requestFont(const FontVisualParams *params)
{
std::string cacheKey = cacheFontKey(params);
SharedFont cached = m_fontCache.find(cacheKey);
// If not available in cache, create new one and cache it :)
if(cached)
{
delete params;
return cached;
}
else
{
// Create a new Font
SharedFont newFont(new Font(this));
// Load the font
if(!newFont->load(params))
{
delete params;
return SharedFont();
}
// Append to cache
m_fontCache.insert(cacheKey, newFont);
return newFont;
}
}
GlyphAffinePair *Converter::requestGlyph(GlyphRenderParams *params, Rectangle &bbox, Affine &affine, bool onlyLatin)
{
// 1. Select glyph to have glyphIndex information,
// needed to generate the cache lookup key
selectGlyph(params);
SharedGlyph cached = m_glyphCache.find(cacheGlyphKey(params));
// If not available in cache, render new one and cache it :)
// If we're mixing ie. japanese and latin characters (TTB layout),
// then we also have to re-calculate the glyph again with the appropriate rotation matrix (Niko)
if(!cached || !onlyLatin)
cached = calcGlyph(params, affine, onlyLatin);
// Correct bezier path by setting up the correct scaling matrix
// and multiplying with the current glyph affine
double fontSize = params->font()->fontParams()->size();
T2P::Affine correctAffine;
correctAffine.scale(0.001 * fontSize, -0.001 * fontSize);
correctAffine *= affine;
// Get bbox information
Point p1(FT_TRUNC(cached->ftBbox()->xMin), FT_TRUNC(cached->ftBbox()->yMin));
Point p2(FT_TRUNC(cached->ftBbox()->xMax), FT_TRUNC(cached->ftBbox()->yMax));
bbox.setA(affine.mapPoint(p1));
bbox.setB(affine.mapPoint(p2));
return new GlyphAffinePair(cached.get(), correctAffine);
}
void Converter::selectGlyph(GlyphRenderParams *params)
{
// 1. Select glyph + test if it exists
// |
// |-> if it doesn't exist, replace by a '?' and check if exists
// |-> if it does exist, use it!
params->setGlyphIndex(FT_Get_Char_Index(params->font()->fontFace(), (FT_ULong) params->character()));
if(params->glyphIndex() == 0)
params->setGlyphIndex(FT_Get_Char_Index(params->font()->fontFace(), '?'));
// 2. Load glyph into font's glyphSlot, according to the GlyphLayoutParams
int flags = FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP;
// 3. Don't pass FT_LOAD_VERTICAL_LAYOUT on TTB layouts when rendering
// a latin glyph because it needs to be rotated...
if(params->layout()->tb())
{
Script script;
SCRIPT_FOR_CHAR(script, params->character())
if(script != Latin || params->layout()->glyphOrientationVertical() == 0)
flags |= FT_LOAD_VERTICAL_LAYOUT;
}
FT_Error error = FT_Load_Glyph(params->font()->fontFace(), params->glyphIndex(), flags);
if(error)
params->setGlyphIndex(0);
}
SharedGlyph Converter::calcGlyph(const GlyphRenderParams *params, Affine &affine, bool onlyLatin)
{
// 2. Apply kerning if needed
if(m_kerning && params->lastGlyph() != 0 && params->glyphIndex() != 0)
{
FT_Vector kerningVector;
double kx, ky;
FT_Get_Kerning(params->font()->fontFace(), params->lastGlyph(), params->glyphIndex(), ft_kerning_default, &kerningVector);
kx = FT_TRUNC(kerningVector.x);
ky = FT_TRUNC(kerningVector.y);
affine.dx() += kx + affine.m21() * ky;
// Only apply y kerning in TB mode
if(params->layout()->tb())
affine.dy() += kx + affine.m22() * ky;
}
// 3. Prepare path tracing
FT_OutlineGlyph ftGlyph;
FT_Get_Glyph(params->font()->fontFace()->glyph, reinterpret_cast<FT_Glyph *>(&ftGlyph));
// 3a. Prepare tracing matrix
Affine traceAffine;
traceAffine.scale(1000.0 / params->font()->fontFace()->units_per_EM);
// 3b. Enable character rotation, if needed
if(params->layout()->tb())
{
Script script;
SCRIPT_FOR_CHAR(script, params->character())
if(!onlyLatin && script == Latin)
{
FT_Matrix matrix;
double angle = deg2rad * params->layout()->glyphOrientationVertical();
matrix.xx = (FT_Fixed)( cos(angle) * 0x10000L);
matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
matrix.yx = (FT_Fixed)( sin(angle) * 0x10000L);
matrix.yy = (FT_Fixed)( cos(angle) * 0x10000L);
FT_Glyph_Transform(reinterpret_cast<FT_Glyph>(ftGlyph), &matrix, 0);
}
}
// 4. Begin path tracing
// - Setup glyphOutlineing glyph
// - Start tracing
// - Close path
FT_Outline *ftOut = &ftGlyph->outline;
SharedGlyph glyphOutline(new Glyph());
glyphOutline->setBezierPath(m_glyphTracer->allocBezierPath(ftOut->n_points * 2 + ftOut->n_contours + 1));
glyphOutline->setAffine(traceAffine);
FT_Outline_Decompose(ftOut, m_glyphTracer->outlineFuncs(), glyphOutline.get());
m_glyphTracer->closePath(glyphOutline.get());
// 5. Determine bounding box (aka "cbox")
FT_Glyph_Get_CBox(reinterpret_cast<FT_Glyph>(ftGlyph), ft_glyph_bbox_unscaled, glyphOutline->ftBbox());
// 6. Cache glyph!
m_glyphCache.insert(cacheGlyphKey(params), glyphOutline);
FT_Done_Glyph(reinterpret_cast<FT_Glyph>(ftGlyph));
return glyphOutline;
}
GlyphSet *Converter::calcString(Font *font, const unsigned short *text, unsigned int length, Affine &affine, const GlyphLayoutParams *params, BezierPath *bpath)
{
unsigned short *bidi = 0;
// 0. Apply BiDi Rules
if(params->useBidi())
{
FriBidiCharType baseDir = FRIBIDI_TYPE_N;
FriBidiChar *unicodeIn = new FriBidiChar[length + 1];
FriBidiChar *unicodeOut = new FriBidiChar[length + 1];
bidi = new unsigned short[length + 1];
for(unsigned int i = 0; i < length; i++)
unicodeIn[i] = text[i];
fribidi_log2vis(unicodeIn, length, &baseDir, unicodeOut, 0, 0, 0);
for(unsigned int i = 0; i < length; i++)
bidi[i] = unicodeOut[i];
bidi[length] = 0;
delete []unicodeIn;
delete []unicodeOut;
}
else
bidi = const_cast<unsigned short *>(text);
// Detect character rotation if needed,
// probably working in 0.1% of the cases :/
Script script;
bool onlyLatin = true;
for(unsigned int i = 0; i < length; i++)
{
SCRIPT_FOR_CHAR(script, bidi[i])
if(script != Latin)
{
onlyLatin = false;
break;
}
}
// 1. Set initial font size
double fontSize = font->fontParams()->size();
FT_Set_Char_Size(font->fontFace(), FT_FROMFLOAT(fontSize), FT_FROMFLOAT(fontSize), 0, 0);
// 2. Compute positioning metrics
// - See http://www.freetype.org/freetype2/docs/tutorial/stepX.html (X = 1 or 2)
int pixelHeight = (int) (FT_TOFLOAT(font->fontFace()->size->metrics.ascender - font->fontFace()->size->metrics.descender) * affine.m22());
int pixelBaseline = (int) (FT_TOFLOAT(font->fontFace()->size->metrics.ascender) * affine.m22());
int pixelUnderlinePosition = (int) (((font->fontFace()->ascender - font->fontFace()->underline_position - font->fontFace()->underline_thickness / 2) * fontSize / font->fontFace()->units_per_EM) * affine.m22());
int pixelUnderlineThickness = T2PMAX(1, (int) ((font->fontFace()->underline_thickness * fontSize / font->fontFace()->units_per_EM) * affine.m22()));
// 3. Prepare needed variables for the rendering loop
// - rendering params (layout, font...)
// - bounding box (per glyph, overall)
// - glyph matrix (overall)
// - resulting glyph sets
GlyphRenderParams *renderParams = new GlyphRenderParams();
renderParams->setFont(font);
renderParams->setLayout(params);
renderParams->setLastGlyph(0);
renderParams->setGlyphIndex(0);
Affine glyphAffine(affine);
Rectangle bbox, currentBbox;
GlyphSet *result = new GlyphSet();
// Align to path start, if bpath != 0
Point pathPoint, pathTangent, pathNormal;
double pathLength = 0, pathAdvance = 0;
double startOffset = params->textPathStartOffset();
if(bpath)
{
pathLength = bpath->length();
bpath->pointTangentNormalAt(startOffset, &pathPoint, &pathTangent);
glyphAffine.dx() = pathPoint.x();
glyphAffine.dy() = pathPoint.y();
glyphAffine.rotate(atan2(pathTangent.y(), pathTangent.x()));
std::cout << "[T2P] Starting textPath at: " << pathPoint.x() << ", " << pathPoint.y() << std::endl;
}
// <tspan> elements can have different baseline-shift's
// baseline-shift support (Niko)
double baseline = 0;
if(!params->baselineShift().empty())
{
if(params->baselineShift() == "sub")
baseline += fontSize / 5 + 1;
else if(params->baselineShift() == "super")
baseline -= fontSize / 3 + 1;
else
{
std::string temp = params->baselineShift();
if(temp.find("%") > 0)
baseline -= (atof(temp.c_str()) / 100.0) * fontSize;
else
baseline -= atoi(temp.c_str());
}
if(!params->tb())
glyphAffine.translate(0, baseline);
else
glyphAffine.translate(-baseline, 0);
}
// 4. Enter main rendering loop
for(unsigned int i = 0; i < length; i++)
{
// 4a. Modify character to render
renderParams->setCharacter(bidi[i]);
// 4c. Get glyph outline
GlyphAffinePair *glyphOutline = requestGlyph(renderParams, currentBbox, glyphAffine, onlyLatin);
m_glyphTracer->correctGlyph(glyphOutline);
// 4d. Save advance values
result->m_glyphXAdvance.push_back((font->fontFace()->glyph->advance.x * fontSize / font->fontFace()->units_per_EM) * affine.m11());
result->m_glyphYAdvance.push_back((font->fontFace()->glyph->advance.y * fontSize / font->fontFace()->units_per_EM) * affine.m22());
// 4e. Misc
// - union current + overall bounding box
// - add glyph to list of glyphs
// - apply letter & word spacing
if(renderParams->glyphIndex() != 0)
{
// Remember last glyph for kerning
renderParams->setLastGlyph(renderParams->glyphIndex());
// Union current + overall bounding box
bbox.bboxUnion(bbox, currentBbox);
// Move glyph locations for the next glyph to be
// rendered apply letter & word spacing
bool addGlyph = true;
if(!params->tb())
{
if(bpath)
{
double advance = startOffset + (pathAdvance / pathLength);
if(advance < 0.0 || advance > 1.0) // dont render off-path glyphs
addGlyph = false;
pathAdvance += (result->m_glyphXAdvance.back() + params->letterSpacing() * affine.m11()) + ((bidi[(i + 1)] == ' ') ? params->wordSpacing() * affine.m11() : 0);
std::cout << "[T2P] Adjusting textPath advance: " << pathAdvance << " Path Length: " << pathLength << " StartOffset: " << startOffset << " Position in Path (relative 0-1): " << pathAdvance/pathLength << " Adjusted by offset: " << startOffset + (pathAdvance / pathLength) << std::endl;
bpath->pointTangentNormalAt(startOffset + (pathAdvance / pathLength), &pathPoint, &pathTangent, &pathNormal);
glyphAffine.m11() = affine.m11();
glyphAffine.m12() = affine.m12();
glyphAffine.m21() = affine.m21();
glyphAffine.m22() = affine.m22();
glyphAffine.dx() = pathPoint.x();
glyphAffine.dy() = pathPoint.y();
glyphAffine.rotateAround(atan2(pathTangent.y(), pathTangent.x()), Point(result->m_glyphXAdvance.back() / 2, result->m_glyphYAdvance.back() / 2));
std::cout << "[T2P] Aligning textPath to: " << pathPoint.x() << ", " << pathPoint.y() << std::endl;
if(!params->tb())
glyphAffine.translate(0, baseline);
else
glyphAffine.translate(-baseline, 0);
}
else
glyphAffine.dx() += (result->m_glyphXAdvance.back() + params->letterSpacing() * affine.m11()) + ((bidi[(i + 1)] == ' ') ? params->wordSpacing() * affine.m11() : 0);
}
else
{
double advy = result->m_glyphYAdvance.back();
Script script;
SCRIPT_FOR_CHAR(script, bidi[i])
if(!onlyLatin && script == Latin && params->glyphOrientationVertical() != 0)
advy = result->m_glyphXAdvance.back();
glyphAffine.dy() += (advy + params->letterSpacing() * affine.m22()) + ((bidi[(i + 1)] == ' ') ? params->wordSpacing() * affine.m22() : 0);
}
// Copy bezier path/affine pair
if(addGlyph)
{
result->m_set.push_back(glyphOutline);
result->m_glyphCount++;
}
else
delete glyphOutline;
}
}
// 5. Fill out resulting data structures
result->m_underlinePosition = pixelUnderlinePosition;
result->m_overlinePosition = pixelHeight - pixelUnderlinePosition;
result->m_strikeThroughPosition = (result->m_underlinePosition + result->m_overlinePosition) / 2;
result->m_pixelBaseline = pixelBaseline;
result->m_underlineThickness = pixelUnderlineThickness;
result->m_xpen = glyphAffine.dx() - affine.dx();
result->m_ypen = glyphAffine.dy() - affine.dy();
result->m_bboxX = T2PMAX(1, int(bbox.a().x()));
result->m_bboxY = T2PMAX(1, int(bbox.a().y() - int(pixelHeight / 2)));
result->m_height = T2PMAX(1, int(bbox.b().y() - bbox.a().y()));
// Correct bounding box information also on
// vertical layouts! (Niko)
if(!params->tb())
{
if(bpath)
result->m_width = int(pathAdvance);
else
result->m_width = T2PMAX(1, int(bbox.b().x() - bbox.a().x()));
}
else
result->m_width = T2PMAX(1, pixelHeight);
// 6. Cleanup memory
if(text != bidi)
delete []bidi;
delete renderParams;
// 7. Eventually, dump cache statistics
// m_glyphCache.dump();
// m_fontCache.dump();
return result;
}
std::string Converter::cacheFontKey(const FontVisualParams *params) const
{
// TODO: Tune the key
std::string key;
key += Tools::joinList('|', const_cast<FontVisualParams *>(params)->fontList());
key += Tools::a2str(params->weight());
key += Tools::a2str(params->slant());
key += Tools::a2str(params->size());
// std::cout << "Font cache key: " << key << std::endl;
return key;
}
std::string Converter::cacheGlyphKey(const GlyphRenderParams *params) const
{
// TODO: Tune the key
std::string key;
key += params->font()->fontFile();
key += Tools::a2str(params->character());
key += Tools::a2str(params->glyphIndex());
key += Tools::a2str(params->font()->fontParams()->size());
key += Tools::a2str(params->font()->fontParams()->weight());
key += Tools::a2str(params->font()->fontParams()->slant());
// std::cout << "Glyph cache key: " << key << std::endl;
return key;
}