From 5456ccc0727da3f6cb38f94d547208d13c8f64b8 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Mon, 22 Jan 2024 13:32:32 +0200 Subject: [PATCH 1/3] Implement rotated watermark text support --- common/rfb/ServerCore.cxx | 4 + common/rfb/ServerCore.h | 1 + common/rfb/Watermark.cxx | 176 +++++++++++++++++++++++++++++++++-- unix/xserver/hw/vnc/Xvnc.man | 4 + 4 files changed, 179 insertions(+), 6 deletions(-) diff --git a/common/rfb/ServerCore.cxx b/common/rfb/ServerCore.cxx index b9ed8df..03a69d9 100644 --- a/common/rfb/ServerCore.cxx +++ b/common/rfb/ServerCore.cxx @@ -201,6 +201,10 @@ rfb::IntParameter rfb::Server::DLP_WatermarkTimeOffsetMinutes ("DLP_WatermarkTimeOffsetMinutes", "Offset from UTC for -DLP_WatermarkText, minutes", 0, -24 * 60, 24 * 60); +rfb::IntParameter rfb::Server::DLP_WatermarkTextAngle +("DLP_WatermarkTextAngle", + "Angle for -DLP_WatermarkText rotation", + 0, -359, 359); rfb::StringParameter rfb::Server::DLP_WatermarkImage ("DLP_WatermarkImage", "PNG file to use as a watermark", diff --git a/common/rfb/ServerCore.h b/common/rfb/ServerCore.h index e2ae96b..2035cf6 100644 --- a/common/rfb/ServerCore.h +++ b/common/rfb/ServerCore.h @@ -52,6 +52,7 @@ namespace rfb { static IntParameter DLP_WatermarkFontSize; static IntParameter DLP_WatermarkTimeOffset; static IntParameter DLP_WatermarkTimeOffsetMinutes; + static IntParameter DLP_WatermarkTextAngle; static StringParameter DLP_ClipLog; static StringParameter DLP_Region; static StringParameter DLP_Clip_Types; diff --git a/common/rfb/Watermark.cxx b/common/rfb/Watermark.cxx index acee33c..dda0763 100644 --- a/common/rfb/Watermark.cxx +++ b/common/rfb/Watermark.cxx @@ -16,6 +16,7 @@ * USA. */ +#include #include #include #include @@ -28,6 +29,7 @@ #include "font.h" #include #include FT_FREETYPE_H +#include FT_GLYPH_H #include "Watermark.h" @@ -187,6 +189,149 @@ static uint32_t drawnwidth(const char *txt) { return x; } +static void angle2mat(FT_Matrix &mat) { + const float angle = Server::DLP_WatermarkTextAngle / 360.f * 2 * -3.14159f; + + mat.xx = (FT_Fixed)( cosf(angle) * 0x10000L); + mat.xy = (FT_Fixed)(-sinf(angle) * 0x10000L); + mat.yx = (FT_Fixed)( sinf(angle) * 0x10000L); + mat.yy = (FT_Fixed)( cosf(angle) * 0x10000L); +} + +// Note: w and h are absolute +static void angledstr(uint8_t *buf, const char *txt, const uint32_t x_, const uint32_t y_, + const uint32_t w, const uint32_t h, + const uint32_t stride, const bool invx, const bool invy) { + + unsigned ucs[256], i, ucslen; + unsigned len = strlen(txt); + i = 0; + ucslen = 0; + while (len > 0 && txt[i]) { + size_t ret = rfb::utf8ToUCS4(&txt[i], len, &ucs[ucslen]); + i += ret; + len -= ret; + ucslen++; + } + + FT_Matrix mat; + FT_Vector pen; + + angle2mat(mat); + + pen.x = 0; + pen.y = 0; + + uint32_t x, y; + + x = x_; + y = y_; + for (i = 0; i < ucslen; i++) { + FT_Set_Transform(face, &mat, &pen); + + if (FT_Load_Char(face, ucs[i], FT_LOAD_RENDER)) + continue; + const FT_Bitmap * const map = &(face->glyph->bitmap); + + uint32_t row, col; + for (row = 0; row < (uint32_t) map->rows; row++) { + int ny = row + y - face->glyph->bitmap_top; + if (ny < 0) + continue; + if ((unsigned) ny >= h) + continue; + + uint8_t *dst = (uint8_t *) buf; + dst += ny * stride + x; + + const uint8_t *src = map->buffer + map->pitch * row; + for (col = 0; col < (uint32_t) map->width; col++) { + if (col + x >= w) + continue; + const uint8_t out = (src[col] + 8) >> 4; + dst[col] |= out < 16 ? out : 15; + } + } + + x += face->glyph->advance.x >> 6; + + pen.x += face->glyph->advance.x; + pen.y += face->glyph->advance.y; + } +} + +static void angledsize(const char *txt, uint32_t &w, uint32_t &h, + uint32_t &recw, uint32_t &recy, + bool &invx, bool &invy) { + + unsigned ucs[256], i, ucslen; + unsigned len = strlen(txt); + i = 0; + ucslen = 0; + while (len > 0 && txt[i]) { + size_t ret = rfb::utf8ToUCS4(&txt[i], len, &ucs[ucslen]); + i += ret; + len -= ret; + ucslen++; + } + + FT_Matrix mat; + FT_Vector pen; + + angle2mat(mat); + + pen.x = 0; + pen.y = 0; + + FT_BBox firstbox, lastbox; + + for (i = 0; i < ucslen; i++) { + FT_Set_Transform(face, &mat, &pen); + + if (FT_Load_Char(face, ucs[i], FT_LOAD_DEFAULT)) + continue; + + if (i == 0) { + FT_Glyph glyph; + + FT_Get_Glyph(face->glyph, &glyph); + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &firstbox); + FT_Done_Glyph(glyph); + + // recommended y; if the angle is steep enough, use the X bearing + #define EDGE 22 + const int angle = abs(Server::DLP_WatermarkTextAngle); + if ((angle > (45 + EDGE) && angle < (135 - EDGE)) || + (angle > (225 + EDGE) && angle < (315 - EDGE))) + recy = face->glyph->metrics.horiBearingX >> 6; + else + recy = face->glyph->metrics.horiBearingY >> 6; + #undef EDGE + } else if (i == ucslen - 1) { + FT_Glyph glyph; + + FT_Get_Glyph(face->glyph, &glyph); + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &lastbox); + FT_Done_Glyph(glyph); + } + + if (i != ucslen - 1) { + pen.x += face->glyph->advance.x; + pen.y += face->glyph->advance.y; + } + } + + // recommended width, used when X is inverted + recw = face->size->metrics.max_advance >> 6; + + // The used area is an union of first box, last box, and their relative distance + invx = pen.x < 0; + invy = pen.y > 0; + + w = (firstbox.xMax - firstbox.xMin) + (lastbox.xMax - lastbox.xMin) + abs(pen.x >> 6); + h = (firstbox.yMax - firstbox.yMin) + (lastbox.yMax - lastbox.yMin) + abs(pen.y >> 6); +} + static bool drawtext(const char fmt[], const int16_t utcOff, const char fontpath[], const uint8_t fontsize) { char buf[PATH_MAX]; @@ -213,14 +358,33 @@ static bool drawtext(const char fmt[], const int16_t utcOff, const char fontpath return false; free(watermarkInfo.src); - const uint32_t h = fontsize + 4; - const uint32_t w = drawnwidth(buf); + if (Server::DLP_WatermarkTextAngle) { + uint32_t w, h, recw, recy = fontsize; + bool invx, invy; + angledsize(buf, w, h, recw, recy, invx, invy); - watermarkInfo.w = w; - watermarkInfo.h = h; - watermarkInfo.src = (uint8_t *) calloc(w, h); + // The max is because a rotated text with the time can change size. + // With the max op, at least it will only grow instead of bouncing. + w = __rfbmax(w, watermarkInfo.w); + h = __rfbmax(h, watermarkInfo.h); - str(watermarkInfo.src, buf, 0, fontsize, w, h, w); + watermarkInfo.w = w; + watermarkInfo.h = h; + watermarkInfo.src = (uint8_t *) calloc(w, h); + + angledstr(watermarkInfo.src, buf, + invx ? w - recw: 0, invy ? h - recy : recy, + w, h, w, invx, invy); + } else { + const uint32_t h = fontsize + 4; + const uint32_t w = drawnwidth(buf); + + watermarkInfo.w = w; + watermarkInfo.h = h; + watermarkInfo.src = (uint8_t *) calloc(w, h); + + str(watermarkInfo.src, buf, 0, fontsize, w, h, w); + } return true; } diff --git a/unix/xserver/hw/vnc/Xvnc.man b/unix/xserver/hw/vnc/Xvnc.man index cfb1ac5..54cbb63 100644 --- a/unix/xserver/hw/vnc/Xvnc.man +++ b/unix/xserver/hw/vnc/Xvnc.man @@ -384,6 +384,10 @@ Instead of an image, render this text as the watermark. Takes time formatting op for \fBstrftime\fP. . .TP +.B \-DLP_WatermarkTextAngle \fIangle\fP +Rotate the text by this many degrees, increasing clockwise. Default \fB0\fP. +. +.TP .B \-DLP_WatermarkFont \fI/path/to/font.ttf\fP Use a different font for -DLP_WatermarkText than the bundled one. TTF and OTF fonts are accepted. From 659a4198fc29fab01de6c735e22a39311e37458f Mon Sep 17 00:00:00 2001 From: Matt McClaskey Date: Mon, 22 Jan 2024 07:30:46 -0500 Subject: [PATCH 2/3] KASM-5466 add text rotation yaml config --- spec/fixtures/defaults_config.yaml | 6 ++++++ unix/kasmvnc_defaults.yaml | 1 + unix/vncserver | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/spec/fixtures/defaults_config.yaml b/spec/fixtures/defaults_config.yaml index b40d6de..cd602da 100644 --- a/spec/fixtures/defaults_config.yaml +++ b/spec/fixtures/defaults_config.yaml @@ -50,6 +50,12 @@ data_loss_prevention: # location: 10,10 # tint: 255,20,20,128 # repeat_spacing: 10 + #text: + # template: "${USER} %H:%M" + # font: auto + # font_size: 48 + # timezone_name: Australia/Adelaide + # rotation: 0 logging: level: off diff --git a/unix/kasmvnc_defaults.yaml b/unix/kasmvnc_defaults.yaml index e042730..357efb5 100644 --- a/unix/kasmvnc_defaults.yaml +++ b/unix/kasmvnc_defaults.yaml @@ -100,6 +100,7 @@ data_loss_prevention: # font: auto # font_size: 48 # timezone_name: Australia/Adelaide + # rotation: 0 logging: # "verbose" SETTING LOGS YOUR PRIVATE INFORMATION. Keypresses and clipboard content level: off diff --git a/unix/vncserver b/unix/vncserver index 531b185..23e127d 100755 --- a/unix/vncserver +++ b/unix/vncserver @@ -1862,6 +1862,24 @@ sub DefineConfigToCLIConversion { $offset_in_seconds/60; } }), + KasmVNC::CliOption->new({ + name => 'DLP_WatermarkTextAngle', + configKeys => [ + KasmVNC::ConfigKey->new({ + name => "data_loss_prevention.watermark.text.angle", + validator => KasmVNC::CallbackValidator->new({ + isValidCallback => sub { + my $value = shift; + + return 0 unless $value =~ /^\d+$/; + + $value >= -359 && $value <= 359; + }, + errorMessage => "must be in range -359..359" + }), + }) + ] + }), KasmVNC::CliOption->new({ name => 'DLP_Log', configKeys => [ From ed9fbcbec2fc66a9d5ade2e506668f94f8e2f6ca Mon Sep 17 00:00:00 2001 From: Matt McClaskey Date: Mon, 22 Jan 2024 08:58:45 -0500 Subject: [PATCH 3/3] [skip CI] correct example config --- spec/fixtures/defaults_config.yaml | 2 +- unix/kasmvnc_defaults.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/fixtures/defaults_config.yaml b/spec/fixtures/defaults_config.yaml index cd602da..71a6920 100644 --- a/spec/fixtures/defaults_config.yaml +++ b/spec/fixtures/defaults_config.yaml @@ -55,7 +55,7 @@ data_loss_prevention: # font: auto # font_size: 48 # timezone_name: Australia/Adelaide - # rotation: 0 + # angle: 0 logging: level: off diff --git a/unix/kasmvnc_defaults.yaml b/unix/kasmvnc_defaults.yaml index 357efb5..292e93d 100644 --- a/unix/kasmvnc_defaults.yaml +++ b/unix/kasmvnc_defaults.yaml @@ -100,7 +100,7 @@ data_loss_prevention: # font: auto # font_size: 48 # timezone_name: Australia/Adelaide - # rotation: 0 + # angle: 0 logging: # "verbose" SETTING LOGS YOUR PRIVATE INFORMATION. Keypresses and clipboard content level: off