Commit 6dd7cf66 authored by Dileep Sankhla's avatar Dileep Sankhla Committed by Tobias Deiminger

Add typewriter annotation tool

Summary:
Typewriter is originally specified by the PDF reference as special FreeText annotation, where Intent=FreeTextTypewriter. It features opaque letters on transparent background, so that users can fill non interactive forms. Herewith typewriter is implemented natively for PDF, and there's also an Okular specific implementation for other document types. The added tool reuses the inline note UI.

This work was done during GSoC 2018. See https://community.kde.org/GSoC/2018/StatusReports/DileepSankhla for details.

FEATURE: 353401

Test Plan:
- okularpartrc is generated (if not yet existing) with typewriter as 10th tool
- typewriter tool is also available in Annotation Tools -> Add, Typ "Typewriter"
- selecting the tool and left click into document opens inline note input dialog
- finishing creates an annotation similar to inline note, but with transparent background
- saving into PDF results in /Subtype FreeText /IT /FreeTextTypeWriter
- saving typewriter into archive stores color with alpha channel = 0x00
- opening annotated archive works, if archive was created with old Okular, and opened in patched Okular
- opening annotated archive works, if archive was created with patched Okular, and opened in old Okular

Reviewers: sander

Reviewed By: sander

Subscribers: ngraham, sander, okular-devel

Tags: #okular

