[education/rkward] /: Implement glyph() functionality in RK() device
Thomas Friedrichsmeier
null at kde.org
Sat Sep 7 10:22:47 BST 2024
Git commit 97176b97a7ce7afa6f9f761b85bfdfa11bd5fca0 by Thomas Friedrichsmeier.
Committed on 18/08/2024 at 08:18.
Pushed by tfry into branch 'master'.
Implement glyph() functionality in RK() device
Needs (much) more testing and cleanup
M +1 -0 ChangeLog
M +8 -0 rkward/rbackend/rkrapi.h
M +54 -0 rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
M +1 -0 rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
M +30 -6 rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
M +15 -2 rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
M +17 -7 rkward/rbackend/rkwarddevice/rkgraphicsdevice_setup.cpp
M +21 -1 rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
https://invent.kde.org/education/rkward/-/commit/97176b97a7ce7afa6f9f761b85bfdfa11bd5fca0
diff --git a/ChangeLog b/ChangeLog
index 1d196d361..b15a0be48 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,5 @@
--- Version 0.8.1 - UNRELEASED
+- Added: Impoement glyph()-functionality in RK() device
- Fixed: Failure to paint symbols in onscreen device, under some circumstances
- Fixed: Potential library conflicts on systems shipping a more recent version of libglib2 than included in the AppImage
- Fixed: Implement capbilities report for RK() device (may fix graphics limitation, but also potential crashes with R >= 4.2.0)
diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index 80e7fef3e..7507dc8e0 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -350,6 +350,14 @@ IMPORT_R_API(R_GE_clipPathFillRule);
IMPORT_R_API(R_GE_maskType);
#endif
+#if R_VERSION >= R_Version(4, 3, 0)
+IMPORT_R_API(R_GE_glyphFontWeight);
+IMPORT_R_API(R_GE_glyphFontStyle);
+IMPORT_R_API(R_GE_glyphFontFile);
+IMPORT_R_API(R_GE_glyphFontIndex);
+IMPORT_R_API(R_GE_glyphFontFamily);
+#endif
+
public:
static void init(void* libr_dll_handle, void* (*dlsym_fun)(void*, const char*));
};
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
index a5725ee28..db9cf1d95 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.cpp
@@ -12,6 +12,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialog>
#include <QTransform>
#include <QFontMetricsF>
+#include <QRawFont>
#include <KLocalizedString>
#include <sys/stat.h>
@@ -498,6 +499,59 @@ void RKGraphicsDevice::text(double x, double y, const QString& text, double rot,
triggerUpdate();
}
+void RKGraphicsDevice::glyph(const QString &font, quint8 index, const QString &family, quint32 weight, QFont::Style style, double size, const QColor &col, double rot, QVector<QPointF> points, QVector<quint32> glyphs) {
+ RK_TRACE(GRAPHICS_DEVICE);
+ RK_ASSERT(points.size() == glyphs.size());
+
+ QRawFont rfnt;
+ if (index == 0) {
+ rfnt = QRawFont(font, size);
+ }
+ if (!rfnt.isValid()) {
+ QFont fnt(family, size, weight);
+ fnt.setStyle(style);
+ rfnt = QRawFont::fromFont(fnt);
+ }
+ // TODO Do we need to adjust size?
+ // R has this is Cairo_Glyph:
+ /* Text size (in "points") MUST match the scale of the glyph
+ * location (in "bigpts"). The latter depends on device dpi.
+ */
+ //cairo_set_font_size(xd->cc, size / (72*dd->ipr[0]));
+
+ // Qt API (6.7) is a little inconsistent in this niche. There is a QGlyphRun, for painting several glyphs at once,
+ // but this cannot procude a QPainterPath. Further, QGlyphRun does not support R's requirement that glyph can be
+ // drawn with rotation, which would necessitate transforming all postions.
+ // For simplicity of code, what we do is to always create painter paths, assembled from individual glyphs.
+ QPainterPath path;
+ for (int i = 0; i < glyphs.size(); ++i) {
+ QPainterPath sub = rfnt.pathForGlyph(glyphs[i]);
+ QTransform trans;
+ auto p = points[i];
+ trans.translate(p.x(), p.y());
+ trans.rotate(-rot);
+ path.addPath(trans.map(sub));
+ }
+
+ if (recording_path) {
+ recorded_path.addPath(path);
+ return;
+ }
+
+ if (current_mask) initMaskedDraw();
+ // While QPainter can draw a whole QGlyphRun at once, it cannot do so with rotation of the individual glyphs (Qt 6.7)
+ // Here we opt for the easy way, and always draw glyphs one by one.
+ for (int i = 0; i < glyphs.size(); ++i) {
+ painter.save();
+ painter.setPen(QPen(col));
+ painter.drawPath(path);
+ painter.restore(); // undo rotation / translation
+ }
+ if (current_mask) commitMaskedDraw();
+
+ triggerUpdate();
+}
+
void RKGraphicsDevice::metricInfo (const QChar& c, const QFont& font, double* ascent, double* descent, double* width) {
RK_TRACE (GRAPHICS_DEVICE);
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
index 86572544e..f634375bc 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice.h
@@ -51,6 +51,7 @@ public:
void rect (const QRectF& rec, const QPen& pen, const QBrush& brush);
QSizeF strSize (const QString &text, const QFont& font);
void text (double x, double y, const QString &text, double rot, double hadj, const QColor& col, const QFont& font);
+ void glyph(const QString &font, quint8 index, const QString &family, quint32 weight, QFont::Style style, double size, const QColor &col, double rot, QVector<QPointF> points, QVector<quint32> glyphs);
void metricInfo (const QChar& c, const QFont& font, double *ascent, double *descent, double *width);
void setClip (const QRectF& new_clip);
void polygon (const QPolygonF& pol, const QPen& pen, const QBrush &brush);
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
index d91f72deb..33cff7328 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_frontendtransmitter.cpp
@@ -1,6 +1,6 @@
/*
rkgraphicsdevice_frontendtransmitter - This file is part of RKWard (https://rkward.kde.org). Created: Mon Mar 18 2013
-SPDX-FileCopyrightText: 2013-2014 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2013-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -205,15 +205,19 @@ static QFont readFont (QDataStream &instream) {
return ret;
}
-static QVector<QPointF> readPoints (QDataStream &instream) {
+static QPointF readPoint(QDataStream &instream) {
+ double x, y;
+ instream >> x >> y;
+ return(QPointF(x, y));
+}
+
+static QVector<QPointF> readPoints(QDataStream &instream) {
quint32 n;
instream >> n;
QVector<QPointF> points;
- points.reserve (n);
+ points.reserve(n);
for (quint32 i = 0; i < n; ++i) {
- double x, y;
- instream >> x >> y;
- points.append (QPointF (x, y));
+ points.append(readPoint(instream));
}
return points;
}
@@ -449,6 +453,26 @@ void RKGraphicsDeviceFrontendTransmitter::newData () {
matrix = QTransform(m[0], m[3], m[1], m[4], m[2], m[5]);
}
device->useGroup(index, matrix);
+ } else if (opcode == RKDGlyph) {
+ QString font, family;
+ quint8 index, style;
+ quint32 weight;
+ double rot, size;
+ QColor col = readColor(streamer.instream);
+ streamer.instream >> font >> index >> family >> weight >> style >> size >> rot;
+ quint32 n;
+ streamer.instream >> n;
+ QVector<QPointF> points;
+ QVector<quint32> glyphs;
+ points.reserve(n);
+ glyphs.reserve(n);
+ for (int i = 0; i < n; ++i) {
+ qint32 glyphi;
+ points.append(readPoint(streamer.instream));
+ streamer.instream >> glyphi;
+ glyphs.append(glyphi);
+ }
+ device->glyph(font, index, family, weight, static_cast<QFont::Style>(style), size, col, rot, points, glyphs);
} else if (opcode == RKDCapture) {
QImage image = device->capture ();
quint32 w = image.width ();
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
index 171867a42..54b754ece 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_protocol_shared.h
@@ -89,6 +89,7 @@ enum RKDOpcodes {
RKDDefineGroupBegin,
RKDDefineGroupStep2,
RKDUseGroup, // 25
+ RKDGlyph,
// Synchronous operations
RKDFetchNextEvent = 100,
@@ -150,7 +151,7 @@ enum RKDEventCodes {
#define MapEnum(Rval,Ival,Qval) case Ival: static_assert(Ival == (int) Qval, "Enum mismatch"); return Qval;
#define MapDefault(Message,Ival,Qval) return Qval;
#define RKD_IN_FRONTEND true
-#define RKD_RGE_VERSION 99999
+#define R_GE_version 99999 // always support latest features in frontend. Backend may or may not use them
#endif
static inline quint8 mapLineEndStyle(quint8 from) {
@@ -174,7 +175,7 @@ static inline quint8 mapLineJoinStyle(quint8 from) {
MapDefault({}, 0x00, Qt::MiterJoin);
}
-#if RKD_RGE_VERSION >= 15
+#if R_GE_version >= 15
static inline int mapCompositionModeEnum(int from) {
if (RKD_IN_FRONTEND) return from;
switch(from) {
@@ -218,4 +219,16 @@ static inline quint8 mapFillRule(quint8 from) {
}
#endif
+#if R_GE_version >= 16
+static inline quint8 mapTextStyle(quint8 from) {
+ if (RKD_IN_FRONTEND) return from;
+ switch(from) {
+ MapEnum(R_GE_text_style_normal, 0, QFont::StyleNormal);
+ MapEnum(R_GE_text_style_italic, 1, QFont::StyleItalic);
+ MapEnum(R_GE_text_style_oblique, 2, QFont::StyleOblique);
+ }
+ MapDefault({}, 0x00, QFont::StyleNormal);
+}
+#endif
+
#endif
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_setup.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_setup.cpp
index 86f80fdf6..38098481d 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_setup.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_setup.cpp
@@ -193,7 +193,7 @@ bool RKGraphicsDeviceDesc::init (pDevDesc dev, double pointsize, const QStringLi
dev->holdflush = RKD_HoldFlush;
#if R_VERSION >= R_Version (4, 1, 0)
- static_assert(RKD_RGE_VERSION >= 13);
+ static_assert(R_GE_version >= 13);
// NOTE: We need both a compiletime and a runtime check, in order to support running with an R older than what was used at compile time
if (RFn::R_GE_getVersion() >= 13) {
// patterns and gradients
@@ -205,7 +205,7 @@ bool RKGraphicsDeviceDesc::init (pDevDesc dev, double pointsize, const QStringLi
// masks
dev->setMask = RKD_SetMask;
dev->releaseMask = RKD_ReleaseMask;
- dev->deviceVersion = qMin(qMin(15, R_GE_version), RFn::R_GE_getVersion());
+ dev->deviceVersion = qMin(qMin(16, R_GE_version), RFn::R_GE_getVersion());
if (RFn::R_GE_getVersion() >= 14) {
dev->deviceClip = TRUE; // for now
}
@@ -213,7 +213,7 @@ bool RKGraphicsDeviceDesc::init (pDevDesc dev, double pointsize, const QStringLi
#endif
#if R_VERSION >= R_Version (4, 2, 0)
- static_assert(RKD_RGE_VERSION >= 15);
+ static_assert(R_GE_version >= 15);
if (RFn::R_GE_getVersion() >= 15) {
// groups
dev->defineGroup = RKD_DefineGroup;
@@ -228,6 +228,14 @@ bool RKGraphicsDeviceDesc::init (pDevDesc dev, double pointsize, const QStringLi
}
#endif
+#if R_VERSION >= R_Version (4, 3, 0)
+ static_assert(R_GE_version >= 16);
+ if (RFn::R_GE_getVersion() >= 16) {
+ // glyhp
+ dev->glyph = RKD_Glyph;
+ }
+#endif
+
// NOTE: When extending support don't forget to adjust dev->devicVersion, above!
return true;
@@ -284,11 +292,13 @@ static SEXP RKD_capabilities(SEXP capabilities) {
});
setCapabilityStruct(capabilities, R_GE_capability_transformations, { 1 });
setCapabilityStruct(capabilities, R_GE_capability_paths, { 1 });
-/*
-#if RKD_RGE_VERSION >= 16 // R >= 4.3.0
- setCapabilityStruct(capabilities, R_GE_capability_glyphs, { 1 });
+
+#if R_GE_version >= 16 // R >= 4.3.0
+ if (RFn::R_GE_getVersion() >= 16) {
+ setCapabilityStruct(capabilities, R_GE_capability_glyphs, { 1 });
+ }
#endif
-*/
+
return capabilities;
}
#endif
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
index 5b54ab940..f4e081796 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
@@ -10,7 +10,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
* It is meant to be included, there. */
#define RKD_BACKEND_CODE
-#define RKD_RGE_VERSION R_GE_version
#include "rkgraphicsdevice_protocol_shared.h"
#include "rkgraphicsdevice_backendtransmitter.h"
#include "../rkrbackend.h"
@@ -935,3 +934,24 @@ void RKD_FillStroke(SEXP path, int rule, const pGEcontext gc, pDevDesc dev) {
doFillAndOrStroke(path, gc, dev, true, rule, true);
}
#endif
+
+#if R_VERSION >= R_Version(4,3,0)
+void RKD_Glyph(int n, int *glyphs, double *x, double *y, SEXP font, double size, int colour, double rot, pDevDesc dev) {
+ RK_TRACE(GRAPHICS_DEVICE);
+ RKGraphicsDataStreamWriteGuard guard;
+ WRITE_HEADER(RKDGlyph, dev);
+
+ QString qfont = QString(RFn::R_GE_glyphFontFile(font));
+ quint8 index = RFn::R_GE_glyphFontIndex(font);
+ QString family = QString(RFn::R_GE_glyphFontFamily(font));
+ quint32 weight = RFn::R_GE_glyphFontWeight(font);
+ quint8 style = mapTextStyle(RFn::R_GE_glyphFontStyle(font));
+ // NOTE: family, weight, and style are used as fallback, if font(-file), and index don't work
+
+ WRITE_COLOR_BYTES(colour);
+ RKD_OUT_STREAM << qfont << index << family << weight << style << size << rot << (quint32) n;
+ for (int i = 0; i < n; ++i) {
+ RKD_OUT_STREAM << x[i] << y[i] << (quint32) glyphs[i];
+ }
+}
+#endif
More information about the rkward-tracker
mailing list