/*
* This file is part of the WebKit project.
*
* Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
*
* 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
* along 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"
#if ENABLE(SVG)
#include "SVGCharacterLayoutInfo.h"
#include "InlineFlowBox.h"
#include "InlineTextBox.h"
#include "SVGLengthList.h"
#include "SVGNumberList.h"
#include "SVGTextPositioningElement.h"
#include "RenderSVGTextPath.h"
#include <float.h>
namespace WebCore {
// Helper function
static float calculateBaselineShift(RenderObject* item)
{
const Font& font = item->style()->font();
const SVGRenderStyle* svgStyle = item->style()->svgStyle();
float baselineShift = 0.0f;
if (svgStyle->baselineShift() == BS_LENGTH) {
CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->baselineShiftValue());
baselineShift = primitive->getFloatValue();
if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
baselineShift = baselineShift / 100.0f * font.pixelSize();
} else {
float baselineAscent = font.ascent() + font.descent();
switch (svgStyle->baselineShift()) {
case BS_BASELINE:
break;
case BS_SUB:
baselineShift = -baselineAscent / 2.0f;
break;
case BS_SUPER:
baselineShift = baselineAscent / 2.0f;
break;
default:
ASSERT_NOT_REACHED();
}
}
return baselineShift;
}
SVGCharacterLayoutInfo::SVGCharacterLayoutInfo(Vector<SVGChar>& chars)
: curx(0.0f)
, cury(0.0f)
, angle(0.0f)
, dx(0.0f)
, dy(0.0f)
, shiftx(0.0f)
, shifty(0.0f)
, pathExtraAdvance(0.0f)
, pathTextLength(0.0f)
, pathChunkLength(0.0f)
, svgChars(chars)
, nextDrawnSeperated(false)
, xStackChanged(false)
, yStackChanged(false)
, dxStackChanged(false)
, dyStackChanged(false)
, angleStackChanged(false)
, baselineShiftStackChanged(false)
, pathLayout(false)
, currentOffset(0.0f)
, startOffset(0.0f)
, layoutPathLength(0.0f)
{
}
bool SVGCharacterLayoutInfo::xValueAvailable() const
{
return xStack.isEmpty() ? false : xStack.last().position() < xStack.last().size();
}
bool SVGCharacterLayoutInfo::yValueAvailable() const
{
return yStack.isEmpty() ? false : yStack.last().position() < yStack.last().size();
}
bool SVGCharacterLayoutInfo::dxValueAvailable() const
{
return dxStack.isEmpty() ? false : dxStack.last().position() < dxStack.last().size();
}
bool SVGCharacterLayoutInfo::dyValueAvailable() const
{
return dyStack.isEmpty() ? false : dyStack.last().position() < dyStack.last().size();
}
bool SVGCharacterLayoutInfo::angleValueAvailable() const
{
return angleStack.isEmpty() ? false : angleStack.last().position() < angleStack.last().size();
}
bool SVGCharacterLayoutInfo::baselineShiftValueAvailable() const
{
return !baselineShiftStack.isEmpty();
}
float SVGCharacterLayoutInfo::xValueNext() const
{
ASSERT(!xStack.isEmpty());
return xStack.last().valueAtCurrentPosition();
}
float SVGCharacterLayoutInfo::yValueNext() const
{
ASSERT(!yStack.isEmpty());
return yStack.last().valueAtCurrentPosition();
}
float SVGCharacterLayoutInfo::dxValueNext() const
{
ASSERT(!dxStack.isEmpty());
return dxStack.last().valueAtCurrentPosition();
}
float SVGCharacterLayoutInfo::dyValueNext() const
{
ASSERT(!dyStack.isEmpty());
return dyStack.last().valueAtCurrentPosition();
}
float SVGCharacterLayoutInfo::angleValueNext() const
{
ASSERT(!angleStack.isEmpty());
return angleStack.last().valueAtCurrentPosition();
}
float SVGCharacterLayoutInfo::baselineShiftValueNext() const
{
ASSERT(!baselineShiftStack.isEmpty());
return baselineShiftStack.last();
}
void SVGCharacterLayoutInfo::processedSingleCharacter()
{
xStackWalk();
yStackWalk();
dxStackWalk();
dyStackWalk();
angleStackWalk();
baselineShiftStackWalk();
}
void SVGCharacterLayoutInfo::processedChunk(float savedShiftX, float savedShiftY)
{
// baseline-shift doesn't span across ancestors, unlike dx/dy.
curx += savedShiftX - shiftx;
cury += savedShiftY - shifty;
if (inPathLayout()) {
shiftx = savedShiftX;
shifty = savedShiftY;
}
// rotation also doesn't span
angle = 0.0f;
if (xStackChanged) {
ASSERT(!xStack.isEmpty());
xStack.removeLast();
xStackChanged = false;
}
if (yStackChanged) {
ASSERT(!yStack.isEmpty());
yStack.removeLast();
yStackChanged = false;
}
if (dxStackChanged) {
ASSERT(!dxStack.isEmpty());
dxStack.removeLast();
dxStackChanged = false;
}
if (dyStackChanged) {
ASSERT(!dyStack.isEmpty());
dyStack.removeLast();
dyStackChanged = false;
}
if (angleStackChanged) {
ASSERT(!angleStack.isEmpty());
angleStack.removeLast();
angleStackChanged = false;
}
if (baselineShiftStackChanged) {
ASSERT(!baselineShiftStack.isEmpty());
baselineShiftStack.removeLast();
baselineShiftStackChanged = false;
}
}
bool SVGCharacterLayoutInfo::nextPathLayoutPointAndAngle(float glyphAdvance, float extraAdvance, float newOffset)
{
if (layoutPathLength <= 0.0f)
return false;
if (newOffset != FLT_MIN)
currentOffset = startOffset + newOffset;
// Respect translation along path (extraAdvance is orthogonal to the path)
currentOffset += extraAdvance;
float offset = currentOffset + glyphAdvance / 2.0f;
currentOffset += glyphAdvance + pathExtraAdvance;
if (offset < 0.0f || offset > layoutPathLength)
return false;
bool ok = false;
FloatPoint point = layoutPath.pointAtLength(offset, ok);
ASSERT(ok);
curx = point.x();
cury = point.y();
angle = layoutPath.normalAngleAtLength(offset, ok);
ASSERT(ok);
// fprintf(stderr, "t: %f, x: %f, y: %f, angle: %f, glyphAdvance: %f\n", currentOffset, x, y, angle, glyphAdvance);
return true;
}
bool SVGCharacterLayoutInfo::inPathLayout() const
{
return pathLayout;
}
void SVGCharacterLayoutInfo::setInPathLayout(bool value)
{
pathLayout = value;
pathExtraAdvance = 0.0f;
pathTextLength = 0.0f;
pathChunkLength = 0.0f;
}
void SVGCharacterLayoutInfo::addLayoutInformation(InlineFlowBox* flowBox, float textAnchorStartOffset)
{
bool isInitialLayout = xStack.isEmpty() && yStack.isEmpty() &&
dxStack.isEmpty() && dyStack.isEmpty() &&
angleStack.isEmpty() && baselineShiftStack.isEmpty() &&
curx == 0.0f && cury == 0.0f;
RenderSVGTextPath* textPath = toRenderSVGTextPath(flowBox->renderer());
Path path = textPath->layoutPath();
float baselineShift = calculateBaselineShift(textPath);
layoutPath = path;
layoutPathLength = path.length();
if (layoutPathLength <= 0.0f)
return;
startOffset = textPath->startOffset();
if (textPath->startOffset() >= 0.0f && textPath->startOffset() <= 1.0f)
startOffset *= layoutPathLength;
startOffset += textAnchorStartOffset;
currentOffset = startOffset;
// Only baseline-shift is handled through the normal layout system
addStackContent(BaselineShiftStack, baselineShift);
if (isInitialLayout) {
xStackChanged = false;
yStackChanged = false;
dxStackChanged = false;
dyStackChanged = false;
angleStackChanged = false;
baselineShiftStackChanged = false;
}
}
void SVGCharacterLayoutInfo::addLayoutInformation(SVGTextPositioningElement* element)
{
bool isInitialLayout = xStack.isEmpty() && yStack.isEmpty() &&
dxStack.isEmpty() && dyStack.isEmpty() &&
angleStack.isEmpty() && baselineShiftStack.isEmpty() &&
curx == 0.0f && cury == 0.0f;
float baselineShift = calculateBaselineShift(element->renderer());
addStackContent(XStack, element->x(), element);
addStackContent(YStack, element->y(), element);
addStackContent(DxStack, element->dx(), element);
addStackContent(DyStack, element->dy(), element);
addStackContent(AngleStack, element->rotate());
addStackContent(BaselineShiftStack, baselineShift);
if (isInitialLayout) {
xStackChanged = false;
yStackChanged = false;
dxStackChanged = false;
dyStackChanged = false;
angleStackChanged = false;
baselineShiftStackChanged = false;
}
}
void SVGCharacterLayoutInfo::addStackContent(StackType type, SVGNumberList* list)
{
unsigned length = list->numberOfItems();
if (!length)
return;
PositionedFloatVector newLayoutInfo;
// TODO: Convert more efficiently!
ExceptionCode ec = 0;
for (unsigned i = 0; i < length; ++i) {
float value = list->getItem(i, ec);
ASSERT(ec == 0);
newLayoutInfo.append(value);
}
addStackContent(type, newLayoutInfo);
}
void SVGCharacterLayoutInfo::addStackContent(StackType type, SVGLengthList* list, const SVGElement* context)
{
unsigned length = list->numberOfItems();
if (!length)
return;
PositionedFloatVector newLayoutInfo;
ExceptionCode ec = 0;
for (unsigned i = 0; i < length; ++i) {
float value = list->getItem(i, ec).value(context);
ASSERT(ec == 0);
newLayoutInfo.append(value);
}
addStackContent(type, newLayoutInfo);
}
void SVGCharacterLayoutInfo::addStackContent(StackType type, const PositionedFloatVector& list)
{
switch (type) {
case XStack:
xStackChanged = true;
xStack.append(list);
break;
case YStack:
yStackChanged = true;
yStack.append(list);
break;
case DxStack:
dxStackChanged = true;
dxStack.append(list);
break;
case DyStack:
dyStackChanged = true;
dyStack.append(list);
break;
case AngleStack:
angleStackChanged = true;
angleStack.append(list);
break;
default:
ASSERT_NOT_REACHED();
}
}
void SVGCharacterLayoutInfo::addStackContent(StackType type, float value)
{
if (value == 0.0f)
return;
switch (type) {
case BaselineShiftStack:
baselineShiftStackChanged = true;
baselineShiftStack.append(value);
break;
default:
ASSERT_NOT_REACHED();
}
}
void SVGCharacterLayoutInfo::xStackWalk()
{
unsigned i = 1;
while (!xStack.isEmpty()) {
PositionedFloatVector& cur = xStack.last();
if (i + cur.position() < cur.size()) {
cur.advance(i);
break;
}
i += cur.position();
xStack.removeLast();
xStackChanged = false;
}
}
void SVGCharacterLayoutInfo::yStackWalk()
{
unsigned i = 1;
while (!yStack.isEmpty()) {
PositionedFloatVector& cur = yStack.last();
if (i + cur.position() < cur.size()) {
cur.advance(i);
break;
}
i += cur.position();
yStack.removeLast();
yStackChanged = false;
}
}
void SVGCharacterLayoutInfo::dxStackWalk()
{
unsigned i = 1;
while (!dxStack.isEmpty()) {
PositionedFloatVector& cur = dxStack.last();
if (i + cur.position() < cur.size()) {
cur.advance(i);
break;
}
i += cur.position();
dxStack.removeLast();
dxStackChanged = false;
}
}
void SVGCharacterLayoutInfo::dyStackWalk()
{
unsigned i = 1;
while (!dyStack.isEmpty()) {
PositionedFloatVector& cur = dyStack.last();
if (i + cur.position() < cur.size()) {
cur.advance(i);
break;
}
i += cur.position();
dyStack.removeLast();
dyStackChanged = false;
}
}
void SVGCharacterLayoutInfo::angleStackWalk()
{
unsigned i = 1;
while (!angleStack.isEmpty()) {
PositionedFloatVector& cur = angleStack.last();
if (i + cur.position() < cur.size()) {
cur.advance(i);
break;
}
i += cur.position();
angleStack.removeLast();
angleStackChanged = false;
}
}
void SVGCharacterLayoutInfo::baselineShiftStackWalk()
{
if (!baselineShiftStack.isEmpty()) {
baselineShiftStack.removeLast();
baselineShiftStackChanged = false;
}
}
bool SVGChar::isHidden() const
{
return pathData && pathData->hidden;
}
AffineTransform SVGChar::characterTransform() const
{
AffineTransform ctm;
// Rotate character around angle, and possibly scale.
ctm.translate(x, y);
ctm.rotate(angle);
if (pathData) {
ctm.scaleNonUniform(pathData->xScale, pathData->yScale);
ctm.translate(pathData->xShift, pathData->yShift);
ctm.rotate(pathData->orientationAngle);
}
ctm.translate(orientationShiftX - x, orientationShiftY - y);
return ctm;
}
} // namespace WebCore
#endif // ENABLE(SVG)