From 057b15d87002b424418a05d39a855772d693c790 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 1 Oct 2024 13:12:08 +0200 Subject: [PATCH] Build with external link support --- build/jsroot.js | 88 +++++++++++++++++++++++++++++++++++------------- changes.md | 12 +++---- modules/core.mjs | 2 +- 3 files changed, 71 insertions(+), 31 deletions(-) diff --git a/build/jsroot.js b/build/jsroot.js index aaa48a7b0..6b6b1069d 100644 --- a/build/jsroot.js +++ b/build/jsroot.js @@ -12,7 +12,7 @@ const version_id = 'dev', /** @summary version date * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '30/09/2024', +version_date = '1/10/2024', /** @summary version id and date * @desc Produced by concatenation of {@link version_id} and {@link version_date} @@ -12397,7 +12397,7 @@ class ObjectPainter extends BasePainter { * or svg:g element created in specified frame layer ('main_layer' will be used when true specified) * @param {boolean|string} [frame_layer] - when specified, element will be created inside frame layer, otherwise in the pad * @protected */ - createG(frame_layer) { + createG(frame_layer, use_a = false) { let layer; if (frame_layer === 'frame2d') { @@ -12425,7 +12425,7 @@ class ObjectPainter extends BasePainter { // clear all elements, keep g element on its place this.draw_g.selectAll('*').remove(); } else { - this.draw_g = layer.append('svg:g'); + this.draw_g = layer.append(use_a ? 'svg:a' : 'svg:g'); if (!frame_layer) layer.selectChildren('.most_upper_primitives').raise(); @@ -137322,19 +137322,28 @@ function getBoundingBoxByChildren(context, svgnode) { if (getAttribute(svgnode.element, context.styleSheets, 'display') === 'none') { return [0, 0, 0, 0]; } - var boundingBox = [0, 0, 0, 0]; + var boundingBox = []; svgnode.children.forEach(function (child) { var nodeBox = child.getBoundingBox(context); - boundingBox = [ - Math.min(boundingBox[0], nodeBox[0]), - Math.min(boundingBox[1], nodeBox[1]), - Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) - + if ((nodeBox[0] === 0) && (nodeBox[1] === 0) && (nodeBox[2] === 0) && (nodeBox[3] === 0)) + return; + var transform = child.computeNodeTransform(context); + // TODO: check and apply rotation matrix if any + nodeBox[0] += transform.tx; + nodeBox[1] += transform.ty; + if (boundingBox.length === 0) + boundingBox = nodeBox; + else + boundingBox = [ Math.min(boundingBox[0], nodeBox[0]), - Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) - - Math.min(boundingBox[1], nodeBox[1]) - ]; + Math.min(boundingBox[1], nodeBox[1]), + Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) - + Math.min(boundingBox[0], nodeBox[0]), + Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) - + Math.min(boundingBox[1], nodeBox[1]) + ]; }); - return boundingBox; + return boundingBox.length === 0 ? [0, 0, 0, 0] : boundingBox; } function defaultBoundingBox(element, context) { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -139059,7 +139068,9 @@ var TextChunk = /** @class */ (function () { var TextNode = /** @class */ (function (_super) { __extends(TextNode, _super); function TextNode() { - return _super !== null && _super.apply(this, arguments) || this; + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.boundingBox = []; + return _this; } TextNode.prototype.processTSpans = function (textNode, node, context, textChunks, currentTextSegment, trimInfo) { var pdfFontSize = context.pdf.getFontSize(); @@ -139155,6 +139166,9 @@ var TextNode = /** @class */ (function (_super) { renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, charSpace: charSpace === 0 ? void 0 : charSpace }); + this.boundingBox = [textX + dx - xOffset, textY + dy - pdfFontSize, context.textMeasure.measureTextWidth(transformedText, context.attributeState), pdfFontSize]; + if (alignmentBaseline === 'baseline') + this.boundingBox[1] += pdfFontSize * 0.2; } } else { @@ -139206,7 +139220,7 @@ var TextNode = /** @class */ (function (_super) { return svgNodeAndChildrenVisible(this, parentVisible, context); }; TextNode.prototype.getBoundingBoxCore = function (context) { - return defaultBoundingBox(this.element, context); + return this.boundingBox.length > 0 ? this.boundingBox : defaultBoundingBox(this.element, context); }; TextNode.prototype.computeNodeTransformCore = function (context) { return context.pdf.unitMatrix; @@ -141328,6 +141342,33 @@ var Group = /** @class */ (function (_super) { }; return Group; }(ContainerNode)); +var GroupA = /** @class */ (function (_super) { + __extends(GroupA, _super); + function GroupA() { + return _super !== null && _super.apply(this, arguments) || this; + } + GroupA.prototype.renderCore = function (context) { + return __awaiter(this, void 0, void 0, function () { + var href, box, scale, ph; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, _super.prototype.renderCore.call(this, context)]; + case 1: + _a.sent(); + href = getAttribute(this.element, context.styleSheets, 'href'); + if (href) { + box = this.getBoundingBox(context); + scale = context.pdf.internal.scaleFactor; + ph = context.pdf.internal.pageSize.getHeight(); + context.pdf.link(scale * box[0], ph - scale * box[1], scale * box[2], scale * box[3], { url: href }); + } + return [2 /*return*/]; + } + }); + }); + }; + return GroupA; +}(Group)); var ClipPath = /** @class */ (function (_super) { __extends(ClipPath, _super); @@ -141389,6 +141430,8 @@ function parse$1(node, idMap) { forEachChild(node, function (i, n) { return children.push(parse$1(n, idMap)); }); switch (node.tagName.toLowerCase()) { case 'a': + svgnode = new GroupA(node, children); + break; case 'g': svgnode = new Group(node, children); break; @@ -146985,7 +147028,8 @@ async function drawText$1() { pp = this.getPadPainter(), w = pp.getPadWidth(), h = pp.getPadHeight(), - fp = this.getFramePainter(); + fp = this.getFramePainter(), + is_url = text.fName.startsWith('http://') || text.fName.startsWith('https://'); let pos_x = text.fX, pos_y = text.fY, use_frame = false, fact = 1, annot = this.matchObjectType(clTAnnotation); @@ -147012,7 +147056,7 @@ async function drawText$1() { text.fTextAlign = 22; } - this.createG(use_frame ? 'frame2d' : undefined); + this.createG(use_frame ? 'frame2d' : undefined, is_url); this.draw_g.attr('transform', null); // remove transform from interactive changes @@ -147033,16 +147077,12 @@ async function drawText$1() { fact = 0.8; } - let draw_g = this.draw_g; - if (text.fName.startsWith('http://') || text.fName.startsWith('https://')) { - const a = draw_g.append('a').attr('href', text.fName).attr('title', `Link on ${text.fName}`); - draw_g = a; - arg.draw_g = a; - } + if (is_url) + this.draw_g.attr('href', text.fName).append('title').text(`Link on ${text.fName}`); - return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(w, h, fact, 0.05), draw_g) + return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(w, h, fact, 0.05)) .then(() => this.drawText(arg)) - .then(() => this.finishTextDrawing(draw_g)) + .then(() => this.finishTextDrawing()) .then(() => { if (this.isBatchMode()) return this; diff --git a/changes.md b/changes.md index d30d7cada..a12ea60b4 100644 --- a/changes.md +++ b/changes.md @@ -28,12 +28,12 @@ 25. Upgrade three.js r162 -> r168, use r162 only in node.js because of "gl" module 26. Create unified svg2pdf/jspdf ES6 modules, integrate in jsroot builds 27. Let create multipage PDF document - in TWebCanvas batch mode -28. Internals - upgrade to eslint 9 -29. Internals - do not select pad (aka gPad) for objects drawing, always use assigned pad painter -30. Fix - properly save zoomed ranges in drawingJSON() -31. Fix - properly redraw TMultiGraph -32. Fix - show empty bin in TProfile2D if it has entries #316 -33. Fix - saving embed TGeo in TCanvas into image +28. Let add external link when rendering text - including PDF support +29. Internals - upgrade to eslint 9 +30. Internals - do not select pad (aka gPad) for objects drawing, always use assigned pad painter +31. Fix - properly save zoomed ranges in drawingJSON() +32. Fix - properly redraw TMultiGraph +33. Fix - show empty bin in TProfile2D if it has entries #316 34. Fix - unzooming on log scale was extending range forevever diff --git a/modules/core.mjs b/modules/core.mjs index f80f0810b..4935b6d25 100644 --- a/modules/core.mjs +++ b/modules/core.mjs @@ -4,7 +4,7 @@ const version_id = 'dev', /** @summary version date * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '30/09/2024', +version_date = '1/10/2024', /** @summary version id and date * @desc Produced by concatenation of {@link version_id} and {@link version_date}