diff --git a/src/nodes/anchor.ts b/src/nodes/anchor.ts new file mode 100644 index 00000000..0601bbfc --- /dev/null +++ b/src/nodes/anchor.ts @@ -0,0 +1,19 @@ +import { Group } from './group' +import { Context } from '../context/context' +import { getAttribute } from '../utils/node' + +export class Anchor extends Group { + protected async renderCore(context: Context): Promise { + await super.renderCore(context) + + const href = getAttribute(this.element, context.styleSheets, 'href') + if (href) { + const box = this.getBoundingBox(context) + const scale = context.pdf.internal.scaleFactor + const ph = context.pdf.internal.pageSize.getHeight() + + context.pdf.link(scale*(box[0] * context.transform.sx + context.transform.tx), + ph - scale*(box[1] * context.transform.sy + context.transform.ty), scale*box[2], scale*box[3], { url: href }) + } + } +} diff --git a/src/nodes/text.ts b/src/nodes/text.ts index 4cd0c07e..ba73d068 100644 --- a/src/nodes/text.ts +++ b/src/nodes/text.ts @@ -24,6 +24,7 @@ interface TrimInfo { } export class TextNode extends GraphicsNode { + private boundingBox: number[] = [] private processTSpans( textNode: SvgNode, node: Element, @@ -159,6 +160,7 @@ export class TextNode extends GraphicsNode { renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, charSpace: charSpace === 0 ? void 0 : charSpace }) + this.boundingBox = [textX + dx - xOffset, textY + dy + 0.1 * pdfFontSize, context.textMeasure.measureTextWidth(transformedText, context.attributeState), pdfFontSize] } } else { // otherwise loop over tspans and position each relative to the previous one @@ -228,7 +230,7 @@ export class TextNode extends GraphicsNode { } protected getBoundingBoxCore(context: Context): Rect { - return defaultBoundingBox(this.element, context) + return this.boundingBox.length > 0 ? this.boundingBox : defaultBoundingBox(this.element, context) } protected computeNodeTransformCore(context: Context): Matrix { diff --git a/src/parse.ts b/src/parse.ts index f0708aac..7bfa0615 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -17,6 +17,7 @@ import { RadialGradient } from './nodes/radialgradient' import { Polyline } from './nodes/polyline' import { Svg } from './nodes/svg' import { Group } from './nodes/group' +import { Anchor } from './nodes/anchor' import cssesc from 'cssesc' import { ClipPath } from './nodes/clippath' import { Symbol } from './nodes/symbol' @@ -29,6 +30,8 @@ export function parse(node: Element, idMap?: { [id: string]: SvgNode }): SvgNode switch (node.tagName.toLowerCase()) { case 'a': + svgnode = new Anchor(node, children) + break case 'g': svgnode = new Group(node, children) break diff --git a/src/utils/bbox.ts b/src/utils/bbox.ts index 66d3236a..c9091962 100644 --- a/src/utils/bbox.ts +++ b/src/utils/bbox.ts @@ -7,9 +7,20 @@ export function getBoundingBoxByChildren(context: Context, svgnode: SvgNode): nu if (getAttribute(svgnode.element, context.styleSheets, 'display') === 'none') { return [0, 0, 0, 0] } - let boundingBox = [0, 0, 0, 0] + let boundingBox : number[] = [] svgnode.children.forEach(child => { const nodeBox = child.getBoundingBox(context) + if ((nodeBox[0] === 0) && (nodeBox[1] === 0) && (nodeBox[2] === 0) && (nodeBox[3] === 0)) + return + const transform = child.computeNodeTransform(context) + // TODO: take into account rotation matrix + nodeBox[0] = nodeBox[0] * transform.sx + transform.tx + nodeBox[1] = nodeBox[1] * transform.sy + transform.ty + nodeBox[2] = nodeBox[2] * transform.sx + nodeBox[3] = nodeBox[3] * transform.sy + if (boundingBox.length === 0) + boundingBox = nodeBox + else boundingBox = [ Math.min(boundingBox[0], nodeBox[0]), Math.min(boundingBox[1], nodeBox[1]), @@ -19,7 +30,7 @@ export function getBoundingBoxByChildren(context: Context, svgnode: SvgNode): nu Math.min(boundingBox[1], nodeBox[1]) ] }) - return boundingBox + return boundingBox.length === 0 ? [0, 0, 0, 0] : boundingBox } export function defaultBoundingBox(element: Element, context: Context): Rect { diff --git a/test/common/tests.js b/test/common/tests.js index 1b9818fb..6bd06da5 100644 --- a/test/common/tests.js +++ b/test/common/tests.js @@ -1,4 +1,5 @@ window.tests = [ + 'anchor', 'attribute-style-precedence', 'clippath', 'clippath-empty', diff --git a/test/specs/anchor/reference.pdf b/test/specs/anchor/reference.pdf new file mode 100644 index 00000000..21cac67a Binary files /dev/null and b/test/specs/anchor/reference.pdf differ diff --git a/test/specs/anchor/spec.svg b/test/specs/anchor/spec.svg new file mode 100644 index 00000000..89ac6d1d --- /dev/null +++ b/test/specs/anchor/spec.svg @@ -0,0 +1,34 @@ + + + + + hanging + + + + mathematical + + + + middle + + + + central + + + + alphabetic + + + + ideographic + + + + + + + + + diff --git a/test/specs/complete-computer-network/reference.pdf b/test/specs/complete-computer-network/reference.pdf index 6b4258bb..448ca454 100644 Binary files a/test/specs/complete-computer-network/reference.pdf and b/test/specs/complete-computer-network/reference.pdf differ diff --git a/test/specs/complete-movies/reference.pdf b/test/specs/complete-movies/reference.pdf index 1491c96a..f1ff97b7 100644 Binary files a/test/specs/complete-movies/reference.pdf and b/test/specs/complete-movies/reference.pdf differ diff --git a/test/specs/complete-organization-chart-new/reference.pdf b/test/specs/complete-organization-chart-new/reference.pdf index d5f717d9..8c0a37bf 100644 Binary files a/test/specs/complete-organization-chart-new/reference.pdf and b/test/specs/complete-organization-chart-new/reference.pdf differ diff --git a/test/specs/complete-organization-chart/reference.pdf b/test/specs/complete-organization-chart/reference.pdf index 7eb4319f..7f25e04a 100644 Binary files a/test/specs/complete-organization-chart/reference.pdf and b/test/specs/complete-organization-chart/reference.pdf differ diff --git a/test/specs/complete-social-network/reference.pdf b/test/specs/complete-social-network/reference.pdf index 4e95b905..307ade46 100644 Binary files a/test/specs/complete-social-network/reference.pdf and b/test/specs/complete-social-network/reference.pdf differ