diff --git a/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.spec.ts b/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.spec.ts index 464fa7c9..2a378619 100644 --- a/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.spec.ts +++ b/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.spec.ts @@ -10,6 +10,7 @@ describe('SvgToDrawingHelper', () => { it('should parse attributes', () => { const node = document.createElement("text"); + node.innerText = "Text"; node.setAttribute("fill", "#00000"); node.setAttribute("fill-opacity", "1.0"); node.setAttribute("font-family", "TypeWriter"); @@ -17,10 +18,12 @@ describe('SvgToDrawingHelper', () => { node.setAttribute("font-weight", "bold"); const drawing = textConverter.convert(node); + expect(drawing.text).toEqual("Text"); expect(drawing.fill).toEqual("#00000"); expect(drawing.fill_opacity).toEqual(1.0); expect(drawing.font_family).toEqual("TypeWriter"); expect(drawing.font_size).toEqual(10.0); expect(drawing.font_weight).toEqual("bold"); }); + }); diff --git a/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.ts b/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.ts index e73cb3b2..04dc7360 100644 --- a/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.ts +++ b/src/app/cartography/map/helpers/svg-to-drawing-converter/text-converter.ts @@ -6,6 +6,8 @@ export class TextConverter implements SvgConverter { convert(node: Node): TextElement { const drawing = new TextElement(); + drawing.text = node.textContent; + const fill = node.attributes.getNamedItem('fill'); if (fill) { drawing.fill = fill.value; diff --git a/src/app/cartography/shared/models/drawings/text-element.ts b/src/app/cartography/shared/models/drawings/text-element.ts index cbf2d7cc..ccddf868 100644 --- a/src/app/cartography/shared/models/drawings/text-element.ts +++ b/src/app/cartography/shared/models/drawings/text-element.ts @@ -4,6 +4,7 @@ import { DrawingElement } from "./drawing-element"; export class TextElement implements DrawingElement { height: number; width: number; + text: string; fill: string; fill_opacity: number; font_family: string; diff --git a/src/app/cartography/shared/widgets/drawings.ts b/src/app/cartography/shared/widgets/drawings.ts index f729b92b..7ddf8d0d 100644 --- a/src/app/cartography/shared/widgets/drawings.ts +++ b/src/app/cartography/shared/widgets/drawings.ts @@ -2,6 +2,7 @@ import {Widget} from "./widget"; import {Drawing} from "../models/drawing"; import {SVGSelection} from "../models/types"; import {Layer} from "../models/layer"; +import { TextDrawingWidget } from "./drawings/text-drawing"; export class DrawingsWidget implements Widget { @@ -20,43 +21,48 @@ export class DrawingsWidget implements Widget { .append('g') .attr('class', 'drawing'); - const parser = new DOMParser(); + // const parser = new DOMParser(); - const drawing_image = drawing_enter.append('image') - .attr('xlink:href', (d: Drawing) => { - let svg = d.svg; - if (svg.indexOf("xmlns") < 0) { - svg = svg.replace('svg', 'svg xmlns="http://www.w3.org/2000/svg"'); - } - - return 'data:image/svg+xml;base64,' + btoa(svg); - }) - .attr('width', (d: Drawing) => { - const svg_dom = parser.parseFromString(d.svg, 'text/xml'); - const roots = svg_dom.getElementsByTagName('svg'); - if (roots.length > 0) { - if (roots[0].hasAttribute('width')) { - return roots[0].getAttribute('width'); - } - } - return 0; - }) - .attr('height', (d: Drawing) => { - const svg_dom = parser.parseFromString(d.svg, 'text/xml'); - const roots = svg_dom.getElementsByTagName('svg'); - if (roots.length > 0) { - if (roots[0].hasAttribute('height')) { - return roots[0].getAttribute('height'); - } - } - return 0; - }); + // const drawing_image = drawing_enter.append('image') + // .attr('xlink:href', (d: Drawing) => { + // let svg = d.svg; + // if (svg.indexOf("xmlns") < 0) { + // svg = svg.replace('svg', 'svg xmlns="http://www.w3.org/2000/svg"'); + // } + // + // return 'data:image/svg+xml;base64,' + btoa(svg); + // }) + // .attr('width', (d: Drawing) => { + // const svg_dom = parser.parseFromString(d.svg, 'text/xml'); + // const roots = svg_dom.getElementsByTagName('svg'); + // if (roots.length > 0) { + // if (roots[0].hasAttribute('width')) { + // return roots[0].getAttribute('width'); + // } + // } + // return 0; + // }) + // .attr('height', (d: Drawing) => { + // const svg_dom = parser.parseFromString(d.svg, 'text/xml'); + // const roots = svg_dom.getElementsByTagName('svg'); + // if (roots.length > 0) { + // if (roots[0].hasAttribute('height')) { + // return roots[0].getAttribute('height'); + // } + // } + // return 0; + // }); const drawing_merge = drawing.merge(drawing_enter) .attr('transform', (d: Drawing) => { return `translate(${d.x},${d.y})`; }); - drawing.exit().remove(); + const text_drawing = new TextDrawingWidget(); + text_drawing.draw(drawing_merge); + + drawing + .exit() + .remove(); } } diff --git a/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts new file mode 100644 index 00000000..6dc7d824 --- /dev/null +++ b/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts @@ -0,0 +1,61 @@ +import { instance, mock, when } from "ts-mockito"; +import { TestSVGCanvas } from "../../../testing"; +import { TextDrawingWidget } from "./text-drawing"; +import { Drawing } from "../../models/drawing"; +import { TextElement } from "../../models/drawings/text-element"; + +describe('TextDrawingWidget', () => { + let svg: TestSVGCanvas; + let widget: TextDrawingWidget; + let drawing: Drawing; + + + beforeEach(() => { + svg = new TestSVGCanvas(); + drawing = new Drawing(); + widget = new TextDrawingWidget(); + }); + + afterEach(() => { + svg.destroy(); + }); + + it('should draw text drawing', () => { + drawing.svg = 'THIS IS TEXT'; + + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings_enter = drawings.enter().append('g').classed('drawing', true); + const drawings_merge = drawings.merge(drawings_enter); + + widget.draw(drawings_merge); + + const drew = drawings_merge.selectAll('text.text_element'); + expect(drew.size()).toEqual(1); + const text_element = drew.nodes()[0]; + expect(text_element.innerHTML).toEqual("THIS IS TEXT"); + expect(text_element.getAttribute('style')).toEqual('font-family: "TypeWriter"; font-size: 10pt; font-weight: bold'); + }); + + it('should draw multiline text', () => { + drawing.svg = 'THIS' + "\n" + 'IS TEXT'; + + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings_enter = drawings.enter().append('g').classed('drawing', true); + const drawings_merge = drawings.merge(drawings_enter); + + widget.draw(drawings_merge); + + const drew = drawings_merge.selectAll('tspan'); + expect(drew.nodes().length).toEqual(2); + + expect(drew.nodes()[0].innerHTML).toEqual('THIS'); + expect(drew.nodes()[0].getAttribute('x')).toEqual('0'); + expect(drew.nodes()[0].getAttribute('dy')).toEqual('0em'); + + expect(drew.nodes()[1].innerHTML).toEqual('IS TEXT'); + expect(drew.nodes()[1].getAttribute('x')).toEqual('0'); + expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.2em'); + }); + +}); diff --git a/src/app/cartography/shared/widgets/drawings/text-drawing.ts b/src/app/cartography/shared/widgets/drawings/text-drawing.ts new file mode 100644 index 00000000..b7078abb --- /dev/null +++ b/src/app/cartography/shared/widgets/drawings/text-drawing.ts @@ -0,0 +1,71 @@ +import { SVGSelection } from "../../models/types"; +import { TextElement } from "../../models/drawings/text-element"; +import { Drawing } from "../../models/drawing"; +import { SvgToDrawingConverter } from "../../../map/helpers/svg-to-drawing-converter"; +import { DrawingElement } from "../../models/drawings/drawing-element"; + + +export class TextDrawingWidget { + public draw(view: SVGSelection) { + const drawing = view + .selectAll('text.text_element') + .data((d: Drawing) => { + const svgConverter = new SvgToDrawingConverter(); + const elements: DrawingElement[] = []; + try { + const element = svgConverter.convert(d.svg); + elements.push(element); + } catch (error) { + console.log(`Cannot convert due to Error: '${error}'`); + } + return elements.filter((e: DrawingElement) => e instanceof TextElement); + }); + + const drawing_enter = drawing + .enter() + .append('text') + .attr('class', 'text_element noselect'); + + const merge = drawing.merge(drawing_enter); + + merge + .attr('style', (text: TextElement) => { + const styles: string[] = []; + if (text.font_family) { + styles.push(`font-family: "${text.font_family}"`); + } + if (text.font_size) { + styles.push(`font-size: ${text.font_size}pt`); + } + if (text.font_weight) { + styles.push(`font-weight: ${text.font_weight}`); + } + return styles.join("; "); + }); + + const lines = merge.selectAll('tspan') + .data((text: TextElement) => { + return text.text.split(/\r?\n/); + }); + + const lines_enter = lines + .enter() + .append('tspan'); + + const lines_merge = lines.merge(lines_enter); + + lines_merge + .text((line) => line) + .attr('x', 0) + .attr("dy", (line, i) => i === 0 ? '0em' : '1.2em'); + + lines + .exit() + .remove(); + + drawing + .exit() + .remove(); + + } +} diff --git a/src/app/cartography/shared/widgets/layers.ts b/src/app/cartography/shared/widgets/layers.ts index 92bd133c..ffaa7158 100644 --- a/src/app/cartography/shared/widgets/layers.ts +++ b/src/app/cartography/shared/widgets/layers.ts @@ -18,7 +18,7 @@ export class LayersWidget implements Widget { const layers_enter = layers_selection .enter() .append('g') - .attr('class', 'layer') + .attr('class', 'layer'); // add container for links layers_enter