[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