Differential Revision: https://phabricator.kde.org/D15204
parent 61e8b1d7
......@@ -92,6 +92,7 @@ EditAnnotToolDialog::EditAnnotToolDialog( QWidget *parent, const QDomElement &in
m_type->addItem( i18n("Text markup"), qVariantFromValue( ToolTextMarkup ) );
m_type->addItem( i18n("Geometrical shape"), qVariantFromValue( ToolGeometricalShape ) );
m_type->addItem( i18n("Stamp"), qVariantFromValue( ToolStamp ) );
m_type->addItem( i18n("Typewriter"), qVariantFromValue( ToolTypewriter ) );
createStubAnnotation();
......@@ -265,6 +266,19 @@ QDomDocument EditAnnotToolDialog::toolXml() const
annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Stamp") );
annotationElement.setAttribute( QStringLiteral("icon"), sa->stampIconName() );
}
else if ( toolType == ToolTypewriter )
{
Okular::TextAnnotation * ta = static_cast<Okular::TextAnnotation*>( m_stubann );
toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("typewriter") );
engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PickPoint") );
engineElement.setAttribute( QStringLiteral("color"), color );
engineElement.setAttribute( QStringLiteral("block"), QStringLiteral("true") );
annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Typewriter") );
annotationElement.setAttribute( QStringLiteral("color"), color );
annotationElement.setAttribute( QStringLiteral("width"), width );
if ( ta->textFont() != QApplication::font() )
annotationElement.setAttribute( QStringLiteral("font"), ta->textFont().toString() );
}
if ( opacity != QStringLiteral("1") )
annotationElement.setAttribute( QStringLiteral("opacity"), opacity );
......@@ -341,6 +355,15 @@ void EditAnnotToolDialog::createStubAnnotation()
sa->setStampIconName( QStringLiteral("okular") );
m_stubann = sa;
}
else if ( toolType == ToolTypewriter )
{
Okular::TextAnnotation * ta = new Okular::TextAnnotation();
ta->setTextType( Okular::TextAnnotation::InPlace );
ta->setInplaceIntent( Okular::TextAnnotation::TypeWriter );
ta->style().setWidth( 0.0 );
ta->style().setColor( QColor(255,255,255,0) );
m_stubann = ta;
}
}
void EditAnnotToolDialog::rebuildAppearanceBox()
......@@ -471,6 +494,17 @@ void EditAnnotToolDialog::loadTool( const QDomElement &toolElement )
Okular::HighlightAnnotation * ha = static_cast<Okular::HighlightAnnotation*>( m_stubann );
ha->setHighlightType( Okular::HighlightAnnotation::Underline );
}
else if ( annotType == QLatin1String("typewriter") )
{
setToolType( ToolTypewriter );
Okular::TextAnnotation * ta = static_cast<Okular::TextAnnotation*>( m_stubann );
if ( annotationElement.hasAttribute( QStringLiteral("font") ) )
{
QFont f;
f.fromString( annotationElement.attribute( QStringLiteral("font") ) );
ta->setTextFont( f );
}
}
// Common properties
if ( annotationElement.hasAttribute( QStringLiteral("color") ) )
......
......@@ -40,7 +40,8 @@ class EditAnnotToolDialog : public QDialog
ToolPolygon,
ToolTextMarkup,
ToolGeometricalShape,
ToolStamp
ToolStamp,
ToolTypewriter
};
explicit EditAnnotToolDialog( QWidget *parent = nullptr, const QDomElement &initialState = QDomElement() );
......
......@@ -117,7 +117,12 @@ void AnnotsPropertiesDialog::setCaptionTextbyAnnotType()
if(((Okular::TextAnnotation*)m_annot)->textType()==Okular::TextAnnotation::Linked)
captiontext = i18n( "Pop-up Note Properties" );
else
captiontext = i18n( "Inline Note Properties" );
{
if(((Okular::TextAnnotation*)m_annot)->inplaceIntent()==Okular::TextAnnotation::TypeWriter)
captiontext = i18n( "Typewriter Properties" );
else
captiontext = i18n( "Inline Note Properties" );
}
break;
case Okular::Annotation::ALine:
if ( ((Okular::LineAnnotation*)m_annot)->linePoints().count() == 2 )
......
This diff is collapsed.
......@@ -91,13 +91,20 @@ protected:
virtual QWidget * createStyleWidget();
virtual QWidget * createExtraWidget();
private:
virtual bool hasColorButton() const { return true; }
virtual bool hasOpacityBox() const { return true; }
Okular::Annotation * m_ann;
QWidget * m_appearanceWidget;
QWidget * m_extraWidget;
KColorButton *m_colorBn;
QSpinBox *m_opacity;
QWidget * m_appearanceWidget { nullptr };
QWidget * m_extraWidget { nullptr };
KColorButton *m_colorBn { nullptr };
QSpinBox *m_opacity { nullptr };
};
class QVBoxLayout;
class QGridLayout;
class TextAnnotationWidget
: public AnnotationWidget
{
......@@ -112,11 +119,24 @@ protected:
QWidget * createStyleWidget() override;
private:
virtual bool hasColorButton() const override;
virtual bool hasOpacityBox() const override;
void createPopupNoteStyleUi( QWidget * widget, QVBoxLayout * layout );
void createInlineNoteStyleUi( QWidget * widget, QVBoxLayout * layout );
void createTypewriterStyleUi( QWidget * widget, QVBoxLayout * layout );
void addPixmapSelector( QWidget * widget, QLayout * layout );
void addFontRequester( QWidget * widget, QGridLayout * layout );
void addTextAlignComboBox( QWidget * widget, QGridLayout * layout );
void addWidthSpinBox( QWidget * widget, QGridLayout * layout );
inline bool isTypewriter() const { return ( m_textAnn->inplaceIntent() == Okular::TextAnnotation::TypeWriter ); }
Okular::TextAnnotation * m_textAnn;
PixmapPreviewSelector * m_pixmapSelector;
KFontRequester * m_fontReq;
QComboBox * m_textAlign;
QDoubleSpinBox * m_spinWidth;
PixmapPreviewSelector * m_pixmapSelector { nullptr };
KFontRequester * m_fontReq { nullptr };
QComboBox * m_textAlign { nullptr };
QDoubleSpinBox * m_spinWidth { nullptr };
};
class StampAnnotationWidget
......
......@@ -259,7 +259,15 @@ void AnnotWindow::updateAnnotation( Okular::Annotation * a )
void AnnotWindow::reloadInfo()
{
const QColor newcolor = m_annot->style().color().isValid() ? m_annot->style().color() : Qt::yellow;
QColor newcolor;
if ( m_annot->subType() == Okular::Annotation::AText )
{
Okular::TextAnnotation * textAnn = static_cast< Okular::TextAnnotation * >( m_annot );
if ( textAnn->textType() == Okular::TextAnnotation::InPlace && textAnn->inplaceIntent() == Okular::TextAnnotation::TypeWriter )
newcolor = QColor("#fdfd96");
}
if ( !newcolor.isValid() )
newcolor = m_annot->style().color().isValid() ? QColor(m_annot->style().color().red(), m_annot->style().color().green(), m_annot->style().color().blue(), 255) : Qt::yellow;
if ( newcolor != m_color )
{
m_color = newcolor;
......
......@@ -21,6 +21,8 @@ install(FILES
tool-note-inline.png
tool-note-inline-okular-colorizable.png
tool-note-inline-okular-colorizable@2x.png
tool-typewriter-okular-colorizable.png
tool-typewriter-okular-colorizable@2x.png
DESTINATION ${KDE_INSTALL_DATADIR}/okular/pics)
# install annotation page images
install(FILES
......
......@@ -18,37 +18,37 @@ Engine/Annotation Types [specific attributes]:
-->
<annotatingTools>
<tool id="1" type="note-linked">
<engine type="PickPoint" color="#ffff00" hoverIcon="tool-note">
<engine type="PickPoint" color="#ffffff00" hoverIcon="tool-note">
<annotation type="Text" color="#ffffff00" icon="Note" />
</engine>
<shortcut>1</shortcut>
</tool>
<tool id="2" type="note-inline">
<engine type="PickPoint" color="#ffff00" hoverIcon="tool-note-inline" block="true">
<engine type="PickPoint" color="#ffffff00" hoverIcon="tool-note-inline" block="true">
<annotation type="FreeText" color="#ffffff00" />
</engine>
<shortcut>2</shortcut>
</tool>
<tool id="3" type="ink">
<engine type="SmoothLine" color="#00ff00">
<engine type="SmoothLine" color="#ff00ff00">
<annotation type="Ink" color="#ff00ff00" width="2" />
</engine>
<shortcut>3</shortcut>
</tool>
<tool id="4" type="highlight">
<engine type="TextSelector" color="#ffff00">
<engine type="TextSelector" color="#ffffff00">
<annotation type="Highlight" color="#ffffff00" />
</engine>
<shortcut>4</shortcut>
</tool>
<tool id="5" type="straight-line">
<engine type="PolyLine" color="#ffe000" points="2">
<engine type="PolyLine" color="#ffffe000" points="2">
<annotation type="Line" width="1" color="#ffffe000" />
</engine>
<shortcut>5</shortcut>
</tool>
<tool id="6" type="polygon">
<engine type="PolyLine" color="#007eee" points="-1">
<engine type="PolyLine" color="#ff007eee" points="-1">
<annotation type="Line" width="1" color="#ff007eee" />
</engine>
<shortcut>6</shortcut>
......@@ -60,15 +60,20 @@ Engine/Annotation Types [specific attributes]:
<shortcut>7</shortcut>
</tool>
<tool id="8" type="underline">
<engine type="TextSelector" color="#000000">
<engine type="TextSelector" color="#ff000000">
<annotation type="Underline" color="#ff000000" />
</engine>
<shortcut>8</shortcut>
</tool>
<tool id="9" type="ellipse">
<engine type="PickPoint" color="#00ffff" block="true">
<engine type="PickPoint" color="#ff00ffff" block="true">
<annotation type="GeomCircle" width="5" color="#ff00ffff" />
</engine>
<shortcut>9</shortcut>
</tool>
<tool id="10" type="typewriter">
<engine type="PickPoint" block="true">
<annotation type="Typewriter" color="#00ffffff" width="0" />
</engine>
</tool>
</annotatingTools>
......@@ -73,7 +73,12 @@ QString captionForAnnotation( const Okular::Annotation * ann )
if( ( (Okular::TextAnnotation*)ann )->textType() == Okular::TextAnnotation::Linked )
ret = i18n( "Pop-up Note" );
else
ret = i18n( "Inline Note" );
{
if( ( (Okular::TextAnnotation*)ann )->inplaceIntent() == Okular::TextAnnotation::TypeWriter )
ret = i18n( "Typewriter" );
else
ret = i18n( "Inline Note" );
}
break;
case Okular::Annotation::ALine:
if( ( (Okular::LineAnnotation*)ann )->linePoints().count() == 2 )
......
......@@ -656,7 +656,7 @@ void PagePainter::paintCroppedPageOnPainter( QPainter * destPainter, const Okula
Okular::Annotation * a = *aIt;
// honor opacity settings on supported types
unsigned int opacity = (unsigned int)( 255.0 * a->style().opacity() );
unsigned int opacity = (unsigned int)( a->style().color().alpha() * a->style().opacity() );
// skip the annotation drawing if all the annotation is fully
// transparent, but not with text annotations
if ( opacity <= 0 && a->subType() != Okular::Annotation::AText )
......
......@@ -141,6 +141,44 @@ class PickPointEngine : public AnnotatorEngine
}
}
void addInPlaceTextAnnotation( Okular::Annotation * &ann, const QString summary, const QString content, Okular::TextAnnotation::InplaceIntent inplaceIntent )
{
Okular::TextAnnotation * ta = new Okular::TextAnnotation();
ann = ta;
ta->setFlags( ta->flags() | Okular::Annotation::FixedRotation );
ta->setContents( content );
ta->setTextType( Okular::TextAnnotation::InPlace );
ta->setInplaceIntent( inplaceIntent );
//set alignment
if ( m_annotElement.hasAttribute( QStringLiteral("align") ) )
ta->setInplaceAlignment( m_annotElement.attribute( QStringLiteral("align") ).toInt() );
//set font
if ( m_annotElement.hasAttribute( QStringLiteral("font") ) )
{
QFont f;
f.fromString( m_annotElement.attribute( QStringLiteral("font") ) );
ta->setTextFont( f );
}
//set width
if ( m_annotElement.hasAttribute( QStringLiteral ( "width" ) ) )
{
ta->style().setWidth( m_annotElement.attribute( QStringLiteral ( "width" ) ).toDouble() );
}
//set boundary
rect.left = qMin(startpoint.x,point.x);
rect.top = qMin(startpoint.y,point.y);
rect.right = qMax(startpoint.x,point.x);
rect.bottom = qMax(startpoint.y,point.y);
qCDebug(OkularUiDebug).nospace() << "xyScale=" << xscale << "," << yscale;
static const int padding = 2;
const QFontMetricsF mf(ta->textFont());
const QRectF rcf = mf.boundingRect( Okular::NormalizedRect( rect.left, rect.top, 1.0, 1.0 ).geometry( (int)pagewidth, (int)pageheight ).adjusted( padding, padding, -padding, -padding ),
Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap, ta->contents() );
rect.right = qMax(rect.right, rect.left+(rcf.width()+padding*2)/pagewidth);
rect.bottom = qMax(rect.bottom, rect.top+(rcf.height()+padding*2)/pageheight);
ta->window().setSummary( summary );
}
QList< Okular::Annotation* > end() override
{
// find out annotation's description node
......@@ -154,52 +192,22 @@ class PickPointEngine : public AnnotatorEngine
// find out annotation's type
Okular::Annotation * ann = nullptr;
const QString typeString = m_annotElement.attribute( QStringLiteral("type") );
// create TextAnnotation from path
if ( typeString == QLatin1String("FreeText")) //<annotation type="Text"
// create InPlace TextAnnotation from path
if ( typeString == QLatin1String("FreeText") )
{
//note dialog
const QString prompt = i18n( "Text of the new note:" );
bool resok;
const QString note = QInputDialog::getMultiLineText(nullptr, i18n( "New Text Note" ), prompt, QString(), &resok);
if(resok)
{
//add note
Okular::TextAnnotation * ta = new Okular::TextAnnotation();
ann = ta;
ta->setFlags( ta->flags() | Okular::Annotation::FixedRotation );
ta->setContents( note );
ta->setTextType( Okular::TextAnnotation::InPlace );
//set alignment
if ( m_annotElement.hasAttribute( QStringLiteral("align") ) )
ta->setInplaceAlignment( m_annotElement.attribute( QStringLiteral("align") ).toInt() );
//set font
if ( m_annotElement.hasAttribute( QStringLiteral("font") ) )
{
QFont f;
f.fromString( m_annotElement.attribute( QStringLiteral("font") ) );
ta->setTextFont( f );
}
//set width
if ( m_annotElement.hasAttribute( QStringLiteral ( "width" ) ) )
{
ta->style().setWidth( m_annotElement.attribute( QStringLiteral ( "width" ) ).toDouble() );
}
//set boundary
rect.left = qMin(startpoint.x,point.x);
rect.top = qMin(startpoint.y,point.y);
rect.right = qMax(startpoint.x,point.x);
rect.bottom = qMax(startpoint.y,point.y);
qCDebug(OkularUiDebug).nospace() << "xyScale=" << xscale << "," << yscale;
static const int padding = 2;
const QFontMetricsF mf(ta->textFont());
const QRectF rcf = mf.boundingRect( Okular::NormalizedRect( rect.left, rect.top, 1.0, 1.0 ).geometry( (int)pagewidth, (int)pageheight ).adjusted( padding, padding, -padding, -padding ),
Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap, ta->contents() );
rect.right = qMax(rect.right, rect.left+(rcf.width()+padding*2)/pagewidth);
rect.bottom = qMax(rect.bottom, rect.top+(rcf.height()+padding*2)/pageheight);
ta->window().setSummary( i18n( "Inline Note" ) );
}
const QString content = QInputDialog::getMultiLineText(nullptr, i18n( "New Text Note" ), i18n( "Text of the new note:" ), QString(), &resok);
if( resok )
addInPlaceTextAnnotation( ann, i18n("Inline Note"), content, Okular::TextAnnotation::Unknown );
}
else if ( typeString == QLatin1String("Text"))
else if ( typeString == QLatin1String("Typewriter") )
{
bool resok;
const QString content = QInputDialog::getMultiLineText(nullptr, i18n( "New Text Note" ), i18n( "Text of the new note:" ), QString(), &resok);
if( resok )
addInPlaceTextAnnotation( ann, i18n("Typewriter"), content, Okular::TextAnnotation::TypeWriter );
}
else if ( typeString == QLatin1String("Text") )
{
Okular::TextAnnotation * ta = new Okular::TextAnnotation();
ann = ta;
......@@ -1028,6 +1036,8 @@ void PageViewAnnotator::slotToolSelected( int toolID )
tip = i18nc( "Annotation tool", "Strike out text" );
else if ( annotType == QLatin1String("underline") )
tip = i18nc( "Annotation tool", "Underline text" );
else if ( annotType == QLatin1String("typewriter") )
tip = i18nc( "Annotation tool", "Typewriter Annotation (drag to select a zone)" );
if ( !tip.isEmpty() && !m_continuousMode )
m_pageView->displayMessage( tip, QString(), PageViewMessage::Annotation );
......@@ -1089,6 +1099,8 @@ QString PageViewAnnotator::defaultToolName( const QDomElement &toolElement )
return i18n( "Strike out" );
else if ( annotType == QLatin1String("underline") )
return i18n( "Underline" );
else if ( annotType == QLatin1String("typewriter") )
return i18n( "Typewriter" );
else
return QString();
}
......@@ -1109,7 +1121,7 @@ QPixmap PageViewAnnotator::makeToolPixmap( const QDomElement &toolElement )
pixmap.load( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-base-okular" + imageVariant + ".png") ) );
/* Parse color, innerColor and icon (if present) */
QColor engineColor, innerColor;
QColor engineColor, innerColor, annotColor;
QString icon;
QDomNodeList engineNodeList = toolElement.elementsByTagName( QStringLiteral("engine") );
if ( engineNodeList.size() > 0 )
......@@ -1122,6 +1134,8 @@ QPixmap PageViewAnnotator::makeToolPixmap( const QDomElement &toolElement )
if ( annotationNodeList.size() > 0 )
{
QDomElement annotationEl = annotationNodeList.item( 0 ).toElement();
if ( !annotationEl.isNull() && annotationEl.hasAttribute( QStringLiteral("color") ) )
annotColor = annotationEl.attribute( QStringLiteral("color") );
if ( !annotationEl.isNull() && annotationEl.hasAttribute( QStringLiteral("innerColor") ) )
innerColor = QColor( annotationEl.attribute( QStringLiteral("innerColor") ) );
if ( !annotationEl.isNull() && annotationEl.hasAttribute( QStringLiteral("icon") ) )
......@@ -1231,6 +1245,13 @@ QPixmap PageViewAnnotator::makeToolPixmap( const QDomElement &toolElement )
p.drawLine( 1, 13, 16, 13 );
p.drawLine( 0, 20, 19, 20 );
}
else if ( annotType == QLatin1String("typewriter") )
{
QImage overlay( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("okular/pics/tool-typewriter-okular-colorizable" + imageVariant + ".png") ) );
/* Here we want to colorize the icon with font color instead of background color. Font color is black a.t.m. */
GuiUtils::colorizeImage( overlay, Qt::black );
p.drawImage( QPoint(-2,2), overlay );
}
else
{
/* Unrecognized annotation type -- It shouldn't happen */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment