Commit 9cf91f4f authored by Jan Hambrecht's avatar Jan Hambrecht
Browse files

added support for baseline shift

baseline shift is used for sub and super script as well and more.

BUG:15909
parent e37c1413
......@@ -76,6 +76,10 @@ ArtisticTextRange ArtisticTextRange::extract(int from, int count)
if (from < m_rotations.count())
extracted.setRotations(m_rotations.mid(from, count));
extracted.setLetterSpacing(m_letterSpacing);
extracted.setWordSpacing(m_wordSpacing);
extracted.setBaselineShift(m_baselineShift, m_baselineShiftValue);
// remove text
m_text.remove(from, count < 0 ? m_text.length()-from : count);
// remove character transformations
......@@ -199,6 +203,16 @@ void ArtisticTextRange::setBaselineShift(BaselineShift mode, qreal value)
m_baselineShiftValue = value;
}
qreal ArtisticTextRange::subAndSuperScriptSizeFactor()
{
return 0.58; // taken from wikipedia
}
qreal ArtisticTextRange::subAndSuperScriptOffsetFactor()
{
return 1./3.; // taken from wikipedia
}
void ArtisticTextRange::printDebug() const
{
kDebug() << "text:" << m_text;
......
......@@ -133,8 +133,17 @@ public:
/// Returns the optional baseline shift value
qreal baselineShiftValue() const;
/// Returns the normalized baseline shift value in point
qreal baselineShiftValueNormalized() const;
/// Sets baseline shift mode and optional value
void setBaselineShift(BaselineShift mode, qreal value = 0);
void setBaselineShift(BaselineShift mode, qreal value);
/// Returns the factor to calculate sub and super script font size
static qreal subAndSuperScriptSizeFactor();
/// Returns the factor to calculatre sub and super script offsets
static qreal subAndSuperScriptOffsetFactor();
/// Prints debug output
void printDebug() const;
......
......@@ -40,6 +40,7 @@
ArtisticTextShape::ArtisticTextShape()
: m_path(0), m_startOffset(0.0), m_baselineOffset(0.0)
, m_textAnchor( AnchorStart ), m_textUpdateCounter(0)
, m_defaultFont("ComicSans", 20)
{
setShapeId( ArtisticTextShapeID );
cacheGlyphOutlines();
......@@ -109,7 +110,51 @@ QRectF ArtisticTextShape::nullBoundBox() const
QFont ArtisticTextShape::defaultFont() const
{
return QFont("ComicSans", 20);
return m_defaultFont;
}
QPointF adjustCharPosition(const ArtisticTextRange &range, int charIndex, const QPointF &charPos, const QPointF &advance)
{
qreal x = charPos.x();
qreal y = charPos.y();
const bool absoluteXOffset = range.xOffsetType() == ArtisticTextRange::AbsoluteOffset;
const bool absoluteYOffset = range.yOffsetType() == ArtisticTextRange::AbsoluteOffset;
if (range.hasXOffset(charIndex)) {
if (absoluteXOffset)
x = range.xOffset(charIndex);
else
x += range.xOffset(charIndex);
} else {
x += advance.x();
}
if (range.hasYOffset(charIndex)) {
if (absoluteYOffset)
y = range.yOffset(charIndex);
else
y += range.yOffset(charIndex);
} else {
y += advance.y();
}
return QPointF(x, y);
}
qreal baselineShiftValueNormalized(const ArtisticTextRange &range, const QFontMetricsF &metrics)
{
switch(range.baselineShift()) {
case ArtisticTextRange::Sub:
return range.baselineShiftValue();
case ArtisticTextRange::Super:
return range.baselineShiftValue();
case ArtisticTextRange::Percent:
return range.baselineShiftValue() * metrics.height();
case ArtisticTextRange::Length:
return range.baselineShiftValue();
default:
return 0.0;
}
}
void ArtisticTextShape::createOutline()
......@@ -148,38 +193,19 @@ void ArtisticTextShape::createOutline()
charOffset -= QPointF(anchorPosition, 0.0);
QPointF pathPoint;
QPointF offset;
QPointF advance;
qreal rotation = 0.0;
foreach (const ArtisticTextRange &range, m_ranges) {
QFontMetricsF metrics(QFont(range.font(), &m_paintDevice));
const QString localText = range.text();
const int localTextLength = localText.length();
const bool absoluteXOffset = range.xOffsetType() == ArtisticTextRange::AbsoluteOffset;
const bool absoluteYOffset = range.yOffsetType() == ArtisticTextRange::AbsoluteOffset;
const qreal letterSpacing = range.letterSpacing();
for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) {
// apply offset to character
// TODO: handle absolute x and y offsets correctly see svg spec 10.13.3 Text on a path layout rules
if (range.hasXOffset(localCharIndex)) {
if (absoluteXOffset)
charOffset.rx() = range.xOffset(localCharIndex);
else
charOffset.rx() += range.xOffset(localCharIndex);
} else {
charOffset.rx() += offset.x();
}
if (range.hasYOffset(localCharIndex)) {
if (absoluteYOffset)
charOffset.ry() = range.yOffset(localCharIndex);
else
charOffset.ry() += range.yOffset(localCharIndex);
} else {
charOffset.ry() += offset.y();
}
charOffset = adjustCharPosition(range, localCharIndex, charOffset, advance);
// get the percent value of the actual char position
qreal t = m_baseline.percentAtLength( charOffset.x() );
......@@ -207,7 +233,7 @@ void ArtisticTextShape::createOutline()
m_charPositions[globalCharIndex] = pathPoint;
// add the advance of the current character to the character position
offset = QPointF(currentCharWidth+letterSpacing, 0.0);
advance = QPointF(currentCharWidth+letterSpacing, 0.0);
if (t <= 0.0) {
// if this is not the first character but our position is still
......@@ -234,43 +260,30 @@ void ArtisticTextShape::createOutline()
}
}
// save offset and position after last character
m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(charOffset.x()+offset.x());
m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(charOffset.x()+advance.x());
m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(m_charOffsets[globalCharIndex]);
} else {
QPointF charPos(0, 0);
QPointF offset(0, 0);
QPointF advance(0, 0);
qreal rotation = 0.0;
foreach(const ArtisticTextRange &range, m_ranges) {
QFontMetricsF metrics(QFont(range.font(), &m_paintDevice));
const QString textRange = range.text();
const bool absoluteXOffset = range.xOffsetType() == ArtisticTextRange::AbsoluteOffset;
const bool absoluteYOffset = range.yOffsetType() == ArtisticTextRange::AbsoluteOffset;
const qreal letterSpacing = range.letterSpacing();
const int localTextLength = textRange.length();
// set baseline shift
const qreal baselineShift = baselineShiftValueNormalized(range, metrics);
for(int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) {
// apply offset to character
if (range.hasXOffset(localCharIndex)) {
if (absoluteXOffset)
charPos.rx() = range.xOffset(localCharIndex);
else
charPos.rx() += range.xOffset(localCharIndex);
} else {
charPos.rx() += offset.x();
}
if (range.hasYOffset(localCharIndex)) {
if (absoluteYOffset)
charPos.ry() = range.yOffset(localCharIndex);
else
charPos.ry() += range.yOffset(localCharIndex);
} else {
charPos.ry() += offset.y();
}
charPos = adjustCharPosition(range, localCharIndex, charPos, advance);
if (range.hasRotation(localCharIndex))
rotation = range.rotation(localCharIndex);
charPos.ry() += baselineShift;
QTransform m;
m.translate(charPos.x(), charPos.y());
m.rotate(rotation);
......@@ -278,10 +291,12 @@ void ArtisticTextShape::createOutline()
// save character position of current character
m_charPositions[globalCharIndex] = charPos;
// advance character position
offset = QPointF(metrics.width(textRange[localCharIndex])+letterSpacing, 0.0);
advance = QPointF(metrics.width(textRange[localCharIndex])+letterSpacing, 0.0);
charPos.ry() -= baselineShift;
}
}
m_charPositions[globalCharIndex] = charPos + offset;
m_charPositions[globalCharIndex] = charPos + advance;
}
}
......@@ -354,6 +369,8 @@ void ArtisticTextShape::setFont(const QFont &newFont)
m_ranges[i].setFont(newFont);
}
m_defaultFont = newFont;
finishTextUpdate();
}
......@@ -362,6 +379,11 @@ void ArtisticTextShape::setFont(int charIndex, int charCount, const QFont &font)
if (isEmpty() || charCount <= 0)
return;
if (charIndex == 0 && charCount == plainText().length()) {
setFont(font);
return;
}
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || charPos.first >= m_ranges.count())
return;
......@@ -603,6 +625,33 @@ QList<ArtisticTextRange> ArtisticTextShape::removeText(int charIndex, int charCo
return extractedRanges;
}
QList<ArtisticTextRange> ArtisticTextShape::copyText(int charIndex, int charCount)
{
QList<ArtisticTextRange> extractedRanges;
if (!charCount)
return extractedRanges;
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || charPos.first >= m_ranges.count())
return extractedRanges;
int extractedTextLength = 0;
while(extractedTextLength < charCount) {
ArtisticTextRange copy = m_ranges[charPos.first];
ArtisticTextRange r = copy.extract(charPos.second, charCount-extractedTextLength);
extractedTextLength += r.text().length();
extractedRanges.append(r);
if (extractedTextLength == charCount)
break;
charPos.first++;
if(charPos.first >= m_ranges.count())
break;
charPos.second = 0;
}
return extractedRanges;
}
void ArtisticTextShape::insertText(int charIndex, const QString &str)
{
if (isEmpty()) {
......@@ -715,6 +764,13 @@ void ArtisticTextShape::appendText(const ArtisticTextRange &text)
}
bool ArtisticTextShape::replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange)
{
QList<ArtisticTextRange> ranges;
ranges.append(textRange);
return replaceText(charIndex, charCount, ranges);
}
bool ArtisticTextShape::replaceText(int charIndex, int charCount, const QList<ArtisticTextRange> &textRanges)
{
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || !charCount)
......@@ -723,7 +779,7 @@ bool ArtisticTextShape::replaceText(int charIndex, int charCount, const Artistic
beginTextUpdate();
removeText(charIndex, charCount);
insertText(charIndex, textRange);
insertText(charIndex, textRanges);
finishTextUpdate();
......
......@@ -103,6 +103,9 @@ public:
*/
QFont fontAt(int charIndex) const;
/// Returns the default font
QFont defaultFont() const;
/// Attaches this text shape to the given path shape
bool putOnPath(KoPathShape *path);
......@@ -143,9 +146,12 @@ public:
/// Returns a pointer to the shape used as baseline
KoPathShape * baselineShape() const;
/// Removes a range of text from the given index
/// Removes a range of text starting from the given character
QList<ArtisticTextRange> removeText(int charIndex, int charCount);
/// Copies a range of text starting from the given character
QList<ArtisticTextRange> copyText(int charIndex, int charCount);
/// Adds a range of text at the given index
void insertText(int charIndex, const QString &plainText);
......@@ -164,6 +170,9 @@ public:
/// Replaces a range of text with the specified text range
bool replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange);
/// Replaces a range of text with the specified text ranges
bool replaceText(int charIndex, int charCount, const QList<ArtisticTextRange> &textRanges);
/// Gets the angle of the char with the given index
qreal charAngleAt(int charIndex) const;
......@@ -191,9 +200,6 @@ private:
/// Returns the bounding box for an empty text shape
QRectF nullBoundBox() const;
/// Returns the default font
QFont defaultFont() const;
QList<ArtisticTextRange> m_ranges;
KoPostscriptPaintDevice m_paintDevice;
KoPathShape * m_path; ///< the path shape we are attached to
......@@ -207,6 +213,7 @@ private:
QVector<qreal> m_charOffsets; ///< char positions [0..1] on baseline path
QVector<QPointF> m_charPositions; ///< char positions in shape coordinates
int m_textUpdateCounter;
QFont m_defaultFont;
};
#endif // ARTISTICTEXTSHAPE_H
......@@ -31,6 +31,7 @@
#include "ChangeTextOffsetCommand.h"
#include "ChangeTextFontCommand.h"
#include "ChangeTextAnchorCommand.h"
#include "ReplaceTextRangeCommand.h"
#include <KoCanvasBase.h>
#include <KoSelection.h>
......@@ -83,10 +84,12 @@ ArtisticTextTool::ArtisticTextTool(KoCanvasBase *canvas)
addAction("artistictext_font_italic", m_fontItalic);
m_superScript = new KAction(KIcon("format-text-superscript"), i18n("Superscript"), this);
m_superScript->setCheckable(true);
connect(m_superScript, SIGNAL(triggered()), this, SLOT(setSuperScript()));
addAction("artistictext_superscript", m_superScript);
m_subScript = new KAction(KIcon("format-text-subscript"), i18n("Subscript"), this);
m_subScript->setCheckable(true);
connect(m_subScript, SIGNAL(triggered()), this, SLOT(setSubScript()));
addAction("artistictext_subscript", m_subScript);
......@@ -498,8 +501,12 @@ void ArtisticTextTool::deactivate()
void ArtisticTextTool::updateActions()
{
if( m_currentShape ) {
const bool hasSelection = m_selection.hasSelection();
const QFont font = m_currentShape->fontAt(textCursor());
const CharIndex index = m_currentShape->indexOfChar(textCursor());
ArtisticTextRange::BaselineShift baselineShift = ArtisticTextRange::None;
if (index.first >= 0) {
baselineShift = m_currentShape->text().at(index.first).baselineShift();
}
m_fontBold->blockSignals(true);
m_fontBold->setChecked(font.bold());
m_fontBold->blockSignals(false);
......@@ -517,8 +524,14 @@ void ArtisticTextTool::updateActions()
}
m_anchorGroup->blockSignals(false);
m_anchorGroup->setEnabled(true);
m_superScript->setEnabled(hasSelection);
m_subScript->setEnabled(hasSelection);
m_superScript->blockSignals(true);
m_superScript->setChecked(baselineShift == ArtisticTextRange::Super);
m_superScript->blockSignals(false);
m_subScript->blockSignals(true);
m_subScript->setChecked(baselineShift == ArtisticTextRange::Sub);
m_subScript->blockSignals(false);
m_superScript->setEnabled(true);
m_subScript->setEnabled(true);
} else {
m_detachPath->setEnabled(false);
m_convertText->setEnabled(false);
......@@ -839,12 +852,46 @@ void ArtisticTextTool::setFontSize(int size)
void ArtisticTextTool::setSuperScript()
{
toggleSubSuperScript(ArtisticTextRange::Super);
}
void ArtisticTextTool::setSubScript()
{
toggleSubSuperScript(ArtisticTextRange::Sub);
}
void ArtisticTextTool::toggleSubSuperScript(ArtisticTextRange::BaselineShift mode)
{
if (!m_currentShape || !m_selection.hasSelection())
return;
const int from = m_selection.selectionStart();
const int count = m_selection.selectionCount();
const qreal offsetDirection = mode == ArtisticTextRange::Sub ? 1.0 : -1.0;
const qreal offsetFactor = offsetDirection * ArtisticTextRange::subAndSuperScriptOffsetFactor();
QList<ArtisticTextRange> ranges = m_currentShape->copyText(from, count);
const int rangeCount = ranges.count();
if (!rangeCount)
return;
// determine if we want to disable the specified mode
const bool disableMode = ranges.first().baselineShift() == mode;
const qreal fontSize = m_currentShape->defaultFont().pointSizeF();
for (int i = 0; i < rangeCount; ++i) {
ArtisticTextRange &currentRange = ranges[i];
QFont font = currentRange.font();
if (disableMode) {
currentRange.setBaselineShift(ArtisticTextRange::None, 0.0);
font.setPointSizeF(fontSize);
} else {
currentRange.setBaselineShift(mode, fontSize*offsetFactor);
font.setPointSizeF(fontSize*ArtisticTextRange::subAndSuperScriptSizeFactor());
}
currentRange.setFont(font);
}
canvas()->addCommand(new ReplaceTextRangeCommand(m_currentShape, ranges, from, count, this));
}
#include <ArtisticTextTool.moc>
......@@ -115,6 +115,9 @@ private:
/// Changes the specified font property for the current text selection
void changeFontProperty(FontProperty property, const QVariant &value);
/// Toggle sub and super script
void toggleSubSuperScript(ArtisticTextRange::BaselineShift mode);
/// returns the transformation matrix for the text cursor
QTransform cursorTransform() const;
......
......@@ -22,6 +22,7 @@ SET ( ArtisticTextShape_SRCS
RemoveTextRangeCommand.cpp
MoveStartOffsetStrategy.cpp
SelectTextStrategy.cpp
ReplaceTextRangeCommand.cpp
)
kde4_add_ui_files( ArtisticTextShape_SRCS
......
/* This file is part of the KDE project
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
*
* 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 "ReplaceTextRangeCommand.h"
#include "ArtisticTextShape.h"
#include <KLocale>
ReplaceTextRangeCommand::ReplaceTextRangeCommand(ArtisticTextShape *shape, const QString &text, int from, int count, ArtisticTextTool *tool, QUndoCommand *parent)
: QUndoCommand(parent), m_tool(tool), m_shape(shape), m_from(from), m_count(count)
{
setText( i18n("Replace text range") );
m_newFormattedText.append(ArtisticTextRange(text, shape->fontAt(m_from)));
m_oldFormattedText = shape->text();
}
ReplaceTextRangeCommand::ReplaceTextRangeCommand(ArtisticTextShape *shape, const ArtisticTextRange &text, int from, int count, ArtisticTextTool *tool, QUndoCommand *parent)
: QUndoCommand(parent), m_tool(tool), m_shape(shape), m_from(from), m_count(count)
{
setText( i18n("Replace text range") );
m_newFormattedText.append(text);
m_oldFormattedText = shape->text();
}
ReplaceTextRangeCommand::ReplaceTextRangeCommand(ArtisticTextShape *shape, const QList<ArtisticTextRange> &text, int from, int count, ArtisticTextTool *tool, QUndoCommand *parent)
: QUndoCommand(parent), m_tool(tool), m_shape(shape), m_from(from), m_count(count)
{
setText( i18n("Replace text range") );
m_newFormattedText = text;
m_oldFormattedText = shape->text();
}
void ReplaceTextRangeCommand::redo()
{
QUndoCommand::redo();
if ( !m_shape )
return;
m_shape->replaceText(m_from, m_count, m_newFormattedText);
if (m_tool) {
int length = 0;
foreach(const ArtisticTextRange &range, m_newFormattedText) {
length += range.text().length();
}
m_tool->setTextCursor(m_shape, m_from + length);
}
}
void ReplaceTextRangeCommand::undo()
{
QUndoCommand::undo();
if ( ! m_shape )
return;
m_shape->clear();
foreach(const ArtisticTextRange &range, m_oldFormattedText) {
m_shape->appendText(range);
}
if (m_tool) {
m_tool->setTextCursor(m_shape, m_from);
}
}
/* This file is part of the KDE project
* Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
*
* 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.
*/
#ifndef REPLACETEXTRANGECOMMAND_H
#define REPLACETEXTRANGECOMMAND_H
#include <QtGui/QUndoCommand>
#include "ArtisticTextTool.h"
#include "ArtisticTextRange.h"
#include <QtCore/QPointer>
class ArtisticTextShape;
/// Undo command to replace a range of text on an artistic text shape
class ReplaceTextRangeCommand : public QUndoCommand
{
public:
ReplaceTextRangeCommand(ArtisticTextShape *shape, const QString &text, int from, int count, ArtisticTextTool *tool, QUndoCommand *parent = 0);
ReplaceTextRangeCommand(ArtisticTextShape *shape, const ArtisticTextRange &text, int from, int count, ArtisticTextTool *tool, QUndoCommand *parent = 0);
ReplaceTextRangeCommand(ArtisticTextShape *shape, const QList<ArtisticTextRange> &text, int from, int count, ArtisticTextTool *tool, QUndoCommand *parent = 0);
virtual void redo();
virtual void undo();
private:
QPointer<ArtisticTextTool> m_tool;
ArtisticTextShape *m_shape;
QList<ArtisticTextRange> m_newFormattedText;
QList<ArtisticTextRange> m_oldFormattedText;