Received: (at submit) by debbugs.gnu.org; 27 Jan 2026 07:50:06 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Jan 27 02:50:05 2026
Received: from localhost ([127.0.0.1]:57884 helo=debbugs.gnu.org)
by debbugs.gnu.org with esmtp (Exim 4.84_2)
(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
id 1vkdpt-00023p-PJ
for submit <at> debbugs.gnu.org; Tue, 27 Jan 2026 02:50:05 -0500
Received: from lists.gnu.org ([2001:470:142::17]:50434)
by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.84_2) (envelope-from <arthur@HIDDEN>)
id 1vkdps-00023A-5v
for submit <at> debbugs.gnu.org; Tue, 27 Jan 2026 02:50:04 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10])
by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
(Exim 4.90_1) (envelope-from <arthur@HIDDEN>)
id 1vkdpm-0002S4-M1
for bug-gnu-emacs@HIDDEN; Tue, 27 Jan 2026 02:49:58 -0500
Received: from mail-ej1-x629.google.com ([2a00:1450:4864:20::629])
by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
(Exim 4.90_1) (envelope-from <arthur@HIDDEN>)
id 1vkdpb-00025g-IT
for bug-gnu-emacs@HIDDEN; Tue, 27 Jan 2026 02:49:58 -0500
Received: by mail-ej1-x629.google.com with SMTP id
a640c23a62f3a-b7cf4a975d2so772647066b.2
for <bug-gnu-emacs@HIDDEN>; Mon, 26 Jan 2026 23:49:47 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=aheymans-xyz.20230601.gappssmtp.com; s=20230601; t=1769500185; x=1770104985;
darn=gnu.org;
h=mime-version:message-id:date:user-agent:subject:to:from:from:to:cc
:subject:date:message-id:reply-to;
bh=XFxUO+bNtihqESTU0nEe4/a6vL6Ac6BfuzKAk4A5Kyw=;
b=R6+12e+fwfCnrAepcKrlPKH4ZOXgVKreko/bjuB3yvZZ/swfdQ5UcDG+lscVoXAuRD
YwF6aqRHu4cEH2mEpYnnQqN2ZzqmEL+Bp2NsdJm4/WdKjx8s7cGbGUxQRaoVh5GXm8Z7
Qy5xd270XsEq1HIx2dDpr65QLxabC8iCidVmMij+TcIJTo2mGYNWDJy1Ez7M5WU1YJ92
yBvQIYa5x3Rf5auGi3It6v6A4PyKhpAC/4lklL3xEAu8S/J+Z9PaVbYv4PTsvxSHN7J1
UhRlQ+o4kJS3XmnK96LMfcJ/r+m88x82WvWaY8aT4xQ47MppSARY4WxDkCic0jEIHTu7
H5DA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20230601; t=1769500185; x=1770104985;
h=mime-version:message-id:date:user-agent:subject:to:from:x-gm-gg
:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;
bh=XFxUO+bNtihqESTU0nEe4/a6vL6Ac6BfuzKAk4A5Kyw=;
b=IUBAdlTyxBM64V7yO4krnv+rREuXnXh4C22F1d+YBl7SO+om5j8vt8AIrnHwfmXdES
8vSwjchZA5T/MLDDdZZfnTxwH2r6KKAhX3KDhxBPbYSXLG4wIaSdT3YMQteDmhS+7U1G
6hQqhRFXDFGo5Nx7QF1CJXtcUf8ABm/tQl3OpvgM34WeCi63i/5BKyFGCpz6JjQwUHNf
JesgI6CHBVsVq1+V4K9/Nlc9v3rKpheIdgqdR6KEv/GzxKdTtkQuFCKwrfWeS1CgKMYC
TGg7db5KuLVe4MqjChBEOvaXXaxkkGdTbt8MjU39+36fvWC13cz2oTkZCH9Uen6PK55z
zC+g==
X-Gm-Message-State: AOJu0YwbpBoUg1md+65m5p7yxEFfPySu/5ksU/Wg8om9t/t7IxFLyRuH
Py3VvW9rLw+wP8hFVEybTmckEj967RYLaQwWR8wYfA1If8xPC+cLUQ20AVIKGdxWRVUJuGpXx2K
ZxiN6U7I=
X-Gm-Gg: AZuq6aJxbKxbpD0rPmmQ/Z1pfvYY32Gd3a8cksrgF2pZcW6N0xrPrSCtMiMOScVKyAk
VcDFGdxjV0euXRqM/N3EoEJNdNUfSjE4iIjDyP+1/iHMdX2QFlu5eodz8YMsaaFBV8AiiecmjuX
Sm+j3GaEC3cyNPOhjsUK0zFLuWqZNpRpQEFS5+frcNKzKg6UK3x3aMK+nI/Z6lry69/2hYwwzV1
vQa3xrF6e6oYT8tv5fUFOR2cgg+p9gni1JsVm7Ei8OkjXGCMz74pXlFZPz8DEFAZ+hHaFCNFw2a
YJKxBPtbzlDxX2b2qX0VXUQRtxwysI8i2lp2wtRajw0jWj5hEEIiYRaMjcobKML2JiVPBzsWMLq
sj3Mf+j4KfGwMLjZXY6eNDLccGKI7Uo7Wf3PncNfB7Cjn+L+6Yx/aLu6c2pGpt57SoYgsALViX8
1Xe0gyLVJAnkgr/0Z4ZwrQizka5mQUF7aPmVGqwJDytM8qZLIUYMlSMCgjojfzkEw60UqA6GF4F
xhhl3rMY9e0FxfwMnJtPXQ=
X-Received: by 2002:a17:907:3e8c:b0:b87:8172:257 with SMTP id
a640c23a62f3a-b8dab4a09f3mr69786466b.64.1769500185030;
Mon, 26 Jan 2026 23:49:45 -0800 (PST)
Received: from gmktec-k11 (228.243-245-81.adsl-dyn.isp.belgacom.be.
[81.245.243.228]) by smtp.gmail.com with ESMTPSA id
a640c23a62f3a-b885b445747sm742450166b.30.2026.01.26.23.49.44
for <bug-gnu-emacs@HIDDEN>
(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
Mon, 26 Jan 2026 23:49:44 -0800 (PST)
From: Arthur Heymans <arthur@HIDDEN>
To: bug-gnu-emacs@HIDDEN
Subject: [PATCH 0/6] Add Skia as alternative graphics backend for PGTK
(resend with attachments)
User-Agent: mu4e 1.12.13; emacs 30.2
Date: Tue, 27 Jan 2026 08:49:43 +0100
Message-ID: <874io7xzoo.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=-=-="
Received-SPF: pass client-ip=2a00:1450:4864:20::629;
envelope-from=arthur@HIDDEN; helo=mail-ej1-x629.google.com
X-Spam_score_int: 14
X-Spam_score: 1.4
X-Spam_bar: +
X-Spam_report: (1.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,
FROM_SUSPICIOUS_NTLD=0.499, FROM_SUSPICIOUS_NTLD_FP=0.843,
PDS_OTHER_BAD_TLD=1.999, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,
SPF_PASS=-0.001 autolearn=no autolearn_force=no
X-Spam_action: no action
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>,
<mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>,
<mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
filename=0001-Add-Skia-C-wrapper-library.patch
From a91e741c90d748029ad29b2b3cb126196bc37ddc Mon Sep 17 00:00:00 2001
From: Arthur Heymans <arthur@HIDDEN>
Date: Tue, 27 Jan 2026 08:03:17 +0100
Subject: [PATCH 1/6] Add Skia C wrapper library
This adds a C wrapper around Google's Skia 2D graphics library. The
wrapper provides a C API for Emacs to use Skia's drawing operations
without requiring C++ in the main Emacs codebase.
* src/skia/emacs_skia.h: New file. C API wrapper for Skia library
providing surface, canvas, paint, font, image, and GL context types
with associated functions for 2D drawing operations.
* src/skia/emacs_skia.cpp: New file. Implementation of the Skia C
wrapper using Skia's C++ API. Supports both CPU and GPU (OpenGL)
rendering backends.
---
src/skia/emacs_skia.cpp | 2271 +++++++++++++++++++++++++++++++++++++++
src/skia/emacs_skia.h | 650 +++++++++++
2 files changed, 2921 insertions(+)
create mode 100644 src/skia/emacs_skia.cpp
create mode 100644 src/skia/emacs_skia.h
diff --git a/src/skia/emacs_skia.cpp b/src/skia/emacs_skia.cpp
new file mode 100644
index 00000000000..5fd0594d083
--- /dev/null
+++ b/src/skia/emacs_skia.cpp
@@ -0,0 +1,2271 @@
+/* Minimal Skia C API implementation for Emacs
+ Copyright (C) 2024-2026 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include "emacs_skia.h"
+
+#include <cstdio>
+
+/* Debug logging for Skia failures.
+ Define SKIA_DEBUG to enable verbose error messages to stderr.
+ This is useful for diagnosing font loading, image decoding,
+ or GL context creation failures. */
+#ifdef SKIA_DEBUG
+#define SKIA_LOG_ERROR(fmt, ...) \
+ fprintf (stderr, "Skia error: " fmt "\n", ##__VA_ARGS__)
+#else
+#define SKIA_LOG_ERROR(fmt, ...) ((void)0)
+#endif
+
+/* Skia C++ headers - paths work with both:
+ - Nix skia package: -I.../include/skia -> codec/SkCodec.h
+ - Source build: -I.../skia -> include/codec/SkCodec.h */
+#include "codec/SkCodec.h"
+#include "core/SkCanvas.h"
+#include "core/SkColorSpace.h"
+#include "core/SkData.h"
+#include "core/SkFont.h"
+#include "core/SkFontMetrics.h"
+#include "core/SkFontMgr.h"
+#include "core/SkImage.h"
+#include "core/SkPaint.h"
+#include "core/SkPath.h"
+#include "core/SkPathBuilder.h"
+#include "core/SkPathEffect.h"
+#include "core/SkShader.h"
+#include "core/SkStream.h"
+#include "core/SkSurface.h"
+#include "core/SkTileMode.h"
+#include "core/SkTypeface.h"
+#include "effects/SkDashPathEffect.h"
+#include "encode/SkPngEncoder.h"
+
+/* Platform-specific font manager for Linux (fontconfig) */
+#ifdef HAVE_FONTCONFIG
+# include <fontconfig/fontconfig.h>
+# include "ports/SkFontMgr_fontconfig.h"
+# include "ports/SkFontScanner_FreeType.h"
+#endif
+
+#include <cmath>
+#include <cstring>
+#include <map>
+#include <memory>
+
+/* PDF and SVG support */
+#ifdef SK_PDF
+# include "docs/SkPDFDocument.h"
+#endif
+
+#ifdef SK_SVG
+# include "svg/SkSVGCanvas.h"
+#endif
+
+#ifdef SK_GL
+# include "gpu/ganesh/GrBackendSurface.h"
+# include "gpu/ganesh/GrDirectContext.h"
+# include "gpu/ganesh/SkSurfaceGanesh.h"
+# include "gpu/ganesh/gl/GrGLAssembleInterface.h"
+# include "gpu/ganesh/gl/GrGLBackendSurface.h"
+# include "gpu/ganesh/gl/GrGLDirectContext.h"
+# include "gpu/ganesh/gl/GrGLInterface.h"
+/* GL types for fence sync - use epoxy for portable GL loading. */
+# include <epoxy/gl.h>
+/* For dlsym fallback when getting GL proc addresses. */
+# include <dlfcn.h>
+#endif
+
+/* ============================================================
+ Internal type wrappers
+ ============================================================ */
+
+/* Canvas wrapper must be defined before surface so it can be embedded. */
+struct emacs_skia_canvas
+{
+ SkCanvas *canvas; /* Borrowed pointer, owned by surface/document */
+};
+
+struct emacs_skia_surface
+{
+ sk_sp<SkSurface> surface;
+ void *pixels; /* For raster surfaces with external pixels */
+#ifdef SK_GL
+ GrDirectContext *context; /* For GPU surfaces, to flush */
+#endif
+ /* Per-surface canvas wrapper to avoid thread-safety issues with
+ static variables. The wrapper holds a borrowed pointer to the
+ SkCanvas owned by the surface. */
+ emacs_skia_canvas canvas_wrapper;
+};
+
+struct emacs_skia_paint
+{
+ SkPaint paint;
+};
+
+struct emacs_skia_font
+{
+ SkFont font;
+ sk_sp<SkTypeface> typeface;
+};
+
+struct emacs_skia_typeface
+{
+ sk_sp<SkTypeface> typeface;
+};
+
+struct emacs_skia_image
+{
+ sk_sp<SkImage> image;
+};
+
+struct emacs_skia_path
+{
+ SkPathBuilder builder;
+};
+
+#ifdef SK_GL
+struct emacs_skia_gl_context
+{
+ sk_sp<GrDirectContext> context;
+};
+#else
+struct emacs_skia_gl_context
+{
+ int dummy;
+};
+#endif
+
+/* ============================================================
+ Constants
+ ============================================================ */
+
+/* GL surface configuration. */
+#ifdef SK_GL
+/* Number of MSAA samples for GL surfaces. 0 = no multisampling. */
+constexpr int GL_SURFACE_MSAA_SAMPLES = 0;
+/* Number of stencil buffer bits. Skia needs stencil for clip mask
+ operations. 8 bits is standard and widely supported. */
+constexpr int GL_SURFACE_STENCIL_BITS = 8;
+#endif
+
+/* ============================================================
+ Helper functions
+ ============================================================ */
+
+static inline SkRect
+to_sk_rect (const emacs_skia_rect_t *r)
+{
+ return SkRect::MakeLTRB (r->left, r->top, r->right, r->bottom);
+}
+
+static inline SkIRect
+to_sk_irect (const emacs_skia_irect_t *r)
+{
+ return SkIRect::MakeLTRB (r->left, r->top, r->right, r->bottom);
+}
+
+static inline SkPoint
+to_sk_point (emacs_skia_point_t p)
+{
+ return SkPoint::Make (p.x, p.y);
+}
+
+static inline SkColor
+to_sk_color (emacs_skia_color_t c)
+{
+ return static_cast<SkColor> (c);
+}
+
+static inline SkBlendMode
+to_sk_blend_mode (emacs_skia_blend_mode_t mode)
+{
+ switch (mode)
+ {
+ case EMACS_SKIA_BLEND_SRC:
+ return SkBlendMode::kSrc;
+ case EMACS_SKIA_BLEND_SRC_OVER:
+ return SkBlendMode::kSrcOver;
+ case EMACS_SKIA_BLEND_DST_OVER:
+ return SkBlendMode::kDstOver;
+ case EMACS_SKIA_BLEND_CLEAR:
+ return SkBlendMode::kClear;
+ case EMACS_SKIA_BLEND_XOR:
+ return SkBlendMode::kXor;
+ case EMACS_SKIA_BLEND_DIFFERENCE:
+ return SkBlendMode::kDifference;
+ case EMACS_SKIA_BLEND_EXCLUSION:
+ return SkBlendMode::kExclusion;
+ default:
+ return SkBlendMode::kSrcOver;
+ }
+}
+
+static inline SkFontHinting
+to_sk_hinting (emacs_skia_hinting_t h)
+{
+ switch (h)
+ {
+ case EMACS_SKIA_HINTING_NONE:
+ return SkFontHinting::kNone;
+ case EMACS_SKIA_HINTING_SLIGHT:
+ return SkFontHinting::kSlight;
+ case EMACS_SKIA_HINTING_NORMAL:
+ return SkFontHinting::kNormal;
+ case EMACS_SKIA_HINTING_FULL:
+ return SkFontHinting::kFull;
+ default:
+ return SkFontHinting::kNormal;
+ }
+}
+
+static inline SkFont::Edging
+to_sk_edging (emacs_skia_antialias_t aa)
+{
+ switch (aa)
+ {
+ case EMACS_SKIA_ANTIALIAS_NONE:
+ return SkFont::Edging::kAlias;
+ case EMACS_SKIA_ANTIALIAS_NORMAL:
+ return SkFont::Edging::kAntiAlias;
+ case EMACS_SKIA_ANTIALIAS_SUBPIXEL:
+ return SkFont::Edging::kSubpixelAntiAlias;
+ default:
+ return SkFont::Edging::kAntiAlias;
+ }
+}
+
+/* ============================================================
+ Global font manager
+ ============================================================ */
+
+/* Thread safety: Initialized lazily on first use from the main thread.
+ Emacs display code runs single-threaded, so no synchronization is
+ needed. The Skia SkFontMgr is itself thread-safe once created. */
+static sk_sp<SkFontMgr> global_font_mgr;
+
+static sk_sp<SkFontMgr>
+get_font_mgr (void)
+{
+ if (!global_font_mgr)
+ {
+#ifdef HAVE_FONTCONFIG
+ /* Use fontconfig-based font manager on Linux */
+ global_font_mgr
+ = SkFontMgr_New_FontConfig (nullptr,
+ SkFontScanner_Make_FreeType ());
+#else
+ /* Fallback to empty font manager (typeface creation will fail)
+ */
+ global_font_mgr = SkFontMgr::RefEmpty ();
+#endif
+ }
+ return global_font_mgr;
+}
+
+/* ============================================================
+ Initialization / Cleanup
+ ============================================================ */
+
+void
+emacs_skia_init (void)
+{
+ /* Initialize global font manager */
+ (void) get_font_mgr ();
+}
+
+/* Forward declaration for SVG cleanup. */
+#ifdef SK_SVG
+static void cleanup_svg_canvas_map (void);
+#endif
+
+void
+emacs_skia_cleanup (void)
+{
+#ifdef SK_SVG
+ /* Clean up any leaked SVG canvases. */
+ cleanup_svg_canvas_map ();
+#endif
+
+ /* Release global font manager */
+ global_font_mgr.reset ();
+}
+
+/* ============================================================
+ Capability Queries
+ ============================================================ */
+
+bool
+emacs_skia_has_gl_support (void)
+{
+#ifdef SK_GL
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool
+emacs_skia_has_pdf_support (void)
+{
+#ifdef SK_PDF
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool
+emacs_skia_has_svg_support (void)
+{
+#ifdef SK_SVG
+ return true;
+#else
+ return false;
+#endif
+}
+
+/* ============================================================
+ GL Context
+ ============================================================ */
+
+/* Create GL context using the native GL interface. This uses the
+ currently active GL context (e.g., set by GDK). */
+
+#ifdef SK_GL
+/* GL proc loader using dlsym. This works for both GLX and EGL contexts
+ when the context is already current. libepoxy is used for GL types
+ and function declarations, but we use dlsym for proc address lookup
+ for maximum compatibility.
+
+ Thread safety: Initialized lazily on first GL context creation from
+ the main thread. Once loaded, the handle is never modified or closed.
+ Emacs display code runs single-threaded, so no synchronization needed. */
+static void *gl_lib_handle = nullptr;
+
+static GrGLFuncPtr
+get_gl_proc (void *ctx, const char *name)
+{
+ (void) ctx;
+
+ /* Lazy-load GL library. Try libGL first (desktop), then GLES (mobile/Wayland). */
+ if (!gl_lib_handle)
+ {
+ gl_lib_handle = dlopen ("libGL.so.1", RTLD_LAZY | RTLD_GLOBAL);
+ if (!gl_lib_handle)
+ gl_lib_handle = dlopen ("libGL.so", RTLD_LAZY | RTLD_GLOBAL);
+ if (!gl_lib_handle)
+ gl_lib_handle = dlopen ("libGLESv2.so.2", RTLD_LAZY | RTLD_GLOBAL);
+ if (!gl_lib_handle)
+ gl_lib_handle = dlopen ("libGLESv2.so", RTLD_LAZY | RTLD_GLOBAL);
+ }
+
+ if (!gl_lib_handle)
+ return nullptr;
+
+ return (GrGLFuncPtr) dlsym (gl_lib_handle, name);
+}
+#endif
+
+emacs_skia_gl_context_t *
+emacs_skia_gl_context_create_native (void)
+{
+#ifdef SK_GL
+ /* Use GrGLMakeAssembledInterface with libepoxy's function loader. */
+ auto interface = GrGLMakeAssembledInterface (nullptr, get_gl_proc);
+ if (!interface)
+ {
+ fprintf (stderr, "Skia: Failed to create GL interface "
+ "(is GL context current?)\n");
+ return nullptr;
+ }
+
+ auto grContext = GrDirectContexts::MakeGL (interface);
+ if (!grContext)
+ {
+ fprintf (stderr, "Skia: Failed to create GrDirectContext "
+ "(GL version may be too old)\n");
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_gl_context_t;
+ result->context = grContext;
+ return result;
+#else
+ return nullptr;
+#endif
+}
+
+#ifdef SK_GL
+emacs_skia_gl_context_t *
+emacs_skia_gl_context_create (emacs_skia_gl_get_proc_fn get_proc,
+ void *ctx)
+{
+ auto interface = GrGLMakeAssembledInterface (ctx, (GrGLGetProc)
+ get_proc);
+ if (!interface)
+ {
+ fprintf (stderr, "Skia: Failed to assemble GL interface\n");
+ return nullptr;
+ }
+
+ auto grContext = GrDirectContexts::MakeGL (interface);
+ if (!grContext)
+ {
+ fprintf (stderr, "Skia: Failed to create GrDirectContext\n");
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_gl_context_t;
+ result->context = grContext;
+ return result;
+}
+
+void
+emacs_skia_gl_context_destroy (emacs_skia_gl_context_t *ctx)
+{
+ if (ctx)
+ {
+ ctx->context->abandonContext ();
+ delete ctx;
+ }
+}
+
+void
+emacs_skia_gl_context_flush (emacs_skia_gl_context_t *ctx)
+{
+ if (ctx && ctx->context)
+ ctx->context->flushAndSubmit ();
+}
+
+void
+emacs_skia_gl_context_reset (emacs_skia_gl_context_t *ctx)
+{
+ if (ctx && ctx->context)
+ {
+ /* Reset Skia's internal GL state tracking. This is needed
+ after destroying a surface that was wrapped around a backend
+ render target, as Skia may have cached state related to that
+ target. resetContext() tells Skia to re-query GL state on
+ next use. */
+ ctx->context->resetContext ();
+ }
+}
+#else
+emacs_skia_gl_context_t *
+emacs_skia_gl_context_create (emacs_skia_gl_get_proc_fn get_proc,
+ void *ctx)
+{
+ (void) get_proc;
+ (void) ctx;
+ return nullptr;
+}
+
+void
+emacs_skia_gl_context_destroy (emacs_skia_gl_context_t *ctx)
+{
+ (void) ctx;
+}
+
+void
+emacs_skia_gl_context_flush (emacs_skia_gl_context_t *ctx)
+{
+ (void) ctx;
+}
+
+void
+emacs_skia_gl_context_reset (emacs_skia_gl_context_t *ctx)
+{
+ (void) ctx;
+}
+#endif
+
+/* ============================================================
+ GL Fence Sync
+ ============================================================ */
+
+#ifdef SK_GL
+struct emacs_skia_fence
+{
+ GLsync sync;
+};
+
+emacs_skia_fence_t *
+emacs_skia_fence_create (void)
+{
+ auto *fence = new emacs_skia_fence_t;
+ fence->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ return fence;
+}
+
+bool
+emacs_skia_fence_wait (emacs_skia_fence_t *fence, uint64_t timeout_ns)
+{
+ if (!fence || !fence->sync)
+ return true;
+
+ GLenum result = glClientWaitSync (fence->sync,
+ GL_SYNC_FLUSH_COMMANDS_BIT,
+ timeout_ns);
+ return result != GL_TIMEOUT_EXPIRED;
+}
+
+bool
+emacs_skia_fence_is_signaled (emacs_skia_fence_t *fence)
+{
+ if (!fence || !fence->sync)
+ return true;
+
+ GLint status = GL_UNSIGNALED;
+ GLsizei length;
+ glGetSynciv (fence->sync, GL_SYNC_STATUS, sizeof (status),
+ &length, &status);
+ return status == GL_SIGNALED;
+}
+
+void
+emacs_skia_fence_destroy (emacs_skia_fence_t *fence)
+{
+ if (fence)
+ {
+ if (fence->sync)
+ glDeleteSync (fence->sync);
+ delete fence;
+ }
+}
+
+#else /* !SK_GL */
+
+emacs_skia_fence_t *
+emacs_skia_fence_create (void)
+{
+ return nullptr;
+}
+
+bool
+emacs_skia_fence_wait (emacs_skia_fence_t *fence, uint64_t timeout_ns)
+{
+ (void) fence;
+ (void) timeout_ns;
+ return true;
+}
+
+bool
+emacs_skia_fence_is_signaled (emacs_skia_fence_t *fence)
+{
+ (void) fence;
+ return true;
+}
+
+void
+emacs_skia_fence_destroy (emacs_skia_fence_t *fence)
+{
+ (void) fence;
+}
+
+#endif /* SK_GL */
+
+/* ============================================================
+ Surface
+ ============================================================ */
+
+emacs_skia_surface_t *
+emacs_skia_surface_create_raster (int width, int height)
+{
+ SkImageInfo info = SkImageInfo::MakeN32Premul (width, height);
+ auto surface = SkSurfaces::Raster (info);
+ if (!surface)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_surface_t;
+ result->surface = surface;
+ result->pixels = nullptr;
+#ifdef SK_GL
+ result->context = nullptr;
+#endif
+ return result;
+}
+
+#ifdef SK_GL
+emacs_skia_surface_t *
+emacs_skia_surface_create_gl (emacs_skia_gl_context_t *ctx, int width,
+ int height, unsigned int framebuffer_id,
+ unsigned int format)
+{
+ if (!ctx || !ctx->context)
+ return nullptr;
+
+ GrGLFramebufferInfo fbInfo;
+ fbInfo.fFBOID = framebuffer_id;
+ fbInfo.fFormat = format;
+
+ /* Create backend render target with stencil buffer for clip mask
+ operations. */
+ auto backendRT
+ = GrBackendRenderTargets::MakeGL (width, height,
+ GL_SURFACE_MSAA_SAMPLES,
+ GL_SURFACE_STENCIL_BITS, fbInfo);
+
+ auto surface = SkSurfaces::
+ WrapBackendRenderTarget (ctx->context.get (), backendRT,
+ kBottomLeft_GrSurfaceOrigin,
+ kRGBA_8888_SkColorType, nullptr,
+ nullptr);
+
+ if (!surface)
+ return nullptr;
+
+ auto result = new emacs_skia_surface_t;
+ result->surface = surface;
+ result->pixels = nullptr;
+ result->context = ctx->context.get ();
+ return result;
+}
+#else
+emacs_skia_surface_t *
+emacs_skia_surface_create_gl (emacs_skia_gl_context_t *ctx, int width,
+ int height, unsigned int framebuffer_id,
+ unsigned int format)
+{
+ (void) ctx;
+ (void) width;
+ (void) height;
+ (void) framebuffer_id;
+ (void) format;
+ return nullptr;
+}
+#endif
+
+emacs_skia_surface_t *
+emacs_skia_surface_create_from_pixels (int width, int height,
+ void *pixels, size_t row_bytes)
+{
+ SkImageInfo info = SkImageInfo::MakeN32Premul (width, height);
+ auto surface = SkSurfaces::WrapPixels (info, pixels, row_bytes);
+ if (!surface)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_surface_t;
+ result->surface = surface;
+ result->pixels = pixels;
+#ifdef SK_GL
+ result->context = nullptr;
+#endif
+ return result;
+}
+
+void
+emacs_skia_surface_destroy (emacs_skia_surface_t *surface)
+{
+ if (surface)
+ {
+ delete surface;
+ }
+}
+
+emacs_skia_canvas_t *
+emacs_skia_surface_get_canvas (emacs_skia_surface_t *surface)
+{
+ if (!surface || !surface->surface)
+ return nullptr;
+
+ /* Use the per-surface canvas wrapper to avoid thread-safety issues.
+ The canvas pointer is borrowed from the SkSurface. */
+ surface->canvas_wrapper.canvas = surface->surface->getCanvas ();
+ return &surface->canvas_wrapper;
+}
+
+void *
+emacs_skia_surface_get_pixels (emacs_skia_surface_t *surface)
+{
+ if (!surface || !surface->surface)
+ {
+ return nullptr;
+ }
+ SkPixmap pixmap;
+ if (surface->surface->peekPixels (&pixmap))
+ {
+ return const_cast<void *> (pixmap.addr ());
+ }
+ return surface->pixels;
+}
+
+void
+emacs_skia_surface_flush (emacs_skia_surface_t *surface)
+{
+ if (surface && surface->surface)
+ {
+ /* For raster surfaces (which we currently use), pixels are
+ directly accessible and no flush is needed. For GPU
+ surfaces, we would need to call
+ GrDirectContext::flushAndSubmit() instead. */
+#ifdef SK_GL
+ if (surface->context)
+ surface->context->flushAndSubmit ();
+#endif
+ }
+}
+
+int
+emacs_skia_surface_get_width (emacs_skia_surface_t *surface)
+{
+ return surface ? surface->surface->width () : 0;
+}
+
+int
+emacs_skia_surface_get_height (emacs_skia_surface_t *surface)
+{
+ return surface ? surface->surface->height () : 0;
+}
+
+emacs_skia_image_t *
+emacs_skia_surface_make_image_snapshot (emacs_skia_surface_t *surface)
+{
+ if (!surface || !surface->surface)
+ return nullptr;
+
+ sk_sp<SkImage> image = surface->surface->makeImageSnapshot ();
+ if (!image)
+ return nullptr;
+
+ auto *result = new emacs_skia_image_t;
+ result->image = image;
+ return result;
+}
+
+emacs_skia_image_t *
+emacs_skia_surface_make_image_snapshot_rect (
+ emacs_skia_surface_t *surface, const emacs_skia_irect_t *rect)
+{
+ if (!surface || !surface->surface || !rect)
+ return nullptr;
+
+ SkIRect sk_rect = SkIRect::MakeLTRB (rect->left, rect->top,
+ rect->right, rect->bottom);
+ sk_sp<SkImage> image
+ = surface->surface->makeImageSnapshot (sk_rect);
+ if (!image)
+ return nullptr;
+
+ auto *result = new emacs_skia_image_t;
+ result->image = image;
+ return result;
+}
+
+/* ============================================================
+ Canvas
+ ============================================================ */
+
+void
+emacs_skia_canvas_save (emacs_skia_canvas_t *canvas)
+{
+ if (canvas && canvas->canvas)
+ {
+ canvas->canvas->save ();
+ }
+}
+
+void
+emacs_skia_canvas_save_layer (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *bounds)
+{
+ if (canvas && canvas->canvas)
+ {
+ if (bounds)
+ canvas->canvas->saveLayer (to_sk_rect (bounds), nullptr);
+ else
+ canvas->canvas->saveLayer (nullptr, nullptr);
+ }
+}
+
+void
+emacs_skia_canvas_restore (emacs_skia_canvas_t *canvas)
+{
+ if (canvas && canvas->canvas)
+ {
+ canvas->canvas->restore ();
+ }
+}
+
+int
+emacs_skia_canvas_get_save_count (emacs_skia_canvas_t *canvas)
+{
+ return canvas && canvas->canvas ? canvas->canvas->getSaveCount ()
+ : 0;
+}
+
+void
+emacs_skia_canvas_restore_to_count (emacs_skia_canvas_t *canvas,
+ int count)
+{
+ if (canvas && canvas->canvas)
+ {
+ canvas->canvas->restoreToCount (count);
+ }
+}
+
+void
+emacs_skia_canvas_clear (emacs_skia_canvas_t *canvas,
+ emacs_skia_color_t color)
+{
+ if (canvas && canvas->canvas)
+ {
+ canvas->canvas->clear (to_sk_color (color));
+ }
+}
+
+void
+emacs_skia_canvas_clip_rect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *rect)
+{
+ if (canvas && canvas->canvas && rect)
+ {
+ canvas->canvas->clipRect (to_sk_rect (rect));
+ }
+}
+
+void
+emacs_skia_canvas_clip_irect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_irect_t *rect)
+{
+ if (canvas && canvas->canvas && rect)
+ {
+ canvas->canvas->clipIRect (to_sk_irect (rect));
+ }
+}
+
+void
+emacs_skia_canvas_get_clip_bounds (emacs_skia_canvas_t *canvas,
+ emacs_skia_rect_t *bounds)
+{
+ if (canvas && canvas->canvas && bounds)
+ {
+ SkRect sk_bounds = canvas->canvas->getLocalClipBounds ();
+ bounds->left = sk_bounds.left ();
+ bounds->top = sk_bounds.top ();
+ bounds->right = sk_bounds.right ();
+ bounds->bottom = sk_bounds.bottom ();
+ }
+}
+
+void
+emacs_skia_canvas_translate (emacs_skia_canvas_t *canvas, float dx,
+ float dy)
+{
+ if (canvas && canvas->canvas)
+ {
+ canvas->canvas->translate (dx, dy);
+ }
+}
+
+void
+emacs_skia_canvas_scale (emacs_skia_canvas_t *canvas, float sx,
+ float sy)
+{
+ if (canvas && canvas->canvas)
+ {
+ canvas->canvas->scale (sx, sy);
+ }
+}
+
+void
+emacs_skia_canvas_draw_rect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *rect,
+ emacs_skia_paint_t *paint)
+{
+ if (canvas && canvas->canvas && rect && paint)
+ {
+ canvas->canvas->drawRect (to_sk_rect (rect), paint->paint);
+ }
+}
+
+void
+emacs_skia_canvas_draw_irect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_irect_t *rect,
+ emacs_skia_paint_t *paint)
+{
+ if (canvas && canvas->canvas && rect && paint)
+ {
+ canvas->canvas->drawIRect (to_sk_irect (rect), paint->paint);
+ }
+}
+
+void
+emacs_skia_canvas_draw_line (emacs_skia_canvas_t *canvas, float x0,
+ float y0, float x1, float y1,
+ emacs_skia_paint_t *paint)
+{
+ if (canvas && canvas->canvas && paint)
+ {
+ canvas->canvas->drawLine (x0, y0, x1, y1, paint->paint);
+ }
+}
+
+/* Stack buffer size for glyph positions - covers most common cases. */
+constexpr int GLYPH_STACK_BUFFER_SIZE = 64;
+
+void
+emacs_skia_canvas_draw_glyphs (emacs_skia_canvas_t *canvas, int count,
+ const emacs_skia_glyph_t *glyphs,
+ const emacs_skia_point_t *positions,
+ emacs_skia_point_t origin,
+ emacs_skia_font_t *font,
+ emacs_skia_paint_t *paint)
+{
+ if (!canvas || !canvas->canvas || !glyphs || !positions || !font
+ || !paint || count <= 0)
+ return;
+
+ /* Use stack buffer for small glyph counts to avoid heap allocation.
+ Most text rendering fits within 64 glyphs per call. */
+ SkPoint stack_buffer[GLYPH_STACK_BUFFER_SIZE];
+ SkPoint *sk_positions;
+ bool heap_allocated = false;
+
+ if (count <= GLYPH_STACK_BUFFER_SIZE)
+ {
+ sk_positions = stack_buffer;
+ }
+ else
+ {
+ sk_positions = new SkPoint[count];
+ heap_allocated = true;
+ }
+
+ /* Convert positions to SkPoint array. */
+ for (int i = 0; i < count; i++)
+ {
+ sk_positions[i] = to_sk_point (positions[i]);
+ }
+
+ /* New Skia API uses SkSpan instead of raw pointers. */
+ SkSpan<const SkGlyphID> glyph_span (glyphs, count);
+ SkSpan<const SkPoint> pos_span (sk_positions, count);
+ canvas->canvas->drawGlyphs (glyph_span, pos_span,
+ to_sk_point (origin), font->font,
+ paint->paint);
+
+ if (heap_allocated)
+ delete[] sk_positions;
+}
+
+void
+emacs_skia_canvas_draw_image (emacs_skia_canvas_t *canvas,
+ emacs_skia_image_t *image, float x,
+ float y, emacs_skia_paint_t *paint)
+{
+ if (!canvas || !canvas->canvas || !image || !image->image)
+ {
+ return;
+ }
+
+ SkSamplingOptions sampling (SkFilterMode::kLinear);
+ canvas->canvas->drawImage (image->image.get (), x, y, sampling,
+ paint ? &paint->paint : nullptr);
+}
+
+void
+emacs_skia_canvas_draw_image_rect (emacs_skia_canvas_t *canvas,
+ emacs_skia_image_t *image,
+ const emacs_skia_rect_t *src,
+ const emacs_skia_rect_t *dst,
+ emacs_skia_paint_t *paint)
+{
+ if (!canvas || !canvas->canvas || !image || !image->image || !dst)
+ {
+ return;
+ }
+
+ SkSamplingOptions sampling (SkFilterMode::kLinear);
+ SkRect sk_src = src ? to_sk_rect (src)
+ : SkRect::MakeWH (image->image->width (),
+ image->image->height ());
+
+ canvas->canvas->drawImageRect (image->image.get (), sk_src,
+ to_sk_rect (dst), sampling,
+ paint ? &paint->paint : nullptr,
+ SkCanvas::kStrict_SrcRectConstraint);
+}
+
+void
+emacs_skia_canvas_draw_path (emacs_skia_canvas_t *canvas,
+ emacs_skia_path_t *path,
+ emacs_skia_paint_t *paint)
+{
+ if (canvas && canvas->canvas && path && paint)
+ {
+ /* Convert builder to path for drawing. */
+ canvas->canvas->drawPath (path->builder.snapshot (),
+ paint->paint);
+ }
+}
+
+void
+emacs_skia_canvas_draw_arc (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *oval,
+ float start_angle, float sweep_angle,
+ bool use_center,
+ emacs_skia_paint_t *paint)
+{
+ if (!canvas || !canvas->canvas || !oval || !paint)
+ return;
+
+ canvas->canvas->drawArc (to_sk_rect (oval), start_angle,
+ sweep_angle, use_center, paint->paint);
+}
+
+void
+emacs_skia_canvas_draw_image_with_mask (emacs_skia_canvas_t *canvas,
+ emacs_skia_image_t *image,
+ emacs_skia_image_t *mask,
+ float x, float y,
+ emacs_skia_paint_t *paint)
+{
+ if (!canvas || !canvas->canvas || !image || !image->image)
+ return;
+
+ /* If no mask, just draw the image normally */
+ if (!mask || !mask->image)
+ {
+ emacs_skia_canvas_draw_image (canvas, image, x, y, paint);
+ return;
+ }
+
+ /* Use a layer with mask as alpha:
+ 1. Save the canvas state and create a layer
+ 2. Draw the mask as the alpha channel
+ 3. Draw the image with SrcIn blend mode (uses mask alpha)
+ 4. Restore the layer */
+ canvas->canvas->save ();
+
+ SkRect bounds = SkRect::MakeXYWH (x, y, image->image->width (),
+ image->image->height ());
+ canvas->canvas->saveLayer (bounds, nullptr);
+
+ /* Draw mask as grayscale (will become alpha) */
+ SkSamplingOptions sampling (SkFilterMode::kNearest);
+ canvas->canvas->drawImage (mask->image.get (), x, y, sampling,
+ nullptr);
+
+ /* Draw image with SrcIn to use mask as alpha */
+ SkPaint srcInPaint;
+ srcInPaint.setBlendMode (SkBlendMode::kSrcIn);
+ if (paint)
+ srcInPaint.setColor (paint->paint.getColor ());
+ canvas->canvas->drawImage (image->image.get (), x, y, sampling,
+ &srcInPaint);
+
+ canvas->canvas->restore (); /* Restore layer */
+ canvas->canvas->restore (); /* Restore original state */
+}
+
+void
+emacs_skia_canvas_clip_path (emacs_skia_canvas_t *canvas,
+ emacs_skia_path_t *path)
+{
+ if (canvas && canvas->canvas && path)
+ {
+ /* Convert builder to path for clipping. */
+ canvas->canvas->clipPath (path->builder.snapshot ());
+ }
+}
+
+/* ============================================================
+ Paint
+ ============================================================ */
+
+emacs_skia_paint_t *
+emacs_skia_paint_create (void)
+{
+ auto paint = new emacs_skia_paint_t;
+ paint->paint.setAntiAlias (true);
+ return paint;
+}
+
+void
+emacs_skia_paint_destroy (emacs_skia_paint_t *paint)
+{
+ delete paint;
+}
+
+void
+emacs_skia_paint_set_color (emacs_skia_paint_t *paint,
+ emacs_skia_color_t color)
+{
+ if (paint)
+ {
+ paint->paint.setColor (to_sk_color (color));
+ }
+}
+
+emacs_skia_color_t
+emacs_skia_paint_get_color (emacs_skia_paint_t *paint)
+{
+ return paint ? paint->paint.getColor () : 0;
+}
+
+void
+emacs_skia_paint_set_alpha (emacs_skia_paint_t *paint, uint8_t alpha)
+{
+ if (paint)
+ {
+ paint->paint.setAlpha (alpha);
+ }
+}
+
+void
+emacs_skia_paint_set_antialias (emacs_skia_paint_t *paint,
+ bool antialias)
+{
+ if (paint)
+ {
+ paint->paint.setAntiAlias (antialias);
+ }
+}
+
+void
+emacs_skia_paint_set_blend_mode (emacs_skia_paint_t *paint,
+ emacs_skia_blend_mode_t mode)
+{
+ if (paint)
+ {
+ paint->paint.setBlendMode (to_sk_blend_mode (mode));
+ }
+}
+
+void
+emacs_skia_paint_set_stroke (emacs_skia_paint_t *paint, bool stroke)
+{
+ if (paint)
+ {
+ paint->paint.setStyle (stroke ? SkPaint::kStroke_Style
+ : SkPaint::kFill_Style);
+ }
+}
+
+void
+emacs_skia_paint_set_stroke_width (emacs_skia_paint_t *paint,
+ float width)
+{
+ if (paint)
+ {
+ paint->paint.setStrokeWidth (width);
+ }
+}
+
+void
+emacs_skia_paint_set_dash (emacs_skia_paint_t *paint,
+ const float *intervals, int count,
+ float phase)
+{
+ if (!paint || !intervals || count < 2)
+ return;
+
+ /* Skia requires SkScalar array, which is float.
+ Newer Skia versions use SkSpan instead of pointer+count. */
+ auto effect
+ = SkDashPathEffect::Make (SkSpan<const float> (intervals, count),
+ phase);
+ paint->paint.setPathEffect (effect);
+}
+
+void
+emacs_skia_paint_clear_dash (emacs_skia_paint_t *paint)
+{
+ if (paint)
+ {
+ paint->paint.setPathEffect (nullptr);
+ }
+}
+
+void
+emacs_skia_paint_set_image_shader (emacs_skia_paint_t *paint,
+ emacs_skia_image_t *image)
+{
+ if (!paint || !image || !image->image)
+ return;
+
+ /* Create a tiled shader from the image */
+ SkSamplingOptions sampling (SkFilterMode::kNearest);
+ auto shader
+ = image->image->makeShader (SkTileMode::kRepeat,
+ SkTileMode::kRepeat, sampling);
+ paint->paint.setShader (shader);
+}
+
+void
+emacs_skia_paint_clear_shader (emacs_skia_paint_t *paint)
+{
+ if (paint)
+ {
+ paint->paint.setShader (nullptr);
+ }
+}
+
+/* ============================================================
+ Font and Typeface
+ ============================================================ */
+
+emacs_skia_typeface_t *
+emacs_skia_typeface_create_from_file (const char *path)
+{
+ sk_sp<SkFontMgr> fontMgr = get_font_mgr ();
+ if (!fontMgr)
+ {
+ SKIA_LOG_ERROR ("typeface_create_from_file: no font manager available");
+ return nullptr;
+ }
+
+ auto typeface = fontMgr->makeFromFile (path);
+ if (!typeface)
+ {
+ SKIA_LOG_ERROR ("typeface_create_from_file: failed to load '%s'", path);
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_typeface_t;
+ result->typeface = typeface;
+ return result;
+}
+
+emacs_skia_typeface_t *
+emacs_skia_typeface_create_from_data (const void *data, size_t size)
+{
+ sk_sp<SkFontMgr> fontMgr = get_font_mgr ();
+ if (!fontMgr)
+ {
+ return nullptr;
+ }
+
+ auto skdata = SkData::MakeWithCopy (data, size);
+ auto typeface = fontMgr->makeFromData (skdata);
+ if (!typeface)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_typeface_t;
+ result->typeface = typeface;
+ return result;
+}
+
+emacs_skia_typeface_t *
+emacs_skia_typeface_create_from_name (const char *family_name,
+ int weight, int width,
+ int slant)
+{
+ sk_sp<SkFontMgr> fontMgr = get_font_mgr ();
+ if (!fontMgr)
+ {
+ return nullptr;
+ }
+
+ SkFontStyle style (weight, width,
+ static_cast<SkFontStyle::Slant> (slant));
+ auto typeface = fontMgr->legacyMakeTypeface (family_name, style);
+ if (!typeface)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_typeface_t;
+ result->typeface = typeface;
+ return result;
+}
+
+void
+emacs_skia_typeface_destroy (emacs_skia_typeface_t *typeface)
+{
+ delete typeface;
+}
+
+#ifdef HAVE_FONTCONFIG
+emacs_skia_typeface_t *
+emacs_skia_typeface_create_from_fc_pattern (FcPattern *pattern)
+{
+ if (!pattern)
+ {
+ SKIA_LOG_ERROR ("typeface_create_from_fc_pattern: null pattern");
+ return nullptr;
+ }
+
+ /* Extract the font file path and index from the pattern */
+ FcChar8 *file = nullptr;
+ int index = 0;
+
+ if (FcPatternGetString (pattern, FC_FILE, 0, &file) != FcResultMatch
+ || !file)
+ {
+ SKIA_LOG_ERROR ("typeface_create_from_fc_pattern: no file in pattern");
+ return nullptr;
+ }
+
+ FcPatternGetInteger (pattern, FC_INDEX, 0, &index);
+
+ sk_sp<SkFontMgr> fontMgr = get_font_mgr ();
+ if (!fontMgr)
+ {
+ SKIA_LOG_ERROR ("typeface_create_from_fc_pattern: no font manager");
+ return nullptr;
+ }
+
+ /* Load the typeface from file with the specified face index */
+ auto typeface
+ = fontMgr->makeFromFile (reinterpret_cast<const char *> (file),
+ index);
+ if (!typeface)
+ {
+ SKIA_LOG_ERROR ("typeface_create_from_fc_pattern: failed to load '%s' index %d",
+ file, index);
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_typeface_t;
+ result->typeface = typeface;
+ return result;
+}
+#endif
+
+const char *
+emacs_skia_typeface_get_path (emacs_skia_typeface_t *typeface)
+{
+ /* Skia doesn't expose the font file path directly.
+ This would need platform-specific code or tracking during
+ creation. For now, return nullptr - callers should use the path
+ they originally provided. */
+ (void) typeface;
+ return nullptr;
+}
+
+int
+emacs_skia_typeface_get_index (emacs_skia_typeface_t *typeface)
+{
+ /* Skia doesn't expose the face index directly.
+ Return 0 as the default. */
+ (void) typeface;
+ return 0;
+}
+
+emacs_skia_font_t *
+emacs_skia_font_create (emacs_skia_typeface_t *typeface, float size)
+{
+ auto font = new emacs_skia_font_t;
+ if (typeface && typeface->typeface)
+ {
+ font->font = SkFont (typeface->typeface, size);
+ font->typeface = typeface->typeface;
+ }
+ else
+ {
+ font->font = SkFont (nullptr, size);
+ }
+ font->font.setSubpixel (true);
+ return font;
+}
+
+void
+emacs_skia_font_destroy (emacs_skia_font_t *font)
+{
+ delete font;
+}
+
+void
+emacs_skia_font_set_size (emacs_skia_font_t *font, float size)
+{
+ if (font)
+ {
+ font->font.setSize (size);
+ }
+}
+
+float
+emacs_skia_font_get_size (emacs_skia_font_t *font)
+{
+ return font ? font->font.getSize () : 0;
+}
+
+void
+emacs_skia_font_set_hinting (emacs_skia_font_t *font,
+ emacs_skia_hinting_t hinting)
+{
+ if (font)
+ {
+ font->font.setHinting (to_sk_hinting (hinting));
+ }
+}
+
+void
+emacs_skia_font_set_edging (emacs_skia_font_t *font,
+ emacs_skia_antialias_t edging)
+{
+ if (font)
+ {
+ font->font.setEdging (to_sk_edging (edging));
+ }
+}
+
+void
+emacs_skia_font_set_subpixel (emacs_skia_font_t *font, bool subpixel)
+{
+ if (font)
+ {
+ font->font.setSubpixel (subpixel);
+ }
+}
+
+void
+emacs_skia_font_get_metrics (emacs_skia_font_t *font,
+ emacs_skia_font_metrics_t *metrics)
+{
+ if (!font || !metrics)
+ {
+ return;
+ }
+
+ SkFontMetrics sk_metrics;
+ font->font.getMetrics (&sk_metrics);
+
+ metrics->ascent = sk_metrics.fAscent;
+ metrics->descent = sk_metrics.fDescent;
+ metrics->leading = sk_metrics.fLeading;
+ metrics->avg_char_width = sk_metrics.fAvgCharWidth;
+ metrics->max_char_width = sk_metrics.fMaxCharWidth;
+ metrics->x_height = sk_metrics.fXHeight;
+ metrics->cap_height = sk_metrics.fCapHeight;
+}
+
+void
+emacs_skia_font_get_extents (emacs_skia_font_t *font,
+ emacs_skia_font_extents_t *extents)
+{
+ if (!font || !extents)
+ return;
+
+ SkFontMetrics sk_metrics;
+ font->font.getMetrics (&sk_metrics);
+
+ /* Cairo convention: ascent and descent are positive values.
+ Skia convention: ascent is negative (distance up from baseline).
+ */
+ extents->ascent = -sk_metrics.fAscent;
+ extents->descent = sk_metrics.fDescent;
+ extents->height
+ = -sk_metrics.fAscent + sk_metrics.fDescent + sk_metrics.fLeading;
+ extents->max_x_advance = sk_metrics.fMaxCharWidth;
+ extents->max_y_advance = 0; /* Horizontal fonts */
+}
+
+void
+emacs_skia_font_get_glyph_extents (
+ emacs_skia_font_t *font, const emacs_skia_glyph_t *glyphs,
+ int count, emacs_skia_glyph_extents_t *extents)
+{
+ if (!font || !glyphs || !extents || count <= 0)
+ return;
+
+ /* Get bounds and widths for all glyphs */
+ std::vector<SkRect> bounds (count);
+ std::vector<SkScalar> widths (count);
+
+ SkSpan<const SkGlyphID> glyph_span (glyphs, count);
+ SkSpan<SkScalar> width_span (widths.data (), count);
+ SkSpan<SkRect> bounds_span (bounds.data (), count);
+
+ font->font.getWidthsBounds (glyph_span, width_span, bounds_span,
+ nullptr);
+
+ /* Convert to emacs_skia_glyph_extents_t format
+ (compatible with Cairo's cairo_text_extents_t) */
+ for (int i = 0; i < count; i++)
+ {
+ extents[i].x_bearing = bounds[i].left ();
+ extents[i].y_bearing = bounds[i].top ();
+ extents[i].width = bounds[i].width ();
+ extents[i].height = bounds[i].height ();
+ extents[i].x_advance = widths[i];
+ extents[i].y_advance = 0; /* Horizontal fonts */
+ }
+}
+
+void
+emacs_skia_font_get_glyph_bounds (emacs_skia_font_t *font,
+ const emacs_skia_glyph_t *glyphs,
+ int count,
+ emacs_skia_rect_t *bounds)
+{
+ if (!font || !glyphs || !bounds || count <= 0)
+ return;
+
+ std::vector<SkRect> sk_bounds (count);
+ SkSpan<const SkGlyphID> glyph_span (glyphs, count);
+ SkSpan<SkRect> bounds_span (sk_bounds.data (), count);
+
+ font->font.getBounds (glyph_span, bounds_span, nullptr);
+
+ for (int i = 0; i < count; i++)
+ {
+ bounds[i].left = sk_bounds[i].left ();
+ bounds[i].top = sk_bounds[i].top ();
+ bounds[i].right = sk_bounds[i].right ();
+ bounds[i].bottom = sk_bounds[i].bottom ();
+ }
+}
+
+int
+emacs_skia_font_text_to_glyphs (emacs_skia_font_t *font,
+ const char *text, size_t byte_length,
+ emacs_skia_glyph_t *glyphs,
+ int max_glyphs)
+{
+ if (!font || !text)
+ {
+ return 0;
+ }
+
+ /* New Skia API uses SkSpan for output buffer */
+ SkSpan<SkGlyphID> glyph_span (glyphs, max_glyphs);
+ size_t count
+ = font->font.textToGlyphs (text, byte_length,
+ SkTextEncoding::kUTF8, glyph_span);
+ return static_cast<int> (count);
+}
+
+emacs_skia_glyph_t
+emacs_skia_font_char_to_glyph (emacs_skia_font_t *font,
+ int32_t codepoint)
+{
+ if (!font)
+ {
+ return 0;
+ }
+ return font->font.unicharToGlyph (codepoint);
+}
+
+void
+emacs_skia_font_get_widths (emacs_skia_font_t *font,
+ const emacs_skia_glyph_t *glyphs,
+ int count, float *widths)
+{
+ if (!font || !glyphs || !widths)
+ {
+ return;
+ }
+ /* New Skia API uses SkSpan instead of raw pointers */
+ SkSpan<const SkGlyphID> glyph_span (glyphs, count);
+ SkSpan<SkScalar> width_span (widths, count);
+ font->font.getWidths (glyph_span, width_span);
+}
+
+float
+emacs_skia_font_measure_text (emacs_skia_font_t *font,
+ const char *text, size_t byte_length)
+{
+ if (!font || !text)
+ {
+ return 0;
+ }
+ return font->font.measureText (text, byte_length,
+ SkTextEncoding::kUTF8);
+}
+
+/* ============================================================
+ Image
+ ============================================================ */
+
+emacs_skia_image_t *
+emacs_skia_image_create_from_pixels (int width, int height,
+ const void *pixels,
+ size_t row_bytes, bool has_alpha)
+{
+ SkColorType colorType
+ = has_alpha ? kRGBA_8888_SkColorType : kRGB_888x_SkColorType;
+ SkAlphaType alphaType
+ = has_alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType;
+ SkImageInfo info
+ = SkImageInfo::Make (width, height, colorType, alphaType);
+
+ auto image = SkImages::RasterFromPixmapCopy (
+ SkPixmap (info, pixels, row_bytes));
+
+ if (!image)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_image_t;
+ result->image = image;
+ return result;
+}
+
+emacs_skia_image_t *
+emacs_skia_image_create_from_bgra_pixels (int width, int height,
+ const void *pixels,
+ size_t row_bytes,
+ bool has_alpha)
+{
+ /* BGRA is Cairo's native format on little-endian machines.
+ Skia supports BGRA natively with kBGRA_8888_SkColorType. */
+ SkColorType colorType = kBGRA_8888_SkColorType;
+ SkAlphaType alphaType
+ = has_alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType;
+ SkImageInfo info
+ = SkImageInfo::Make (width, height, colorType, alphaType);
+
+ auto image = SkImages::RasterFromPixmapCopy (
+ SkPixmap (info, pixels, row_bytes));
+
+ if (!image)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_image_t;
+ result->image = image;
+ return result;
+}
+
+emacs_skia_image_t *
+emacs_skia_image_create_from_encoded (const void *data, size_t size)
+{
+ auto skdata = SkData::MakeWithCopy (data, size);
+ auto image = SkImages::DeferredFromEncodedData (skdata);
+
+ if (!image)
+ {
+ return nullptr;
+ }
+
+ auto result = new emacs_skia_image_t;
+ result->image = image;
+ return result;
+}
+
+void
+emacs_skia_image_destroy (emacs_skia_image_t *image)
+{
+ delete image;
+}
+
+int
+emacs_skia_image_get_width (emacs_skia_image_t *image)
+{
+ return image && image->image ? image->image->width () : 0;
+}
+
+int
+emacs_skia_image_get_height (emacs_skia_image_t *image)
+{
+ return image && image->image ? image->image->height () : 0;
+}
+
+emacs_skia_image_t *
+emacs_skia_image_create_from_bitmap (const unsigned char *data,
+ int width, int height,
+ int stride)
+{
+ if (!data || width <= 0 || height <= 0)
+ return nullptr;
+
+ /* Calculate stride if not provided */
+ if (stride <= 0)
+ stride = (width + 7) / 8;
+
+ /* Convert 1-bit packed data to 8-bit alpha.
+ Cairo A1 format: MSB first, rows padded to 32-bit boundary.
+ We convert to Skia A8 format for compatibility. */
+ int a8_stride = width;
+ std::vector<uint8_t> a8_data (width * height);
+
+ for (int y = 0; y < height; y++)
+ {
+ const unsigned char *src_row = data + y * stride;
+ uint8_t *dst_row = a8_data.data () + y * a8_stride;
+
+ for (int x = 0; x < width; x++)
+ {
+ int byte_idx = x / 8;
+ int bit_idx = 7 - (x % 8); /* MSB first */
+ bool bit_set = (src_row[byte_idx] >> bit_idx) & 1;
+ dst_row[x] = bit_set ? 255 : 0;
+ }
+ }
+
+ /* Create A8 (alpha-only) image */
+ SkImageInfo info
+ = SkImageInfo::Make (width, height, kAlpha_8_SkColorType,
+ kPremul_SkAlphaType);
+ auto image = SkImages::RasterFromPixmapCopy (
+ SkPixmap (info, a8_data.data (), a8_stride));
+
+ if (!image)
+ return nullptr;
+
+ auto result = new emacs_skia_image_t;
+ result->image = image;
+ return result;
+}
+
+/* ============================================================
+ Path
+ ============================================================ */
+
+emacs_skia_path_t *
+emacs_skia_path_create (void)
+{
+ return new emacs_skia_path_t;
+}
+
+void
+emacs_skia_path_destroy (emacs_skia_path_t *path)
+{
+ delete path;
+}
+
+void
+emacs_skia_path_reset (emacs_skia_path_t *path)
+{
+ if (path)
+ path->builder.reset ();
+}
+
+void
+emacs_skia_path_move_to (emacs_skia_path_t *path, float x, float y)
+{
+ if (path)
+ path->builder.moveTo (x, y);
+}
+
+void
+emacs_skia_path_line_to (emacs_skia_path_t *path, float x, float y)
+{
+ if (path)
+ path->builder.lineTo (x, y);
+}
+
+void
+emacs_skia_path_rel_line_to (emacs_skia_path_t *path, float dx,
+ float dy)
+{
+ if (path)
+ path->builder.rLineTo (dx, dy);
+}
+
+void
+emacs_skia_path_arc_to (emacs_skia_path_t *path,
+ const emacs_skia_rect_t *oval,
+ float start_angle, float sweep_angle,
+ bool force_move_to)
+{
+ if (!path || !oval)
+ return;
+
+ /* SkPathBuilder::arcTo uses forceMoveTo parameter. */
+ path->builder.arcTo (to_sk_rect (oval), start_angle, sweep_angle,
+ force_move_to);
+}
+
+void
+emacs_skia_path_close (emacs_skia_path_t *path)
+{
+ if (path)
+ path->builder.close ();
+}
+
+void
+emacs_skia_path_add_rect (emacs_skia_path_t *path,
+ const emacs_skia_rect_t *rect)
+{
+ if (path && rect)
+ path->builder.addRect (to_sk_rect (rect));
+}
+
+/* ============================================================
+ Document Export (PDF/SVG)
+ ============================================================ */
+
+/* Custom SkWStream that writes to a callback function. */
+class CallbackWStream : public SkWStream
+{
+public:
+ CallbackWStream (emacs_skia_write_fn fn, void *ctx)
+ : write_fn_ (fn), ctx_ (ctx), bytes_written_ (0)
+ {
+ }
+
+ bool write (const void *buffer, size_t size) override
+ {
+ if (!write_fn_)
+ return false;
+ size_t written = write_fn_ (ctx_, buffer, size);
+ bytes_written_ += written;
+ return written == size;
+ }
+
+ void flush () override
+ {
+ /* Callback stream doesn't buffer, so nothing to flush. */
+ }
+
+ size_t bytesWritten () const override { return bytes_written_; }
+
+private:
+ emacs_skia_write_fn write_fn_;
+ void *ctx_;
+ size_t bytes_written_;
+};
+
+struct emacs_skia_document
+{
+#ifdef SK_PDF
+ sk_sp<SkDocument> document;
+ std::unique_ptr<CallbackWStream> stream;
+ SkCanvas *current_page; /* Borrowed from document */
+ /* Per-document canvas wrapper for thread safety. */
+ emacs_skia_canvas canvas_wrapper;
+#else
+ int dummy;
+#endif
+};
+
+/* Canvas wrapper for SVG that owns its stream. */
+struct emacs_skia_svg_canvas_data
+{
+ std::unique_ptr<CallbackWStream> stream;
+ std::unique_ptr<SkCanvas> canvas;
+ /* Per-SVG-canvas wrapper for thread safety. */
+ emacs_skia_canvas canvas_wrapper;
+};
+
+#ifdef SK_PDF
+emacs_skia_document_t *
+emacs_skia_document_create_pdf (emacs_skia_write_fn write_fn,
+ void *write_ctx, float width,
+ float height)
+{
+ auto stream
+ = std::make_unique<CallbackWStream> (write_fn, write_ctx);
+
+ SkPDF::Metadata metadata;
+ /* Could add metadata like title, creator, etc. here. */
+
+ auto document = SkPDF::MakeDocument (stream.get (), metadata);
+ if (!document)
+ return nullptr;
+
+ auto result = new emacs_skia_document_t;
+ result->document = document;
+ result->stream = std::move (stream);
+ result->current_page = nullptr;
+
+ return result;
+}
+
+emacs_skia_canvas_t *
+emacs_skia_document_begin_page (emacs_skia_document_t *doc,
+ float width, float height)
+{
+ if (!doc || !doc->document)
+ return nullptr;
+
+ /* End any existing page first. */
+ if (doc->current_page)
+ {
+ doc->document->endPage ();
+ doc->current_page = nullptr;
+ }
+
+ doc->current_page
+ = doc->document->beginPage (width, height, nullptr);
+ if (!doc->current_page)
+ return nullptr;
+
+ /* Use the per-document canvas wrapper for thread safety. */
+ doc->canvas_wrapper.canvas = doc->current_page;
+ return &doc->canvas_wrapper;
+}
+
+void
+emacs_skia_document_end_page (emacs_skia_document_t *doc)
+{
+ if (doc && doc->document && doc->current_page)
+ {
+ doc->document->endPage ();
+ doc->current_page = nullptr;
+ }
+}
+
+void
+emacs_skia_document_close (emacs_skia_document_t *doc)
+{
+ if (doc)
+ {
+ if (doc->document)
+ {
+ /* End any open page. */
+ if (doc->current_page)
+ doc->document->endPage ();
+ doc->document->close ();
+ }
+ delete doc;
+ }
+}
+#else
+/* Stub implementations when PDF support is not compiled in. */
+emacs_skia_document_t *
+emacs_skia_document_create_pdf (emacs_skia_write_fn write_fn,
+ void *write_ctx, float width,
+ float height)
+{
+ (void) write_fn;
+ (void) write_ctx;
+ (void) width;
+ (void) height;
+ return nullptr;
+}
+
+emacs_skia_canvas_t *
+emacs_skia_document_begin_page (emacs_skia_document_t *doc,
+ float width, float height)
+{
+ (void) doc;
+ (void) width;
+ (void) height;
+ return nullptr;
+}
+
+void
+emacs_skia_document_end_page (emacs_skia_document_t *doc)
+{
+ (void) doc;
+}
+
+void
+emacs_skia_document_close (emacs_skia_document_t *doc)
+{
+ (void) doc;
+}
+#endif
+
+#ifdef SK_SVG
+/* Global map to track SVG canvas data.
+ This is needed because we return a generic emacs_skia_canvas_t
+ pointer but need to track the underlying stream ownership.
+
+ Thread safety: Accessed only from the main thread during SVG export
+ operations. Emacs display code runs single-threaded, so no mutex
+ is needed. The map is cleaned up in emacs_skia_cleanup() to prevent
+ leaks if canvases are not properly finished. */
+static std::map<SkCanvas *, emacs_skia_svg_canvas_data *>
+ svg_canvas_map;
+
+/* Clean up any remaining SVG canvases that were not properly finished.
+ Called from emacs_skia_cleanup() to prevent memory leaks. */
+static void
+cleanup_svg_canvas_map (void)
+{
+ for (auto &entry : svg_canvas_map)
+ delete entry.second;
+ svg_canvas_map.clear ();
+}
+
+emacs_skia_canvas_t *
+emacs_skia_svg_canvas_create (emacs_skia_write_fn write_fn,
+ void *write_ctx, float width,
+ float height)
+{
+ auto stream
+ = std::make_unique<CallbackWStream> (write_fn, write_ctx);
+
+ SkRect bounds = SkRect::MakeWH (width, height);
+ auto canvas = SkSVGCanvas::Make (bounds, stream.get ());
+ if (!canvas)
+ return nullptr;
+
+ /* Store the canvas data so we can clean up later. */
+ auto data = new emacs_skia_svg_canvas_data;
+ data->stream = std::move (stream);
+ data->canvas = std::move (canvas);
+
+ svg_canvas_map[data->canvas.get ()] = data;
+
+ /* Use the per-SVG-canvas wrapper for thread safety. */
+ data->canvas_wrapper.canvas = data->canvas.get ();
+ return &data->canvas_wrapper;
+}
+
+void
+emacs_skia_svg_canvas_finish (emacs_skia_canvas_t *canvas)
+{
+ if (!canvas || !canvas->canvas)
+ return;
+
+ auto it = svg_canvas_map.find (canvas->canvas);
+ if (it != svg_canvas_map.end ())
+ {
+ emacs_skia_svg_canvas_data *data = it->second;
+ /* Deleting the canvas flushes and finishes the SVG output. */
+ svg_canvas_map.erase (it);
+ delete data;
+ }
+}
+#else
+/* Stub implementations when SVG support is not compiled in. */
+emacs_skia_canvas_t *
+emacs_skia_svg_canvas_create (emacs_skia_write_fn write_fn,
+ void *write_ctx, float width,
+ float height)
+{
+ (void) write_fn;
+ (void) write_ctx;
+ (void) width;
+ (void) height;
+ return nullptr;
+}
+
+void
+emacs_skia_svg_canvas_finish (emacs_skia_canvas_t *canvas)
+{
+ (void) canvas;
+}
+#endif
+
+/* ============================================================
+ PNG Export
+ ============================================================ */
+
+/* Helper to encode pixmap to PNG and write via callback.
+ Uses SkDynamicMemoryWStream to avoid RTTI issues with custom streams. */
+static bool
+encode_png_to_callback (const SkPixmap &pixmap,
+ emacs_skia_write_fn write_fn, void *write_ctx)
+{
+ SkDynamicMemoryWStream stream;
+ SkPngEncoder::Options options;
+
+ if (!SkPngEncoder::Encode (&stream, pixmap, options))
+ return false;
+
+ /* Write the encoded data to the callback. */
+ sk_sp<SkData> data = stream.detachAsData ();
+ if (!data)
+ return false;
+
+ size_t written = write_fn (write_ctx, data->data (), data->size ());
+ return written == data->size ();
+}
+
+/* Write surface to PNG using a callback function.
+ Returns true on success, false on failure. */
+bool
+emacs_skia_surface_write_to_png (emacs_skia_surface_t *surface,
+ emacs_skia_write_fn write_fn,
+ void *write_ctx)
+{
+ if (!surface || !surface->surface || !write_fn)
+ return false;
+
+ /* Create an image snapshot of the surface. */
+ sk_sp<SkImage> image = surface->surface->makeImageSnapshot ();
+ if (!image)
+ return false;
+
+ /* Read pixels into a raster format suitable for encoding. */
+ SkPixmap pixmap;
+ if (!image->peekPixels (&pixmap))
+ {
+ /* For GPU surfaces, we need to read back the pixels. */
+ SkImageInfo info
+ = SkImageInfo::Make (image->width (), image->height (),
+ kRGBA_8888_SkColorType,
+ kUnpremul_SkAlphaType);
+ std::vector<uint8_t> pixels (info.computeMinByteSize ());
+ if (!image->readPixels (info, pixels.data (), info.minRowBytes (),
+ 0, 0))
+ return false;
+
+ SkPixmap temp_pixmap (info, pixels.data (), info.minRowBytes ());
+ return encode_png_to_callback (temp_pixmap, write_fn, write_ctx);
+ }
+
+ return encode_png_to_callback (pixmap, write_fn, write_ctx);
+}
+
+/* Write image to PNG using a callback function.
+ Returns true on success, false on failure. */
+bool
+emacs_skia_image_write_to_png (emacs_skia_image_t *image,
+ emacs_skia_write_fn write_fn,
+ void *write_ctx)
+{
+ if (!image || !image->image || !write_fn)
+ return false;
+
+ SkPixmap pixmap;
+ if (!image->image->peekPixels (&pixmap))
+ {
+ /* Need to read pixels back. */
+ SkImageInfo info
+ = SkImageInfo::Make (image->image->width (),
+ image->image->height (),
+ kRGBA_8888_SkColorType,
+ kUnpremul_SkAlphaType);
+ std::vector<uint8_t> pixels (info.computeMinByteSize ());
+ if (!image->image->readPixels (info, pixels.data (),
+ info.minRowBytes (), 0, 0))
+ return false;
+
+ SkPixmap temp_pixmap (info, pixels.data (), info.minRowBytes ());
+ return encode_png_to_callback (temp_pixmap, write_fn, write_ctx);
+ }
+
+ return encode_png_to_callback (pixmap, write_fn, write_ctx);
+}
+
+/* ============================================================
+ Image Transformation
+ ============================================================ */
+
+/* Image transformation data for Skia.
+ This is used to store transformation matrices for images,
+ replacing the use of cairo_pattern_t for this purpose. */
+struct emacs_skia_image_transform
+{
+ /* 3x3 transformation matrix in row-major order.
+ [a c e] [0 2 4]
+ [b d f] = [1 3 5]
+ [0 0 1] (implicit) */
+ float matrix[6];
+ /* Filter mode: true = bilinear, false = nearest neighbor. */
+ bool smoothing;
+};
+
+emacs_skia_image_transform_t *
+emacs_skia_image_transform_create (void)
+{
+ auto result = new emacs_skia_image_transform_t;
+ /* Initialize to identity matrix. */
+ result->matrix[0] = 1.0f; /* a = scale x */
+ result->matrix[1] = 0.0f; /* b = skew y */
+ result->matrix[2] = 0.0f; /* c = skew x */
+ result->matrix[3] = 1.0f; /* d = scale y */
+ result->matrix[4] = 0.0f; /* e = translate x */
+ result->matrix[5] = 0.0f; /* f = translate y */
+ result->smoothing = true;
+ return result;
+}
+
+void
+emacs_skia_image_transform_destroy (emacs_skia_image_transform_t *transform)
+{
+ delete transform;
+}
+
+void
+emacs_skia_image_transform_set_matrix (emacs_skia_image_transform_t *transform,
+ const float matrix[6])
+{
+ if (transform && matrix)
+ {
+ for (int i = 0; i < 6; i++)
+ transform->matrix[i] = matrix[i];
+ }
+}
+
+void
+emacs_skia_image_transform_get_matrix (emacs_skia_image_transform_t *transform,
+ float matrix[6])
+{
+ if (transform && matrix)
+ {
+ for (int i = 0; i < 6; i++)
+ matrix[i] = transform->matrix[i];
+ }
+}
+
+void
+emacs_skia_image_transform_set_smoothing (emacs_skia_image_transform_t *transform,
+ bool smoothing)
+{
+ if (transform)
+ transform->smoothing = smoothing;
+}
+
+bool
+emacs_skia_image_transform_get_smoothing (emacs_skia_image_transform_t *transform)
+{
+ return transform ? transform->smoothing : true;
+}
+
+/* Draw an image with transformation applied. */
+void
+emacs_skia_canvas_draw_image_transformed (emacs_skia_canvas_t *canvas,
+ emacs_skia_image_t *image,
+ emacs_skia_image_transform_t *transform,
+ float x, float y,
+ emacs_skia_paint_t *paint)
+{
+ if (!canvas || !canvas->canvas || !image || !image->image)
+ return;
+
+ canvas->canvas->save ();
+
+ if (transform)
+ {
+ /* Apply the transformation matrix.
+ Skia uses column-major SkMatrix, but our matrix is in
+ [a c e; b d f] format which maps to:
+ SkMatrix: [scaleX, skewX, transX, skewY, scaleY, transY, ...] */
+ SkMatrix sk_matrix;
+ sk_matrix.setAll (transform->matrix[0], /* scaleX = a */
+ transform->matrix[2], /* skewX = c */
+ transform->matrix[4], /* transX = e */
+ transform->matrix[1], /* skewY = b */
+ transform->matrix[3], /* scaleY = d */
+ transform->matrix[5], /* transY = f */
+ 0, 0, 1);
+ canvas->canvas->concat (sk_matrix);
+ }
+
+ SkSamplingOptions sampling (
+ transform && transform->smoothing ? SkFilterMode::kLinear
+ : SkFilterMode::kNearest);
+
+ if (paint)
+ canvas->canvas->drawImage (image->image.get (), x, y, sampling,
+ &paint->paint);
+ else
+ canvas->canvas->drawImage (image->image.get (), x, y, sampling);
+
+ canvas->canvas->restore ();
+}
+
+/* Calculate stride for pixel buffers (replacement for
+ cairo_format_stride_for_width). Skia uses 4-byte aligned rows for RGBA. */
+int
+emacs_skia_format_stride_for_width (int format, int width)
+{
+ /* format: 0 = A8 (1 byte per pixel), 1 = RGB24/ARGB32 (4 bytes per pixel) */
+ int bytes_per_pixel = (format == 0) ? 1 : 4;
+ int stride = width * bytes_per_pixel;
+ /* Align to 4 bytes (Skia's default alignment). */
+ return (stride + 3) & ~3;
+}
diff --git a/src/skia/emacs_skia.h b/src/skia/emacs_skia.h
new file mode 100644
index 00000000000..230ca35c169
--- /dev/null
+++ b/src/skia/emacs_skia.h
@@ -0,0 +1,650 @@
+/* Minimal Skia C API for Emacs
+ Copyright (C) 2024-2026 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifndef EMACS_SKIA_H
+#define EMACS_SKIA_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* Opaque types */
+ typedef struct emacs_skia_surface emacs_skia_surface_t;
+ typedef struct emacs_skia_canvas emacs_skia_canvas_t;
+ typedef struct emacs_skia_paint emacs_skia_paint_t;
+ typedef struct emacs_skia_font emacs_skia_font_t;
+ typedef struct emacs_skia_typeface emacs_skia_typeface_t;
+ typedef struct emacs_skia_image emacs_skia_image_t;
+ typedef struct emacs_skia_gl_context emacs_skia_gl_context_t;
+
+ /* Color representation: ARGB in native byte order */
+ typedef uint32_t emacs_skia_color_t;
+
+#define EMACS_SKIA_COLOR(a, r, g, b) \
+ (((uint32_t) (a) << 24) | ((uint32_t) (r) << 16) \
+ | ((uint32_t) (g) << 8) | (uint32_t) (b))
+
+#define EMACS_SKIA_COLOR_RGB(r, g, b) EMACS_SKIA_COLOR (255, r, g, b)
+
+ /* Rectangle */
+ typedef struct
+ {
+ float left, top, right, bottom;
+ } emacs_skia_rect_t;
+
+ /* Integer rectangle */
+ typedef struct
+ {
+ int32_t left, top, right, bottom;
+ } emacs_skia_irect_t;
+
+ /* Point */
+ typedef struct
+ {
+ float x, y;
+ } emacs_skia_point_t;
+
+ /* Glyph ID */
+ typedef uint16_t emacs_skia_glyph_t;
+
+ /* Font metrics */
+ typedef struct
+ {
+ float ascent;
+ float descent;
+ float leading;
+ float avg_char_width;
+ float max_char_width;
+ float x_height;
+ float cap_height;
+ } emacs_skia_font_metrics_t;
+
+ /* Glyph extents (replacement for cairo_text_extents_t) */
+ typedef struct
+ {
+ float x_bearing; /* Left side bearing */
+ float
+ y_bearing; /* Top side bearing (negative = above baseline) */
+ float width; /* Glyph width */
+ float height; /* Glyph height */
+ float x_advance; /* Horizontal advance */
+ float y_advance; /* Vertical advance (usually 0) */
+ } emacs_skia_glyph_extents_t;
+
+ /* Font extents (replacement for cairo_font_extents_t) */
+ typedef struct
+ {
+ float ascent; /* Distance from baseline to top */
+ float descent; /* Distance from baseline to bottom */
+ float height; /* Recommended line height */
+ float max_x_advance; /* Maximum horizontal advance */
+ float max_y_advance; /* Maximum vertical advance */
+ } emacs_skia_font_extents_t;
+
+ /* Blend modes */
+ typedef enum
+ {
+ EMACS_SKIA_BLEND_SRC,
+ EMACS_SKIA_BLEND_SRC_OVER,
+ EMACS_SKIA_BLEND_DST_OVER,
+ EMACS_SKIA_BLEND_CLEAR,
+ EMACS_SKIA_BLEND_XOR,
+ EMACS_SKIA_BLEND_DIFFERENCE,
+ EMACS_SKIA_BLEND_EXCLUSION,
+ } emacs_skia_blend_mode_t;
+
+ /* Opaque path type for complex shapes */
+ typedef struct emacs_skia_path emacs_skia_path_t;
+
+ /* Anti-alias mode */
+ typedef enum
+ {
+ EMACS_SKIA_ANTIALIAS_NONE,
+ EMACS_SKIA_ANTIALIAS_NORMAL,
+ EMACS_SKIA_ANTIALIAS_SUBPIXEL,
+ } emacs_skia_antialias_t;
+
+ /* Font hinting */
+ typedef enum
+ {
+ EMACS_SKIA_HINTING_NONE,
+ EMACS_SKIA_HINTING_SLIGHT,
+ EMACS_SKIA_HINTING_NORMAL,
+ EMACS_SKIA_HINTING_FULL,
+ } emacs_skia_hinting_t;
+
+ /* ============================================================
+ Initialization / Cleanup
+ ============================================================ */
+
+ /* Initialize the Skia subsystem. Call once at startup. */
+ void emacs_skia_init (void);
+
+ /* Cleanup the Skia subsystem. Call once at shutdown. */
+ void emacs_skia_cleanup (void);
+
+ /* ============================================================
+ Capability Queries
+ ============================================================ */
+
+ /* Query whether specific Skia features are available.
+ These functions return true if the feature was compiled in. */
+ bool emacs_skia_has_gl_support (void);
+ bool emacs_skia_has_pdf_support (void);
+ bool emacs_skia_has_svg_support (void);
+
+ /* ============================================================
+ GL Context (for GPU-accelerated rendering)
+ ============================================================ */
+
+ /* Create a GL context for GPU rendering.
+ gl_get_proc: function to get GL proc addresses
+ Returns NULL on failure. */
+ typedef void (*emacs_skia_gl_proc_t) (void);
+ typedef emacs_skia_gl_proc_t (*emacs_skia_gl_get_proc_fn) (
+ void *ctx, const char *name);
+
+ emacs_skia_gl_context_t *
+ emacs_skia_gl_context_create (emacs_skia_gl_get_proc_fn get_proc,
+ void *ctx);
+
+ /* Create a GL context using the native GL interface. This uses
+ the currently active GL context (e.g., set by GDK). */
+ emacs_skia_gl_context_t *emacs_skia_gl_context_create_native (void);
+
+ void emacs_skia_gl_context_destroy (emacs_skia_gl_context_t *ctx);
+
+ /* Flush pending GPU operations */
+ void emacs_skia_gl_context_flush (emacs_skia_gl_context_t *ctx);
+
+ /* Reset the GL context state tracking. Call this after destroying
+ a GL surface to clear Skia's internal caches. */
+ void emacs_skia_gl_context_reset (emacs_skia_gl_context_t *ctx);
+
+ /* ============================================================
+ GL Fence Sync (for async GPU synchronization)
+ ============================================================ */
+
+ /* Opaque fence object for non-blocking GPU synchronization. */
+ typedef struct emacs_skia_fence emacs_skia_fence_t;
+
+ /* Create a fence that will be signaled when all preceding GL
+ commands have completed on the GPU. */
+ emacs_skia_fence_t *emacs_skia_fence_create (void);
+
+ /* Wait for fence to be signaled. Returns true if signaled within
+ timeout_ns nanoseconds, false if timed out. Pass 0 for no wait
+ (poll), or UINT64_MAX for infinite wait. */
+ bool emacs_skia_fence_wait (emacs_skia_fence_t *fence, uint64_t timeout_ns);
+
+ /* Check if fence is signaled without waiting. */
+ bool emacs_skia_fence_is_signaled (emacs_skia_fence_t *fence);
+
+ /* Destroy fence object. */
+ void emacs_skia_fence_destroy (emacs_skia_fence_t *fence);
+
+ /* ============================================================
+ Surface
+ ============================================================ */
+
+ /* Create a raster (CPU) surface */
+ emacs_skia_surface_t *emacs_skia_surface_create_raster (int width,
+ int height);
+
+ /* Create a GPU-backed surface using GL framebuffer */
+ emacs_skia_surface_t *emacs_skia_surface_create_gl (
+ emacs_skia_gl_context_t *ctx, int width, int height,
+ unsigned int framebuffer_id, unsigned int format);
+
+ /* Create a surface wrapping existing pixel data */
+ emacs_skia_surface_t *emacs_skia_surface_create_from_pixels (
+ int width, int height, void *pixels, size_t row_bytes);
+
+ void emacs_skia_surface_destroy (emacs_skia_surface_t *surface);
+
+ /* Get canvas from surface */
+ emacs_skia_canvas_t *
+ emacs_skia_surface_get_canvas (emacs_skia_surface_t *surface);
+
+ /* Get pixel data (for raster surfaces) */
+ void *emacs_skia_surface_get_pixels (emacs_skia_surface_t *surface);
+
+ /* Flush surface drawing */
+ void emacs_skia_surface_flush (emacs_skia_surface_t *surface);
+
+ int emacs_skia_surface_get_width (emacs_skia_surface_t *surface);
+ int emacs_skia_surface_get_height (emacs_skia_surface_t *surface);
+
+ /* Make an image snapshot from the surface (for scrolling/copying)
+ */
+ emacs_skia_image_t *emacs_skia_surface_make_image_snapshot (
+ emacs_skia_surface_t *surface);
+ emacs_skia_image_t *emacs_skia_surface_make_image_snapshot_rect (
+ emacs_skia_surface_t *surface, const emacs_skia_irect_t *rect);
+
+ /* Write callback for PNG/document output.
+ Returns number of bytes written, or 0 on error. */
+ typedef size_t (*emacs_skia_write_fn) (void *ctx, const void *data,
+ size_t size);
+
+ /* Write surface to PNG using a callback function.
+ Returns true on success, false on failure. */
+ bool emacs_skia_surface_write_to_png (emacs_skia_surface_t *surface,
+ emacs_skia_write_fn write_fn,
+ void *write_ctx);
+
+ /* Write image to PNG using a callback function.
+ Returns true on success, false on failure. */
+ bool emacs_skia_image_write_to_png (emacs_skia_image_t *image,
+ emacs_skia_write_fn write_fn,
+ void *write_ctx);
+
+ /* Calculate stride for pixel buffers.
+ format: 0 = A8 (1 byte/pixel), 1 = RGB24/ARGB32 (4 bytes/pixel).
+ Returns stride in bytes (4-byte aligned). */
+ int emacs_skia_format_stride_for_width (int format, int width);
+
+ /* ============================================================
+ Image Transformation
+ ============================================================ */
+
+ /* Opaque image transformation type. */
+ typedef struct emacs_skia_image_transform emacs_skia_image_transform_t;
+
+ /* Create/destroy image transformation. */
+ emacs_skia_image_transform_t *emacs_skia_image_transform_create (void);
+ void emacs_skia_image_transform_destroy (
+ emacs_skia_image_transform_t *transform);
+
+ /* Set transformation matrix.
+ matrix is [a, b, c, d, e, f] representing:
+ [a c e]
+ [b d f]
+ [0 0 1] */
+ void emacs_skia_image_transform_set_matrix (
+ emacs_skia_image_transform_t *transform, const float matrix[6]);
+ void emacs_skia_image_transform_get_matrix (
+ emacs_skia_image_transform_t *transform, float matrix[6]);
+
+ /* Set/get smoothing (filter mode). */
+ void emacs_skia_image_transform_set_smoothing (
+ emacs_skia_image_transform_t *transform, bool smoothing);
+ bool emacs_skia_image_transform_get_smoothing (
+ emacs_skia_image_transform_t *transform);
+
+ /* ============================================================
+ Canvas (drawing context)
+ ============================================================ */
+
+ /* State save/restore */
+ void emacs_skia_canvas_save (emacs_skia_canvas_t *canvas);
+ void emacs_skia_canvas_save_layer (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *bounds);
+ void emacs_skia_canvas_restore (emacs_skia_canvas_t *canvas);
+ int emacs_skia_canvas_get_save_count (emacs_skia_canvas_t *canvas);
+ void
+ emacs_skia_canvas_restore_to_count (emacs_skia_canvas_t *canvas,
+ int count);
+
+ /* Clear entire canvas */
+ void emacs_skia_canvas_clear (emacs_skia_canvas_t *canvas,
+ emacs_skia_color_t color);
+
+ /* Clipping */
+ void emacs_skia_canvas_clip_rect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *rect);
+ void emacs_skia_canvas_clip_irect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_irect_t *rect);
+
+ /* Get current clip bounds */
+ void emacs_skia_canvas_get_clip_bounds (emacs_skia_canvas_t *canvas,
+ emacs_skia_rect_t *bounds);
+
+ /* Transform */
+ void emacs_skia_canvas_translate (emacs_skia_canvas_t *canvas,
+ float dx, float dy);
+ void emacs_skia_canvas_scale (emacs_skia_canvas_t *canvas, float sx,
+ float sy);
+
+ /* Drawing primitives */
+ void emacs_skia_canvas_draw_rect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *rect,
+ emacs_skia_paint_t *paint);
+
+ void emacs_skia_canvas_draw_irect (emacs_skia_canvas_t *canvas,
+ const emacs_skia_irect_t *rect,
+ emacs_skia_paint_t *paint);
+
+ void emacs_skia_canvas_draw_line (emacs_skia_canvas_t *canvas,
+ float x0, float y0, float x1,
+ float y1,
+ emacs_skia_paint_t *paint);
+
+ /* Draw glyphs at specified positions */
+ void emacs_skia_canvas_draw_glyphs (
+ emacs_skia_canvas_t *canvas, int count,
+ const emacs_skia_glyph_t *glyphs,
+ const emacs_skia_point_t *positions, emacs_skia_point_t origin,
+ emacs_skia_font_t *font, emacs_skia_paint_t *paint);
+
+ /* Draw image */
+ void emacs_skia_canvas_draw_image (emacs_skia_canvas_t *canvas,
+ emacs_skia_image_t *image,
+ float x, float y,
+ emacs_skia_paint_t *paint);
+
+ void emacs_skia_canvas_draw_image_rect (
+ emacs_skia_canvas_t *canvas, emacs_skia_image_t *image,
+ const emacs_skia_rect_t *src, const emacs_skia_rect_t *dst,
+ emacs_skia_paint_t *paint);
+
+ /* Draw image with transformation applied. */
+ void emacs_skia_canvas_draw_image_transformed (
+ emacs_skia_canvas_t *canvas, emacs_skia_image_t *image,
+ emacs_skia_image_transform_t *transform, float x, float y,
+ emacs_skia_paint_t *paint);
+
+ /* Draw a path */
+ void emacs_skia_canvas_draw_path (emacs_skia_canvas_t *canvas,
+ emacs_skia_path_t *path,
+ emacs_skia_paint_t *paint);
+
+ /* Draw an arc (part of an ellipse)
+ oval: bounding rectangle of the ellipse
+ start_angle: starting angle in degrees (0 = 3 o'clock)
+ sweep_angle: arc extent in degrees (positive = clockwise)
+ use_center: if true, draws pie slice; if false, draws arc */
+ void emacs_skia_canvas_draw_arc (emacs_skia_canvas_t *canvas,
+ const emacs_skia_rect_t *oval,
+ float start_angle,
+ float sweep_angle, bool use_center,
+ emacs_skia_paint_t *paint);
+
+ /* Draw image with a mask (for stipples and transparency masks)
+ image: the image to draw
+ mask: alpha mask image (A8 format preferred)
+ x, y: destination position */
+ void emacs_skia_canvas_draw_image_with_mask (
+ emacs_skia_canvas_t *canvas, emacs_skia_image_t *image,
+ emacs_skia_image_t *mask, float x, float y,
+ emacs_skia_paint_t *paint);
+
+ /* ============================================================
+ Path (for complex shapes)
+ ============================================================ */
+
+ emacs_skia_path_t *emacs_skia_path_create (void);
+ void emacs_skia_path_destroy (emacs_skia_path_t *path);
+
+ /* Reset path to empty */
+ void emacs_skia_path_reset (emacs_skia_path_t *path);
+
+ /* Path construction */
+ void emacs_skia_path_move_to (emacs_skia_path_t *path, float x,
+ float y);
+ void emacs_skia_path_line_to (emacs_skia_path_t *path, float x,
+ float y);
+ void emacs_skia_path_rel_line_to (emacs_skia_path_t *path, float dx,
+ float dy);
+
+ /* Add an arc to the path
+ oval: bounding rectangle of the ellipse
+ start_angle: starting angle in degrees
+ sweep_angle: arc extent in degrees */
+ void emacs_skia_path_arc_to (emacs_skia_path_t *path,
+ const emacs_skia_rect_t *oval,
+ float start_angle, float sweep_angle,
+ bool force_move_to);
+
+ /* Close the current contour */
+ void emacs_skia_path_close (emacs_skia_path_t *path);
+
+ /* Add a rectangle to the path */
+ void emacs_skia_path_add_rect (emacs_skia_path_t *path,
+ const emacs_skia_rect_t *rect);
+
+ /* Clip canvas to path */
+ void emacs_skia_canvas_clip_path (emacs_skia_canvas_t *canvas,
+ emacs_skia_path_t *path);
+
+ /* ============================================================
+ Paint (style and color)
+ ============================================================ */
+
+ emacs_skia_paint_t *emacs_skia_paint_create (void);
+ void emacs_skia_paint_destroy (emacs_skia_paint_t *paint);
+
+ void emacs_skia_paint_set_color (emacs_skia_paint_t *paint,
+ emacs_skia_color_t color);
+ emacs_skia_color_t
+ emacs_skia_paint_get_color (emacs_skia_paint_t *paint);
+
+ void emacs_skia_paint_set_alpha (emacs_skia_paint_t *paint,
+ uint8_t alpha);
+
+ void emacs_skia_paint_set_antialias (emacs_skia_paint_t *paint,
+ bool antialias);
+ void emacs_skia_paint_set_blend_mode (emacs_skia_paint_t *paint,
+ emacs_skia_blend_mode_t mode);
+
+ /* Fill vs stroke */
+ void emacs_skia_paint_set_stroke (emacs_skia_paint_t *paint,
+ bool stroke);
+ void emacs_skia_paint_set_stroke_width (emacs_skia_paint_t *paint,
+ float width);
+
+ /* Dash pattern for strokes.
+ intervals: array of on/off lengths (must have even count)
+ count: number of elements in intervals array
+ phase: offset into the dash pattern */
+ void emacs_skia_paint_set_dash (emacs_skia_paint_t *paint,
+ const float *intervals, int count,
+ float phase);
+
+ /* Clear dash pattern (solid line) */
+ void emacs_skia_paint_clear_dash (emacs_skia_paint_t *paint);
+
+ /* ============================================================
+ Font and Typeface
+ ============================================================ */
+
+ /* Load a typeface from a file path */
+ emacs_skia_typeface_t *
+ emacs_skia_typeface_create_from_file (const char *path);
+
+ /* Load a typeface from memory */
+ emacs_skia_typeface_t *
+ emacs_skia_typeface_create_from_data (const void *data,
+ size_t size);
+
+ /* Load a typeface by family name and style */
+ emacs_skia_typeface_t *emacs_skia_typeface_create_from_name (
+ const char *family_name, int weight, int width, int slant);
+
+#ifdef HAVE_FONTCONFIG
+ /* Forward declaration for FcPattern */
+ struct _FcPattern;
+ typedef struct _FcPattern FcPattern;
+
+ /* Load a typeface from a fontconfig pattern
+ (replacement for cairo_ft_font_face_create_for_pattern) */
+ emacs_skia_typeface_t *
+ emacs_skia_typeface_create_from_fc_pattern (FcPattern *pattern);
+#endif
+
+ void emacs_skia_typeface_destroy (emacs_skia_typeface_t *typeface);
+
+ /* Get the font file path from a typeface (if available) */
+ const char *
+ emacs_skia_typeface_get_path (emacs_skia_typeface_t *typeface);
+
+ /* Get FreeType face index within the font file */
+ int emacs_skia_typeface_get_index (emacs_skia_typeface_t *typeface);
+
+ /* Create a font from a typeface at given size */
+ emacs_skia_font_t *
+ emacs_skia_font_create (emacs_skia_typeface_t *typeface,
+ float size);
+ void emacs_skia_font_destroy (emacs_skia_font_t *font);
+
+ void emacs_skia_font_set_size (emacs_skia_font_t *font, float size);
+ float emacs_skia_font_get_size (emacs_skia_font_t *font);
+
+ void emacs_skia_font_set_hinting (emacs_skia_font_t *font,
+ emacs_skia_hinting_t hinting);
+ void emacs_skia_font_set_edging (emacs_skia_font_t *font,
+ emacs_skia_antialias_t edging);
+ void emacs_skia_font_set_subpixel (emacs_skia_font_t *font,
+ bool subpixel);
+
+ /* Get font metrics */
+ void
+ emacs_skia_font_get_metrics (emacs_skia_font_t *font,
+ emacs_skia_font_metrics_t *metrics);
+
+ /* Get font extents (replacement for cairo_scaled_font_extents) */
+ void
+ emacs_skia_font_get_extents (emacs_skia_font_t *font,
+ emacs_skia_font_extents_t *extents);
+
+ /* Get glyph extents (replacement for
+ cairo_scaled_font_glyph_extents) Returns extents for each glyph
+ in the array. */
+ void emacs_skia_font_get_glyph_extents (
+ emacs_skia_font_t *font, const emacs_skia_glyph_t *glyphs,
+ int count, emacs_skia_glyph_extents_t *extents);
+
+ /* Get bounds for multiple glyphs (array version) */
+ void emacs_skia_font_get_glyph_bounds (
+ emacs_skia_font_t *font, const emacs_skia_glyph_t *glyphs,
+ int count, emacs_skia_rect_t *bounds);
+
+ /* Convert text to glyphs (UTF-8 input) */
+ int emacs_skia_font_text_to_glyphs (emacs_skia_font_t *font,
+ const char *text,
+ size_t byte_length,
+ emacs_skia_glyph_t *glyphs,
+ int max_glyphs);
+
+ /* Convert single Unicode codepoint to glyph */
+ emacs_skia_glyph_t
+ emacs_skia_font_char_to_glyph (emacs_skia_font_t *font,
+ int32_t codepoint);
+
+ /* Get glyph advance widths */
+ void emacs_skia_font_get_widths (emacs_skia_font_t *font,
+ const emacs_skia_glyph_t *glyphs,
+ int count, float *widths);
+
+ /* Measure text width */
+ float emacs_skia_font_measure_text (emacs_skia_font_t *font,
+ const char *text,
+ size_t byte_length);
+
+ /* ============================================================
+ Image
+ ============================================================ */
+
+ /* Create an image from pixel data (RGBA format) */
+ emacs_skia_image_t *emacs_skia_image_create_from_pixels (
+ int width, int height, const void *pixels, size_t row_bytes,
+ bool has_alpha);
+
+ /* Create an image from BGRA pixel data (Cairo/native format).
+ This is the format used by Cairo (ARGB32 in native byte order),
+ which on little-endian machines is BGRA when viewed as bytes. */
+ emacs_skia_image_t *emacs_skia_image_create_from_bgra_pixels (
+ int width, int height, const void *pixels, size_t row_bytes,
+ bool has_alpha);
+
+ /* Create an image from encoded data (PNG, JPEG, etc.) */
+ emacs_skia_image_t *
+ emacs_skia_image_create_from_encoded (const void *data,
+ size_t size);
+
+ void emacs_skia_image_destroy (emacs_skia_image_t *image);
+
+ int emacs_skia_image_get_width (emacs_skia_image_t *image);
+ int emacs_skia_image_get_height (emacs_skia_image_t *image);
+
+ /* Create a 1-bit (A1 format) image from packed bitmap data.
+ This is used for fringe bitmaps and stipple patterns.
+ data: packed 1-bit data, MSB first, rows padded to byte boundary
+ width, height: dimensions in pixels
+ stride: bytes per row (0 = calculate from width) */
+ emacs_skia_image_t *emacs_skia_image_create_from_bitmap (
+ const unsigned char *data, int width, int height, int stride);
+
+ /* Create a tiled shader/pattern from an image for stipple fills.
+ Returns a paint configured with the tiled image. */
+ void emacs_skia_paint_set_image_shader (emacs_skia_paint_t *paint,
+ emacs_skia_image_t *image);
+
+ /* Clear the shader from paint (return to solid color) */
+ void emacs_skia_paint_clear_shader (emacs_skia_paint_t *paint);
+
+ /* ============================================================
+ Document Export (PDF/SVG)
+ ============================================================ */
+
+ /* Opaque document type for multi-page PDF output */
+ typedef struct emacs_skia_document emacs_skia_document_t;
+
+ /* Create a PDF document that writes to a callback.
+ width/height are the initial page dimensions in points. */
+ emacs_skia_document_t *
+ emacs_skia_document_create_pdf (emacs_skia_write_fn write_fn,
+ void *write_ctx, float width,
+ float height);
+
+ /* Begin a new page in the document.
+ Returns a canvas to draw on. */
+ emacs_skia_canvas_t *
+ emacs_skia_document_begin_page (emacs_skia_document_t *doc,
+ float width, float height);
+
+ /* End the current page. */
+ void emacs_skia_document_end_page (emacs_skia_document_t *doc);
+
+ /* Close and finalize the document.
+ This must be called to complete the output. */
+ void emacs_skia_document_close (emacs_skia_document_t *doc);
+
+ /* Create an SVG canvas that writes to a callback.
+ SVG is a single-page format, so there's no document concept.
+ The returned canvas should be destroyed with
+ emacs_skia_svg_canvas_finish() when done. */
+ emacs_skia_canvas_t *
+ emacs_skia_svg_canvas_create (emacs_skia_write_fn write_fn,
+ void *write_ctx, float width,
+ float height);
+
+ /* Finish and close the SVG canvas.
+ This writes the closing SVG tags and finalizes output. */
+ void emacs_skia_svg_canvas_finish (emacs_skia_canvas_t *canvas);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EMACS_SKIA_H */
--
2.52.0
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
filename=0002-Add-with-skia-configure-option-and-build-system-supp.patch
From 474d36197608da5228acfee9fa6daaaf04a5559f Mon Sep 17 00:00:00 2001
From: Arthur Heymans <arthur@HIDDEN>
Date: Tue, 27 Jan 2026 08:05:15 +0100
Subject: [PATCH 2/6] Add --with-skia configure option and build system support
This adds the build infrastructure for the Skia graphics backend without
yet enabling its use. The Skia C wrapper is compiled but Emacs continues
to use Cairo for rendering.
* configure.ac: Add --with-skia option for Skia graphics backend.
(HAVE_SKIA, SKIA_CFLAGS, SKIA_LIBS, SKIA_CXX_OBJ): New substitutions.
Check for Skia library via pkg-config or manual detection.
Check for OpenGL support via libepoxy for GPU acceleration.
Make Cairo optional when Skia is enabled for PGTK.
* src/Makefile.in (SKIA_CXX_OBJ, SKIA_CFLAGS, SKIA_LIBS): New variables.
(.cpp.o): New suffix rule for C++ compilation.
(obj): Add SKIA_CXX_OBJ.
(LIBES): Add SKIA_LIBS.
(skia/emacs_skia.o): New target for Skia wrapper.
(temacs): Link with C++ when HAVE_SKIA.
(mostlyclean): Clean skia/*.o files.
---
configure.ac | 183 +++++++++++++++++++++++++++++++++++++++++++-----
src/Makefile.in | 27 ++++++-
2 files changed, 190 insertions(+), 20 deletions(-)
diff --git a/configure.ac b/configure.ac
index 0d7c58d8020..f40abe80ee9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -553,6 +553,7 @@ AC_DEFUN
OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support])
OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing])
OPTION_DEFAULT_OFF([cairo-xcb], [use XCB surfaces for Cairo support])
+OPTION_DEFAULT_OFF([skia],[use Skia for GPU-accelerated drawing (experimental)])
OPTION_DEFAULT_ON([xml2],[don't compile with XML parsing support])
OPTION_DEFAULT_OFF([imagemagick],[compile with ImageMagick image support])
OPTION_DEFAULT_ON([native-image-api], [don't use native image APIs (GDI+ on Windows)])
@@ -4431,7 +4432,8 @@ AC_DEFUN
HAVE_CAIRO=no
if test "${HAVE_X11}" = "yes"; then
- if test "${with_cairo}" != "no"; then
+ dnl Skip Cairo check if Skia is explicitly enabled (Skia replaces Cairo)
+ if test "${with_cairo}" != "no" && test "${with_skia}" != "yes"; then
CAIRO_REQUIRED=1.8.0
CAIRO_MODULE="cairo >= $CAIRO_REQUIRED"
EMACS_CHECK_MODULES([CAIRO], [$CAIRO_MODULE])
@@ -4471,7 +4473,8 @@ AC_DEFUN
fi
HAVE_XWIDGETS=$HAVE_WEBKIT
XWIDGETS_OBJ="xwidget.o"
- if test "$HAVE_X_WINDOWS" = "yes" && test "${with_cairo}" = "no"; then
+ dnl xwidgets on X11 requires cairo-xlib for compositing, even when using Skia
+ if test "$HAVE_X_WINDOWS" = "yes" && test "$HAVE_CAIRO" != "yes"; then
CAIRO_XLIB_MODULES="cairo >= 1.8.0 cairo-xlib >= 1.8.0"
EMACS_CHECK_MODULES([CAIRO_XLIB], [$CAIRO_XLIB_MODULES])
if test $HAVE_CAIRO_XLIB = "yes"; then
@@ -4514,20 +4517,149 @@ AC_DEFUN
fi
AC_SUBST([XWIDGETS_OBJ])
-if test "$window_system" = "pgtk"; then
- CAIRO_REQUIRED=1.12.0
- CAIRO_MODULE="cairo >= $CAIRO_REQUIRED"
- EMACS_CHECK_MODULES([CAIRO], [$CAIRO_MODULE])
- if test $HAVE_CAIRO = yes; then
- AC_DEFINE([USE_CAIRO], [1], [Define to 1 if using cairo.])
+dnl Skia support (experimental, for PGTK)
+HAVE_SKIA=no
+if test "${with_skia}" = "yes"; then
+ dnl Check for Skia library
+ AC_MSG_CHECKING([for Skia])
+
+ dnl Allow user to specify paths via environment variables
+ AC_ARG_VAR([SKIA_DIR], [Path to Skia source/installation])
+ AC_ARG_VAR([SKIA_CFLAGS], [C compiler flags for Skia])
+ AC_ARG_VAR([SKIA_LIBS], [linker flags for Skia])
+
+ dnl First check if SKIA_CFLAGS and SKIA_LIBS are already set (e.g., from Nix)
+ if test -n "$SKIA_CFLAGS" && test -n "$SKIA_LIBS"; then
+ HAVE_SKIA=yes
else
- AC_MSG_ERROR([cairo required but not found.])
+ dnl Try pkg-config
+ PKG_CHECK_EXISTS([skia], [
+ PKG_CHECK_MODULES([SKIA], [skia], [HAVE_SKIA=yes], [HAVE_SKIA=no])
+ ], [
+ dnl Manual detection - check for header file existence
+ if test -n "$SKIA_DIR"; then
+ dnl Check for Nix-style layout (include/skia/core/SkCanvas.h)
+ if test -f "$SKIA_DIR/include/skia/core/SkCanvas.h"; then
+ HAVE_SKIA=yes
+ SKIA_CFLAGS="-I$SKIA_DIR/include/skia"
+ if test -f "$SKIA_DIR/lib/libskia.so"; then
+ SKIA_LIBS="-L$SKIA_DIR/lib -lskia"
+ else
+ SKIA_LIBS="-lskia"
+ fi
+ dnl Check for source-build layout (include/core/SkCanvas.h)
+ elif test -f "$SKIA_DIR/include/core/SkCanvas.h"; then
+ HAVE_SKIA=yes
+ SKIA_CFLAGS="-I$SKIA_DIR"
+ if test -f "$SKIA_DIR/out/Release/libskia.a"; then
+ SKIA_LIBS="-L$SKIA_DIR/out/Release -lskia"
+ elif test -f "$SKIA_DIR/out/Release/libskia.so"; then
+ SKIA_LIBS="-L$SKIA_DIR/out/Release -lskia"
+ else
+ SKIA_LIBS="-L$SKIA_DIR/out/Release -lskia"
+ AC_MSG_WARN([Skia library not found at $SKIA_DIR/out/Release - you need to build Skia first])
+ fi
+ else
+ HAVE_SKIA=no
+ fi
+ else
+ dnl Try system paths
+ save_CPPFLAGS="$CPPFLAGS"
+ AC_CHECK_HEADER([skia/core/SkCanvas.h], [
+ HAVE_SKIA=yes
+ SKIA_CFLAGS=""
+ SKIA_LIBS="-lskia"
+ ], [HAVE_SKIA=no])
+ CPPFLAGS="$save_CPPFLAGS"
+ fi
+ ])
fi
- CFLAGS="$CFLAGS $CAIRO_CFLAGS"
- LIBS="$LIBS $CAIRO_LIBS"
- AC_SUBST([CAIRO_CFLAGS])
- AC_SUBST([CAIRO_LIBS])
+ if test "$HAVE_SKIA" = "yes"; then
+ AC_DEFINE([USE_SKIA], [1], [Define to 1 if using Skia for drawing.])
+ CFLAGS="$CFLAGS $SKIA_CFLAGS"
+ CXXFLAGS="$CXXFLAGS $SKIA_CFLAGS"
+ dnl Ensure we link with C++ standard library
+ case "$SKIA_LIBS" in
+ *-lstdc++*) ;;
+ *) SKIA_LIBS="$SKIA_LIBS -lstdc++" ;;
+ esac
+
+ dnl Auto-detect OpenGL support for Skia GPU acceleration
+ dnl We use libepoxy which GTK3 already depends on
+ dnl SK_GANESH is required by Skia to enable the Ganesh GPU backend,
+ dnl without it SK_GL gets undefined by SkTypes.h
+ HAVE_SKIA_GL=no
+ AC_MSG_CHECKING([for OpenGL support (libepoxy) for Skia GPU acceleration])
+ PKG_CHECK_EXISTS([epoxy], [
+ PKG_CHECK_MODULES([EPOXY], [epoxy], [
+ HAVE_SKIA_GL=yes
+ AC_DEFINE([SK_GL], [1], [Define to 1 if Skia GL backend is available.])
+ AC_DEFINE([SK_GANESH], [1], [Define to 1 to enable Skia Ganesh GPU backend.])
+ SKIA_CFLAGS="$SKIA_CFLAGS -DSK_GL=1 -DSK_GANESH=1 $EPOXY_CFLAGS"
+ SKIA_LIBS="$SKIA_LIBS $EPOXY_LIBS"
+ AC_MSG_RESULT([yes])
+ ], [
+ AC_MSG_RESULT([no (libepoxy not usable)])
+ ])
+ ], [
+ dnl Try direct GL detection as fallback
+ AC_CHECK_LIB([GL], [glClear], [
+ HAVE_SKIA_GL=yes
+ AC_DEFINE([SK_GL], [1], [Define to 1 if Skia GL backend is available.])
+ AC_DEFINE([SK_GANESH], [1], [Define to 1 to enable Skia Ganesh GPU backend.])
+ SKIA_CFLAGS="$SKIA_CFLAGS -DSK_GL=1 -DSK_GANESH=1"
+ SKIA_LIBS="$SKIA_LIBS -lGL"
+ AC_MSG_RESULT([yes (direct libGL)])
+ ], [
+ AC_MSG_RESULT([no])
+ ])
+ ])
+
+ dnl OpenGL is required for Skia - fail if not found
+ if test "$HAVE_SKIA_GL" != "yes"; then
+ AC_MSG_ERROR([Skia backend requires OpenGL support (libepoxy or libGL). Install libepoxy-dev or mesa-dev.])
+ fi
+ AC_SUBST([HAVE_SKIA_GL])
+
+ AC_SUBST([SKIA_CFLAGS])
+ AC_SUBST([SKIA_LIBS])
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ AC_MSG_WARN([Skia requested but not found. Install Skia or set SKIA_DIR/SKIA_CFLAGS/SKIA_LIBS.])
+ fi
+fi
+AC_SUBST([HAVE_SKIA])
+
+dnl Skia C++ objects (must come after HAVE_SKIA is set)
+SKIA_CXX_OBJ=
+if test "$HAVE_SKIA" = "yes"; then
+ SKIA_CXX_OBJ="skia/emacs_skia.o"
+fi
+AC_SUBST([SKIA_CXX_OBJ])
+
+if test "$window_system" = "pgtk"; then
+ dnl For PGTK, either Cairo or Skia is required
+ if test "$HAVE_SKIA" = "yes"; then
+ dnl Using Skia for PGTK
+ AC_MSG_NOTICE([Using Skia for PGTK rendering])
+ else
+ dnl Fall back to Cairo
+ CAIRO_REQUIRED=1.12.0
+ CAIRO_MODULE="cairo >= $CAIRO_REQUIRED"
+ EMACS_CHECK_MODULES([CAIRO], [$CAIRO_MODULE])
+ if test $HAVE_CAIRO = yes; then
+ AC_DEFINE([USE_CAIRO], [1], [Define to 1 if using cairo.])
+ else
+ AC_MSG_ERROR([Either cairo or skia required for PGTK but neither found.])
+ fi
+
+ CFLAGS="$CFLAGS $CAIRO_CFLAGS"
+ LIBS="$LIBS $CAIRO_LIBS"
+ AC_SUBST([CAIRO_CFLAGS])
+ AC_SUBST([CAIRO_LIBS])
+ fi
fi
if test "${HAVE_BE_APP}" = "yes"; then
@@ -4554,19 +4686,27 @@ AC_DEFUN
### Start of font-backend (under X11) section.
is_xft_version_outdated=no
if test "${HAVE_X11}" = "yes"; then
- if test $HAVE_CAIRO = yes; then
+ if test "$HAVE_SKIA" = "yes" || test "$HAVE_CAIRO" = "yes"; then
+ dnl Skia and Cairo both use ftfont, which requires freetype/fontconfig.
dnl Strict linkers fail with
dnl ftfont.o: undefined reference to symbol 'FT_New_Face'
dnl if -lfreetype is not specified.
dnl The following is needed to set FREETYPE_LIBS.
EMACS_CHECK_MODULES([FREETYPE], [freetype2])
- test "$HAVE_FREETYPE" = "no" && AC_MSG_ERROR([cairo requires libfreetype])
+ if test "$HAVE_SKIA" = "yes"; then
+ test "$HAVE_FREETYPE" = "no" && AC_MSG_ERROR([skia requires libfreetype])
+ else
+ test "$HAVE_FREETYPE" = "no" && AC_MSG_ERROR([cairo requires libfreetype])
+ fi
EMACS_CHECK_MODULES([FONTCONFIG], [fontconfig >= 2.2.0])
- test "$HAVE_FONTCONFIG" = "no" &&
- AC_MSG_ERROR([cairo requires libfontconfig])
+ if test "$HAVE_SKIA" = "yes"; then
+ test "$HAVE_FONTCONFIG" = "no" && AC_MSG_ERROR([skia requires libfontconfig])
+ else
+ test "$HAVE_FONTCONFIG" = "no" && AC_MSG_ERROR([cairo requires libfontconfig])
+ fi
dnl For the "Does Emacs use" message at the end.
HAVE_XFT=no
else
@@ -4646,6 +4786,10 @@ AC_DEFUN
if test "${HAVE_FREETYPE}" = "yes"; then
AC_DEFINE([HAVE_FREETYPE], [1],
[Define to 1 if using the freetype and fontconfig libraries.])
+ if test "${HAVE_FONTCONFIG}" = "yes"; then
+ AC_DEFINE([HAVE_FONTCONFIG], [1],
+ [Define to 1 if using the fontconfig library.])
+ fi
OLD_CFLAGS=$CFLAGS
OLD_LIBS=$LIBS
CFLAGS="$CFLAGS $FREETYPE_CFLAGS"
@@ -4693,6 +4837,8 @@ AC_DEFUN
HAVE_LIBOTF=no
AC_DEFINE([HAVE_FREETYPE], [1],
[Define to 1 if using the freetype and fontconfig libraries.])
+ AC_DEFINE([HAVE_FONTCONFIG], [1],
+ [Define to 1 if using the fontconfig library.])
if test "${with_libotf}" != "no"; then
EMACS_CHECK_MODULES([LIBOTF], [libotf])
if test "$HAVE_LIBOTF" = "yes"; then
@@ -7227,6 +7373,7 @@ AC_DEFUN
XMENU_OBJ=xmenu.o
XOBJ="xterm.o xfns.o xselect.o xrdb.o xsmfns.o xsettings.o"
FONT_OBJ=xfont.o
+ dnl Note: skiafont.o added in later commit; for now Skia falls through to Cairo fonts
if test "$HAVE_CAIRO" = "yes"; then
FONT_OBJ="$FONT_OBJ ftfont.o ftcrfont.o"
elif test "$HAVE_XFT" = "yes"; then
@@ -7237,6 +7384,7 @@ AC_DEFUN
fi
if test "${window_system}" = "pgtk"; then
+ dnl Note: skiafont.o added in later commit; for now use ftcrfont.o
FONT_OBJ="ftfont.o ftcrfont.o"
fi
@@ -7669,6 +7817,7 @@ AC_DEFUN
Does Emacs use -lwebp? ${HAVE_WEBP}
Does Emacs use -lsqlite3? ${HAVE_SQLITE3}
Does Emacs use cairo? ${HAVE_CAIRO}
+ Does Emacs use skia? ${HAVE_SKIA}
Does Emacs use -llcms2? ${HAVE_LCMS2}
Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK}
Does Emacs use native APIs for images? ${NATIVE_IMAGE_API}
diff --git a/src/Makefile.in b/src/Makefile.in
index 111f94fb0ba..464582e2602 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -380,6 +380,10 @@ HAIKU_CXX_OBJ =
HAIKU_LIBS = @HAIKU_LIBS@
HAIKU_CFLAGS = @HAIKU_CFLAGS@
+SKIA_CXX_OBJ = @SKIA_CXX_OBJ@
+SKIA_CFLAGS = @SKIA_CFLAGS@
+SKIA_LIBS = @SKIA_LIBS@
+
ANDROID_OBJ = @ANDROID_OBJ@
ANDROID_LIBS = @ANDROID_LIBS@
ANDROID_LDFLAGS = @ANDROID_LDFLAGS@
@@ -392,6 +396,7 @@ CHECK_STRUCTS =
HAVE_PDUMPER = @HAVE_PDUMPER@
HAVE_BE_APP = @HAVE_BE_APP@
+HAVE_SKIA = @HAVE_SKIA@
## ARM Macs require that all code have a valid signature. Since pdump
## invalidates the signature, we must re-sign to fix it.
@@ -439,13 +444,15 @@ ALL_OBJC_CFLAGS =
ALL_CXX_CFLAGS = $(EMACS_CFLAGS) \
$(filter-out $(NON_CXX_CFLAGS),$(WARN_CFLAGS)) $(CXXFLAGS)
-.SUFFIXES: .c .m .cc
+.SUFFIXES: .c .m .cc .cpp
.c.o:
$(AM_V_CC)$(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) $(PROFILING_CFLAGS) $<
.m.o:
$(AM_V_CC)$(CC) -c $(CPPFLAGS) $(ALL_OBJC_CFLAGS) $(PROFILING_CFLAGS) $<
.cc.o:
$(AM_V_CXX)$(CXX) -c $(CPPFLAGS) $(ALL_CXX_CFLAGS) $(PROFILING_CFLAGS) $<
+.cpp.o:
+ $(AM_V_CXX)$(CXX) -c $(CPPFLAGS) $(ALL_CXX_CFLAGS) $(PROFILING_CFLAGS) $<
base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o $(XMENU_OBJ) window.o \
charset.o coding.o category.o ccl.o character.o chartab.o bidi.o \
@@ -468,7 +475,7 @@ base_obj =
$(W32_OBJ) $(WINDOW_SYSTEM_OBJ) $(XGSELOBJ) \
$(HAIKU_OBJ) $(PGTK_OBJ) $(ANDROID_OBJ)
doc_obj = $(base_obj) $(NS_OBJC_OBJ)
-obj = $(doc_obj) $(HAIKU_CXX_OBJ)
+obj = $(doc_obj) $(HAIKU_CXX_OBJ) $(SKIA_CXX_OBJ)
## Object files used on some machine or other.
## These go in the DOC file on all machines in case they are needed.
@@ -577,7 +584,7 @@ LIBES =
$(EUIDACCESS_LIBGEN) $(TIMER_TIME_LIB) $(DBUS_LIBS) \
$(LIB_EXECINFO) $(XRANDR_LIBS) $(XINERAMA_LIBS) $(XFIXES_LIBS) \
$(XDBE_LIBS) $(XSYNC_LIBS) \
- $(LIBXML2_LIBS) $(LIBGPM) $(LIBS_SYSTEM) $(CAIRO_LIBS) \
+ $(LIBXML2_LIBS) $(LIBGPM) $(LIBS_SYSTEM) $(CAIRO_LIBS) $(SKIA_LIBS) \
$(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \
$(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(HARFBUZZ_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
$(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \
@@ -688,6 +695,15 @@ globals.h:
$(ALLOBJS): globals.h
+## Skia C++ wrapper
+ifneq ($(SKIA_CXX_OBJ),)
+skia:
+ $(MKDIR_P) skia
+
+skia/emacs_skia.o: $(srcdir)/skia/emacs_skia.cpp skia $(config_h)
+ $(AM_V_CXX)$(CXX) -c $(CPPFLAGS) $(ALL_CXX_CFLAGS) $(SKIA_CFLAGS) -o $@ $<
+endif
+
LIBEGNU_ARCHIVE = $(lib)/libgnu.a
$(LIBEGNU_ARCHIVE): $(config_h)
@@ -709,6 +725,10 @@ temacs$(EXEEXT):
$(AM_V_CXXLD)$(CXX) -o $@.tmp \
$(ALL_CFLAGS) $(TEMACS_LDFLAGS) $(LDFLAGS) \
$(ALLOBJS) $(LIBEGNU_ARCHIVE) $(W32_RES_LINK) $(LIBES) -lstdc++
+else ifeq ($(HAVE_SKIA),yes)
+ $(AM_V_CXXLD)$(CXX) -o $@.tmp \
+ $(ALL_CFLAGS) $(TEMACS_LDFLAGS) $(LDFLAGS) \
+ $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(W32_RES_LINK) $(LIBES) -lstdc++
else
$(AM_V_CCLD)$(CC) -o $@.tmp \
$(ALL_CFLAGS) $(CXXFLAGS) $(TEMACS_LDFLAGS) $(LDFLAGS) \
@@ -794,6 +814,7 @@ .PHONY:
mostlyclean:
rm -f android-emacs libemacs.so
rm -f temacs$(EXEEXT) core ./*.core \#* ./*.o build-counter.c
+ rm -f skia/*.o
rm -f dmpstruct.h
rm -f emacs.pdmp
rm -f ../etc/DOC
--
2.52.0
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
filename=0003-Add-Skia-type-definitions-and-declarations-to-header.patch
From 55ac1cf641ad93b79db9c9b9ad24edff020c116d Mon Sep 17 00:00:00 2001
From: Arthur Heymans <arthur@HIDDEN>
Date: Tue, 27 Jan 2026 08:06:13 +0100
Subject: [PATCH 3/6] Add Skia type definitions and declarations to headers
This adds the necessary type definitions, struct fields, and function
declarations to support the Skia graphics backend. No functional
changes yet - these are just declarations.
* src/dispextern.h (Emacs_Pixmap, Emacs_Pix_Container)
(Emacs_Pix_Context): Extend conditionals for USE_SKIA.
(HAVE_NATIVE_TRANSFORMS): Include USE_SKIA.
(struct image): Add skia_data and skia_transform fields.
* src/pgtkterm.h: Include skia/emacs_skia.h when USE_SKIA.
(struct pgtk_bitmap_record): Add skia_image field.
(struct pgtk_output): Add Skia surface, canvas, GL context, and
related fields for GPU rendering.
(FRAME_SKIA_SURFACE, FRAME_SKIA_CANVAS, FRAME_SKIA_GL_CONTEXT)
(FRAME_SKIA_PAINT, FRAME_GL_AREA, FRAME_GDK_GL_CONTEXT): New macros.
* src/font.h (skiafont_driver, skiahbfont_driver, syms_of_skiafont):
New declarations for Skia font backend.
* src/ftfont.h (struct font_info): Add Skia-specific fields for
FT_Face access and metrics caching.
---
src/dispextern.h | 23 +++++++++------
src/font.h | 7 +++++
src/ftfont.h | 7 +++++
src/pgtkterm.h | 74 ++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 99 insertions(+), 12 deletions(-)
diff --git a/src/dispextern.h b/src/dispextern.h
index 30785b9ccdf..b0bcd14ce70 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -38,7 +38,7 @@ #define DISPEXTERN_H_INCLUDED
typedef XColor Emacs_Color;
typedef Cursor Emacs_Cursor;
#define No_Cursor (None)
-#ifndef USE_CAIRO
+#if !defined (USE_CAIRO) && !defined (USE_SKIA)
typedef Pixmap Emacs_Pixmap;
#endif
typedef XRectangle Emacs_Rectangle;
@@ -113,14 +113,14 @@ xstrcasecmp (char const *a, char const *b)
#ifdef HAVE_X_WINDOWS
#include <X11/Xresource.h> /* for XrmDatabase */
typedef struct x_display_info Display_Info;
-#ifndef USE_CAIRO
+#if !defined (USE_CAIRO) && !defined (USE_SKIA)
typedef XImage *Emacs_Pix_Container;
typedef XImage *Emacs_Pix_Context;
-#endif /* !USE_CAIRO */
+#endif /* !USE_CAIRO && !USE_SKIA */
#define NativeRectangle XRectangle
#endif
-#ifdef USE_CAIRO
+#if defined (USE_CAIRO) || defined (USE_SKIA)
/* Minimal version of XImage. */
typedef struct
{
@@ -3166,9 +3166,8 @@ reset_mouse_highlight (Mouse_HLInfo *hlinfo)
#ifdef HAVE_WINDOW_SYSTEM
-# if (defined USE_CAIRO || defined HAVE_XRENDER \
- || defined HAVE_NS || defined HAVE_NTGUI || defined HAVE_HAIKU \
- || defined HAVE_ANDROID)
+# if (defined HAVE_X_WINDOWS || defined USE_CAIRO || defined USE_SKIA \
+ || defined HAVE_HAIKU || defined HAVE_NS || defined HAVE_ANDROID)
# define HAVE_NATIVE_TRANSFORMS
# endif
@@ -3188,6 +3187,12 @@ reset_mouse_highlight (Mouse_HLInfo *hlinfo)
#ifdef USE_CAIRO
void *cr_data;
#endif
+#ifdef USE_SKIA
+ /* Skia image for this image (separate from cr_data for hybrid builds). */
+ void *skia_data;
+ /* Skia image transformation (for rotation/scaling). */
+ void *skia_transform;
+#endif
#ifdef HAVE_X_WINDOWS
/* X images of the image, corresponding to the above Pixmaps.
Non-NULL means it and its Pixmap counterpart may be out of sync
@@ -3712,8 +3717,8 @@ #define TRY_WINDOW_IGNORE_FONTS_CHANGE (1 << 1)
ptrdiff_t lookup_image (struct frame *, Lisp_Object, int);
Lisp_Object image_spec_value (Lisp_Object, Lisp_Object, bool *);
-#if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined HAVE_NS \
- || defined HAVE_HAIKU || defined HAVE_ANDROID
+#if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined USE_SKIA \
+ || defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_ANDROID
#define RGB_PIXEL_COLOR unsigned long
#endif
diff --git a/src/font.h b/src/font.h
index b6db84aa10b..c7d139f0100 100644
--- a/src/font.h
+++ b/src/font.h
@@ -993,6 +993,13 @@ valid_font_driver (struct font_driver const *d)
#endif /* HAVE_HARFBUZZ */
extern void syms_of_ftcrfont (void);
#endif
+#ifdef USE_SKIA
+extern struct font_driver const skiafont_driver;
+#ifdef HAVE_HARFBUZZ
+extern struct font_driver skiahbfont_driver;
+#endif /* HAVE_HARFBUZZ */
+extern void syms_of_skiafont (void);
+#endif
#ifndef FONT_DEBUG
#define FONT_DEBUG
diff --git a/src/ftfont.h b/src/ftfont.h
index ee56e2d7608..36636ab58e3 100644
--- a/src/ftfont.h
+++ b/src/ftfont.h
@@ -76,6 +76,13 @@ #define EMACS_FTFONT_H
/* Font metrics cache. */
struct font_metrics **metrics;
short metrics_nrows;
+/* USE_SKIA uses pure Skia + FreeType for font rendering. */
+#elif defined (USE_SKIA)
+ /* Pure Skia path: use FreeType directly for FT_Face access. */
+ FT_Face ft_face; /* Direct FreeType face for metrics. */
+ double bitmap_position_unit;
+ struct font_metrics **metrics;
+ short metrics_nrows;
#else
/* These are used by the XFT backend. */
Display *display;
diff --git a/src/pgtkterm.h b/src/pgtkterm.h
index abf16b3aef1..03d41e80e2d 100644
--- a/src/pgtkterm.h
+++ b/src/pgtkterm.h
@@ -40,12 +40,20 @@ #define _PGTKTERM_H_
#include <cairo-svg.h>
#endif
+# ifdef USE_SKIA
+# include "skia/emacs_skia.h"
+# endif
+
struct pgtk_bitmap_record
{
char *file;
int refcount;
int height, width, depth;
+# ifdef USE_SKIA
+ emacs_skia_image_t *skia_image;
+# else
cairo_pattern_t *pattern;
+# endif
};
struct pgtk_device_t
@@ -412,12 +420,33 @@ #define BLUE_FROM_ULONG(color) ((color) & 0xff)
Zero if not using an external tool bar or if tool bar is horizontal. */
int toolbar_left_width, toolbar_right_width;
-#ifdef USE_CAIRO
- /* Cairo drawing contexts. */
+#if defined (USE_CAIRO) || defined (USE_SKIA)
+ /* Cairo drawing contexts (also used for PDF/SVG export with Skia). */
cairo_t *cr_context, *cr_active;
int cr_surface_desired_width, cr_surface_desired_height;
+#endif
+#ifdef USE_CAIRO
/* Cairo surface for double buffering */
cairo_surface_t *cr_surface_visible_bell;
+#endif
+#ifdef USE_SKIA
+ /* Skia drawing contexts. */
+ emacs_skia_surface_t *skia_surface;
+ emacs_skia_canvas_t *skia_canvas;
+ emacs_skia_gl_context_t *skia_gl_context;
+ int skia_surface_desired_width, skia_surface_desired_height;
+ emacs_skia_paint_t *skia_paint; /* Reusable paint object */
+ emacs_skia_surface_t *skia_surface_visible_bell;
+ bool skia_gl_initialized;
+ /* Skia GL rendering support. */
+ GtkWidget *gl_area;
+ GdkGLContext *gdk_gl_context;
+ unsigned int gl_framebuffer;
+ unsigned int gl_texture;
+ unsigned int gl_stencil;
+ gint64 last_render_time;
+ /* Track when GL state needs reset - avoids unnecessary resetContext calls. */
+ bool skia_gl_state_dirty;
#endif
struct atimer *atimer_visible_bell;
@@ -523,10 +552,33 @@ #define FIRST_CHAR_POSITION(f) \
(! (FRAME_HAS_VERTICAL_SCROLL_BARS_ON_LEFT (f)) ? 0 \
: FRAME_SCROLL_BAR_COLS (f))
+#if defined (USE_CAIRO) || defined (USE_SKIA)
#define FRAME_CR_SURFACE_DESIRED_WIDTH(f) \
((f)->output_data.pgtk->cr_surface_desired_width)
#define FRAME_CR_SURFACE_DESIRED_HEIGHT(f) \
((f)->output_data.pgtk->cr_surface_desired_height)
+#endif
+
+#ifdef USE_SKIA
+# define FRAME_SKIA_SURFACE(f) ((f)->output_data.pgtk->skia_surface)
+# define FRAME_SKIA_CANVAS(f) ((f)->output_data.pgtk->skia_canvas)
+# define FRAME_SKIA_GL_CONTEXT(f) \
+ ((f)->output_data.pgtk->skia_gl_context)
+# define FRAME_SKIA_PAINT(f) ((f)->output_data.pgtk->skia_paint)
+# define FRAME_SKIA_SURFACE_DESIRED_WIDTH(f) \
+ ((f)->output_data.pgtk->skia_surface_desired_width)
+# define FRAME_SKIA_SURFACE_DESIRED_HEIGHT(f) \
+ ((f)->output_data.pgtk->skia_surface_desired_height)
+# define FRAME_SKIA_GL_INITIALIZED(f) \
+ ((f)->output_data.pgtk->skia_gl_initialized)
+# define FRAME_GL_AREA(f) ((f)->output_data.pgtk->gl_area)
+# define FRAME_GDK_GL_CONTEXT(f) ((f)->output_data.pgtk->gdk_gl_context)
+# define FRAME_GL_FRAMEBUFFER(f) ((f)->output_data.pgtk->gl_framebuffer)
+# define FRAME_GL_TEXTURE(f) ((f)->output_data.pgtk->gl_texture)
+# define FRAME_GL_STENCIL(f) ((f)->output_data.pgtk->gl_stencil)
+# define FRAME_LAST_RENDER_TIME(f) ((f)->output_data.pgtk->last_render_time)
+# define FRAME_SKIA_GL_STATE_DIRTY(f) ((f)->output_data.pgtk->skia_gl_state_dirty)
+#endif
/* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT
@@ -607,7 +659,9 @@ #define SELECTION_EVENT_TIME(eventp) \
extern void pgtk_set_no_accept_focus (struct frame *, Lisp_Object, Lisp_Object);
extern void pgtk_set_z_group (struct frame *, Lisp_Object, Lisp_Object);
-/* Cairo related functions implemented in pgtkterm.c */
+#if defined (USE_CAIRO) || defined (USE_SKIA)
+/* Cairo related functions implemented in pgtkterm.c
+ (also needed as bridge for Skia rendering to GTK). */
extern void pgtk_cr_update_surface_desired_size (struct frame *, int, int, bool);
extern cairo_t *pgtk_begin_cr_clip (struct frame *);
extern void pgtk_end_cr_clip (struct frame *);
@@ -617,6 +671,20 @@ #define SELECTION_EVENT_TIME(eventp) \
extern void pgtk_cr_draw_frame (cairo_t *, struct frame *);
extern void pgtk_cr_destroy_frame_context (struct frame *);
extern Lisp_Object pgtk_cr_export_frames (Lisp_Object , cairo_surface_type_t);
+#endif
+
+#ifdef USE_SKIA
+/* Skia related functions implemented in pgtkterm.c */
+extern void pgtk_skia_update_surface_desired_size (struct frame *, int, int, bool);
+extern emacs_skia_canvas_t *pgtk_begin_skia_clip (struct frame *);
+extern void pgtk_end_skia_clip (struct frame *);
+extern void pgtk_skia_set_paint_foreground (struct frame *, Emacs_GC *);
+extern void pgtk_skia_set_paint_background (struct frame *, Emacs_GC *);
+extern void pgtk_skia_set_paint_color (struct frame *, unsigned long, bool);
+extern void pgtk_skia_draw_frame (struct frame *);
+extern Lisp_Object pgtk_skia_export_frames (Lisp_Object frames, Lisp_Object type);
+extern void pgtk_skia_destroy_frame_context (struct frame *);
+#endif
/* Defined in pgtkmenu.c */
extern Lisp_Object pgtk_popup_dialog (struct frame *, Lisp_Object, Lisp_Object);
--
2.52.0
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment; filename=0004-Add-Skia-font-driver.patch
From ebebb20005b8daf20f44b17e5b3563b3e167948a Mon Sep 17 00:00:00 2001
From: Arthur Heymans <arthur@HIDDEN>
Date: Tue, 27 Jan 2026 08:06:52 +0100
Subject: [PATCH 4/6] Add Skia font driver
This adds the Skia font driver which uses FreeType for font discovery
and Skia for glyph rendering. The driver implements the font_driver
interface with HarfBuzz support when available.
* src/skiafont.c: New file. Skia font driver using FreeType for font
discovery and Skia for rendering. Implements font_driver interface
with HarfBuzz support when available.
* src/font.c (syms_of_font): Register Skia font driver when USE_SKIA.
* configure.ac: Add skiafont.o to FONT_OBJ when HAVE_SKIA for both
X11 and PGTK builds.
---
configure.ac | 12 +-
src/font.c | 8 +-
src/skiafont.c | 774 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 788 insertions(+), 6 deletions(-)
create mode 100644 src/skiafont.c
diff --git a/configure.ac b/configure.ac
index f40abe80ee9..e775707ff6f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7373,8 +7373,9 @@ AC_DEFUN
XMENU_OBJ=xmenu.o
XOBJ="xterm.o xfns.o xselect.o xrdb.o xsmfns.o xsettings.o"
FONT_OBJ=xfont.o
- dnl Note: skiafont.o added in later commit; for now Skia falls through to Cairo fonts
- if test "$HAVE_CAIRO" = "yes"; then
+ if test "$HAVE_SKIA" = "yes"; then
+ FONT_OBJ="$FONT_OBJ ftfont.o skiafont.o"
+ elif test "$HAVE_CAIRO" = "yes"; then
FONT_OBJ="$FONT_OBJ ftfont.o ftcrfont.o"
elif test "$HAVE_XFT" = "yes"; then
FONT_OBJ="$FONT_OBJ ftfont.o xftfont.o"
@@ -7384,8 +7385,11 @@ AC_DEFUN
fi
if test "${window_system}" = "pgtk"; then
- dnl Note: skiafont.o added in later commit; for now use ftcrfont.o
- FONT_OBJ="ftfont.o ftcrfont.o"
+ if test "${HAVE_SKIA}" = "yes"; then
+ FONT_OBJ="ftfont.o skiafont.o"
+ else
+ FONT_OBJ="ftfont.o ftcrfont.o"
+ fi
fi
if test "${HAVE_BE_APP}" = "yes" ; then
diff --git a/src/font.c b/src/font.c
index fed90084219..e4e2fad5a97 100644
--- a/src/font.c
+++ b/src/font.c
@@ -6055,7 +6055,9 @@ syms_of_font (void)
syms_of_ftfont ();
#ifdef HAVE_X_WINDOWS
syms_of_xfont ();
-#ifdef USE_CAIRO
+#ifdef USE_SKIA
+ syms_of_skiafont ();
+#elif defined USE_CAIRO
syms_of_ftcrfont ();
#else
#ifdef HAVE_XFT
@@ -6063,7 +6065,9 @@ syms_of_font (void)
#endif /* HAVE_XFT */
#endif /* not USE_CAIRO */
#else /* not HAVE_X_WINDOWS */
-#ifdef USE_CAIRO
+#ifdef USE_SKIA
+ syms_of_skiafont ();
+#elif defined USE_CAIRO
syms_of_ftcrfont ();
#endif
#endif /* not HAVE_X_WINDOWS */
diff --git a/src/skiafont.c b/src/skiafont.c
new file mode 100644
index 00000000000..8430343d84e
--- /dev/null
+++ b/src/skiafont.c
@@ -0,0 +1,774 @@
+/* skiafont.c -- FreeType font driver with Skia rendering.
+ Copyright (C) 2024-2026 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+/* This font driver uses FreeType for font discovery and Skia for
+ rendering. It uses Skia for all font metrics and rendering. */
+
+#include <config.h>
+
+#ifdef USE_SKIA
+
+#include <stdio.h>
+
+/* Debug logging for Skia font driver failures.
+ Define SKIA_DEBUG at compile time to enable verbose error messages. */
+#ifdef SKIA_DEBUG
+#define SKIAFONT_LOG_ERROR(fmt, ...) \
+ fprintf (stderr, "skiafont: " fmt "\n", ##__VA_ARGS__)
+#else
+#define SKIAFONT_LOG_ERROR(fmt, ...) ((void)0)
+#endif
+
+# include <fontconfig/fontconfig.h>
+# include <ft2build.h>
+# include <math.h>
+# include FT_FREETYPE_H
+
+# include "lisp.h"
+# include "blockinput.h"
+# include "charset.h"
+# include "composite.h"
+# include "dispextern.h"
+# include "font.h"
+# include "ftfont.h"
+# include "pdumper.h"
+# include "pgtkterm.h"
+# include "xsettings.h"
+
+/* We extend font_info to include Skia-specific data. */
+struct skia_font_info
+{
+ /* The base font_info from ftfont. */
+ struct font_info base;
+
+ /* Skia typeface for this font. */
+ emacs_skia_typeface_t *skia_typeface;
+
+ /* Skia font object (typeface + size). */
+ emacs_skia_font_t *skia_font;
+};
+
+# define METRICS_NCOLS_PER_ROW (128)
+
+enum metrics_status
+{
+ METRICS_INVALID = -1, /* metrics entry is invalid */
+};
+
+# define METRICS_STATUS(metrics) \
+ ((metrics)->ascent + (metrics)->descent)
+# define METRICS_SET_STATUS(metrics, status) \
+ ((metrics)->ascent = 0, (metrics)->descent = (status))
+
+static int
+skiafont_glyph_extents (struct font *font, unsigned glyph,
+ struct font_metrics *metrics)
+{
+ struct skia_font_info *skiafont_info
+ = (struct skia_font_info *) font;
+ struct font_info *ftfont_info = &skiafont_info->base;
+ int row, col;
+ struct font_metrics *cache;
+
+ row = glyph / METRICS_NCOLS_PER_ROW;
+ col = glyph % METRICS_NCOLS_PER_ROW;
+ if (row >= ftfont_info->metrics_nrows)
+ {
+ ftfont_info->metrics
+ = xrealloc (ftfont_info->metrics,
+ sizeof (struct font_metrics *) * (row + 1));
+ memset (ftfont_info->metrics + ftfont_info->metrics_nrows, 0,
+ (sizeof (struct font_metrics *)
+ * (row + 1 - ftfont_info->metrics_nrows)));
+ ftfont_info->metrics_nrows = row + 1;
+ }
+ if (ftfont_info->metrics[row] == NULL)
+ {
+ struct font_metrics *new;
+ int i;
+
+ new = xmalloc (sizeof (struct font_metrics)
+ * METRICS_NCOLS_PER_ROW);
+ for (i = 0; i < METRICS_NCOLS_PER_ROW; i++)
+ METRICS_SET_STATUS (new + i, METRICS_INVALID);
+ ftfont_info->metrics[row] = new;
+ }
+ cache = ftfont_info->metrics[row] + col;
+
+ if (METRICS_STATUS (cache) == METRICS_INVALID)
+ {
+ /* Get glyph extents from Skia. */
+ if (skiafont_info->skia_font)
+ {
+ emacs_skia_glyph_t skia_glyph = glyph;
+ emacs_skia_glyph_extents_t extents;
+
+ emacs_skia_font_get_glyph_extents (skiafont_info->skia_font,
+ &skia_glyph, 1,
+ &extents);
+ cache->lbearing = floor (extents.x_bearing);
+ cache->rbearing = ceil (extents.width + extents.x_bearing);
+ cache->width = lround (extents.x_advance);
+ cache->ascent = ceil (-(double)extents.y_bearing - 1.0 / 256);
+ cache->descent = ceil (extents.height + extents.y_bearing);
+ }
+ else
+ {
+ /* Fallback: return zero metrics if no Skia font. */
+ cache->lbearing = 0;
+ cache->rbearing = 0;
+ cache->width = 0;
+ cache->ascent = 0;
+ cache->descent = 0;
+ }
+ }
+
+ if (metrics)
+ *metrics = *cache;
+
+ return cache->width;
+}
+
+static Lisp_Object
+skiafont_list (struct frame *f, Lisp_Object spec)
+{
+ return ftfont_list2 (f, spec, Qskia);
+}
+
+static Lisp_Object
+skiafont_match (struct frame *f, Lisp_Object spec)
+{
+ return ftfont_match2 (f, spec, Qskia);
+}
+
+/* FreeType library handle for direct font access.
+ Thread safety: This global is initialized once from the main thread
+ during font driver setup. Emacs display code runs single-threaded,
+ so no synchronization is needed. If Emacs ever becomes multi-threaded
+ for display operations, this would need mutex protection. */
+static FT_Library ft_library;
+static bool ft_library_initialized;
+
+static bool
+skiafont_init_freetype (void)
+{
+ if (!ft_library_initialized)
+ {
+ if (FT_Init_FreeType (&ft_library) == 0)
+ ft_library_initialized = true;
+ }
+ return ft_library_initialized;
+}
+
+static Lisp_Object
+skiafont_open (struct frame *f, Lisp_Object entity, int pixel_size)
+{
+ FcResult result;
+ Lisp_Object val, filename, font_object;
+ FcPattern *pat, *match;
+ struct skia_font_info *skiafont_info;
+ struct font *font;
+ double size = 0;
+ char *filename_str;
+ int font_index = 0;
+ FT_Face ft_face = NULL;
+
+ val = assq_no_quit (QCfont_entity, AREF (entity, FONT_EXTRA_INDEX));
+ if (!CONSP (val))
+ return Qnil;
+ val = XCDR (val);
+ filename = XCAR (val);
+ size = XFIXNUM (AREF (entity, FONT_SIZE_INDEX));
+ if (size == 0)
+ size = pixel_size;
+
+ block_input ();
+
+ pat = ftfont_entity_pattern (entity, pixel_size);
+ FcConfigSubstitute (NULL, pat, FcMatchPattern);
+ FcDefaultSubstitute (pat);
+ match = FcFontMatch (NULL, pat, &result);
+ ftfont_fix_match (pat, match);
+ FcPatternDestroy (pat);
+
+ /* Get font index from the match. */
+ FcPatternGetInteger (match, FC_INDEX, 0, &font_index);
+
+ font_object = font_build_object (VECSIZE (struct skia_font_info),
+ Qskia, entity, size);
+ skiafont_info
+ = (struct skia_font_info *) XFONT_OBJECT (font_object);
+ font = &skiafont_info->base.font;
+ font->pixel_size = size;
+ font->driver = &skiafont_driver;
+ font->encoding_charset = -1;
+ font->repertory_charset = -1;
+ font->default_ascent = 0;
+ font->vertical_centering = false;
+ font->baseline_offset = 0;
+ font->relative_compose = 0;
+
+ skiafont_info->base.metrics = NULL;
+ skiafont_info->base.metrics_nrows = 0;
+ skiafont_info->base.bitmap_position_unit = 0;
+ skiafont_info->base.ft_face = NULL;
+
+ /* Create Skia typeface and font. */
+ filename_str = SSDATA (filename);
+ skiafont_info->skia_typeface
+ = emacs_skia_typeface_create_from_file (filename_str);
+ if (skiafont_info->skia_typeface)
+ {
+ skiafont_info->skia_font
+ = emacs_skia_font_create (skiafont_info->skia_typeface, size);
+ if (skiafont_info->skia_font)
+ {
+ emacs_skia_font_set_subpixel (skiafont_info->skia_font,
+ true);
+ emacs_skia_font_set_hinting (skiafont_info->skia_font,
+ EMACS_SKIA_HINTING_NORMAL);
+ }
+ }
+ else
+ skiafont_info->skia_font = NULL;
+
+ /* Get font metrics from Skia. */
+ if (skiafont_info->skia_font)
+ {
+ emacs_skia_font_extents_t extents;
+ emacs_skia_font_get_extents (skiafont_info->skia_font,
+ &extents);
+ font->ascent = lround (extents.ascent);
+ font->descent = lround (extents.descent);
+ font->height = lround (extents.height);
+
+ /* Calculate average_width properly by measuring printable ASCII
+ characters, similar to ftfont.c. Using max_x_advance would give
+ the maximum character width, which is incorrect for proportional
+ fonts and causes issues with image scaling. */
+ {
+ int total_width = 0, n = 0, min_w = 0, space_w = 0;
+
+ for (int c = 32; c < 127; c++)
+ {
+ emacs_skia_glyph_t glyph
+ = emacs_skia_font_char_to_glyph (skiafont_info->skia_font, c);
+ if (glyph != 0)
+ {
+ emacs_skia_glyph_extents_t glyph_ext;
+ emacs_skia_font_get_glyph_extents (skiafont_info->skia_font,
+ &glyph, 1, &glyph_ext);
+ int w = lround (glyph_ext.x_advance);
+ if (w > 0)
+ {
+ total_width += w;
+ n++;
+ if (min_w == 0 || w < min_w)
+ min_w = w;
+ if (c == 32)
+ space_w = w;
+ }
+ }
+ }
+
+ if (n > 0)
+ {
+ font->average_width = total_width / n;
+ font->min_width = min_w;
+ font->space_width = space_w > 0 ? space_w : font->average_width;
+ }
+ else
+ {
+ /* Fallback to max_x_advance if we couldn't measure characters. */
+ font->min_width = font->average_width = font->space_width
+ = lround (extents.max_x_advance);
+ }
+ }
+ }
+ else
+ {
+ /* Fallback to default metrics if Skia font creation failed. */
+ SKIAFONT_LOG_ERROR ("failed to create Skia font for '%s', "
+ "using fallback metrics", filename_str);
+ font->ascent = pixel_size;
+ font->descent = pixel_size / 4;
+ font->height = font->ascent + font->descent;
+ font->min_width = font->average_width = font->space_width
+ = pixel_size / 2;
+ }
+
+ /* Get underline metrics from FreeType directly. */
+ if (skiafont_init_freetype ()
+ && FT_New_Face (ft_library, filename_str, font_index, &ft_face)
+ == 0)
+ {
+ if (ft_face->face_flags & FT_FACE_FLAG_SCALABLE)
+ {
+ font->underline_position = -ft_face->underline_position
+ * size / ft_face->units_per_EM;
+ font->underline_thickness = ft_face->underline_thickness
+ * size / ft_face->units_per_EM;
+ }
+ else
+ {
+ font->underline_position = -1;
+ font->underline_thickness = 1;
+ }
+ /* Store ft_face for later use (encode_char, etc.). */
+ skiafont_info->base.ft_face = ft_face;
+ }
+ else
+ {
+ /* Default underline metrics. */
+ font->underline_position = -1;
+ font->underline_thickness = 1;
+ }
+
+ FcPatternDestroy (match);
+ unblock_input ();
+
+ return font_object;
+}
+
+static void
+skiafont_close (struct font *font)
+{
+ struct skia_font_info *skiafont_info
+ = (struct skia_font_info *) font;
+ int i;
+
+ if (skiafont_info->skia_font)
+ {
+ emacs_skia_font_destroy (skiafont_info->skia_font);
+ skiafont_info->skia_font = NULL;
+ }
+ if (skiafont_info->skia_typeface)
+ {
+ emacs_skia_typeface_destroy (skiafont_info->skia_typeface);
+ skiafont_info->skia_typeface = NULL;
+ }
+
+ block_input ();
+
+ /* Close the FreeType face. */
+ if (skiafont_info->base.ft_face)
+ {
+ FT_Done_Face (skiafont_info->base.ft_face);
+ skiafont_info->base.ft_face = NULL;
+ }
+
+ if (skiafont_info->base.metrics)
+ {
+ for (i = 0; i < skiafont_info->base.metrics_nrows; i++)
+ if (skiafont_info->base.metrics[i])
+ xfree (skiafont_info->base.metrics[i]);
+ xfree (skiafont_info->base.metrics);
+ skiafont_info->base.metrics = NULL;
+ }
+
+ unblock_input ();
+}
+
+static int
+skiafont_has_char (Lisp_Object font, int c)
+{
+ if (FONT_ENTITY_P (font))
+ return ftfont_has_char (font, c);
+
+ struct charset *cs = NULL;
+
+ if (EQ (AREF (font, FONT_ADSTYLE_INDEX), Qja)
+ && charset_jisx0208 >= 0)
+ cs = CHARSET_FROM_ID (charset_jisx0208);
+ else if (EQ (AREF (font, FONT_ADSTYLE_INDEX), Qko)
+ && charset_ksc5601 >= 0)
+ cs = CHARSET_FROM_ID (charset_ksc5601);
+ if (cs)
+ return (ENCODE_CHAR (cs, c) != CHARSET_INVALID_CODE (cs));
+
+ return -1;
+}
+
+static unsigned
+skiafont_encode_char (struct font *font, int c)
+{
+ struct skia_font_info *skiafont_info
+ = (struct skia_font_info *) font;
+
+ /* Use Skia if available. */
+ if (skiafont_info->skia_font)
+ {
+ unsigned glyph
+ = emacs_skia_font_char_to_glyph (skiafont_info->skia_font, c);
+ /* Glyph index 0 means the character is not in the font.
+ Return FONT_INVALID_CODE so Emacs will look for a fallback font. */
+ if (glyph)
+ return glyph;
+ return FONT_INVALID_CODE;
+ }
+
+ /* Fall back to FreeType directly. */
+ struct font_info *ftfont_info = &skiafont_info->base;
+ if (ftfont_info->ft_face)
+ {
+ unsigned glyph = FT_Get_Char_Index (ftfont_info->ft_face, c);
+ if (glyph)
+ return glyph;
+ }
+
+ return FONT_INVALID_CODE;
+}
+
+static void
+skiafont_text_extents (struct font *font, const unsigned int *code,
+ int nglyphs, struct font_metrics *metrics)
+{
+ int i, width = 0;
+
+ memset (metrics, 0, sizeof (*metrics));
+
+ for (i = 0; i < nglyphs; i++)
+ {
+ struct font_metrics m;
+ int glyph_width = skiafont_glyph_extents (font, code[i], &m);
+
+ if (i == 0)
+ {
+ metrics->lbearing = m.lbearing;
+ metrics->rbearing = m.rbearing;
+ }
+ else
+ {
+ if (metrics->lbearing > m.lbearing + width)
+ metrics->lbearing = m.lbearing + width;
+ if (metrics->rbearing < m.rbearing + width)
+ metrics->rbearing = m.rbearing + width;
+ }
+ if (metrics->ascent < m.ascent)
+ metrics->ascent = m.ascent;
+ if (metrics->descent < m.descent)
+ metrics->descent = m.descent;
+
+ width += glyph_width;
+ }
+
+ metrics->width = width;
+}
+
+static int
+skiafont_draw (struct glyph_string *s, int from, int to, int x, int y,
+ bool with_background)
+{
+ struct frame *f = s->f;
+ struct skia_font_info *skiafont_info
+ = (struct skia_font_info *) s->font;
+ int len = to - from;
+ int i;
+
+ block_input ();
+
+ emacs_skia_canvas_t *canvas = pgtk_begin_skia_clip (f);
+ if (!canvas)
+ {
+ unblock_input ();
+ return 0;
+ }
+
+ /* Apply clipping from glyph string to prevent drawing outside
+ bounds. */
+ {
+ XRectangle clip_rects[2];
+ int n = get_glyph_string_clip_rects (s, clip_rects, 2);
+ for (int j = 0; j < n; j++)
+ {
+ emacs_skia_irect_t clip
+ = { clip_rects[j].x, clip_rects[j].y,
+ clip_rects[j].x + clip_rects[j].width,
+ clip_rects[j].y + clip_rects[j].height };
+ emacs_skia_canvas_clip_irect (canvas, &clip);
+ }
+ }
+
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+
+ if (with_background)
+ {
+ pgtk_skia_set_paint_color (f, s->xgcv.background,
+ s->hl != DRAW_CURSOR);
+ emacs_skia_irect_t rect
+ = { x, y - FONT_BASE (s->font), x + s->width,
+ y - FONT_BASE (s->font) + FONT_HEIGHT (s->font) };
+ emacs_skia_canvas_draw_irect (canvas, &rect, paint);
+ }
+
+ /* Set foreground color for text. */
+ pgtk_skia_set_paint_color (f, s->xgcv.foreground, false);
+
+ /* Draw glyphs using Skia if we have a Skia font, otherwise fall
+ back to Cairo-style rendering via the raster surface. */
+ if (skiafont_info->skia_font)
+ {
+ /* Allocate glyph and position arrays. */
+ emacs_skia_glyph_t *glyphs
+ = alloca (sizeof (emacs_skia_glyph_t) * len);
+ emacs_skia_point_t *positions
+ = alloca (sizeof (emacs_skia_point_t) * len);
+
+ float current_x = 0;
+ for (i = 0; i < len; i++)
+ {
+ glyphs[i] = s->char2b[from + i];
+ positions[i].x = current_x;
+ positions[i].y = 0;
+ current_x += (s->padding_p
+ ? 1
+ : skiafont_glyph_extents (s->font,
+ glyphs[i], NULL));
+ }
+
+ emacs_skia_point_t origin = { x, y };
+ emacs_skia_canvas_draw_glyphs (canvas, len, glyphs, positions,
+ origin, skiafont_info->skia_font,
+ paint);
+ }
+ else
+ {
+ /* Fallback: draw each glyph as a rectangle (placeholder). */
+ float current_x = x;
+ for (i = 0; i < len; i++)
+ {
+ int glyph_width
+ = skiafont_glyph_extents (s->font, s->char2b[from + i],
+ NULL);
+ /* For fallback, we could potentially use Cairo here,
+ but for now just advance. */
+ current_x += (s->padding_p ? 1 : glyph_width);
+ }
+ }
+
+ pgtk_end_skia_clip (f);
+ unblock_input ();
+
+ return len;
+}
+
+/* These functions use the stored FT_Face directly for FreeType
+ * operations. */
+
+static int
+skiafont_get_bitmap (struct font *font, unsigned int code,
+ struct font_bitmap *bitmap, int bits_per_pixel)
+{
+ struct font_info *ftfont_info = (struct font_info *) font;
+ FT_Face ft_face = ftfont_info->ft_face;
+
+ if (!ft_face)
+ return -1;
+
+ ftfont_info->ft_size = ft_face->size;
+ int result = ftfont_get_bitmap (font, code, bitmap, bits_per_pixel);
+ ftfont_info->ft_size = NULL;
+
+ return result;
+}
+
+static int
+skiafont_anchor_point (struct font *font, unsigned int code, int idx,
+ int *x, int *y)
+{
+ struct font_info *ftfont_info = (struct font_info *) font;
+ FT_Face ft_face = ftfont_info->ft_face;
+
+ if (!ft_face)
+ return -1;
+
+ ftfont_info->ft_size = ft_face->size;
+ int result = ftfont_anchor_point (font, code, idx, x, y);
+ ftfont_info->ft_size = NULL;
+
+ return result;
+}
+
+# ifdef HAVE_LIBOTF
+static Lisp_Object
+skiafont_otf_capability (struct font *font)
+{
+ struct font_info *ftfont_info = (struct font_info *) font;
+ FT_Face ft_face = ftfont_info->ft_face;
+
+ if (!ft_face)
+ return Qnil;
+
+ ftfont_info->ft_size = ft_face->size;
+ Lisp_Object result = ftfont_otf_capability (font);
+ ftfont_info->ft_size = NULL;
+
+ return result;
+}
+# endif
+
+# if defined HAVE_M17N_FLT && defined HAVE_LIBOTF
+static Lisp_Object
+skiafont_shape (Lisp_Object lgstring, Lisp_Object direction)
+{
+ struct font *font
+ = CHECK_FONT_GET_OBJECT (LGSTRING_FONT (lgstring));
+ struct font_info *ftfont_info = (struct font_info *) font;
+ FT_Face ft_face = ftfont_info->ft_face;
+
+ if (!ft_face)
+ return Qnil;
+
+ ftfont_info->ft_size = ft_face->size;
+ Lisp_Object result = ftfont_shape (lgstring, direction);
+ ftfont_info->ft_size = NULL;
+
+ return result;
+}
+# endif
+
+# if defined HAVE_OTF_GET_VARIATION_GLYPHS \
+ || defined HAVE_FT_FACE_GETCHARVARIANTINDEX
+static int
+skiafont_variation_glyphs (struct font *font, int c,
+ unsigned variations[256])
+{
+ struct font_info *ftfont_info = (struct font_info *) font;
+ FT_Face ft_face = ftfont_info->ft_face;
+
+ if (!ft_face)
+ return 0;
+
+ ftfont_info->ft_size = ft_face->size;
+ int result = ftfont_variation_glyphs (font, c, variations);
+ ftfont_info->ft_size = NULL;
+
+ return result;
+}
+# endif
+
+# ifdef HAVE_HARFBUZZ
+
+static Lisp_Object
+skiahbfont_list (struct frame *f, Lisp_Object spec)
+{
+ return ftfont_list2 (f, spec, Qskiahb);
+}
+
+static Lisp_Object
+skiahbfont_match (struct frame *f, Lisp_Object spec)
+{
+ return ftfont_match2 (f, spec, Qskiahb);
+}
+
+static hb_font_t *
+skiahbfont_begin_hb_font (struct font *font, double *position_unit)
+{
+ struct font_info *ftfont_info = (struct font_info *) font;
+ FT_Face ft_face = ftfont_info->ft_face;
+
+ if (!ft_face)
+ return NULL;
+
+ ftfont_info->ft_size = ft_face->size;
+ hb_font_t *hb_font = fthbfont_begin_hb_font (font, position_unit);
+ if ((hb_version_atleast (5, 2, 0) || !hb_version_atleast (5, 0, 0))
+ && ftfont_info->bitmap_position_unit)
+ *position_unit = ftfont_info->bitmap_position_unit;
+
+ return hb_font;
+}
+
+static void
+skiahbfont_end_hb_font (struct font *font, hb_font_t *hb_font)
+{
+ struct font_info *ftfont_info = (struct font_info *) font;
+
+ eassert (hb_font == ftfont_info->hb_font);
+ hb_font_destroy (ftfont_info->hb_font);
+ ftfont_info->hb_font = NULL;
+ ftfont_info->ft_size = NULL;
+}
+
+# endif /* HAVE_HARFBUZZ */
+
+static void syms_of_skiafont_for_pdumper (void);
+
+struct font_driver const skiafont_driver = {
+ .type = LISPSYM_INITIALLY (Qskia),
+ .get_cache = ftfont_get_cache,
+ .list = skiafont_list,
+ .match = skiafont_match,
+ .list_family = ftfont_list_family,
+ .open_font = skiafont_open,
+ .close_font = skiafont_close,
+ .has_char = skiafont_has_char,
+ .encode_char = skiafont_encode_char,
+ .text_extents = skiafont_text_extents,
+ .draw = skiafont_draw,
+ .get_bitmap = skiafont_get_bitmap,
+ .anchor_point = skiafont_anchor_point,
+# ifdef HAVE_LIBOTF
+ .otf_capability = skiafont_otf_capability,
+# endif
+# if defined HAVE_M17N_FLT && defined HAVE_LIBOTF
+ .shape = skiafont_shape,
+# endif
+# if defined HAVE_OTF_GET_VARIATION_GLYPHS \
+ || defined HAVE_FT_FACE_GETCHARVARIANTINDEX
+ .get_variation_glyphs = skiafont_variation_glyphs,
+# endif
+ .filter_properties = ftfont_filter_properties,
+ .combining_capability = ftfont_combining_capability,
+};
+
+# ifdef HAVE_HARFBUZZ
+struct font_driver skiahbfont_driver;
+# endif
+
+void
+syms_of_skiafont (void)
+{
+ DEFSYM (Qskia, "skia");
+# ifdef HAVE_HARFBUZZ
+ DEFSYM (Qskiahb, "skiahb");
+ Fput (Qskia, Qfont_driver_superseded_by, Qskiahb);
+# endif
+ pdumper_do_now_and_after_load (syms_of_skiafont_for_pdumper);
+}
+
+static void
+syms_of_skiafont_for_pdumper (void)
+{
+ register_font_driver (&skiafont_driver, NULL);
+# ifdef HAVE_HARFBUZZ
+ skiahbfont_driver = skiafont_driver;
+ skiahbfont_driver.type = Qskiahb;
+ skiahbfont_driver.list = skiahbfont_list;
+ skiahbfont_driver.match = skiahbfont_match;
+ skiahbfont_driver.otf_capability = hbfont_otf_capability;
+ skiahbfont_driver.shape = hbfont_shape;
+ skiahbfont_driver.combining_capability
+ = hbfont_combining_capability;
+ skiahbfont_driver.begin_hb_font = skiahbfont_begin_hb_font;
+ skiahbfont_driver.end_hb_font = skiahbfont_end_hb_font;
+ register_font_driver (&skiahbfont_driver, NULL);
+# endif
+}
+
+#endif /* USE_SKIA */
--
2.52.0
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
filename=0005-Implement-Skia-rendering-for-PGTK.patch
From 696f6d44d694a222cb00400eb225b0a920e87d99 Mon Sep 17 00:00:00 2001
From: Arthur Heymans <arthur@HIDDEN>
Date: Tue, 27 Jan 2026 08:07:12 +0100
Subject: [PATCH 5/6] Implement Skia rendering for PGTK
This implements the Skia rendering backend for PGTK, providing
GPU-accelerated drawing for all Emacs primitives including text,
rectangles, images, and cursors.
* src/pgtkterm.c: Major changes to support Skia rendering.
Add Skia drawing implementations for all primitives: rectangles,
lines, fringe bitmaps, glyphs, composite glyphs, cursors, and images.
Implement GtkGLArea integration for GPU-accelerated rendering.
Add surface management with GL framebuffer support.
* src/pgtkfns.c (Fx_export_frames): Support Skia PDF/SVG/PNG export.
(update_watched_scale_factor): Use Skia surface update when USE_SKIA.
(Fx_create_frame): Register Skia font drivers when USE_SKIA.
* src/gtkutil.c (xg_tool_item_stale_p, update_frame_tool_bar):
Handle Skia image data for toolbar icons.
* src/image.c: Extend image handling for Skia backend.
(image_create_pix_container): Use Skia stride calculation.
(skia_put_image_to_skia_data): New function to convert pixel
containers to Skia images with proper alpha handling.
---
src/gtkutil.c | 19 +-
src/image.c | 255 +++++-
src/pgtkfns.c | 86 +-
src/pgtkterm.c | 2141 +++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 2343 insertions(+), 158 deletions(-)
diff --git a/src/gtkutil.c b/src/gtkutil.c
index 9645bbad9c3..a463501e826 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -511,7 +511,8 @@ xg_get_image_for_pixmap (struct frame *f,
{
#ifdef USE_CAIRO
cairo_surface_t *surface;
-#else
+#endif
+#ifdef HAVE_X_WINDOWS
GdkPixbuf *icon_buf;
#endif
@@ -568,7 +569,7 @@ xg_get_image_for_pixmap (struct frame *f,
}
#endif /* !HAVE_GTK3 */
}
-#else
+#elif defined HAVE_X_WINDOWS
/* This is a workaround to make icons look good on pseudo color
displays. Apparently GTK expects the images to have an alpha
channel. If they don't, insensitive and activated icons will
@@ -5796,7 +5797,11 @@ xg_tool_item_stale_p (GtkWidget *wbutton, const char *stock_name,
void *old_img = (void *) gold_img;
if (old_img != img->cr_data)
return 1;
-#else
+#elif defined USE_SKIA
+ void *old_img = (void *) gold_img;
+ if (old_img != img->skia_data)
+ return 1;
+#elif defined HAVE_X_WINDOWS
Pixmap old_img = (Pixmap) gold_img;
if (old_img != img->pixmap)
return 1;
@@ -6104,6 +6109,8 @@ update_frame_tool_bar (struct frame *f)
if (img->load_failed_p
#ifdef USE_CAIRO
|| img->cr_data == NULL
+#elif defined USE_SKIA
+ || img->skia_data == NULL
#else
|| img->pixmap == None
#endif
@@ -6162,8 +6169,12 @@ update_frame_tool_bar (struct frame *f)
g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA,
#ifdef USE_CAIRO
(gpointer)img->cr_data
-#else
+#elif defined USE_SKIA
+ (gpointer)img->skia_data
+#elif defined HAVE_X_WINDOWS
(gpointer)img->pixmap
+#else
+ (gpointer)NULL
#endif
);
}
diff --git a/src/image.c b/src/image.c
index 119287db899..056788ef298 100644
--- a/src/image.c
+++ b/src/image.c
@@ -64,6 +64,10 @@ Copyright (C) 1989-2026 Free Software Foundation, Inc.
#include TERM_HEADER
#endif /* HAVE_WINDOW_SYSTEM */
+#ifdef USE_SKIA
+# include "skia/emacs_skia.h"
+#endif
+
#ifdef HAVE_X_WINDOWS
typedef struct x_bitmap_record Bitmap_Record;
#ifndef USE_CAIRO
@@ -76,9 +80,9 @@ #define PIX_MASK_DRAW 1
#endif /* !USE_CAIRO */
#endif /* HAVE_X_WINDOWS */
-#if defined(USE_CAIRO) || defined(HAVE_NS)
+#if defined (USE_CAIRO) || defined (USE_SKIA) || defined (HAVE_NS)
#define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b))
-#ifndef HAVE_NS
+#if defined (USE_CAIRO) && !defined (HAVE_NS)
#define ARGB_TO_ULONG(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
#endif
#define RED_FROM_ULONG(color) (((color) >> 16) & 0xff)
@@ -89,7 +93,7 @@ #define GREEN16_FROM_ULONG(color) (GREEN_FROM_ULONG (color) * 0x101)
#define BLUE16_FROM_ULONG(color) (BLUE_FROM_ULONG (color) * 0x101)
#endif
-#ifdef USE_CAIRO
+#if defined (USE_CAIRO) || defined (USE_SKIA)
#define GET_PIXEL image_pix_context_get_pixel
#define PUT_PIXEL image_pix_container_put_pixel
#define NO_PIXMAP 0
@@ -99,7 +103,7 @@ #define PIX_MASK_DRAW 255
static unsigned long image_alloc_image_color (struct frame *, struct image *,
Lisp_Object, unsigned long);
-#endif /* USE_CAIRO */
+#endif /* USE_CAIRO || USE_SKIA */
#if defined HAVE_PGTK && defined HAVE_IMAGEMAGICK
/* In pgtk, we don't want to create scaled image. If we create scaled
@@ -228,7 +232,7 @@ #define n_planes n_image_planes
static void anim_prune_animation_cache (Lisp_Object);
#endif
-#ifdef USE_CAIRO
+#if defined (USE_CAIRO) || defined (USE_SKIA)
static Emacs_Pix_Container
image_create_pix_container (unsigned int width, unsigned int height,
@@ -240,10 +244,17 @@ image_create_pix_container (unsigned int width, unsigned int height,
pimg->width = width;
pimg->height = height;
pimg->bits_per_pixel = depth == 1 ? 8 : 32;
+#ifdef USE_CAIRO
pimg->bytes_per_line = cairo_format_stride_for_width ((depth == 1
? CAIRO_FORMAT_A8
: CAIRO_FORMAT_RGB24),
width);
+#elif defined (USE_SKIA)
+ /* Use Skia's stride calculation when Cairo is not available.
+ Format 0 = A8 (1 byte/pixel), 1 = RGB24/ARGB32 (4 bytes/pixel). */
+ pimg->bytes_per_line
+ = emacs_skia_format_stride_for_width (depth == 1 ? 0 : 1, width);
+#endif
pimg->data = xmalloc (pimg->bytes_per_line * height);
return pimg;
@@ -268,6 +279,7 @@ image_pix_context_get_pixel (Emacs_Pix_Context image, int x, int y)
return ((uint8_t *)(image->data + y * image->bytes_per_line))[x];
}
+#ifdef USE_CAIRO
static Emacs_Pix_Container
image_pix_container_create_from_bitmap_data (char *data, unsigned int width,
unsigned int height,
@@ -287,7 +299,6 @@ image_pix_container_create_from_bitmap_data (char *data, unsigned int width,
return pimg;
}
-
static cairo_surface_t *
cr_create_surface_from_pix_containers (Emacs_Pix_Container pimg,
Emacs_Pix_Container mask)
@@ -352,6 +363,67 @@ cr_put_image_to_cr_data (struct image *img)
#endif /* USE_CAIRO */
+#endif /* USE_CAIRO || USE_SKIA */
+
+#ifdef USE_SKIA
+/* Create a Skia image from pixel containers.
+ This converts the pixel data into a Skia image for rendering. */
+static void
+skia_put_image_to_skia_data (struct image *img)
+{
+ if (img->pixmap && img->pixmap->data)
+ {
+ int width = img->pixmap->width;
+ int height = img->pixmap->height;
+ int stride = img->pixmap->bytes_per_line;
+ unsigned char *data = (unsigned char *) img->pixmap->data;
+
+ /* Create a copy of the data since Skia needs to own it.
+ For Skia, we create a copy with premultiplied alpha. */
+ size_t data_size = stride * height;
+ unsigned char *data_copy = xmalloc (data_size);
+ memcpy (data_copy, data, data_size);
+
+ /* If we have a mask, apply it to create premultiplied alpha.
+ Otherwise, set alpha to 0xFF for Skia to render correctly. */
+ if (img->mask && img->mask->data)
+ {
+ unsigned char *mask_data = (unsigned char *) img->mask->data;
+ int mask_stride = img->mask->bytes_per_line;
+ for (int y = 0; y < height; y++)
+ {
+ uint32_t *row = (uint32_t *) (data_copy + y * stride);
+ uint8_t *mask_row = mask_data + y * mask_stride;
+ for (int x = 0; x < width; x++)
+ {
+ uint8_t alpha = mask_row[x];
+ uint32_t pixel = row[x];
+ int r = ((pixel >> 16) & 0xFF) * alpha / 255;
+ int g = ((pixel >> 8) & 0xFF) * alpha / 255;
+ int b = (pixel & 0xFF) * alpha / 255;
+ row[x] = (alpha << 24) | (r << 16) | (g << 8) | b;
+ }
+ }
+ }
+ else
+ {
+ /* No mask - set alpha to fully opaque. */
+ for (int y = 0; y < height; y++)
+ {
+ uint32_t *row = (uint32_t *) (data_copy + y * stride);
+ for (int x = 0; x < width; x++)
+ row[x] = row[x] | 0xFF000000;
+ }
+ }
+
+ bool has_alpha = (img->mask && img->mask->data);
+ img->skia_data = emacs_skia_image_create_from_bgra_pixels (
+ width, height, data_copy, stride, has_alpha);
+ xfree (data_copy);
+ }
+}
+#endif /* USE_SKIA */
+
#ifdef HAVE_NS
/* Use with images created by ns_image_for_XPM. */
static unsigned long
@@ -477,7 +549,7 @@ image_reference_bitmap (struct frame *f, ptrdiff_t id)
++FRAME_DISPLAY_INFO (f)->bitmaps[id - 1].refcount;
}
-#ifdef HAVE_PGTK
+#if defined HAVE_PGTK && defined USE_CAIRO
/* Create a Cairo pattern from the bitmap BITS, which should be WIDTH
and HEIGHT in size. BITS's fill order is LSB first, meaning that
@@ -572,7 +644,7 @@ image_bitmap_to_cr_pattern (char *bits, int width, int height)
return pattern;
}
-#endif /* HAVE_PGTK */
+#endif /* HAVE_PGTK && USE_CAIRO */
/* Create a bitmap for frame F from a HEIGHT x WIDTH array of bits at BITS. */
@@ -639,11 +711,11 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
return -1;
#endif
-#ifdef HAVE_PGTK
+#if defined HAVE_PGTK && defined USE_CAIRO
cairo_pattern_t *pattern;
pattern = image_bitmap_to_cr_pattern (bits, width, height);
-#endif /* HAVE_PGTK */
+#endif /* HAVE_PGTK && USE_CAIRO */
#ifdef HAVE_HAIKU
void *bitmap, *stipple;
@@ -677,7 +749,11 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
#ifdef HAVE_PGTK
dpyinfo->bitmaps[id - 1].depth = 1;
+# ifdef USE_SKIA
+ dpyinfo->bitmaps[id - 1].skia_image = NULL; /* Created on demand */
+# else
dpyinfo->bitmaps[id - 1].pattern = pattern;
+# endif
#endif
#ifdef HAVE_HAIKU
@@ -861,8 +937,15 @@ image_create_bitmap_from_file (struct frame *f, Lisp_Object file)
dpyinfo->bitmaps[id - 1].file = xlispstrdup (file);
dpyinfo->bitmaps[id - 1].height = width;
dpyinfo->bitmaps[id - 1].width = height;
+# ifdef USE_SKIA
+ dpyinfo->bitmaps[id - 1].skia_image
+ = emacs_skia_image_create_from_bitmap ((unsigned char *) data,
+ width, height,
+ (width + 7) / 8);
+# else
dpyinfo->bitmaps[id - 1].pattern
= image_bitmap_to_cr_pattern (data, width, height);
+# endif
xfree (contents);
xfree (data);
return id;
@@ -1116,8 +1199,13 @@ free_bitmap_record (Display_Info *dpyinfo, Bitmap_Record *bm)
#endif
#ifdef HAVE_PGTK
+# ifdef USE_SKIA
+ if (bm->skia_image != NULL)
+ emacs_skia_image_destroy (bm->skia_image);
+# else
if (bm->pattern != NULL)
cairo_pattern_destroy (bm->pattern);
+# endif
#endif
#ifdef HAVE_HAIKU
@@ -1860,6 +1948,12 @@ prepare_image_for_display (struct frame *f, struct image *img)
we have img->pixmap->data/img->mask->data. */
IMAGE_BACKGROUND (img, f, img->pixmap);
IMAGE_BACKGROUND_TRANSPARENT (img, f, img->mask);
+# ifdef USE_SKIA
+ /* Create Skia image BEFORE cr_put_image_to_cr_data,
+ which frees the pixel data. */
+ if (img->skia_data == NULL)
+ skia_put_image_to_skia_data (img);
+# endif
cr_put_image_to_cr_data (img);
if (img->cr_data == NULL)
{
@@ -1869,6 +1963,17 @@ prepare_image_for_display (struct frame *f, struct image *img)
}
unblock_input ();
}
+#elif defined (USE_SKIA)
+ /* For Skia without Cairo, create Skia image from pixel containers. */
+ if (!img->load_failed_p)
+ {
+ block_input ();
+ IMAGE_BACKGROUND (img, f, img->pixmap);
+ IMAGE_BACKGROUND_TRANSPARENT (img, f, img->mask);
+ if (img->skia_data == NULL)
+ skia_put_image_to_skia_data (img);
+ unblock_input ();
+ }
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
if (!img->load_failed_p)
{
@@ -2124,6 +2229,19 @@ image_clear_image_1 (struct frame *f, struct image *img, int flags)
img->cr_data = NULL;
}
#endif /* USE_CAIRO */
+
+#ifdef USE_SKIA
+ if (img->skia_data)
+ {
+ emacs_skia_image_destroy (img->skia_data);
+ img->skia_data = NULL;
+ }
+ if (img->skia_transform)
+ {
+ emacs_skia_image_transform_destroy (img->skia_transform);
+ img->skia_transform = NULL;
+ }
+#endif /* USE_SKIA */
}
/* Free X resources of image IMG which is used on frame F. */
@@ -2889,6 +3007,8 @@ compute_image_size (struct frame *f, double width, double height,
typedef double matrix3x3[3][3];
+#if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NTGUI \
+ || defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_ANDROID
static void
matrix3x3_mult (matrix3x3 a, matrix3x3 b, matrix3x3 result)
{
@@ -2901,6 +3021,7 @@ matrix3x3_mult (matrix3x3 a, matrix3x3 b, matrix3x3 result)
result[i][j] = sum;
}
}
+#endif
static void
compute_image_rotation (struct image *img, double *rotation)
@@ -3077,8 +3198,8 @@ image_set_transform (struct frame *f, struct image *img)
/* Determine flipping. */
flip = !NILP (image_spec_value (img->spec, QCflip, NULL));
-# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_HAIKU \
- || defined HAVE_ANDROID || defined HAVE_NTGUI
+# if defined USE_CAIRO || defined USE_SKIA || defined HAVE_XRENDER || defined HAVE_NS \
+ || defined HAVE_HAIKU || defined HAVE_ANDROID || defined HAVE_NTGUI
/* We want scale up operations to use a nearest neighbor filter to
show real pixels instead of munging them, but scale down
operations to use a blended filter, to avoid aliasing and the like. */
@@ -3350,6 +3471,44 @@ image_set_transform (struct frame *f, struct image *img)
drawing time, so store it for later. */
ns_image_set_transform (img->pixmap, matrix);
ns_image_set_smoothing (img->pixmap, smoothing);
+# elif defined USE_SKIA
+ /* Store transformation in Skia-native format. */
+ {
+ emacs_skia_image_transform_t *transform
+ = emacs_skia_image_transform_create ();
+ if (transform)
+ {
+ /* Convert 3x3 matrix to Skia's 6-element format [a,b,c,d,e,f]. */
+ float skia_matrix[6] = {
+ (float) matrix[0][0], /* a = scale x */
+ (float) matrix[0][1], /* b = skew y */
+ (float) matrix[1][0], /* c = skew x */
+ (float) matrix[1][1], /* d = scale y */
+ (float) matrix[2][0], /* e = translate x */
+ (float) matrix[2][1] /* f = translate y */
+ };
+ emacs_skia_image_transform_set_matrix (transform, skia_matrix);
+ emacs_skia_image_transform_set_smoothing (transform, smoothing);
+
+ /* Free any existing transform. */
+ if (img->skia_transform)
+ emacs_skia_image_transform_destroy (img->skia_transform);
+ img->skia_transform = transform;
+ }
+ }
+# ifdef USE_CAIRO
+ /* For hybrid builds, also store in Cairo format for compatibility. */
+ {
+ cairo_matrix_t cr_matrix = {matrix[0][0], matrix[0][1], matrix[1][0],
+ matrix[1][1], matrix[2][0], matrix[2][1]};
+ cairo_pattern_t *pattern = cairo_pattern_create_rgb (0, 0, 0);
+ cairo_pattern_set_matrix (pattern, &cr_matrix);
+ cairo_pattern_set_filter (pattern, smoothing
+ ? CAIRO_FILTER_BEST : CAIRO_FILTER_NEAREST);
+ /* Dummy solid color pattern just to record pattern matrix. */
+ img->cr_data = pattern;
+ }
+# endif
# elif defined USE_CAIRO
cairo_matrix_t cr_matrix = {matrix[0][0], matrix[0][1], matrix[1][0],
matrix[1][1], matrix[2][0], matrix[2][1]};
@@ -4040,7 +4199,7 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int d
Emacs_Pix_Container *pimg,
Emacs_Pixmap *pixmap, Picture *picture)
{
-#ifdef USE_CAIRO
+#if defined USE_CAIRO || defined USE_SKIA
eassert (input_blocked_p ());
/* Allocate a pixmap of the same size. */
@@ -4204,11 +4363,11 @@ image_destroy_x_image (Emacs_Pix_Container pimg)
eassert (input_blocked_p ());
if (pimg)
{
-#if defined USE_CAIRO || defined HAVE_HAIKU || defined HAVE_NS
+#if defined USE_CAIRO || defined USE_SKIA || defined HAVE_HAIKU || defined HAVE_NS
/* On these systems, Emacs_Pix_Containers always point to the same
data as pixmaps in `struct image', and therefore must never be
freed separately. */
-#endif /* USE_CAIRO || HAVE_HAIKU || HAVE_NS */
+#endif /* USE_CAIRO || USE_SKIA || HAVE_HAIKU || HAVE_NS */
#ifdef HAVE_NTGUI
/* Data will be freed by DestroyObject. */
pimg->data = NULL;
@@ -4227,7 +4386,7 @@ image_destroy_x_image (Emacs_Pix_Container pimg)
gui_put_x_image (struct frame *f, Emacs_Pix_Container pimg,
Emacs_Pixmap pixmap, int width, int height)
{
-#if defined USE_CAIRO || defined HAVE_HAIKU || defined HAVE_NS
+#if defined USE_CAIRO || defined USE_SKIA || defined HAVE_HAIKU || defined HAVE_NS
eassert (pimg == pixmap);
#elif defined HAVE_X_WINDOWS
GC gc;
@@ -4343,7 +4502,7 @@ image_unget_x_image_or_dc (struct image *img, bool mask_p,
static Emacs_Pix_Container
image_get_x_image (struct frame *f, struct image *img, bool mask_p)
{
-#if defined USE_CAIRO || defined (HAVE_HAIKU)
+#if defined USE_CAIRO || defined USE_SKIA || defined (HAVE_HAIKU)
return !mask_p ? img->pixmap : img->mask;
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
XImage *ximg_in_img = !mask_p ? img->ximg : img->mask_img;
@@ -5439,8 +5598,8 @@ #define Display xpm_Display
#endif /* not HAVE_NTGUI */
#endif /* HAVE_XPM */
-#if defined HAVE_XPM || defined USE_CAIRO || defined HAVE_NS \
- || defined HAVE_HAIKU || defined HAVE_ANDROID
+#if defined HAVE_XPM || defined USE_CAIRO || defined USE_SKIA \
+ || defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_ANDROID
/* Indices of image specification fields in xpm_format, below. */
@@ -5799,7 +5958,7 @@ x_create_bitmap_from_xpm_data (struct frame *f, const char **bits)
/* Load image IMG which will be displayed on frame F. Value is
true if successful. */
-#if defined HAVE_XPM && !defined USE_CAIRO
+#if defined HAVE_XPM && !defined USE_CAIRO && !defined USE_SKIA
static bool
xpm_load (struct frame *f, struct image *img)
@@ -6108,9 +6267,10 @@ xpm_load (struct frame *f, struct image *img)
return rc == XpmSuccess;
}
-#endif /* HAVE_XPM && !USE_CAIRO */
+#endif /* HAVE_XPM && !USE_CAIRO && !USE_SKIA */
#if (defined USE_CAIRO && defined HAVE_XPM) \
+ || (defined USE_SKIA && defined HAVE_XPM) \
|| (defined HAVE_NS && !defined HAVE_XPM) \
|| (defined HAVE_HAIKU && !defined HAVE_XPM) \
|| (defined HAVE_PGTK && !defined HAVE_XPM) \
@@ -6507,7 +6667,7 @@ #define expect_ident(IDENT) \
}
unsigned long frame_fg = FRAME_FOREGROUND_PIXEL (f);
-#ifdef USE_CAIRO
+#if defined USE_CAIRO || defined USE_SKIA
{
Emacs_Color color = {.pixel = frame_fg};
FRAME_TERMINAL (f)->query_colors (f, &color, 1);
@@ -6617,7 +6777,7 @@ xpm_load (struct frame *f,
return success_p;
}
-#endif /* HAVE_NS && !HAVE_XPM */
+#endif /* USE_CAIRO || USE_SKIA || HAVE_NS || HAVE_HAIKU || HAVE_PGTK || HAVE_ANDROID (internal XPM parser) */
@@ -6878,8 +7038,8 @@ lookup_rgb_color (struct frame *f, int r, int g, int b)
{
#ifdef HAVE_NTGUI
return PALETTERGB (r >> 8, g >> 8, b >> 8);
-#elif defined USE_CAIRO || defined HAVE_NS || defined HAVE_HAIKU \
- || defined HAVE_ANDROID
+#elif defined USE_CAIRO || defined USE_SKIA || defined HAVE_NS \
+ || defined HAVE_HAIKU || defined HAVE_ANDROID
return RGB_TO_ULONG (r >> 8, g >> 8, b >> 8);
#else
xsignal1 (Qfile_error,
@@ -6952,8 +7112,8 @@ image_to_emacs_colors (struct frame *f, struct image *img, bool rgb_p)
p = colors;
for (y = 0; y < img->height; ++y)
{
-#if !defined USE_CAIRO && !defined HAVE_NS && !defined HAVE_HAIKU \
- && !defined HAVE_ANDROID
+#if !defined USE_CAIRO && !defined USE_SKIA && !defined HAVE_NS \
+ && !defined HAVE_HAIKU && !defined HAVE_ANDROID
Emacs_Color *row = p;
for (x = 0; x < img->width; ++x, ++p)
p->pixel = GET_PIXEL (ximg, x, y);
@@ -6961,7 +7121,7 @@ image_to_emacs_colors (struct frame *f, struct image *img, bool rgb_p)
{
FRAME_TERMINAL (f)->query_colors (f, row, img->width);
}
-#else /* USE_CAIRO || HAVE_NS || HAVE_HAIKU || HAVE_ANDROID */
+#else /* USE_CAIRO || USE_SKIA || HAVE_NS || HAVE_HAIKU || HAVE_ANDROID */
for (x = 0; x < img->width; ++x, ++p)
{
p->pixel = GET_PIXEL (ximg, x, y);
@@ -6972,7 +7132,7 @@ image_to_emacs_colors (struct frame *f, struct image *img, bool rgb_p)
p->blue = BLUE16_FROM_ULONG (p->pixel);
}
}
-#endif /* USE_CAIRO || HAVE_NS || HAVE_ANDROID */
+#endif /* USE_CAIRO || USE_SKIA || HAVE_NS || HAVE_ANDROID */
}
image_unget_x_image_or_dc (img, 0, ximg, prev);
@@ -7203,8 +7363,8 @@ image_edge_detection (struct frame *f, struct image *img,
}
-#if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined HAVE_HAIKU \
- || defined HAVE_ANDROID
+#if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined USE_SKIA \
+ || defined HAVE_HAIKU || defined HAVE_ANDROID
static void
image_pixmap_draw_cross (struct frame *f, Emacs_Pixmap pixmap,
@@ -7231,6 +7391,29 @@ image_pixmap_draw_cross (struct frame *f, Emacs_Pixmap pixmap,
cairo_set_line_width (cr, 1);
cairo_stroke (cr);
cairo_destroy (cr);
+#elif defined USE_SKIA
+ /* For Skia without Cairo, draw the cross directly on the pixel data. */
+ if (pixmap && pixmap->data && pixmap->bits_per_pixel == 32)
+ {
+ uint32_t *data = (uint32_t *) pixmap->data;
+ int stride = pixmap->bytes_per_line / 4;
+ uint32_t pixel = 0xFF000000 | (color & 0xFFFFFF); /* ARGB */
+
+ /* Draw diagonal line from top-left to bottom-right. */
+ for (unsigned int i = 0; i < width && i < height; i++)
+ {
+ int px = x + i, py = y + i;
+ if (px >= 0 && px < pixmap->width && py >= 0 && py < pixmap->height)
+ data[py * stride + px] = pixel;
+ }
+ /* Draw diagonal line from bottom-left to top-right. */
+ for (unsigned int i = 0; i < width && i < height; i++)
+ {
+ int px = x + i, py = y + height - 1 - i;
+ if (px >= 0 && px < pixmap->width && py >= 0 && py < pixmap->height)
+ data[py * stride + px] = pixel;
+ }
+ }
#elif HAVE_X_WINDOWS
Display *dpy = FRAME_X_DISPLAY (f);
GC gc = XCreateGC (dpy, pixmap, 0, NULL);
@@ -7299,17 +7482,17 @@ image_disable_image (struct frame *f, struct image *img)
#ifndef HAVE_NTGUI
#ifndef HAVE_NS /* TODO: NS support, however this not needed for toolbars */
-#if !defined USE_CAIRO && !defined HAVE_HAIKU && !defined HAVE_ANDROID
+#if !defined USE_CAIRO && !defined USE_SKIA && !defined HAVE_HAIKU && !defined HAVE_ANDROID
#define CrossForeground(f) BLACK_PIX_DEFAULT (f)
#define MaskForeground(f) WHITE_PIX_DEFAULT (f)
-#else /* USE_CAIRO || HAVE_HAIKU */
+#else /* USE_CAIRO || USE_SKIA || HAVE_HAIKU */
#define CrossForeground(f) 0
#define MaskForeground(f) PIX_MASK_DRAW
-#endif /* USE_CAIRO || HAVE_HAIKU */
+#endif /* USE_CAIRO || USE_SKIA || HAVE_HAIKU */
-#if !defined USE_CAIRO && !defined HAVE_HAIKU
+#if !defined USE_CAIRO && !defined USE_SKIA && !defined HAVE_HAIKU
image_sync_to_pixmaps (f, img);
-#endif /* !USE_CAIRO && !HAVE_HAIKU */
+#endif /* !USE_CAIRO && !USE_SKIA && !HAVE_HAIKU */
image_pixmap_draw_cross (f, img->pixmap, 0, 0, img->width, img->height,
CrossForeground (f));
if (img->mask)
diff --git a/src/pgtkfns.c b/src/pgtkfns.c
index c336ce36d58..8127039cf84 100644
--- a/src/pgtkfns.c
+++ b/src/pgtkfns.c
@@ -890,7 +890,9 @@ DEFUN ("x-export-frames", Fx_export_frames, Sx_export_frames, 0, 2, 0,
(Lisp_Object frames, Lisp_Object type)
{
Lisp_Object rest, tmp;
+#ifdef USE_CAIRO
cairo_surface_type_t surface_type;
+#endif
if (!CONSP (frames))
frames = list1 (frames);
@@ -908,6 +910,12 @@ DEFUN ("x-export-frames", Fx_export_frames, Sx_export_frames, 0, 2, 0,
}
frames = Fnreverse (tmp);
+#ifdef USE_SKIA
+ /* Skia export supports pdf, svg, and png. */
+ if (NILP (type) || EQ (type, Qpdf) || EQ (type, Qsvg) || EQ (type, Qpng))
+ return pgtk_skia_export_frames (frames, type);
+ error ("Skia export supports pdf, svg, and png types");
+#else /* USE_CAIRO */
#ifdef CAIRO_HAS_PDF_SURFACE
if (NILP (type) || EQ (type, Qpdf))
surface_type = CAIRO_SURFACE_TYPE_PDF;
@@ -940,6 +948,7 @@ DEFUN ("x-export-frames", Fx_export_frames, Sx_export_frames, 0, 2, 0,
error ("Unsupported export type");
return pgtk_cr_export_frames (frames, surface_type);
+#endif /* USE_CAIRO */
}
extern frame_parm_handler pgtk_frame_parm_handlers[];
@@ -1122,10 +1131,17 @@ update_watched_scale_factor (struct atimer *timer)
if (scale_factor != FRAME_X_OUTPUT (f)->watched_scale_factor)
{
FRAME_X_OUTPUT (f)->watched_scale_factor = scale_factor;
+#ifdef USE_SKIA
+ pgtk_skia_update_surface_desired_size (f,
+ FRAME_SKIA_SURFACE_DESIRED_WIDTH (f),
+ FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f),
+ true);
+#else
pgtk_cr_update_surface_desired_size (f,
FRAME_CR_SURFACE_DESIRED_WIDTH (f),
FRAME_CR_SURFACE_DESIRED_HEIGHT (f),
true);
+#endif
}
}
@@ -1366,10 +1382,17 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, 1, 1, 0,
specbind (Qx_resource_name, name);
}
+#ifdef USE_SKIA
+ register_font_driver (&skiafont_driver, f);
+# ifdef HAVE_HARFBUZZ
+ register_font_driver (&skiahbfont_driver, f);
+# endif /* HAVE_HARFBUZZ */
+#else /* !USE_SKIA */
register_font_driver (&ftcrfont_driver, f);
-#ifdef HAVE_HARFBUZZ
+# ifdef HAVE_HARFBUZZ
register_font_driver (&ftcrhbfont_driver, f);
-#endif /* HAVE_HARFBUZZ */
+# endif /* HAVE_HARFBUZZ */
+#endif /* !USE_SKIA */
gui_default_parameter (f, parms, Qfont_backend, Qnil,
"fontBackend", "FontBackend", RES_TYPE_STRING);
@@ -1718,7 +1741,12 @@ #define INSTALL_CURSOR(FIELD, NAME) \
FRAME_X_OUTPUT (f)->border_color_css_provider = NULL;
+#ifdef USE_CAIRO
FRAME_X_OUTPUT (f)->cr_surface_visible_bell = NULL;
+#endif
+#ifdef USE_SKIA
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell = NULL;
+#endif
FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
FRAME_X_OUTPUT (f)->watched_scale_factor = 1.0;
struct timespec ts = make_timespec (1, 0);
@@ -2659,10 +2687,17 @@ pgtk_create_tip_frame (struct pgtk_display_info *dpyinfo, Lisp_Object parms, str
specbind (Qx_resource_name, name);
}
+#ifdef USE_SKIA
+ register_font_driver (&skiafont_driver, f);
+# ifdef HAVE_HARFBUZZ
+ register_font_driver (&skiahbfont_driver, f);
+# endif /* HAVE_HARFBUZZ */
+#else /* !USE_SKIA */
register_font_driver (&ftcrfont_driver, f);
-#ifdef HAVE_HARFBUZZ
+# ifdef HAVE_HARFBUZZ
register_font_driver (&ftcrhbfont_driver, f);
-#endif /* HAVE_HARFBUZZ */
+# endif /* HAVE_HARFBUZZ */
+#endif /* !USE_SKIA */
gui_default_parameter (f, parms, Qfont_backend, Qnil,
"fontBackend", "FontBackend", RES_TYPE_STRING);
@@ -3273,7 +3308,11 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
unblock_input ();
+#ifdef USE_SKIA
+ pgtk_skia_update_surface_desired_size (tip_f, width, height, false);
+#else
pgtk_cr_update_surface_desired_size (tip_f, width, height, false);
+#endif
w->must_be_updated_p = true;
update_single_window (w);
@@ -3506,6 +3545,7 @@ position (0, 0) of the selected frame's terminal. */)
}
+#ifdef USE_CAIRO
DEFUN ("pgtk-page-setup-dialog", Fpgtk_page_setup_dialog,
Spgtk_page_setup_dialog, 0, 0, 0,
doc: /* Pop up a page setup dialog.
@@ -3518,7 +3558,20 @@ DEFUN ("pgtk-page-setup-dialog", Fpgtk_page_setup_dialog,
return Qnil;
}
+#elif defined USE_SKIA
+DEFUN ("pgtk-page-setup-dialog", Fpgtk_page_setup_dialog,
+ Spgtk_page_setup_dialog, 0, 0, 0,
+ doc: /* Pop up a page setup dialog.
+The current page setup can be obtained using `x-get-page-setup'.
+Note: Print dialogs are not available when Emacs is built with Skia. */)
+ (void)
+{
+ error ("Print dialogs are not available in Skia builds; rebuild with Cairo for printing support");
+ return Qnil;
+}
+#endif
+#ifdef USE_CAIRO
DEFUN ("pgtk-get-page-setup", Fpgtk_get_page_setup,
Spgtk_get_page_setup, 0, 0, 0,
doc: /* Return the value of the current page setup.
@@ -3548,7 +3601,19 @@ DEFUN ("pgtk-get-page-setup", Fpgtk_get_page_setup,
return result;
}
+#elif defined USE_SKIA
+DEFUN ("pgtk-get-page-setup", Fpgtk_get_page_setup,
+ Spgtk_get_page_setup, 0, 0, 0,
+ doc: /* Return the value of the current page setup.
+Note: Print dialogs are not available when Emacs is built with Skia. */)
+ (void)
+{
+ error ("Print dialogs are not available in Skia builds; rebuild with Cairo for printing support");
+ return Qnil;
+}
+#endif
+#ifdef USE_CAIRO
DEFUN ("pgtk-print-frames-dialog", Fpgtk_print_frames_dialog, Spgtk_print_frames_dialog, 0, 1, "",
doc: /* Pop up a print dialog to print the current contents of FRAMES.
FRAMES should be nil (the selected frame), a frame, or a list of
@@ -3583,6 +3648,17 @@ frames (each of which corresponds to one page). Each frame should be
return Qnil;
}
+#elif defined USE_SKIA
+DEFUN ("pgtk-print-frames-dialog", Fpgtk_print_frames_dialog, Spgtk_print_frames_dialog, 0, 1, "",
+ doc: /* Pop up a print dialog to print the current contents of FRAMES.
+Note: Print dialogs are not available when Emacs is built with Skia. */)
+ (Lisp_Object frames)
+{
+ (void) frames;
+ error ("Print dialogs are not available in Skia builds; rebuild with Cairo for printing support");
+ return Qnil;
+}
+#endif
static void
clean_up_dialog (void)
@@ -3829,9 +3905,11 @@ syms_of_pgtkfns (void)
defsubr (&Sx_hide_tip);
defsubr (&Sx_export_frames);
+#if defined USE_CAIRO || defined USE_SKIA
defsubr (&Spgtk_page_setup_dialog);
defsubr (&Spgtk_get_page_setup);
defsubr (&Spgtk_print_frames_dialog);
+#endif
defsubr (&Spgtk_backend_display_class);
defsubr (&Spgtk_set_monitor_scale_factor);
diff --git a/src/pgtkterm.c b/src/pgtkterm.c
index bf482590bc5..b0f34396aff 100644
--- a/src/pgtkterm.c
+++ b/src/pgtkterm.c
@@ -26,6 +26,16 @@ Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2026 Free Software
#endif
#include <cairo.h>
+#ifdef CAIRO_HAS_PDF_SURFACE
+# include <cairo-pdf.h>
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+# include <cairo-ps.h>
+#endif
+#ifdef CAIRO_HAS_SVG_SURFACE
+# include <cairo-svg.h>
+#endif
+#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <pthread.h>
@@ -69,9 +79,32 @@ Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2026 Free Software
#include <gdk/gdkwayland.h>
#endif
+#ifdef USE_SKIA
+# include "skia/emacs_skia.h"
+# include <epoxy/gl.h> /* For GL types and functions */
+
+/* Convert Emacs pixel color (0xRRGGBB) to Skia color (0xAARRGGBB). */
+static inline emacs_skia_color_t
+pgtk_color_to_skia (unsigned long color)
+{
+ return EMACS_SKIA_COLOR_RGB ((color >> 16) & 0xff,
+ (color >> 8) & 0xff, color & 0xff);
+}
+
+/* Convert Emacs pixel color with explicit alpha to Skia color. */
+static inline emacs_skia_color_t
+pgtk_color_to_skia_alpha (unsigned long color, uint8_t alpha)
+{
+ return EMACS_SKIA_COLOR (alpha, (color >> 16) & 0xff,
+ (color >> 8) & 0xff, color & 0xff);
+}
+#endif
+
+#ifndef USE_SKIA
#define FRAME_CR_CONTEXT(f) ((f)->output_data.pgtk->cr_context)
#define FRAME_CR_ACTIVE_CONTEXT(f) ((f)->output_data.pgtk->cr_active)
#define FRAME_CR_SURFACE(f) (cairo_get_target (FRAME_CR_CONTEXT (f)))
+#endif
/* Non-zero means that a HELP_EVENT has been generated since Emacs
start. */
@@ -112,8 +145,29 @@ #define FRAME_CR_SURFACE(f) (cairo_get_target (FRAME_CR_CONTEXT (f)))
static void pgtk_clear_frame_area (struct frame *, int, int, int, int);
static void pgtk_fill_rectangle (struct frame *, unsigned long, int, int,
int, int, bool);
+#ifdef USE_SKIA
+static void pgtk_skia_fill_rectangle (struct frame *, unsigned long,
+ int, int, int, int, bool);
+static void pgtk_skia_draw_rectangle (struct frame *, unsigned long,
+ int, int, int, int, bool);
+static void pgtk_skia_clip_to_row (struct window *,
+ struct glyph_row *,
+ enum glyph_row_area,
+ emacs_skia_canvas_t *);
+static void pgtk_skia_set_clip_rectangles (struct frame *,
+ emacs_skia_canvas_t *,
+ XRectangle *, int);
+static void
+pgtk_skia_set_glyph_string_clipping (struct glyph_string *,
+ emacs_skia_canvas_t *);
+static void
+pgtk_skia_set_glyph_string_clipping_exactly (struct glyph_string *,
+ struct glyph_string *,
+ emacs_skia_canvas_t *);
+#else
static void pgtk_clip_to_row (struct window *, struct glyph_row *,
enum glyph_row_area, cairo_t *);
+#endif
static struct frame *pgtk_any_window_to_frame (GdkWindow *);
static void pgtk_regenerate_devices (struct pgtk_display_info *);
@@ -266,6 +320,7 @@ pgtk_get_device_for_event (struct pgtk_display_info *dpyinfo,
resize or other fundamental change. This is called when that
context's surface has completed drawing. */
+#ifndef USE_SKIA
static void
flip_cr_context (struct frame *f)
{
@@ -281,6 +336,7 @@ flip_cr_context (struct frame *f)
}
unblock_input ();
}
+#endif
static void
@@ -524,11 +580,20 @@ #define CLEAR_IF_EQ(FIELD) \
gtk_widget_destroy (FRAME_WIDGET (f));
+#ifdef USE_SKIA
+ if (FRAME_X_OUTPUT (f)->skia_surface_visible_bell != NULL)
+ {
+ emacs_skia_surface_destroy (
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell);
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell = NULL;
+ }
+#else
if (FRAME_X_OUTPUT (f)->cr_surface_visible_bell != NULL)
{
cairo_surface_destroy (FRAME_X_OUTPUT (f)->cr_surface_visible_bell);
FRAME_X_OUTPUT (f)->cr_surface_visible_bell = NULL;
}
+#endif
if (FRAME_X_OUTPUT (f)->atimer_visible_bell != NULL)
{
@@ -1228,6 +1293,36 @@ pgtk_set_glyph_string_gc (struct glyph_string *s)
/* Set clipping for output of glyph string S. S may be part of a mode
line or menu if we don't have X toolkit support. */
+#ifdef USE_SKIA
+static void
+pgtk_skia_set_glyph_string_clipping (struct glyph_string *s,
+ emacs_skia_canvas_t *canvas)
+{
+ XRectangle r[2];
+ int n = get_glyph_string_clip_rects (s, r, 2);
+
+ if (n > 0)
+ pgtk_skia_set_clip_rectangles (s->f, canvas, r, n);
+}
+
+static void
+pgtk_skia_set_glyph_string_clipping_exactly (
+ struct glyph_string *src, struct glyph_string *dst,
+ emacs_skia_canvas_t *canvas)
+{
+ dst->clip[0].x = src->x;
+ dst->clip[0].y = src->y;
+ dst->clip[0].width = src->width;
+ dst->clip[0].height = src->height;
+ dst->num_clips = 1;
+
+ emacs_skia_rect_t rect
+ = { src->x, src->y, src->x + src->width, src->y + src->height };
+ emacs_skia_canvas_clip_rect (canvas, &rect);
+}
+#endif
+
+#ifdef USE_CAIRO
static void
pgtk_set_glyph_string_clipping (struct glyph_string *s, cairo_t *cr)
{
@@ -1261,6 +1356,7 @@ pgtk_set_glyph_string_clipping_exactly (struct glyph_string *src,
cairo_rectangle (cr, src->x, src->y, src->width, src->height);
cairo_clip (cr);
}
+#endif
/* RIF:
Compute left and right overhang of glyph string S. */
@@ -1317,6 +1413,51 @@ pgtk_clear_glyph_string_rect (struct glyph_string *s, int x, int y,
fill_background_by_face (struct frame *f, struct face *face, int x, int y,
int width, int height)
{
+#ifdef USE_SKIA
+ if (face->stipple != 0)
+ {
+ /* Full Skia stipple implementation. */
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ uint8_t alpha = (uint8_t) (f->alpha_background * 255);
+
+ emacs_skia_canvas_save (canvas);
+ emacs_skia_rect_t clip = { x, y, x + width, y + height };
+ emacs_skia_canvas_clip_rect (canvas, &clip);
+
+ /* Draw background color. */
+ emacs_skia_paint_set_color (
+ paint, pgtk_color_to_skia_alpha (face->background, alpha));
+ emacs_skia_paint_set_blend_mode (paint, EMACS_SKIA_BLEND_SRC);
+ emacs_skia_paint_set_stroke (paint, false);
+ emacs_skia_canvas_draw_rect (canvas, &clip, paint);
+
+ /* Draw stipple pattern with foreground color. */
+ emacs_skia_image_t *stipple_image
+ = FRAME_DISPLAY_INFO (f)
+ ->bitmaps[face->stipple - 1]
+ .skia_image;
+ if (stipple_image)
+ {
+ emacs_skia_paint_set_color (
+ paint,
+ pgtk_color_to_skia_alpha (face->foreground, alpha));
+ emacs_skia_paint_set_image_shader (paint, stipple_image);
+ emacs_skia_canvas_draw_rect (canvas, &clip, paint);
+ emacs_skia_paint_clear_shader (paint);
+ }
+
+ emacs_skia_paint_set_blend_mode (paint,
+ EMACS_SKIA_BLEND_SRC_OVER);
+ emacs_skia_canvas_restore (canvas);
+ }
+ else
+ {
+ /* Simple solid fill - use Skia. */
+ pgtk_skia_fill_rectangle (f, face->background, x, y, width,
+ height, true);
+ }
+#else
cairo_t *cr = pgtk_begin_cr_clip (f);
double r, g, b, a;
@@ -1344,6 +1485,7 @@ fill_background_by_face (struct frame *f, struct face *face, int x, int y,
}
pgtk_end_cr_clip (f);
+#endif
}
static void
@@ -1396,6 +1538,10 @@ pgtk_draw_glyph_string_background (struct glyph_string *s, bool force_p)
pgtk_draw_rectangle (struct frame *f, unsigned long color, int x, int y,
int width, int height, bool respect_alpha_background)
{
+#ifdef USE_SKIA
+ pgtk_skia_draw_rectangle (f, color, x, y, width, height,
+ respect_alpha_background);
+#else
cairo_t *cr;
cr = pgtk_begin_cr_clip (f);
@@ -1404,6 +1550,7 @@ pgtk_draw_rectangle (struct frame *f, unsigned long color, int x, int y,
cairo_set_line_width (cr, 1);
cairo_stroke (cr);
pgtk_end_cr_clip (f);
+#endif
}
/* Draw the foreground of glyph string S. */
@@ -1719,6 +1866,24 @@ pgtk_compute_lighter_color (struct frame *f, unsigned long *pixel,
pgtk_fill_trapezoid_for_relief (struct frame *f, unsigned long color, int x,
int y, int width, int height, int top_p)
{
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ emacs_skia_path_t *path = emacs_skia_path_create ();
+
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (color));
+ emacs_skia_paint_set_stroke (paint, false);
+
+ emacs_skia_path_move_to (path, top_p ? x : x + height, y);
+ emacs_skia_path_line_to (path, x, y + height);
+ emacs_skia_path_line_to (path, top_p ? x + width - height : x + width,
+ y + height);
+ emacs_skia_path_line_to (path, x + width, y);
+ emacs_skia_path_close (path);
+
+ emacs_skia_canvas_draw_path (canvas, path, paint);
+ emacs_skia_path_destroy (path);
+#else
cairo_t *cr;
cr = pgtk_begin_cr_clip (f);
@@ -1729,6 +1894,7 @@ pgtk_fill_trapezoid_for_relief (struct frame *f, unsigned long color, int x,
cairo_line_to (cr, x + width, y);
cairo_fill (cr);
pgtk_end_cr_clip (f);
+#endif
}
enum corners
@@ -1745,6 +1911,48 @@ pgtk_erase_corners_for_relief (struct frame *f, unsigned long color, int x,
int y, int width, int height, double radius,
double margin, int corners)
{
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ emacs_skia_path_t *clip_path = emacs_skia_path_create ();
+ int i;
+
+ /* Build clipping path from corner arcs */
+ for (i = 0; i < CORNER_LAST; i++)
+ if (corners & (1 << i))
+ {
+ double xm, ym, xc, yc;
+ emacs_skia_rect_t oval;
+
+ if (i == CORNER_TOP_LEFT || i == CORNER_BOTTOM_LEFT)
+ xm = x - margin, xc = xm + radius;
+ else
+ xm = x + width + margin, xc = xm - radius;
+ if (i == CORNER_TOP_LEFT || i == CORNER_TOP_RIGHT)
+ ym = y - margin, yc = ym + radius;
+ else
+ ym = y + height + margin, yc = ym - radius;
+
+ emacs_skia_path_move_to (clip_path, xm, ym);
+ /* Arc bounding box */
+ oval.left = xc - radius;
+ oval.top = yc - radius;
+ oval.right = xc + radius;
+ oval.bottom = yc + radius;
+ /* Convert from radians to degrees: i * 90 degrees */
+ emacs_skia_path_arc_to (clip_path, &oval, i * 90.0, 90.0,
+ false);
+ }
+
+ emacs_skia_canvas_save (canvas);
+ emacs_skia_canvas_clip_path (canvas, clip_path);
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (color));
+ emacs_skia_paint_set_stroke (paint, false);
+ emacs_skia_rect_t rect = { x, y, x + width, y + height };
+ emacs_skia_canvas_draw_rect (canvas, &rect, paint);
+ emacs_skia_canvas_restore (canvas);
+ emacs_skia_path_destroy (clip_path);
+#else
cairo_t *cr;
int i;
@@ -1771,6 +1979,7 @@ pgtk_erase_corners_for_relief (struct frame *f, unsigned long color, int x,
cairo_rectangle (cr, x, y, width, height);
cairo_fill (cr);
pgtk_end_cr_clip (f);
+#endif
}
static void
@@ -1822,6 +2031,27 @@ pgtk_setup_relief_colors (struct glyph_string *s)
}
}
+#ifdef USE_SKIA
+static void
+pgtk_skia_set_clip_rectangles (struct frame *f,
+ emacs_skia_canvas_t *canvas,
+ XRectangle *rectangles, int n)
+{
+ if (n > 0)
+ {
+ for (int i = 0; i < n; i++)
+ {
+ emacs_skia_rect_t rect
+ = { rectangles[i].x, rectangles[i].y,
+ rectangles[i].x + rectangles[i].width,
+ rectangles[i].y + rectangles[i].height };
+ emacs_skia_canvas_clip_rect (canvas, &rect);
+ }
+ }
+}
+#endif
+
+#ifdef USE_CAIRO
static void
pgtk_set_clip_rectangles (struct frame *f, cairo_t *cr,
XRectangle *rectangles, int n)
@@ -1834,6 +2064,7 @@ pgtk_set_clip_rectangles (struct frame *f, cairo_t *cr,
cairo_clip (cr);
}
}
+#endif
/* Draw a relief on frame F inside the rectangle given by LEFT_X,
TOP_Y, RIGHT_X, and BOTTOM_Y. WIDTH is the thickness of the relief
@@ -1853,8 +2084,6 @@ pgtk_draw_relief_rect (struct frame *f,
unsigned long top_left_color, bottom_right_color;
int corners = 0;
- cairo_t *cr = pgtk_begin_cr_clip (f);
-
if (raised_p)
{
top_left_color = FRAME_X_OUTPUT (f)->white_relief.xgcv.foreground;
@@ -1866,7 +2095,14 @@ pgtk_draw_relief_rect (struct frame *f,
bottom_right_color = FRAME_X_OUTPUT (f)->white_relief.xgcv.foreground;
}
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_canvas_save (canvas);
+ pgtk_skia_set_clip_rectangles (f, canvas, clip_rect, 1);
+#else
+ cairo_t *cr = pgtk_begin_cr_clip (f);
pgtk_set_clip_rectangles (f, cr, clip_rect, 1);
+#endif
if (left_p)
{
@@ -1907,8 +2143,8 @@ pgtk_draw_relief_rect (struct frame *f,
right_x + 1 - left_x, hwidth, 0);
}
if (left_p && vwidth > 1)
- pgtk_fill_rectangle (f, bottom_right_color, left_x, top_y,
- 1, bottom_y + 1 - top_y, false);
+ pgtk_fill_rectangle (f, bottom_right_color, left_x, top_y, 1,
+ bottom_y + 1 - top_y, false);
if (top_p && hwidth > 1)
pgtk_fill_rectangle (f, bottom_right_color, left_x, top_y,
right_x + 1 - left_x, 1, false);
@@ -1917,7 +2153,12 @@ pgtk_draw_relief_rect (struct frame *f,
top_y, right_x - left_x + 1,
bottom_y - top_y + 1, 6, 1, corners);
+
+#ifdef USE_SKIA
+ emacs_skia_canvas_restore (canvas);
+#else
pgtk_end_cr_clip (f);
+#endif
}
/* Draw a box on frame F inside the rectangle given by LEFT_X, TOP_Y,
@@ -1935,12 +2176,17 @@ pgtk_draw_box_rect (struct glyph_string *s, int left_x,
{
unsigned long foreground_backup;
- cairo_t *cr = pgtk_begin_cr_clip (s->f);
-
foreground_backup = s->xgcv.foreground;
s->xgcv.foreground = s->face->box_color;
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (s->f);
+ emacs_skia_canvas_save (canvas);
+ pgtk_skia_set_clip_rectangles (s->f, canvas, clip_rect, 1);
+#else
+ cairo_t *cr = pgtk_begin_cr_clip (s->f);
pgtk_set_clip_rectangles (s->f, cr, clip_rect, 1);
+#endif
/* Top. */
pgtk_fill_rectangle (s->f, s->xgcv.foreground,
@@ -1966,7 +2212,11 @@ pgtk_draw_box_rect (struct glyph_string *s, int left_x,
s->xgcv.foreground = foreground_backup;
+#ifdef USE_SKIA
+ emacs_skia_canvas_restore (canvas);
+#else
pgtk_end_cr_clip (s->f);
+#endif
}
@@ -2021,6 +2271,48 @@ pgtk_draw_glyph_string_box (struct glyph_string *s)
pgtk_draw_horizontal_wave (struct frame *f, unsigned long color, int x, int y,
int width, int height, int wave_length)
{
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ emacs_skia_path_t *path = emacs_skia_path_create ();
+ double dx = wave_length, dy = height - 1;
+ int xoffset, n;
+
+ emacs_skia_canvas_save (canvas);
+ emacs_skia_rect_t clip = { x, y, x + width, y + height };
+ emacs_skia_canvas_clip_rect (canvas, &clip);
+
+ if (x >= 0)
+ {
+ xoffset = x % (wave_length * 2);
+ if (xoffset == 0)
+ xoffset = wave_length * 2;
+ }
+ else
+ xoffset = x % (wave_length * 2) + wave_length * 2;
+ n = (width + xoffset) / wave_length + 1;
+ if (xoffset > wave_length)
+ {
+ xoffset -= wave_length;
+ --n;
+ y += height - 1;
+ dy = -dy;
+ }
+
+ emacs_skia_path_move_to (path, x - xoffset + 0.5, y + 0.5);
+ while (--n >= 0)
+ {
+ emacs_skia_path_rel_line_to (path, dx, dy);
+ dy = -dy;
+ }
+
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (color));
+ emacs_skia_paint_set_stroke (paint, true);
+ emacs_skia_paint_set_stroke_width (paint, 1.0);
+ emacs_skia_canvas_draw_path (canvas, path, paint);
+ emacs_skia_canvas_restore (canvas);
+ emacs_skia_path_destroy (path);
+#else
cairo_t *cr;
double dx = wave_length, dy = height - 1;
int xoffset, n;
@@ -2056,6 +2348,7 @@ pgtk_draw_horizontal_wave (struct frame *f, unsigned long color, int x, int y,
cairo_set_line_width (cr, 1);
cairo_stroke (cr);
pgtk_end_cr_clip (f);
+#endif
}
static void
@@ -2172,6 +2465,56 @@ pgtk_draw_glyph_string_bg_rect (struct glyph_string *s, int x, int y, int w,
pgtk_clear_glyph_string_rect (s, x, y, w, h);
}
+#ifdef USE_SKIA
+/* Draw an image using Skia.
+ image: Skia image to draw
+ src_x, src_y: source position within the image
+ width, height: dimensions to draw
+ dest_x, dest_y: destination position on the frame
+ overlay_p: if true, draw on top of existing content; if false, fill
+ background first */
+static void
+pgtk_skia_draw_image (struct frame *f, Emacs_GC *gc,
+ emacs_skia_image_t *image, int src_x, int src_y,
+ int width, int height, int dest_x, int dest_y,
+ bool overlay_p)
+{
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+
+ emacs_skia_canvas_save (canvas);
+
+ /* Clip to destination rectangle */
+ emacs_skia_rect_t clip_rect
+ = { dest_x, dest_y, dest_x + width, dest_y + height };
+ emacs_skia_canvas_clip_rect (canvas, &clip_rect);
+
+ /* Fill background if not overlay mode */
+ if (!overlay_p)
+ {
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (
+ gc->background));
+ emacs_skia_paint_set_stroke (paint, false);
+ emacs_skia_canvas_draw_rect (canvas, &clip_rect, paint);
+ }
+
+ /* Draw the image */
+ emacs_skia_rect_t src_rect
+ = { src_x, src_y, src_x + width, src_y + height };
+ emacs_skia_rect_t dst_rect
+ = { dest_x, dest_y, dest_x + width, dest_y + height };
+
+ /* Reset paint to default for image drawing */
+ emacs_skia_paint_set_color (paint,
+ EMACS_SKIA_COLOR (255, 255, 255, 255));
+ emacs_skia_canvas_draw_image_rect (canvas, image, &src_rect,
+ &dst_rect, paint);
+
+ emacs_skia_canvas_restore (canvas);
+}
+#endif /* USE_SKIA */
+
+#ifdef USE_CAIRO
static void
pgtk_cr_draw_image (struct frame *f, Emacs_GC *gc, cairo_pattern_t *image,
int src_x, int src_y, int width, int height,
@@ -2207,6 +2550,7 @@ pgtk_cr_draw_image (struct frame *f, Emacs_GC *gc, cairo_pattern_t *image,
pgtk_end_cr_clip (f);
}
+#endif /* USE_CAIRO */
/* Draw foreground of image glyph string S. */
@@ -2230,6 +2574,56 @@ pgtk_draw_image_foreground (struct glyph_string *s)
if (s->slice.y == 0)
y += s->img->vmargin;
+#ifdef USE_SKIA
+ if (s->img->skia_data)
+ {
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (s->f);
+
+ emacs_skia_canvas_save (canvas);
+
+ /* Set up clipping */
+ if (s->num_clips > 0)
+ {
+ for (int i = 0; i < s->num_clips; i++)
+ {
+ emacs_skia_rect_t clip_rect
+ = { s->clip[i].x, s->clip[i].y,
+ s->clip[i].x + s->clip[i].width,
+ s->clip[i].y + s->clip[i].height };
+ emacs_skia_canvas_clip_rect (canvas, &clip_rect);
+ }
+ }
+
+ pgtk_skia_draw_image (s->f, &s->xgcv, s->img->skia_data,
+ s->slice.x, s->slice.y, s->slice.width,
+ s->slice.height, x, y, true);
+
+ if (!s->img->mask)
+ {
+ /* When the image has a mask, we can expect that at
+ least part of a mouse highlight or a block cursor will
+ be visible. If the image doesn't have a mask, make
+ a block cursor visible by drawing a rectangle around
+ the image. I believe it's looking better if we do
+ nothing here for mouse-face. */
+ if (s->hl == DRAW_CURSOR)
+ {
+ int relief = eabs (s->img->relief);
+ pgtk_draw_rectangle (s->f, s->xgcv.foreground,
+ x - relief, y - relief,
+ s->slice.width + relief * 2 - 1,
+ s->slice.height + relief * 2 - 1,
+ false);
+ }
+ }
+ emacs_skia_canvas_restore (canvas);
+ }
+ else
+ /* Draw a rectangle if image could not be loaded. */
+ pgtk_draw_rectangle (s->f, s->xgcv.foreground, x, y,
+ s->slice.width - 1, s->slice.height - 1,
+ false);
+#else /* USE_CAIRO */
if (s->img->cr_data)
{
cairo_t *cr = pgtk_begin_cr_clip (s->f);
@@ -2259,6 +2653,7 @@ pgtk_draw_image_foreground (struct glyph_string *s)
/* Draw a rectangle if image could not be loaded. */
pgtk_draw_rectangle (s->f, s->xgcv.foreground, x, y,
s->slice.width - 1, s->slice.height - 1, false);
+#endif /* USE_SKIA */
}
/* Draw image glyph string S.
@@ -2389,6 +2784,20 @@ pgtk_draw_stretch_glyph_string (struct glyph_string *s)
else
color = s->face->background;
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (s->f);
+ emacs_skia_canvas_save (canvas);
+
+ get_glyph_string_clip_rect (s, &r);
+ pgtk_skia_set_clip_rectangles (s->f, canvas, &r, 1);
+
+ if (s->face->stipple)
+ fill_background (s, x, y, w, h);
+ else
+ pgtk_fill_rectangle (s->f, color, x, y, w, h, true);
+
+ emacs_skia_canvas_restore (canvas);
+#else
cairo_t *cr = pgtk_begin_cr_clip (s->f);
get_glyph_string_clip_rect (s, &r);
@@ -2401,6 +2810,7 @@ pgtk_draw_stretch_glyph_string (struct glyph_string *s)
true);
pgtk_end_cr_clip (s->f);
+#endif
}
}
else if (!s->background_filled_p)
@@ -2434,6 +2844,21 @@ pgtk_draw_dash (struct frame *f, struct glyph_string *s,
unsigned long foreground, int width,
char segment, int offset, int thickness)
{
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ float sk_segment = (float) segment;
+ float y_center = s->ybase + offset + (thickness / 2.0);
+ float intervals[2] = { sk_segment, sk_segment };
+
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (foreground));
+ emacs_skia_paint_set_stroke (paint, true);
+ emacs_skia_paint_set_stroke_width (paint, thickness);
+ emacs_skia_paint_set_dash (paint, intervals, 2, (float) s->x);
+ emacs_skia_canvas_draw_line (canvas, s->x, y_center, s->x + width,
+ y_center, paint);
+ emacs_skia_paint_clear_dash (paint);
+#else
cairo_t *cr;
double cr_segment, y_center;
@@ -2448,6 +2873,7 @@ pgtk_draw_dash (struct frame *f, struct glyph_string *s,
cairo_line_to (cr, s->x + width, y_center);
cairo_stroke (cr);
pgtk_end_cr_clip (f);
+#endif
}
/* Draw an underline of STYLE onto F at an offset of POSITION from the
@@ -2495,6 +2921,11 @@ pgtk_fill_underline (struct frame *f, struct glyph_string *s,
pgtk_draw_glyph_string (struct glyph_string *s)
{
bool relief_drawn_p = false;
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas;
+#else
+ cairo_t *cr;
+#endif
/* If S draws into the background of its successors, draw the
background of the successors first so that S can draw into it.
@@ -2509,22 +2940,36 @@ pgtk_draw_glyph_string (struct glyph_string *s)
width += next->width, next = next->next)
if (next->first_glyph->type != IMAGE_GLYPH)
{
- cairo_t *cr = pgtk_begin_cr_clip (next->f);
+#ifdef USE_SKIA
+ canvas = pgtk_begin_skia_clip (next->f);
+ pgtk_set_glyph_string_gc (next);
+ pgtk_skia_set_glyph_string_clipping (next, canvas);
+#else
+ cr = pgtk_begin_cr_clip (next->f);
pgtk_set_glyph_string_gc (next);
pgtk_set_glyph_string_clipping (next, cr);
+#endif
if (next->first_glyph->type == STRETCH_GLYPH)
pgtk_draw_stretch_glyph_string (next);
else
pgtk_draw_glyph_string_background (next, true);
next->num_clips = 0;
+#ifdef USE_SKIA
+ pgtk_end_skia_clip (next->f);
+#else
pgtk_end_cr_clip (next->f);
+#endif
}
}
/* Set up S->gc, set clipping and draw S. */
pgtk_set_glyph_string_gc (s);
- cairo_t *cr = pgtk_begin_cr_clip (s->f);
+#ifdef USE_SKIA
+ canvas = pgtk_begin_skia_clip (s->f);
+#else
+ cr = pgtk_begin_cr_clip (s->f);
+#endif
/* Draw relief (if any) in advance for char/composition so that the
glyph string can be drawn over it. */
@@ -2534,10 +2979,17 @@ pgtk_draw_glyph_string (struct glyph_string *s)
|| s->first_glyph->type == COMPOSITE_GLYPH))
{
+#ifdef USE_SKIA
+ pgtk_skia_set_glyph_string_clipping (s, canvas);
+ pgtk_draw_glyph_string_background (s, true);
+ pgtk_draw_glyph_string_box (s);
+ pgtk_skia_set_glyph_string_clipping (s, canvas);
+#else
pgtk_set_glyph_string_clipping (s, cr);
pgtk_draw_glyph_string_background (s, true);
pgtk_draw_glyph_string_box (s);
pgtk_set_glyph_string_clipping (s, cr);
+#endif
relief_drawn_p = true;
}
else if (!s->clip_head /* draw_glyphs didn't specify a clip mask. */
@@ -2547,9 +2999,15 @@ pgtk_draw_glyph_string (struct glyph_string *s)
/* We must clip just this glyph. left_overhang part has already
drawn when s->prev was drawn, and right_overhang part will be
drawn later when s->next is drawn. */
+#ifdef USE_SKIA
+ pgtk_skia_set_glyph_string_clipping_exactly (s, s, canvas);
+ else
+ pgtk_skia_set_glyph_string_clipping (s, canvas);
+#else
pgtk_set_glyph_string_clipping_exactly (s, s, cr);
else
pgtk_set_glyph_string_clipping (s, cr);
+#endif
switch (s->first_glyph->type)
{
@@ -2744,15 +3202,25 @@ pgtk_draw_glyph_string (struct glyph_string *s)
prev->hl = s->hl;
pgtk_set_glyph_string_gc (prev);
+#ifdef USE_SKIA
+ emacs_skia_canvas_save (canvas);
+ pgtk_skia_set_glyph_string_clipping_exactly (s, prev,
+ canvas);
+#else
cairo_save (cr);
pgtk_set_glyph_string_clipping_exactly (s, prev, cr);
+#endif
if (prev->first_glyph->type == CHAR_GLYPH)
pgtk_draw_glyph_string_foreground (prev);
else
pgtk_draw_composite_glyph_string_foreground (prev);
prev->hl = save;
prev->num_clips = 0;
+#ifdef USE_SKIA
+ emacs_skia_canvas_restore (canvas);
+#else
cairo_restore (cr);
+#endif
}
}
@@ -2770,13 +3238,23 @@ pgtk_draw_glyph_string (struct glyph_string *s)
next->hl = s->hl;
pgtk_set_glyph_string_gc (next);
+#ifdef USE_SKIA
+ emacs_skia_canvas_save (canvas);
+ pgtk_skia_set_glyph_string_clipping_exactly (s, next,
+ canvas);
+#else
cairo_save (cr);
pgtk_set_glyph_string_clipping_exactly (s, next, cr);
+#endif
if (next->first_glyph->type == CHAR_GLYPH)
pgtk_draw_glyph_string_foreground (next);
else
pgtk_draw_composite_glyph_string_foreground (next);
+#ifdef USE_SKIA
+ emacs_skia_canvas_restore (canvas);
+#else
cairo_restore (cr);
+#endif
next->hl = save;
next->num_clips = 0;
next->clip_head = s->next;
@@ -2790,7 +3268,11 @@ pgtk_draw_glyph_string (struct glyph_string *s)
s->row->stipple_p = s->face->stipple;
/* Reset clipping. */
+#ifdef USE_SKIA
+ pgtk_end_skia_clip (s->f);
+#else
pgtk_end_cr_clip (s->f);
+#endif
s->num_clips = 0;
}
@@ -2823,8 +3305,8 @@ pgtk_after_update_window_line (struct window *w,
if (windows_or_buffers_changed
&& desired_row->full_width_p
&& (f = XFRAME (w->frame),
- width = FRAME_INTERNAL_BORDER_WIDTH (f),
- width != 0) && (height = desired_row->visible_height, height > 0))
+ width = FRAME_INTERNAL_BORDER_WIDTH (f), width != 0)
+ && (height = desired_row->visible_height, height > 0))
{
int y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, desired_row->y));
@@ -2861,11 +3343,6 @@ pgtk_draw_hollow_cursor (struct window *w, struct glyph_row *row)
get_phys_cursor_geometry (w, row, cursor_glyph, &x, &y, &h);
wd = w->phys_cursor_width - 1;
- /* The foreground of cursor_gc is typically the same as the normal
- background color, which can cause the cursor box to be invisible. */
- cairo_t *cr = pgtk_begin_cr_clip (f);
- pgtk_set_cr_source_with_color (f, FRAME_X_OUTPUT (f)->cursor_color, false);
-
/* When on R2L character, show cursor at the right edge of the
glyph, unless the cursor box is as wide as the glyph or wider
(the latter happens when x-stretch-cursor is non-nil). */
@@ -2876,11 +3353,40 @@ pgtk_draw_hollow_cursor (struct window *w, struct glyph_row *row)
if (wd > 0)
wd -= 1;
}
+
+#ifdef USE_SKIA
+ /* Use Skia directly with Skia clipping. */
+ emacs_skia_canvas_t *canvas = pgtk_begin_skia_clip (f);
+ if (canvas)
+ {
+ pgtk_skia_clip_to_row (w, row, TEXT_AREA, canvas);
+ pgtk_skia_set_paint_color (f, FRAME_X_OUTPUT (f)->cursor_color,
+ false);
+
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ emacs_skia_paint_set_stroke (paint, true);
+ emacs_skia_paint_set_stroke_width (paint, 1.0f);
+
+ emacs_skia_rect_t rect
+ = { x + 0.5f, y + 0.5f, x + wd + 0.5f, y + h - 1 + 0.5f };
+ emacs_skia_canvas_draw_rect (canvas, &rect, paint);
+
+ emacs_skia_paint_set_stroke (paint, false);
+ pgtk_end_skia_clip (f);
+ }
+#else
+ /* The foreground of cursor_gc is typically the same as the normal
+ background color, which can cause the cursor box to be invisible.
+ */
+ cairo_t *cr = pgtk_begin_cr_clip (f);
+ pgtk_set_cr_source_with_color (f, FRAME_X_OUTPUT (f)->cursor_color,
+ false);
/* Set clipping, draw the rectangle, and reset clipping again. */
pgtk_clip_to_row (w, row, TEXT_AREA, cr);
pgtk_draw_rectangle (f, FRAME_X_OUTPUT (f)->cursor_color,
x, y, wd, h - 1, false);
pgtk_end_cr_clip (f);
+#endif
}
/* Draw a bar cursor on window W in glyph row ROW.
@@ -2922,8 +3428,6 @@ pgtk_draw_bar_cursor (struct window *w, struct glyph_row *row, int width,
struct face *face = FACE_FROM_ID (f, cursor_glyph->face_id);
unsigned long color;
- cairo_t *cr = pgtk_begin_cr_clip (f);
-
/* If the glyph's background equals the color we normally draw
the bars cursor in, the bar cursor in its normal color is
invisible. Use the glyph's foreground color instead in this
@@ -2934,6 +3438,72 @@ pgtk_draw_bar_cursor (struct window *w, struct glyph_row *row, int width,
else
color = FRAME_X_OUTPUT (f)->cursor_color;
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = pgtk_begin_skia_clip (f);
+ if (canvas)
+ {
+ pgtk_skia_clip_to_row (w, row, TEXT_AREA, canvas);
+ pgtk_skia_set_paint_color (f, color, false);
+
+ if (kind == BAR_CURSOR)
+ {
+ int x
+ = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
+
+ if (width < 0)
+ width = FRAME_CURSOR_WIDTH (f);
+ width = min (cursor_glyph->pixel_width, width);
+
+ w->phys_cursor_width = width;
+
+ /* If the character under cursor is R2L, draw the bar
+ cursor on the right of its glyph, rather than on the
+ left. */
+ if ((cursor_glyph->resolved_level & 1) != 0)
+ x += cursor_glyph->pixel_width - width;
+
+ emacs_skia_irect_t rect
+ = { x, WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y),
+ x + width,
+ WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y)
+ + row->height };
+ emacs_skia_canvas_draw_irect (canvas, &rect,
+ FRAME_SKIA_PAINT (f));
+ }
+ else /* HBAR_CURSOR */
+ {
+ int dummy_x, dummy_y, dummy_h;
+ int x
+ = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
+
+ if (width < 0)
+ width = row->height;
+
+ width = min (row->height, width);
+
+ get_phys_cursor_geometry (w, row, cursor_glyph,
+ &dummy_x, &dummy_y, &dummy_h);
+
+ if ((cursor_glyph->resolved_level & 1) != 0
+ && cursor_glyph->pixel_width
+ > w->phys_cursor_width - 1)
+ x += cursor_glyph->pixel_width - w->phys_cursor_width
+ + 1;
+
+ int y = WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y
+ + row->height
+ - width);
+ emacs_skia_irect_t rect
+ = { x, y, x + w->phys_cursor_width - 1, y + width };
+ emacs_skia_canvas_draw_irect (canvas, &rect,
+ FRAME_SKIA_PAINT (f));
+ }
+
+ pgtk_end_skia_clip (f);
+ }
+#else
+ cairo_t *cr = pgtk_begin_cr_clip (f);
+
pgtk_clip_to_row (w, row, TEXT_AREA, cr);
if (kind == BAR_CURSOR)
@@ -2978,6 +3548,7 @@ pgtk_draw_bar_cursor (struct window *w, struct glyph_row *row, int width,
}
pgtk_end_cr_clip (f);
+#endif
}
}
@@ -3050,18 +3621,49 @@ pgtk_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, int x,
pgtk_copy_bits (struct frame *f, cairo_rectangle_t *src_rect,
cairo_rectangle_t *dst_rect)
{
- cairo_t *cr;
- cairo_surface_t *surface; /* temporary surface */
+#ifdef USE_SKIA
+ /* Skia version: snapshot the source region and draw to destination. */
+ emacs_skia_surface_t *skia_surface = FRAME_SKIA_SURFACE (f);
+ if (skia_surface)
+ {
+ emacs_skia_irect_t src_irect
+ = { (int) src_rect->x, (int) src_rect->y,
+ (int) (src_rect->x + src_rect->width),
+ (int) (src_rect->y + src_rect->height) };
- surface
- = cairo_surface_create_similar (FRAME_CR_SURFACE (f),
- CAIRO_CONTENT_COLOR_ALPHA,
- (int) src_rect->width,
- (int) src_rect->height);
+ emacs_skia_image_t *snapshot
+ = emacs_skia_surface_make_image_snapshot_rect (skia_surface,
+ &src_irect);
+ if (snapshot)
+ {
+ emacs_skia_canvas_t *canvas = pgtk_begin_skia_clip (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
- cr = cairo_create (surface);
- cairo_set_source_surface (cr, FRAME_CR_SURFACE (f), -src_rect->x,
- -src_rect->y);
+ /* Set up paint for direct copy (SRC blend mode). */
+ emacs_skia_paint_set_blend_mode (paint,
+ EMACS_SKIA_BLEND_SRC);
+
+ /* Draw the snapshot at the destination position. */
+ emacs_skia_canvas_draw_image (canvas, snapshot,
+ (float) dst_rect->x,
+ (float) dst_rect->y, paint);
+
+ pgtk_end_skia_clip (f);
+ emacs_skia_image_destroy (snapshot);
+ }
+ }
+#else
+ cairo_t *cr;
+ cairo_surface_t *surface; /* temporary surface */
+
+ surface = cairo_surface_create_similar (FRAME_CR_SURFACE (f),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ (int) src_rect->width,
+ (int) src_rect->height);
+
+ cr = cairo_create (surface);
+ cairo_set_source_surface (cr, FRAME_CR_SURFACE (f), -src_rect->x,
+ -src_rect->y);
cairo_rectangle (cr, 0, 0, src_rect->width, src_rect->height);
cairo_clip (cr);
cairo_paint (cr);
@@ -3077,6 +3679,7 @@ pgtk_copy_bits (struct frame *f, cairo_rectangle_t *src_rect,
pgtk_end_cr_clip (f);
cairo_surface_destroy (surface);
+#endif
}
/* Scroll part of the display as described by RUN. */
@@ -3324,18 +3927,21 @@ pgtk_draw_vertical_window_border (struct window *w, int x, int y0, int y1)
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face;
- cairo_t *cr;
-
- cr = pgtk_begin_cr_clip (f);
face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);
- if (face)
- pgtk_set_cr_source_with_color (f, face->foreground, false);
+ unsigned long color
+ = face ? face->foreground : FRAME_FOREGROUND_PIXEL (f);
+#ifdef USE_SKIA
+ pgtk_skia_fill_rectangle (f, color, x, y0, 1, y1 - y0, false);
+#else
+ cairo_t *cr;
+ cr = pgtk_begin_cr_clip (f);
+ pgtk_set_cr_source_with_color (f, color, false);
cairo_rectangle (cr, x, y0, 1, y1 - y0);
cairo_fill (cr);
-
pgtk_end_cr_clip (f);
+#endif
}
/* Draw a window divider from (x0,y0) to (x1,y1) */
@@ -3356,6 +3962,35 @@ pgtk_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
unsigned long color_last = (face_last
? face_last->foreground
: FRAME_FOREGROUND_PIXEL (f));
+ bool alpha = f->borders_respect_alpha_background;
+
+#ifdef USE_SKIA
+ if (y1 - y0 > x1 - x0 && x1 - x0 > 2)
+ /* Vertical. */
+ {
+ pgtk_skia_fill_rectangle (f, color_first, x0, y0, 1, y1 - y0,
+ alpha);
+ pgtk_skia_fill_rectangle (f, color, x0 + 1, y0, x1 - x0 - 2,
+ y1 - y0, alpha);
+ pgtk_skia_fill_rectangle (f, color_last, x1 - 1, y0, 1, y1 - y0,
+ alpha);
+ }
+ else if (x1 - x0 > y1 - y0 && y1 - y0 > 3)
+ /* Horizontal. */
+ {
+ pgtk_skia_fill_rectangle (f, color_first, x0, y0, x1 - x0, 1,
+ alpha);
+ pgtk_skia_fill_rectangle (f, color, x0, y0 + 1, x1 - x0,
+ y1 - y0 - 2, alpha);
+ pgtk_skia_fill_rectangle (f, color_last, x0, y1 - 1, x1 - x0, 1,
+ alpha);
+ }
+ else
+ {
+ pgtk_skia_fill_rectangle (f, color, x0, y0, x1 - x0, y1 - y0,
+ alpha);
+ }
+#else
cairo_t *cr = pgtk_begin_cr_clip (f);
if (y1 - y0 > x1 - x0 && x1 - x0 > 2)
@@ -3399,6 +4034,7 @@ pgtk_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
}
pgtk_end_cr_clip (f);
+#endif
}
/* End update of frame F. This function is installed as a hook in
@@ -3416,11 +4052,50 @@ pgtk_frame_up_to_date (struct frame *f)
{
block_input ();
FRAME_MOUSE_UPDATE (f);
+
+#ifdef USE_SKIA
+ /* For Skia GL rendering, bypass the buffer_flipping_blocked check
+ since we don't use Cairo's double-buffering mechanism. */
+ if (FRAME_GDK_GL_CONTEXT (f) && FRAME_GL_TEXTURE (f))
+ {
+ /* Frame pacing: limit to ~60 FPS (16.6ms) to reduce flickering
+ over waypipe. Skip frame if we rendered too recently. */
+ gint64 now = g_get_monotonic_time ();
+ gint64 elapsed = now - FRAME_LAST_RENDER_TIME (f);
+ /* 16000 microseconds = 16ms ~ 60 FPS. Use 8ms for smoother
+ response. */
+ const gint64 min_frame_interval = 8000;
+
+ if (FRAME_LAST_RENDER_TIME (f) > 0
+ && elapsed < min_frame_interval)
+ {
+ /* Too soon - skip this frame. The next
+ pgtk_frame_up_to_date call will trigger a render. */
+ unblock_input ();
+ return;
+ }
+
+ FRAME_LAST_RENDER_TIME (f) = now;
+
+ /* Flush Skia first. */
+ if (FRAME_SKIA_SURFACE (f))
+ emacs_skia_surface_flush (FRAME_SKIA_SURFACE (f));
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ emacs_skia_gl_context_flush (FRAME_SKIA_GL_CONTEXT (f));
+
+ /* Queue a render on the GtkGLArea. */
+ if (FRAME_GL_AREA (f))
+ gtk_gl_area_queue_render (GTK_GL_AREA (FRAME_GL_AREA (f)));
+ unblock_input ();
+ return;
+ }
+#else /* USE_CAIRO */
if (!buffer_flipping_blocked_p ())
{
flip_cr_context (f);
gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
}
+#endif
unblock_input ();
}
@@ -3433,13 +4108,14 @@ pgtk_frame_up_to_date (struct frame *f)
position on the scroll bar.
If the mouse movement started elsewhere, set *FP to the frame the
- mouse is on, *BAR_WINDOW to nil, and *X and *Y to the character cell
- the mouse is over.
+ mouse is on, *BAR_WINDOW to nil, and *X and *Y to the character
+ cell the mouse is over.
Set *TIMESTAMP to the server time-stamp for the time at which the mouse
was at this position.
- Don't store anything if we don't have a valid set of values to report.
+ Don't store anything if we don't have a valid set of values to
+ report.
This clears the mouse_moved flag, so we can wait for the next mouse
movement. */
@@ -3529,45 +4205,94 @@ pgtk_mouse_position (struct frame **fp, int insist, Lisp_Object * bar_window,
/* Fringe bitmaps. */
static int max_fringe_bmp = 0;
+#ifdef USE_SKIA
+static emacs_skia_image_t **fringe_bmp_skia = 0;
+#else
static cairo_pattern_t **fringe_bmp = 0;
+#endif
static void
pgtk_define_fringe_bitmap (int which, unsigned short *bits, int h, int wd)
{
- int i, stride;
- cairo_surface_t *surface;
- unsigned char *data;
- cairo_pattern_t *pattern;
+ int i;
if (which >= max_fringe_bmp)
{
i = max_fringe_bmp;
max_fringe_bmp = which + 20;
+#ifdef USE_SKIA
+ fringe_bmp_skia
+ = xrealloc (fringe_bmp_skia,
+ max_fringe_bmp * sizeof (emacs_skia_image_t *));
+#else
fringe_bmp
- = xrealloc (fringe_bmp, max_fringe_bmp * sizeof (cairo_pattern_t *));
+ = xrealloc (fringe_bmp,
+ max_fringe_bmp * sizeof (cairo_pattern_t *));
+#endif
while (i < max_fringe_bmp)
- fringe_bmp[i++] = 0;
+ {
+#ifdef USE_SKIA
+ fringe_bmp_skia[i] = 0;
+#else
+ fringe_bmp[i] = 0;
+#endif
+ i++;
+ }
}
block_input ();
- surface = cairo_image_surface_create (CAIRO_FORMAT_A1, wd, h);
- stride = cairo_image_surface_get_stride (surface);
- data = cairo_image_surface_get_data (surface);
+#ifdef USE_SKIA
+ {
+ /* Convert the bits to a format suitable for Skia.
+ The bits are 16-bit values representing each row of the fringe.
+ */
+ int stride = (wd + 7) / 8;
+ unsigned char *bitmap_data = xmalloc (stride * h);
- for (i = 0; i < h; i++)
- {
- *((unsigned short *) data) = bits[i];
- data += stride;
- }
+ for (i = 0; i < h; i++)
+ {
+ /* Copy the low bytes of each 16-bit value. */
+ if (stride >= 2)
+ {
+ bitmap_data[i * stride] = bits[i] & 0xff;
+ bitmap_data[i * stride + 1] = (bits[i] >> 8) & 0xff;
+ }
+ else
+ bitmap_data[i * stride] = bits[i] & 0xff;
+ }
- cairo_surface_mark_dirty (surface);
- pattern = cairo_pattern_create_for_surface (surface);
- cairo_surface_destroy (surface);
+ fringe_bmp_skia[which]
+ = emacs_skia_image_create_from_bitmap (bitmap_data, wd, h,
+ stride);
+ xfree (bitmap_data);
+ }
+#else /* USE_CAIRO */
+ {
+ int stride;
+ cairo_surface_t *surface;
+ unsigned char *data;
+ cairo_pattern_t *pattern;
- unblock_input ();
+ surface = cairo_image_surface_create (CAIRO_FORMAT_A1, wd, h);
+ stride = cairo_image_surface_get_stride (surface);
+ data = cairo_image_surface_get_data (surface);
+
+ for (i = 0; i < h; i++)
+ {
+ *((unsigned short *) data) = bits[i];
+ data += stride;
+ }
- fringe_bmp[which] = pattern;
+ cairo_surface_mark_dirty (surface);
+ pattern = cairo_pattern_create_for_surface (surface);
+ cairo_surface_destroy (surface);
+
+ fringe_bmp[which] = pattern;
+ }
+#endif
+
+ unblock_input ();
}
static void
@@ -3576,15 +4301,26 @@ pgtk_destroy_fringe_bitmap (int which)
if (which >= max_fringe_bmp)
return;
+ block_input ();
+
+#ifdef USE_SKIA
+ if (fringe_bmp_skia[which])
+ {
+ emacs_skia_image_destroy (fringe_bmp_skia[which]);
+ fringe_bmp_skia[which] = 0;
+ }
+#else
if (fringe_bmp[which])
{
- block_input ();
cairo_pattern_destroy (fringe_bmp[which]);
- unblock_input ();
+ fringe_bmp[which] = 0;
}
- fringe_bmp[which] = 0;
+#endif
+
+ unblock_input ();
}
+#ifdef USE_CAIRO
static void
pgtk_clip_to_row (struct window *w, struct glyph_row *row,
enum glyph_row_area area, cairo_t * cr)
@@ -3603,6 +4339,28 @@ pgtk_clip_to_row (struct window *w, struct glyph_row *row,
cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
cairo_clip (cr);
}
+#endif
+
+#ifdef USE_SKIA
+static void
+pgtk_skia_clip_to_row (struct window *w, struct glyph_row *row,
+ enum glyph_row_area area,
+ emacs_skia_canvas_t *canvas)
+{
+ int window_x, window_y, window_width;
+ emacs_skia_rect_t rect;
+
+ window_box (w, area, &window_x, &window_y, &window_width, 0);
+
+ rect.left = window_x;
+ rect.top = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, row->y));
+ rect.top = max (rect.top, window_y);
+ rect.right = rect.left + window_width;
+ rect.bottom = rect.top + row->visible_height;
+
+ emacs_skia_canvas_clip_rect (canvas, &rect);
+}
+#endif
static void
pgtk_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
@@ -3611,6 +4369,63 @@ pgtk_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face = p->face;
+#ifdef USE_SKIA
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+
+ emacs_skia_canvas_save (canvas);
+
+ /* Must clip because of partially visible lines. */
+ pgtk_skia_clip_to_row (w, row, ANY_AREA, canvas);
+
+ if (p->bx >= 0 && !p->overlay_p)
+ fill_background_by_face (f, face, p->bx, p->by, p->nx, p->ny);
+
+ if (p->which && p->which < max_fringe_bmp
+ && p->which < max_used_fringe_bitmap)
+ {
+ unsigned long foreground
+ = (p->cursor_p
+ ? (p->overlay_p ? face->background
+ : FRAME_X_OUTPUT (f)->cursor_color)
+ : face->foreground);
+
+ if (!fringe_bmp_skia[p->which])
+ gui_define_fringe_bitmap (f, p->which);
+
+ if (fringe_bmp_skia[p->which])
+ {
+ emacs_skia_image_t *img = fringe_bmp_skia[p->which];
+
+ /* Draw background if not overlay */
+ if (!p->overlay_p)
+ {
+ emacs_skia_rect_t bg_rect
+ = { p->x, p->y, p->x + p->wd, p->y + p->h };
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (
+ face->background));
+ emacs_skia_paint_set_stroke (paint, false);
+ emacs_skia_canvas_draw_rect (canvas, &bg_rect, paint);
+ }
+
+ /* Draw the fringe bitmap as a mask with foreground color */
+ emacs_skia_paint_set_color (paint, pgtk_color_to_skia (
+ foreground));
+ emacs_skia_paint_set_image_shader (paint, img);
+ emacs_skia_rect_t fringe_rect
+ = { p->x, p->y, p->x + p->wd, p->y + p->h };
+ emacs_skia_canvas_save (canvas);
+ emacs_skia_canvas_translate (canvas, p->x, p->y - p->dh);
+ emacs_skia_canvas_clip_rect (canvas, &fringe_rect);
+ emacs_skia_rect_t img_rect = { 0, 0, p->wd, p->h + p->dh };
+ emacs_skia_canvas_draw_rect (canvas, &img_rect, paint);
+ emacs_skia_canvas_restore (canvas);
+ emacs_skia_paint_clear_shader (paint);
+ }
+ }
+
+ emacs_skia_canvas_restore (canvas);
+#else
cairo_t *cr = pgtk_begin_cr_clip (f);
/* Must clip because of partially visible lines. */
@@ -3646,6 +4461,7 @@ pgtk_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
}
pgtk_end_cr_clip (f);
+#endif
}
static struct atimer *hourglass_atimer = NULL;
@@ -3762,11 +4578,20 @@ recover_from_visible_bell (struct atimer *timer)
{
struct frame *f = timer->client_data;
+#ifdef USE_SKIA
+ if (FRAME_X_OUTPUT (f)->skia_surface_visible_bell != NULL)
+ {
+ emacs_skia_surface_destroy (
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell);
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell = NULL;
+ }
+#else
if (FRAME_X_OUTPUT (f)->cr_surface_visible_bell != NULL)
{
cairo_surface_destroy (FRAME_X_OUTPUT (f)->cr_surface_visible_bell);
FRAME_X_OUTPUT (f)->cr_surface_visible_bell = NULL;
}
+#endif
if (FRAME_X_OUTPUT (f)->atimer_visible_bell != NULL)
FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
@@ -3777,6 +4602,112 @@ recover_from_visible_bell (struct atimer *timer)
static void
pgtk_flash (struct frame *f)
{
+#ifdef USE_SKIA
+ emacs_skia_surface_t *surface_orig = FRAME_SKIA_SURFACE (f);
+ emacs_skia_surface_t *surface;
+ emacs_skia_canvas_t *canvas;
+ emacs_skia_paint_t *paint;
+ emacs_skia_image_t *snapshot;
+ int width, height, flash_height, flash_left, flash_right;
+ struct timespec delay;
+
+ if (!surface_orig)
+ return;
+
+ block_input ();
+
+ width = FRAME_CR_SURFACE_DESIRED_WIDTH (f);
+ height = FRAME_CR_SURFACE_DESIRED_HEIGHT (f);
+
+ /* Create a new surface for the flash effect */
+ surface = emacs_skia_surface_create_raster (width, height);
+ if (!surface)
+ {
+ unblock_input ();
+ return;
+ }
+
+ canvas = emacs_skia_surface_get_canvas (surface);
+ paint = emacs_skia_paint_create ();
+
+ /* Copy original surface content */
+ snapshot = emacs_skia_surface_make_image_snapshot (surface_orig);
+ if (snapshot)
+ {
+ emacs_skia_canvas_draw_image (canvas, snapshot, 0, 0, NULL);
+ emacs_skia_image_destroy (snapshot);
+ }
+
+ /* Set up for DIFFERENCE blend mode with white color */
+ emacs_skia_paint_set_color (paint,
+ EMACS_SKIA_COLOR_RGB (255, 255, 255));
+ emacs_skia_paint_set_blend_mode (paint,
+ EMACS_SKIA_BLEND_DIFFERENCE);
+ emacs_skia_paint_set_stroke (paint, false);
+
+ /* Get the height not including a menu bar widget. */
+ height = FRAME_PIXEL_HEIGHT (f);
+ /* Height of each line to flash. */
+ flash_height = FRAME_LINE_HEIGHT (f);
+ /* These will be the left and right margins of the rectangles. */
+ flash_left = FRAME_INTERNAL_BORDER_WIDTH (f);
+ flash_right
+ = (FRAME_PIXEL_WIDTH (f) - FRAME_INTERNAL_BORDER_WIDTH (f));
+ width = flash_right - flash_left;
+
+ /* If window is tall, flash top and bottom line. */
+ if (height > 3 * FRAME_LINE_HEIGHT (f))
+ {
+ emacs_skia_rect_t rect1
+ = { flash_left,
+ FRAME_INTERNAL_BORDER_WIDTH (f)
+ + FRAME_TOP_MARGIN_HEIGHT (f),
+ flash_left + width,
+ FRAME_INTERNAL_BORDER_WIDTH (f)
+ + FRAME_TOP_MARGIN_HEIGHT (f) + flash_height };
+ emacs_skia_canvas_draw_rect (canvas, &rect1, paint);
+
+ emacs_skia_rect_t rect2
+ = { flash_left,
+ height - flash_height - FRAME_INTERNAL_BORDER_WIDTH (f)
+ - FRAME_BOTTOM_MARGIN_HEIGHT (f),
+ flash_left + width,
+ height - FRAME_INTERNAL_BORDER_WIDTH (f)
+ - FRAME_BOTTOM_MARGIN_HEIGHT (f) };
+ emacs_skia_canvas_draw_rect (canvas, &rect2, paint);
+ }
+ else
+ {
+ /* If it is short, flash it all. */
+ emacs_skia_rect_t rect
+ = { flash_left, FRAME_INTERNAL_BORDER_WIDTH (f),
+ flash_left + width,
+ height - FRAME_INTERNAL_BORDER_WIDTH (f) };
+ emacs_skia_canvas_draw_rect (canvas, &rect, paint);
+ }
+
+ emacs_skia_paint_destroy (paint);
+
+ /* Store the flash surface for later restoration */
+ if (FRAME_X_OUTPUT (f)->skia_surface_visible_bell)
+ emacs_skia_surface_destroy (
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell);
+ FRAME_X_OUTPUT (f)->skia_surface_visible_bell = surface;
+
+ delay = make_timespec (0, 50 * 1000 * 1000);
+
+ if (FRAME_X_OUTPUT (f)->atimer_visible_bell != NULL)
+ {
+ cancel_atimer (FRAME_X_OUTPUT (f)->atimer_visible_bell);
+ FRAME_X_OUTPUT (f)->atimer_visible_bell = NULL;
+ }
+
+ FRAME_X_OUTPUT (f)->atimer_visible_bell
+ = start_atimer (ATIMER_RELATIVE, delay, recover_from_visible_bell,
+ f);
+
+ unblock_input ();
+#else
cairo_surface_t *surface_orig, *surface;
cairo_t *cr;
int width, height, flash_height, flash_left, flash_right;
@@ -3862,6 +4793,7 @@ pgtk_flash (struct frame *f)
cairo_destroy (cr);
unblock_input ();
+#endif
}
/* Make audible bell. */
@@ -4820,8 +5752,25 @@ pgtk_new_focus_frame (struct pgtk_display_info *dpyinfo, struct frame *frame)
pgtk_buffer_flipping_unblocked_hook (struct frame *f)
{
block_input ();
- flip_cr_context (f);
- gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+#ifdef USE_SKIA
+ /* For Skia GL, queue a render on the GtkGLArea. */
+ if (FRAME_SKIA_SURFACE (f))
+ {
+ /* Flush Skia first. */
+ emacs_skia_surface_flush (FRAME_SKIA_SURFACE (f));
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ emacs_skia_gl_context_flush (FRAME_SKIA_GL_CONTEXT (f));
+ if (FRAME_GL_AREA (f))
+ gtk_gl_area_queue_render (GTK_GL_AREA (FRAME_GL_AREA (f)));
+ }
+ else
+#endif
+ {
+#ifdef USE_CAIRO
+ flip_cr_context (f);
+#endif
+ gtk_widget_queue_draw (FRAME_GTK_WIDGET (f));
+ }
unblock_input ();
}
@@ -4994,12 +5943,17 @@ pgtk_handle_event (GtkWidget *widget, GdkEvent *event, gpointer *data)
pgtk_fill_rectangle (struct frame *f, unsigned long color, int x, int y,
int width, int height, bool respect_alpha_background)
{
+#ifdef USE_SKIA
+ pgtk_skia_fill_rectangle (f, color, x, y, width, height,
+ respect_alpha_background);
+#else
cairo_t *cr;
cr = pgtk_begin_cr_clip (f);
pgtk_set_cr_source_with_color (f, color, respect_alpha_background);
cairo_rectangle (cr, x, y, width, height);
cairo_fill (cr);
pgtk_end_cr_clip (f);
+#endif
}
void
@@ -5058,22 +6012,30 @@ pgtk_handle_draw (GtkWidget *widget, cairo_t *cr, gpointer *data)
GdkWindow *win = gtk_widget_get_window (widget);
- if (win != NULL)
+ if (win == NULL)
+ return FALSE;
+
+ f = pgtk_any_window_to_frame (win);
+ if (f == NULL)
+ return FALSE;
+
+#ifdef USE_SKIA
+ /* For Skia with GL rendering, GtkGLArea handles all display via its
+ render callback. This draw callback on the GtkFixed widget is not
+ used. Return FALSE to indicate we didn't handle the draw. */
+ (void) cr; /* Suppress unused parameter warning. */
+ return FALSE;
+#else /* USE_CAIRO */
+ cairo_surface_t *src = NULL;
+ src = FRAME_X_OUTPUT (f)->cr_surface_visible_bell;
+ if (src == NULL && FRAME_CR_ACTIVE_CONTEXT (f) != NULL)
+ src = cairo_get_target (FRAME_CR_ACTIVE_CONTEXT (f));
+ if (src != NULL)
{
- cairo_surface_t *src = NULL;
- f = pgtk_any_window_to_frame (win);
- if (f != NULL)
- {
- src = FRAME_X_OUTPUT (f)->cr_surface_visible_bell;
- if (src == NULL && FRAME_CR_ACTIVE_CONTEXT (f) != NULL)
- src = cairo_get_target (FRAME_CR_ACTIVE_CONTEXT (f));
- }
- if (src != NULL)
- {
- cairo_set_source_surface (cr, src, 0, 0);
- cairo_paint (cr);
- }
+ cairo_set_source_surface (cr, src, 0, 0);
+ cairo_paint (cr);
}
+#endif
return FALSE;
}
@@ -5089,7 +6051,19 @@ size_allocate (GtkWidget *widget, GtkAllocation *alloc,
if (f)
{
xg_frame_resized (f, alloc->width, alloc->height);
- pgtk_cr_update_surface_desired_size (f, alloc->width, alloc->height, false);
+#ifdef USE_SKIA
+ /* Resize the GtkGLArea to fill the frame. */
+ if (FRAME_GL_AREA (f))
+ {
+ gtk_widget_set_size_request (FRAME_GL_AREA (f),
+ alloc->width, alloc->height);
+ }
+ pgtk_skia_update_surface_desired_size (f, alloc->width,
+ alloc->height, false);
+#else
+ pgtk_cr_update_surface_desired_size (f, alloc->width,
+ alloc->height, false);
+#endif
}
}
@@ -6783,7 +7757,13 @@ pgtk_monitors_changed_cb (GdkScreen *screen, gpointer user_data)
static gboolean pgtk_selection_event (GtkWidget *, GdkEvent *, gpointer);
-
+#ifdef USE_SKIA
+/* Forward declarations for GtkGLArea callbacks. */
+static void pgtk_gl_area_realize (GtkGLArea *, gpointer);
+static gboolean pgtk_gl_area_render (GtkGLArea *, GdkGLContext *, gpointer);
+static void pgtk_gl_area_resize (GtkGLArea *, gint, gint, gpointer);
+static bool pgtk_setup_gl_framebuffer (struct frame *, int, int);
+#endif
void
pgtk_set_event_handler (struct frame *f)
@@ -6844,10 +7824,45 @@ pgtk_set_event_handler (struct frame *f)
G_CALLBACK (drag_motion), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "drag-drop",
G_CALLBACK (drag_drop), NULL);
+
+#ifdef USE_SKIA
+ /* For Skia GL rendering, create a GtkGLArea widget. */
+ {
+ GtkWidget *gl_area = gtk_gl_area_new ();
+ FRAME_GL_AREA (f) = gl_area;
+
+ /* Request OpenGL 3.2 core profile for Skia compatibility. */
+ gtk_gl_area_set_required_version (GTK_GL_AREA (gl_area), 3, 2);
+ gtk_gl_area_set_has_depth_buffer (GTK_GL_AREA (gl_area), FALSE);
+ /* Skia needs stencil buffer for clip mask operations. */
+ gtk_gl_area_set_has_stencil_buffer (GTK_GL_AREA (gl_area), TRUE);
+ gtk_gl_area_set_auto_render (GTK_GL_AREA (gl_area), FALSE);
+
+ /* Connect GtkGLArea signals. */
+ g_signal_connect (G_OBJECT (gl_area), "realize",
+ G_CALLBACK (pgtk_gl_area_realize), f);
+ g_signal_connect (G_OBJECT (gl_area), "render",
+ G_CALLBACK (pgtk_gl_area_render), f);
+ g_signal_connect (G_OBJECT (gl_area), "resize",
+ G_CALLBACK (pgtk_gl_area_resize), f);
+
+ /* Add GtkGLArea to the GtkFixed at position (0,0). */
+ gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (f)), gl_area, 0, 0);
+
+ /* Make it fill the entire frame. This will be updated on resize. */
+ gtk_widget_set_size_request (gl_area, 1, 1);
+ gtk_widget_set_hexpand (gl_area, TRUE);
+ gtk_widget_set_vexpand (gl_area, TRUE);
+ gtk_widget_show (gl_area);
+ }
+#else
+ /* For Cairo, use the draw callback. */
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "draw",
G_CALLBACK (pgtk_handle_draw), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "property-notify-event",
G_CALLBACK (pgtk_selection_event), NULL);
+#endif
+
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-clear-event",
G_CALLBACK (pgtk_selection_event), NULL);
g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-request-event",
@@ -7413,62 +8428,779 @@ pgtk_query_color (struct frame *f, Emacs_Color * color)
void
pgtk_clear_area (struct frame *f, int x, int y, int width, int height)
{
- cairo_t *cr;
-
eassert (width > 0 && height > 0);
+#ifdef USE_SKIA
+ pgtk_skia_fill_rectangle (f, FRAME_X_OUTPUT (f)->background_color,
+ x, y, width, height, true);
+#else
+ cairo_t *cr;
+
cr = pgtk_begin_cr_clip (f);
- pgtk_set_cr_source_with_color (f, FRAME_X_OUTPUT (f)->background_color,
+ pgtk_set_cr_source_with_color (f,
+ FRAME_X_OUTPUT (f)->background_color,
true);
cairo_rectangle (cr, x, y, width, height);
cairo_fill (cr);
pgtk_end_cr_clip (f);
+#endif
}
+#ifdef USE_SKIA
+/* ============================================================
+ Skia drawing functions
+ ============================================================ */
-void
-syms_of_pgtkterm (void)
+/* GtkGLArea "realize" callback - set up GL resources. */
+static void
+pgtk_gl_area_realize (GtkGLArea *gl_area, gpointer user_data)
{
- DEFSYM (Qmodifier_value, "modifier-value");
- DEFSYM (Qalt, "alt");
- DEFSYM (Qhyper, "hyper");
- DEFSYM (Qmeta, "meta");
- DEFSYM (Qsuper, "super");
- DEFSYM (Qcontrol, "control");
- DEFSYM (QUTF8_STRING, "UTF8_STRING");
- /* Referenced in gtkutil.c. */
- DEFSYM (Qtheme_name, "theme-name");
- DEFSYM (Qfile_name_sans_extension, "file-name-sans-extension");
+ struct frame *f = (struct frame *) user_data;
- DEFSYM (Qfile, "file");
- DEFSYM (Qurl, "url");
+ /* Make the GtkGLArea's context current. */
+ gtk_gl_area_make_current (gl_area);
- DEFSYM (Qlatin_1, "latin-1");
+ GError *gl_error = gtk_gl_area_get_error (gl_area);
+ if (gl_error != NULL)
+ return;
- xg_default_icon_file
- = build_string ("icons/hicolor/scalable/apps/emacs.svg");
- staticpro (&xg_default_icon_file);
+ /* Get the GDK GL context from GtkGLArea. */
+ GdkGLContext *gl_context = gtk_gl_area_get_context (gl_area);
+ if (!gl_context)
+ return;
- DEFSYM (Qx_gtk_map_stock, "x-gtk-map-stock");
+ FRAME_GDK_GL_CONTEXT (f) = gl_context;
- DEFSYM (Qcopy, "copy");
- DEFSYM (Qmove, "move");
- DEFSYM (Qlink, "link");
- DEFSYM (Qprivate, "private");
+ /* Create Skia GL context using the native GL interface. */
+ FRAME_SKIA_GL_CONTEXT (f) = emacs_skia_gl_context_create_native ();
- Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
- Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
- Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
- Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
- Fput (Qcontrol, Qmodifier_value, make_fixnum (ctrl_modifier));
+ if (!FRAME_SKIA_GL_CONTEXT (f))
+ {
+ FRAME_GDK_GL_CONTEXT (f) = NULL;
+ return;
+ }
- DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym,
- doc: /* SKIP: real doc in xterm.c. */);
- Vx_ctrl_keysym = Qnil;
+ /* Create GL framebuffer and texture for offscreen rendering. */
+ glGenFramebuffers (1, &FRAME_GL_FRAMEBUFFER (f));
+ glGenTextures (1, &FRAME_GL_TEXTURE (f));
- DEFVAR_LISP ("x-alt-keysym", Vx_alt_keysym,
- doc: /* SKIP: real doc in xterm.c. */);
- Vx_alt_keysym = Qnil;
+ /* Initialize GL state as dirty so Skia resets on first use. */
+ FRAME_SKIA_GL_STATE_DIRTY (f) = true;
+
+ FRAME_SKIA_GL_INITIALIZED (f) = true;
+
+ /* Get the initial size and set up the FBO. */
+ GtkAllocation alloc;
+ gtk_widget_get_allocation (GTK_WIDGET (gl_area), &alloc);
+ if (alloc.width > 0 && alloc.height > 0)
+ {
+ pgtk_setup_gl_framebuffer (f, alloc.width, alloc.height);
+ FRAME_SKIA_SURFACE_DESIRED_WIDTH (f) = alloc.width;
+ FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f) = alloc.height;
+ }
+}
+
+/* GtkGLArea "render" callback - blit FBO to screen. */
+static gboolean
+pgtk_gl_area_render (GtkGLArea *gl_area, GdkGLContext *context,
+ gpointer user_data)
+{
+ struct frame *f = (struct frame *) user_data;
+ emacs_skia_surface_t *skia_surface;
+
+ /* Use visible bell surface if active, otherwise main surface. */
+ if (FRAME_X_OUTPUT (f)->skia_surface_visible_bell)
+ skia_surface = FRAME_X_OUTPUT (f)->skia_surface_visible_bell;
+ else
+ skia_surface = FRAME_SKIA_SURFACE (f);
+
+ /* No FBO at all - just clear to background. */
+ if (!FRAME_GL_FRAMEBUFFER (f))
+ {
+ unsigned long bg = FRAME_X_OUTPUT (f)->background_color;
+ float r = RED_FROM_ULONG (bg) / 255.0f;
+ float g = GREEN_FROM_ULONG (bg) / 255.0f;
+ float b = BLUE_FROM_ULONG (bg) / 255.0f;
+ glClearColor (r, g, b, 1.0f);
+ glClear (GL_COLOR_BUFFER_BIT);
+ return TRUE;
+ }
+
+ /* Ensure GtkGLArea's buffers are attached. */
+ gtk_gl_area_attach_buffers (gl_area);
+
+ /* Flush Skia rendering if we have an active surface. */
+ if (skia_surface)
+ {
+ emacs_skia_surface_flush (skia_surface);
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ emacs_skia_gl_context_flush (FRAME_SKIA_GL_CONTEXT (f));
+ glFinish ();
+ }
+
+ /* Get source dimensions from Skia surface if available, otherwise
+ from the FBO texture (for resize case where surface is destroyed
+ but FBO has preserved content). */
+ int src_width, src_height;
+ if (skia_surface)
+ {
+ src_width = emacs_skia_surface_get_width (skia_surface);
+ src_height = emacs_skia_surface_get_height (skia_surface);
+ }
+ else
+ {
+ /* Query FBO texture size directly. */
+ glBindTexture (GL_TEXTURE_2D, FRAME_GL_TEXTURE (f));
+ glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH,
+ &src_width);
+ glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT,
+ &src_height);
+ glBindTexture (GL_TEXTURE_2D, 0);
+ }
+
+ /* Get the actual viewport size (may differ due to HiDPI). */
+ GLint viewport[4];
+ glGetIntegerv (GL_VIEWPORT, viewport);
+ int dst_width = viewport[2];
+ int dst_height = viewport[3];
+
+ /* Disable blending for opaque blit. */
+ glDisable (GL_BLEND);
+ glDisable (GL_SCISSOR_TEST);
+
+ /* Bind our FBO as the read framebuffer. The GtkGLArea's FBO is
+ already bound as the draw framebuffer. */
+ glBindFramebuffer (GL_READ_FRAMEBUFFER, FRAME_GL_FRAMEBUFFER (f));
+
+ /* Blit the FBO to GtkGLArea's framebuffer. Both FBOs use the same
+ OpenGL coordinate system (origin at bottom-left), so no Y-flip
+ is needed. */
+ glBlitFramebuffer (0, 0, src_width, src_height,
+ 0, 0, dst_width, dst_height,
+ GL_COLOR_BUFFER_BIT, GL_LINEAR);
+
+ /* Check for GL errors (silently ignore). */
+ glGetError ();
+
+ /* Restore framebuffer bindings. */
+ glBindFramebuffer (GL_READ_FRAMEBUFFER, 0);
+
+ return TRUE;
+}
+
+/* Resize FBO while preserving existing content. This blits the old
+ content to a temp buffer, resizes the main FBO, clears to background,
+ and blits the preserved content back. */
+static void
+pgtk_resize_fbo_preserve_content (struct frame *f, int old_width,
+ int old_height, int new_width,
+ int new_height)
+{
+ /* Create a temporary FBO to hold the old content. */
+ GLuint temp_fbo, temp_texture;
+ glGenFramebuffers (1, &temp_fbo);
+ glGenTextures (1, &temp_texture);
+
+ /* Copy old texture to temp texture. */
+ glBindTexture (GL_TEXTURE_2D, temp_texture);
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, old_width, old_height,
+ 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ glBindFramebuffer (GL_FRAMEBUFFER, temp_fbo);
+ glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, temp_texture, 0);
+
+ /* Blit old content to temp. */
+ glBindFramebuffer (GL_READ_FRAMEBUFFER, FRAME_GL_FRAMEBUFFER (f));
+ glBindFramebuffer (GL_DRAW_FRAMEBUFFER, temp_fbo);
+ glBlitFramebuffer (0, 0, old_width, old_height,
+ 0, 0, old_width, old_height,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ /* Resize main FBO to new size. */
+ pgtk_setup_gl_framebuffer (f, new_width, new_height);
+
+ /* Clear new FBO to background color first. */
+ glBindFramebuffer (GL_FRAMEBUFFER, FRAME_GL_FRAMEBUFFER (f));
+ unsigned long bg = FRAME_X_OUTPUT (f)->background_color;
+ float r = RED_FROM_ULONG (bg) / 255.0f;
+ float g = GREEN_FROM_ULONG (bg) / 255.0f;
+ float b = BLUE_FROM_ULONG (bg) / 255.0f;
+ glClearColor (r, g, b, 1.0f);
+ glClear (GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ /* Blit preserved content back. Use min of old/new dimensions
+ to handle both grow and shrink. */
+ int blit_width = old_width < new_width ? old_width : new_width;
+ int blit_height = old_height < new_height ? old_height : new_height;
+ glBindFramebuffer (GL_READ_FRAMEBUFFER, temp_fbo);
+ glBindFramebuffer (GL_DRAW_FRAMEBUFFER, FRAME_GL_FRAMEBUFFER (f));
+ glBlitFramebuffer (0, 0, blit_width, blit_height,
+ 0, 0, blit_width, blit_height,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glFinish ();
+
+ /* Clean up temp resources. */
+ glDeleteFramebuffers (1, &temp_fbo);
+ glDeleteTextures (1, &temp_texture);
+ glBindFramebuffer (GL_FRAMEBUFFER, 0);
+}
+
+/* GtkGLArea "resize" callback - resize FBO to match widget. */
+static void
+pgtk_gl_area_resize (GtkGLArea *gl_area, gint width, gint height,
+ gpointer user_data)
+{
+ struct frame *f = (struct frame *) user_data;
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ /* Make context current before GL operations. */
+ gtk_gl_area_make_current (gl_area);
+
+ /* Destroy the old Skia surface if size changed. The surface will
+ be recreated on next draw with the new size. */
+ if (FRAME_SKIA_SURFACE_DESIRED_WIDTH (f) != width
+ || FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f) != height)
+ {
+ /* Destroy only the Skia surface, keep the GL context. */
+ if (FRAME_SKIA_SURFACE (f))
+ {
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ {
+ emacs_skia_gl_context_flush (FRAME_SKIA_GL_CONTEXT (f));
+ glFinish ();
+ }
+ emacs_skia_surface_destroy (FRAME_SKIA_SURFACE (f));
+ FRAME_SKIA_SURFACE (f) = NULL;
+ FRAME_SKIA_CANVAS (f) = NULL;
+ }
+
+ /* Resize the FBO texture, preserving existing content. */
+ if (FRAME_GL_FRAMEBUFFER (f) && FRAME_GL_TEXTURE (f))
+ {
+ int old_width = FRAME_SKIA_SURFACE_DESIRED_WIDTH (f);
+ int old_height = FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f);
+ pgtk_resize_fbo_preserve_content (f, old_width, old_height,
+ width, height);
+ }
+
+ FRAME_SKIA_SURFACE_DESIRED_WIDTH (f) = width;
+ FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f) = height;
+
+ /* Mark the frame as needing a full redraw. */
+ SET_FRAME_GARBAGED (f);
+ }
+}
+
+/* Create and set up GL context for frame F (fallback when no GtkGLArea). */
+static bool
+pgtk_init_gl_area (struct frame *f)
+{
+ GtkWidget *fixed;
+ GdkWindow *gdk_window;
+ GdkGLContext *gl_context;
+ GError *error = NULL;
+
+ if (FRAME_GDK_GL_CONTEXT (f))
+ return true; /* Already initialized. */
+
+ fixed = FRAME_GTK_WIDGET (f);
+
+ /* Make the fixed widget app-paintable so it doesn't draw background. */
+ gtk_widget_set_app_paintable (fixed, TRUE);
+
+ /* Get the GdkWindow from the widget. */
+ gdk_window = gtk_widget_get_window (fixed);
+ if (!gdk_window)
+ return false;
+
+ /* Create a GL context directly from the GdkWindow. */
+ gl_context = gdk_window_create_gl_context (gdk_window, &error);
+ if (!gl_context)
+ {
+ if (error)
+ g_error_free (error);
+ return false;
+ }
+
+ /* Realize the context (required before use). */
+ if (!gdk_gl_context_realize (gl_context, &error))
+ {
+ if (error)
+ g_error_free (error);
+ g_object_unref (gl_context);
+ return false;
+ }
+
+ FRAME_GDK_GL_CONTEXT (f) = gl_context;
+
+ /* Make the context current. */
+ gdk_gl_context_make_current (gl_context);
+
+ /* Create Skia GL context using the native GL interface. */
+ FRAME_SKIA_GL_CONTEXT (f) = emacs_skia_gl_context_create_native ();
+
+ if (!FRAME_SKIA_GL_CONTEXT (f))
+ {
+ g_object_unref (gl_context);
+ FRAME_GDK_GL_CONTEXT (f) = NULL;
+ return false;
+ }
+
+ /* Create GL framebuffer and texture for offscreen rendering. */
+ glGenFramebuffers (1, &FRAME_GL_FRAMEBUFFER (f));
+ glGenTextures (1, &FRAME_GL_TEXTURE (f));
+
+ /* Initialize GL state as dirty so Skia resets on first use. */
+ FRAME_SKIA_GL_STATE_DIRTY (f) = true;
+
+ FRAME_SKIA_GL_INITIALIZED (f) = true;
+ /* No GtkGLArea - we use direct GdkGLContext. */
+ FRAME_GL_AREA (f) = NULL;
+
+ return true;
+}
+
+/* Legacy init function - now just calls pgtk_init_gl_area. */
+static bool
+pgtk_init_gl_context (struct frame *f)
+{
+ if (FRAME_SKIA_GL_INITIALIZED (f))
+ return FRAME_GDK_GL_CONTEXT (f) != NULL;
+
+ return pgtk_init_gl_area (f);
+}
+
+/* Set up GL framebuffer for rendering at given size. */
+static bool
+pgtk_setup_gl_framebuffer (struct frame *f, int width, int height)
+{
+ if (!FRAME_GDK_GL_CONTEXT (f))
+ return false;
+
+ /* Make the GL context current. */
+ gdk_gl_context_make_current (FRAME_GDK_GL_CONTEXT (f));
+
+ /* Bind and configure the texture. */
+ glBindTexture (GL_TEXTURE_2D, FRAME_GL_TEXTURE (f));
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, NULL);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ /* Create or resize the stencil renderbuffer. Skia needs this for
+ clip mask operations. */
+ if (!FRAME_GL_STENCIL (f))
+ glGenRenderbuffers (1, &FRAME_GL_STENCIL (f));
+ glBindRenderbuffer (GL_RENDERBUFFER, FRAME_GL_STENCIL (f));
+ glRenderbufferStorage (GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height);
+
+ /* Bind the framebuffer and attach the texture and stencil. */
+ glBindFramebuffer (GL_FRAMEBUFFER, FRAME_GL_FRAMEBUFFER (f));
+ glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, FRAME_GL_TEXTURE (f), 0);
+ glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, FRAME_GL_STENCIL (f));
+
+ GLenum status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ return false;
+
+ /* Clear the FBO to black initially to avoid garbage data.
+ The actual background color will be set when Skia draws. */
+ glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
+ glClear (GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ glFinish ();
+
+ /* Unbind the framebuffer. */
+ glBindFramebuffer (GL_FRAMEBUFFER, 0);
+
+ /* Mark GL state as dirty so Skia will reset its cached state. */
+ FRAME_SKIA_GL_STATE_DIRTY (f) = true;
+
+ return true;
+}
+
+/* Clean up GL resources for frame F. */
+static void
+pgtk_cleanup_gl_context (struct frame *f)
+{
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ {
+ emacs_skia_gl_context_destroy (FRAME_SKIA_GL_CONTEXT (f));
+ FRAME_SKIA_GL_CONTEXT (f) = NULL;
+ }
+
+ if (FRAME_GDK_GL_CONTEXT (f))
+ {
+ gdk_gl_context_make_current (FRAME_GDK_GL_CONTEXT (f));
+
+ if (FRAME_GL_FRAMEBUFFER (f))
+ {
+ glDeleteFramebuffers (1, &FRAME_GL_FRAMEBUFFER (f));
+ FRAME_GL_FRAMEBUFFER (f) = 0;
+ }
+ if (FRAME_GL_TEXTURE (f))
+ {
+ glDeleteTextures (1, &FRAME_GL_TEXTURE (f));
+ FRAME_GL_TEXTURE (f) = 0;
+ }
+ if (FRAME_GL_STENCIL (f))
+ {
+ glDeleteRenderbuffers (1, &FRAME_GL_STENCIL (f));
+ FRAME_GL_STENCIL (f) = 0;
+ }
+
+ /* We created this context ourselves, so unref it. */
+ gdk_gl_context_clear_current ();
+ g_object_unref (FRAME_GDK_GL_CONTEXT (f));
+ FRAME_GDK_GL_CONTEXT (f) = NULL;
+ }
+
+ /* No GtkGLArea to destroy - we use direct GdkGLContext. */
+ FRAME_GL_AREA (f) = NULL;
+
+ FRAME_SKIA_GL_INITIALIZED (f) = false;
+}
+
+/* Forward declaration. */
+static void pgtk_skia_destroy_surface_only (struct frame *f);
+
+void
+pgtk_skia_update_surface_desired_size (struct frame *f, int width,
+ int height, bool force)
+{
+ if (FRAME_SKIA_SURFACE_DESIRED_WIDTH (f) != width
+ || FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f) != height || force)
+ {
+ /* Only destroy the Skia surface, preserve the GtkGLArea and GL
+ context. This avoids recreating the entire GL setup on every
+ resize, which causes flickering. */
+ pgtk_skia_destroy_surface_only (f);
+ FRAME_SKIA_SURFACE_DESIRED_WIDTH (f) = width;
+ FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f) = height;
+ SET_FRAME_GARBAGED (f);
+ }
+}
+
+emacs_skia_canvas_t *
+pgtk_begin_skia_clip (struct frame *f)
+{
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+
+ if (!canvas)
+ {
+ int width = FRAME_SKIA_SURFACE_DESIRED_WIDTH (f);
+ int height = FRAME_SKIA_SURFACE_DESIRED_HEIGHT (f);
+
+ if (width <= 0)
+ width = 1;
+ if (height <= 0)
+ height = 1;
+
+ /* Use GtkGLArea's context if available. */
+ if (FRAME_GL_AREA (f) && FRAME_GDK_GL_CONTEXT (f))
+ {
+ /* Make the GtkGLArea's context current. */
+ gtk_gl_area_make_current (GTK_GL_AREA (FRAME_GL_AREA (f)));
+
+ /* Set up FBO if not already done, or resize if size changed.
+ This handles cases where size_allocate fires before the
+ GtkGLArea resize signal (e.g., tiling WM fullscreen). */
+ if (!FRAME_GL_FRAMEBUFFER (f))
+ pgtk_setup_gl_framebuffer (f, width, height);
+ else
+ {
+ /* Check if FBO needs resizing by querying the texture size. */
+ GLint tex_width = 0, tex_height = 0;
+ glBindTexture (GL_TEXTURE_2D, FRAME_GL_TEXTURE (f));
+ glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH,
+ &tex_width);
+ glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT,
+ &tex_height);
+ glBindTexture (GL_TEXTURE_2D, 0);
+
+ if (tex_width != width || tex_height != height)
+ pgtk_resize_fbo_preserve_content (f, tex_width, tex_height,
+ width, height);
+ }
+
+ /* Create Skia surface if needed. */
+ if (!FRAME_SKIA_SURFACE (f) && FRAME_GL_FRAMEBUFFER (f))
+ {
+ FRAME_SKIA_SURFACE (f)
+ = emacs_skia_surface_create_gl (FRAME_SKIA_GL_CONTEXT (f),
+ width, height,
+ FRAME_GL_FRAMEBUFFER (f),
+ GL_RGBA8);
+ }
+ }
+ /* Fallback: create offscreen GL context if no GtkGLArea. */
+ else if (pgtk_init_gl_context (f)
+ && pgtk_setup_gl_framebuffer (f, width, height))
+ {
+ /* Make GL context current. */
+ gdk_gl_context_make_current (FRAME_GDK_GL_CONTEXT (f));
+ FRAME_SKIA_SURFACE (f)
+ = emacs_skia_surface_create_gl (FRAME_SKIA_GL_CONTEXT (f),
+ width, height,
+ FRAME_GL_FRAMEBUFFER (f),
+ GL_RGBA8);
+ }
+
+ if (!FRAME_SKIA_SURFACE (f))
+ return NULL;
+
+ canvas = emacs_skia_surface_get_canvas (FRAME_SKIA_SURFACE (f));
+ FRAME_SKIA_CANVAS (f) = canvas;
+
+ /* Create a reusable paint object. */
+ if (!FRAME_SKIA_PAINT (f))
+ FRAME_SKIA_PAINT (f) = emacs_skia_paint_create ();
+
+ /* Clear the newly created surface with the background color.
+ This is critical for GL surfaces where the FBO starts with
+ undefined contents. Without this, the first readback may
+ show garbage data causing flickering. */
+ {
+ unsigned long bg = FRAME_X_OUTPUT (f)->background_color;
+ Emacs_Color col;
+ col.pixel = bg;
+ pgtk_query_color (f, &col);
+ uint8_t r = col.red >> 8;
+ uint8_t g = col.green >> 8;
+ uint8_t b = col.blue >> 8;
+ emacs_skia_canvas_clear (canvas,
+ EMACS_SKIA_COLOR (255, r, g, b));
+ /* Flush the clear operation for GL surfaces and reset Skia's
+ GL state tracking since we just set up the GL context. */
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ {
+ emacs_skia_gl_context_flush (FRAME_SKIA_GL_CONTEXT (f));
+ glFinish ();
+ /* Reset Skia state after initial setup. */
+ emacs_skia_gl_context_reset (FRAME_SKIA_GL_CONTEXT (f));
+ FRAME_SKIA_GL_STATE_DIRTY (f) = false;
+ }
+ }
+ }
+ else if (FRAME_GDK_GL_CONTEXT (f))
+ {
+ /* For GL surfaces, ensure the GL context is current before any
+ drawing operations. Skia's GL backend requires this. */
+ if (FRAME_GL_AREA (f))
+ gtk_gl_area_make_current (GTK_GL_AREA (FRAME_GL_AREA (f)));
+ else
+ gdk_gl_context_make_current (FRAME_GDK_GL_CONTEXT (f));
+
+ /* Tell Skia to re-query GL state since GTK/GDK may have modified
+ it between frames. Without this, Skia's cached GL state may be
+ stale and rendering may go to the wrong target. */
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ emacs_skia_gl_context_reset (FRAME_SKIA_GL_CONTEXT (f));
+ }
+
+ emacs_skia_canvas_save (canvas);
+
+ return canvas;
+}
+
+void
+pgtk_end_skia_clip (struct frame *f)
+{
+ emacs_skia_canvas_t *canvas = FRAME_SKIA_CANVAS (f);
+ if (canvas)
+ emacs_skia_canvas_restore (canvas);
+}
+
+void
+pgtk_skia_set_paint_color (struct frame *f, unsigned long color,
+ bool respects_alpha_background)
+{
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ if (!paint)
+ return;
+
+ Emacs_Color col;
+ col.pixel = color;
+ pgtk_query_color (f, &col);
+
+ uint8_t r = col.red >> 8;
+ uint8_t g = col.green >> 8;
+ uint8_t b = col.blue >> 8;
+ uint8_t a;
+
+ if (!respects_alpha_background)
+ {
+ a = 255;
+ emacs_skia_paint_set_blend_mode (paint,
+ EMACS_SKIA_BLEND_SRC_OVER);
+ }
+ else
+ {
+ a = (uint8_t) (f->alpha_background * 255.0);
+ emacs_skia_paint_set_blend_mode (paint, EMACS_SKIA_BLEND_SRC);
+ }
+
+ emacs_skia_paint_set_color (paint, EMACS_SKIA_COLOR (a, r, g, b));
+}
+
+/* Destroy only the Skia surface, preserving GL context and GtkGLArea.
+ Used during resize to avoid recreating the entire GL setup. */
+static void
+pgtk_skia_destroy_surface_only (struct frame *f)
+{
+ if (FRAME_SKIA_PAINT (f))
+ {
+ emacs_skia_paint_destroy (FRAME_SKIA_PAINT (f));
+ FRAME_SKIA_PAINT (f) = NULL;
+ }
+
+ /* Canvas is owned by surface, so just NULL it. */
+ FRAME_SKIA_CANVAS (f) = NULL;
+
+ if (FRAME_SKIA_SURFACE (f))
+ {
+ /* For GL surfaces, make context current and flush before
+ destroying to ensure any pending operations complete and
+ the GrDirectContext state is clean. */
+ if (FRAME_GDK_GL_CONTEXT (f))
+ {
+ gdk_gl_context_make_current (FRAME_GDK_GL_CONTEXT (f));
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ {
+ emacs_skia_gl_context_flush (FRAME_SKIA_GL_CONTEXT (f));
+ glFinish ();
+ }
+ }
+ emacs_skia_surface_destroy (FRAME_SKIA_SURFACE (f));
+ FRAME_SKIA_SURFACE (f) = NULL;
+
+ /* Reset the GrDirectContext state after destroying the surface.
+ This clears Skia's internal caches that may reference the
+ old surface's backend render target. */
+ if (FRAME_SKIA_GL_CONTEXT (f))
+ {
+ emacs_skia_gl_context_reset (FRAME_SKIA_GL_CONTEXT (f));
+ }
+ }
+}
+
+void
+pgtk_skia_destroy_frame_context (struct frame *f)
+{
+ pgtk_skia_destroy_surface_only (f);
+
+ /* Clean up GL resources (includes Skia GL context, GDK GL context,
+ and GL framebuffer/texture). */
+ pgtk_cleanup_gl_context (f);
+}
+
+/* Skia version of fill rectangle. */
+static void
+pgtk_skia_fill_rectangle (struct frame *f, unsigned long color, int x,
+ int y, int width, int height,
+ bool respect_alpha_background)
+{
+ emacs_skia_canvas_t *canvas = pgtk_begin_skia_clip (f);
+ if (!canvas)
+ return;
+
+ pgtk_skia_set_paint_color (f, color, respect_alpha_background);
+
+ emacs_skia_irect_t rect = { x, y, x + width, y + height };
+ emacs_skia_canvas_draw_irect (canvas, &rect, FRAME_SKIA_PAINT (f));
+
+ pgtk_end_skia_clip (f);
+}
+
+/* Skia version of draw rectangle (stroked outline). */
+static void
+pgtk_skia_draw_rectangle (struct frame *f, unsigned long color, int x,
+ int y, int width, int height,
+ bool respect_alpha_background)
+{
+ emacs_skia_canvas_t *canvas = pgtk_begin_skia_clip (f);
+ if (!canvas)
+ return;
+
+ emacs_skia_paint_t *paint = FRAME_SKIA_PAINT (f);
+ pgtk_skia_set_paint_color (f, color, respect_alpha_background);
+ emacs_skia_paint_set_stroke (paint, true);
+ emacs_skia_paint_set_stroke_width (paint, 1.0f);
+
+ /* Use float rect for proper stroke alignment (0.5 offset for crisp
+ * lines). */
+ emacs_skia_rect_t rect
+ = { x + 0.5f, y + 0.5f, x + width + 0.5f, y + height + 0.5f };
+ emacs_skia_canvas_draw_rect (canvas, &rect, paint);
+
+ /* Reset to fill mode for subsequent operations. */
+ emacs_skia_paint_set_stroke (paint, false);
+
+ pgtk_end_skia_clip (f);
+}
+
+#endif /* USE_SKIA */
+
+#ifdef USE_SKIA
+DEFUN ("pgtk-skia-gl-enabled-p", Fpgtk_skia_gl_enabled_p,
+ Spgtk_skia_gl_enabled_p, 0, 1, 0,
+ doc: /* Return non-nil if Skia GL acceleration is active for FRAME.
+If FRAME is nil, use the selected frame. */)
+(Lisp_Object frame)
+{
+ struct frame *f = decode_window_system_frame (frame);
+ if (FRAME_GDK_GL_CONTEXT (f) && FRAME_SKIA_GL_CONTEXT (f))
+ return Qt;
+ return Qnil;
+}
+#endif
+
+void
+syms_of_pgtkterm (void)
+{
+ DEFSYM (Qmodifier_value, "modifier-value");
+ DEFSYM (Qalt, "alt");
+ DEFSYM (Qhyper, "hyper");
+ DEFSYM (Qmeta, "meta");
+ DEFSYM (Qsuper, "super");
+ DEFSYM (Qcontrol, "control");
+ DEFSYM (QUTF8_STRING, "UTF8_STRING");
+ /* Referenced in gtkutil.c. */
+ DEFSYM (Qtheme_name, "theme-name");
+ DEFSYM (Qfile_name_sans_extension, "file-name-sans-extension");
+
+ DEFSYM (Qfile, "file");
+ DEFSYM (Qurl, "url");
+
+ DEFSYM (Qlatin_1, "latin-1");
+
+ xg_default_icon_file
+ = build_string ("icons/hicolor/scalable/apps/emacs.svg");
+ staticpro (&xg_default_icon_file);
+
+ DEFSYM (Qx_gtk_map_stock, "x-gtk-map-stock");
+
+ DEFSYM (Qcopy, "copy");
+ DEFSYM (Qmove, "move");
+ DEFSYM (Qlink, "link");
+ DEFSYM (Qprivate, "private");
+
+ Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
+ Fput (Qhyper, Qmodifier_value, make_fixnum (hyper_modifier));
+ Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
+ Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
+ Fput (Qcontrol, Qmodifier_value, make_fixnum (ctrl_modifier));
+
+ DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym,
+ doc: /* SKIP: real doc in xterm.c. */);
+ Vx_ctrl_keysym = Qnil;
+
+ DEFVAR_LISP ("x-alt-keysym", Vx_alt_keysym,
+ doc: /* SKIP: real doc in xterm.c. */);
+ Vx_alt_keysym = Qnil;
DEFVAR_LISP ("x-hyper-keysym", Vx_hyper_keysym,
doc: /* SKIP: real doc in xterm.c. */);
@@ -7514,10 +9246,15 @@ syms_of_pgtkterm (void)
window_being_scrolled = Qnil;
staticpro (&window_being_scrolled);
+#ifdef USE_SKIA
+ defsubr (&Spgtk_skia_gl_enabled_p);
+#endif
+
/* Tell Emacs about this window system. */
Fprovide (Qpgtk, Qnil);
}
+#ifdef USE_CAIRO
/* Cairo does not allow resizing a surface/context after it is
created, so we need to trash the old context, create a new context
on the next cr_clip_begin with the new dimensions and request a
@@ -7729,10 +9466,186 @@ pgtk_cr_export_frames (Lisp_Object frames, cairo_surface_type_t surface_type)
cairo_surface_flush (surface);
cairo_surface_write_to_png_stream (surface, pgtk_cr_accumulate_data, &acc);
}
-#endif
+# endif
unblock_input ();
unbind_to (count, Qnil);
return CALLN (Fapply, Qconcat, Fnreverse (acc));
}
+#endif /* USE_CAIRO */
+
+#ifdef USE_SKIA
+/* Skia-based frame export.
+ Export types: pdf, svg (PNG would need additional work). */
+
+/* Callback adapter for Skia write function. */
+struct skia_export_accumulator
+{
+ Lisp_Object data;
+};
+
+static size_t
+pgtk_skia_accumulate_data (void *ctx, const void *data, size_t size)
+{
+ struct skia_export_accumulator *acc
+ = (struct skia_export_accumulator *) ctx;
+ acc->data = Fcons (make_unibyte_string ((const char *) data, size),
+ acc->data);
+ return size;
+}
+
+/* Export frames to PDF, SVG, or PNG using Skia.
+ Returns the exported data as a unibyte string. */
+Lisp_Object
+pgtk_skia_export_frames (Lisp_Object frames, Lisp_Object type)
+{
+ struct frame *f;
+ int width, height;
+ struct skia_export_accumulator acc = { Qnil };
+ specpdl_ref count = SPECPDL_INDEX ();
+ bool is_pdf = NILP (type) || EQ (type, Qpdf);
+ bool is_svg = EQ (type, Qsvg);
+ bool is_png = EQ (type, Qpng);
+
+ if (!is_pdf && !is_svg && !is_png)
+ error ("Skia export supports pdf, svg, and png types");
+
+ if ((is_svg || is_png) && !NILP (XCDR (frames)))
+ error ("SVG and PNG export cannot handle multiple frames");
+
+ redisplay_preserve_echo_area (31);
+
+ f = XFRAME (XCAR (frames));
+ frames = XCDR (frames);
+ width = FRAME_PIXEL_WIDTH (f);
+ height = FRAME_PIXEL_HEIGHT (f);
+
+ block_input ();
+
+ if (is_pdf)
+ {
+ /* Create PDF document. */
+ emacs_skia_document_t *doc
+ = emacs_skia_document_create_pdf (pgtk_skia_accumulate_data,
+ &acc, width, height);
+ if (!doc)
+ {
+ unblock_input ();
+ error ("Failed to create PDF document");
+ }
+
+ while (1)
+ {
+ emacs_skia_canvas_t *canvas
+ = emacs_skia_document_begin_page (doc, width, height);
+ if (!canvas)
+ {
+ emacs_skia_document_close (doc);
+ unblock_input ();
+ error ("Failed to begin PDF page");
+ }
+
+ /* Save current Skia canvas and use document page. */
+ emacs_skia_canvas_t *saved_canvas = FRAME_SKIA_CANVAS (f);
+ FRAME_SKIA_CANVAS (f) = canvas;
+
+ /* Clear and redraw the frame. */
+ emacs_skia_canvas_clear (canvas,
+ EMACS_SKIA_COLOR_RGB (255, 255,
+ 255));
+ expose_frame (f, 0, 0, width, height);
+
+ FRAME_SKIA_CANVAS (f) = saved_canvas;
+ emacs_skia_document_end_page (doc);
+
+ if (NILP (frames))
+ break;
+
+ f = XFRAME (XCAR (frames));
+ frames = XCDR (frames);
+ width = FRAME_PIXEL_WIDTH (f);
+ height = FRAME_PIXEL_HEIGHT (f);
+
+ unblock_input ();
+ maybe_quit ();
+ block_input ();
+ }
+
+ emacs_skia_document_close (doc);
+ }
+ else if (is_svg)
+ {
+ /* Create SVG canvas. */
+ emacs_skia_canvas_t *canvas
+ = emacs_skia_svg_canvas_create (pgtk_skia_accumulate_data,
+ &acc, width, height);
+ if (!canvas)
+ {
+ unblock_input ();
+ error ("Failed to create SVG canvas");
+ }
+
+ /* Save current Skia canvas and use SVG canvas. */
+ emacs_skia_canvas_t *saved_canvas = FRAME_SKIA_CANVAS (f);
+ FRAME_SKIA_CANVAS (f) = canvas;
+
+ /* Clear and redraw the frame. */
+ emacs_skia_canvas_clear (canvas,
+ EMACS_SKIA_COLOR_RGB (255, 255, 255));
+ expose_frame (f, 0, 0, width, height);
+
+ FRAME_SKIA_CANVAS (f) = saved_canvas;
+ emacs_skia_svg_canvas_finish (canvas);
+ }
+ else if (is_png)
+ {
+ /* Create a temporary raster surface for PNG export. */
+ emacs_skia_surface_t *png_surface
+ = emacs_skia_surface_create_raster (width, height);
+ if (!png_surface)
+ {
+ unblock_input ();
+ error ("Failed to create PNG surface");
+ }
+
+ emacs_skia_canvas_t *canvas
+ = emacs_skia_surface_get_canvas (png_surface);
+ if (!canvas)
+ {
+ emacs_skia_surface_destroy (png_surface);
+ unblock_input ();
+ error ("Failed to get PNG canvas");
+ }
+
+ /* Save current Skia canvas and use PNG canvas. */
+ emacs_skia_canvas_t *saved_canvas = FRAME_SKIA_CANVAS (f);
+ FRAME_SKIA_CANVAS (f) = canvas;
+
+ /* Clear and redraw the frame. */
+ emacs_skia_canvas_clear (canvas,
+ EMACS_SKIA_COLOR_RGB (255, 255, 255));
+ expose_frame (f, 0, 0, width, height);
+
+ FRAME_SKIA_CANVAS (f) = saved_canvas;
+
+ /* Flush the surface and encode to PNG. */
+ emacs_skia_surface_flush (png_surface);
+ if (!emacs_skia_surface_write_to_png (png_surface,
+ pgtk_skia_accumulate_data,
+ &acc))
+ {
+ emacs_skia_surface_destroy (png_surface);
+ unblock_input ();
+ error ("Failed to encode PNG");
+ }
+
+ emacs_skia_surface_destroy (png_surface);
+ }
+
+ unblock_input ();
+ unbind_to (count, Qnil);
+
+ return CALLN (Fapply, Qconcat, Fnreverse (acc.data));
+}
+#endif /* USE_SKIA */
--
2.52.0
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
filename=0006-Document-with-skia-configure-option.patch
From 5ac0b7169cc00f2bc751cdee26e41d00e3e3780b Mon Sep 17 00:00:00 2001
From: Arthur Heymans <arthur@HIDDEN>
Date: Tue, 27 Jan 2026 08:07:24 +0100
Subject: [PATCH 6/6] Document --with-skia configure option
* INSTALL: Document --with-skia option, how to obtain and build Skia,
and the required environment variables.
* etc/NEWS: Announce the new --with-skia configure option for
GPU-accelerated rendering in PGTK builds.
---
INSTALL | 27 +++++++++++++++++++++++++++
etc/NEWS | 11 ++++++++++-
2 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/INSTALL b/INSTALL
index 4558f706f06..ee22a5a2cc1 100644
--- a/INSTALL
+++ b/INSTALL
@@ -439,6 +439,33 @@ faster when running over X connections with high latency, it is likely
to crash when a new frame is created on a display connection opened
after a display connection is closed.
+Use --with-skia to compile Emacs with Skia drawing instead of Cairo.
+This option is only available for PGTK builds (--with-pgtk) and provides
+GPU-accelerated rendering via OpenGL. Skia is Google's 2D graphics
+library used in Chrome and Android. Skia must be built and installed
+separately before configuring Emacs.
+
+ To obtain Skia:
+ git clone https://skia.googlesource.com/skia.git
+ cd skia
+ python3 tools/git-sync-deps
+
+ To build Skia (example for Linux):
+ bin/gn gen out/Release --args='is_official_build=true skia_use_system_freetype2=true'
+ ninja -C out/Release
+
+ Set environment variables to point to your Skia installation:
+ export SKIA_DIR=/path/to/skia
+ export SKIA_CFLAGS="-I$SKIA_DIR/include -I$SKIA_DIR"
+ export SKIA_LIBS="-L$SKIA_DIR/out/Release -lskia"
+
+ Alternatively, if using the Nix package manager, the repository's
+ flake.nix provides a development shell with Skia pre-configured:
+ nix develop
+
+ Then configure with:
+ ./configure --with-pgtk --with-skia
+
Use --with-modules to build Emacs with support for dynamic modules.
This needs a C compiler that supports '__attribute__ ((cleanup (...)))',
as in GCC 3.4 and later.
diff --git a/etc/NEWS b/etc/NEWS
index 9d36f6c3d96..99e209467e0 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -48,7 +48,16 @@ incorrectly in rare cases.
This allows to specify the directory where the user unit file for
systemd is installed; default is '${prefix}/usr/lib/systemd/user'.
-
+---
+** New configure option '--with-skia' for GPU-accelerated rendering.
+When configured with '--with-skia', the PGTK build of Emacs uses
+Google's Skia graphics library for all drawing operations instead of
+Cairo. This provides GPU-accelerated rendering via OpenGL. Requires
+OpenGL support (libepoxy or libGL) and is mutually exclusive with
+'--with-cairo'. The Skia library must be installed separately; see
+the INSTALL file for details on obtaining and building Skia.
+
+
* Startup Changes in Emacs 31.1
** In compatible terminals, 'xterm-mouse-mode' is turned on by default.
--
2.52.0
--=-=-=
Content-Type: text/plain
This patch series adds Skia as a GPU-accelerated rendering backend for
the PGTK build of Emacs. Skia is Google's 2D graphics library used in
Chrome and Android.
When configured with --with-skia, Emacs uses Skia for all drawing
operations instead of Cairo, providing GPU-accelerated rendering via
OpenGL through GtkGLArea.
The series is split into logical commits for easier review:
1. Skia C wrapper - standalone C API around Skia's C++ library
2. Build system - configure.ac and Makefile.in support
3. Header definitions - type definitions and declarations
4. Font driver - Skia font rendering with FreeType/HarfBuzz
5. Rendering implementation - all drawing primitives
6. Documentation - INSTALL and NEWS updates
Tested with both --with-pgtk (Cairo) and --with-pgtk --with-skia.
I've send the copyright assignement request.
Disclaimer: I did use an LLM to help with this work.
Arthur Heymans (6):
Add Skia C wrapper library
Add --with-skia configure option and build system support
Add Skia type definitions and declarations to headers
Add Skia font driver
Implement Skia rendering for PGTK
Document --with-skia configure option
INSTALL | 27 +
configure.ac | 191 +++-
etc/NEWS | 11 +-
src/Makefile.in | 27 +-
src/dispextern.h | 23 +-
src/font.c | 8 +-
src/font.h | 7 +
src/ftfont.h | 7 +
src/gtkutil.c | 19 +-
src/image.c | 255 ++++-
src/pgtkfns.c | 86 +-
src/pgtkterm.c | 2141 ++++++++++++++++++++++++++++++++++--
src/pgtkterm.h | 74 +-
src/skia/emacs_skia.cpp | 2271 +++++++++++++++++++++++++++++++++++++++
src/skia/emacs_skia.h | 650 +++++++++++
src/skiafont.c | 774 +++++++++++++
16 files changed, 6376 insertions(+), 195 deletions(-)
create mode 100644 src/skia/emacs_skia.cpp
create mode 100644 src/skia/emacs_skia.h
create mode 100644 src/skiafont.c
--
2.52.0
--=-=-=--
Arthur Heymans <arthur@HIDDEN>:bug-gnu-emacs@HIDDEN.
Full text available.bug-gnu-emacs@HIDDEN:bug#80272; Package emacs.
Full text available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997 nCipher Corporation Ltd,
1994-97 Ian Jackson.