diff --git a/README.md b/README.md index 00112db..cbd8859 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ npm run lint # TODO / Roadmap * Render service should use the tool-options-text debounce on first font load to ensure font is present on document load +* Render service should measure total text bounding box upfront and scale resulting bitmap (can be done after render) * Try very wide Dropbox image on MB Air with space + pan in bottom right area. Won't work. * Restoring positions of documents with positioned rotated objects is not accurate * Issue with drawing mask on mirrored content diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index fe2d48b..ae3d486 100644 --- a/src/components/modal/modal.vue +++ b/src/components/modal/modal.vue @@ -66,7 +66,7 @@ export default { } .actions { - position: fixed; + position: absolute; bottom: 0; left: 0; width: 100%; @@ -79,6 +79,10 @@ export default { flex: 1; margin: $spacing-small; } + + @include mobile() { + position: fixed; + } } @include large() { diff --git a/src/components/options-panel/tool-options-text/messages.json b/src/components/options-panel/tool-options-text/messages.json index e33e7ad..23ef5a6 100644 --- a/src/components/options-panel/tool-options-text/messages.json +++ b/src/components/options-panel/tool-options-text/messages.json @@ -3,6 +3,8 @@ "text": "Text", "font": "Font", "size": "Size", + "lineHeight": "Line height", + "spacing": "Letter spacing", "color": "Color" } } diff --git a/src/components/options-panel/tool-options-text/tool-options-text.vue b/src/components/options-panel/tool-options-text/tool-options-text.vue index 19d9591..a0862f5 100644 --- a/src/components/options-panel/tool-options-text/tool-options-text.vue +++ b/src/components/options-panel/tool-options-text/tool-options-text.vue @@ -48,6 +48,15 @@ :tooltip="'none'" /> +
+ + +
({ internalText: "", renderPending: false, + layerId: null, }), computed: { ...mapGetters([ @@ -108,7 +118,7 @@ export default { this.renderPending = false; this.updateLayer({ index: this.activeLayerIndex, - opts: { text: { value: this.text, size: this.size, font: this.font, color: this.color } }, + opts: this.formatOpts({ value }) }); }, 75 ); } @@ -117,10 +127,21 @@ export default { get() { return this.activeLayer.text?.size; }, - set( value ) { + set( size ) { this.updateLayer({ index: this.activeLayerIndex, - opts: { text: { value: this.text, size: value, font: this.font, color: this.color } }, + opts: this.formatOpts({ size }) + }); + } + }, + lineHeight: { + get() { + return this.activeLayer.text?.lineHeight; + }, + set( lineHeight ) { + this.updateLayer({ + index: this.activeLayerIndex, + opts: this.formatOpts({ lineHeight }), }); } }, @@ -128,10 +149,10 @@ export default { get() { return this.activeLayer.text?.color; }, - set( value ) { + set( color ) { this.updateLayer({ index: this.activeLayerIndex, - opts: { text: { value: this.text, size: this.size, font: this.font, color: value } }, + opts: this.formatOpts({ color }) }); } }, @@ -139,11 +160,11 @@ export default { get() { return this.activeLayer.text?.font; }, - async set( value ) { - const fromCache = await loadGoogleFont( value ); + async set( font ) { + const fromCache = await loadGoogleFont( font ); this.updateLayer({ index: this.activeLayerIndex, - opts: { text: { value: this.text, size: this.size, font: value, color: this.color } }, + opts: this.formatOpts({ font }) }); // on first load, font is not immediately available for rendering if ( !fromCache ) { @@ -158,7 +179,10 @@ export default { activeLayer: { immediate: true, handler( layer ) { - this.internalText = layer.text?.value; + if ( this.layerId !== layer.id ) { + this.internalText = layer.text?.value; + this.layerId = layer.id; + } } }, }, @@ -178,6 +202,18 @@ export default { handleBlur() { KeyboardService.setSuspended( false ); }, + formatOpts( textOpts = {} ) { + return { + text: { + value: this.text, + size: this.size, + lineHeight: this.lineHeight, + font: this.font, + color: this.color, + ...textOpts, + } + }; + }, }, }; diff --git a/src/factories/text-factory.js b/src/factories/text-factory.js index 55674cf..eb440b5 100644 --- a/src/factories/text-factory.js +++ b/src/factories/text-factory.js @@ -23,9 +23,18 @@ import { googleFonts } from "@/services/font-service"; const TextFactory = { - create({ font = googleFonts[ 0 ], value = "", size = 16, color = "red" } = {}) { + create({ + font = googleFonts[ 0 ], + value = "", + size = 16, + lineHeight = 0, + spacing = 0, + color = "red" + } = {}) { return { font, + lineHeight, + spacing, value, size, color, @@ -41,6 +50,8 @@ const TextFactory = { f: text.font, v: text.value, s: text.size, + l: text.lineHeight, + p: text.spacing, c: text.color, }; }, @@ -54,6 +65,8 @@ const TextFactory = { font: text.f, value: text.v, size: text.s, + lineHeight: text.l, + spacing: text.p, color: text.c, }); } diff --git a/src/services/render-service.js b/src/services/render-service.js index 14b334c..e61d629 100644 --- a/src/services/render-service.js +++ b/src/services/render-service.js @@ -157,12 +157,12 @@ const renderText = async layer => { sourceCtx.fillStyle = text.color; const lines = text.value.split( "\n" ); - const lineHeight = text.size; + const lineHeight = text.lineHeight ? text.lineHeight : text.size; let y = 0; lines.forEach(( line, index ) => { - const textMetrics = sourceCtx.measureText( line ); - y = lineHeight + index * lineHeight; - y += Math.abs( textMetrics[ "actualBoundingBoxDescent" ]); + //const textMetrics = sourceCtx.measureText( line ); + y = lineHeight + ( index * lineHeight ); + //y += Math.abs( textMetrics[ "actualBoundingBoxDescent" ]); sourceCtx.fillText( line, 0, y ); }); }; diff --git a/src/styles/component.scss b/src/styles/component.scss index 1932058..5d54f79 100644 --- a/src/styles/component.scss +++ b/src/styles/component.scss @@ -20,7 +20,7 @@ @include large() { padding: $spacing-medium; - height: calc(100% - $heading-height); + height: calc(100% - #{$heading-height}); } @include mobile() { padding: $spacing-xsmall $spacing-small; diff --git a/tests/unit/factories/text-factory.spec.js b/tests/unit/factories/text-factory.spec.js index 89f80c7..dec7ea7 100644 --- a/tests/unit/factories/text-factory.spec.js +++ b/tests/unit/factories/text-factory.spec.js @@ -7,6 +7,8 @@ describe( "Text factory", () => { const text = TextFactory.create(); expect( text ).toEqual({ size: expect.any( Number ), + lineHeight: expect.any( Number ), + spacing: 0, value: "", font: googleFonts[ 0 ], color: "red", @@ -16,12 +18,16 @@ describe( "Text factory", () => { it( "should be able to create a Text structure from given arguments", () => { const text = TextFactory.create({ size: 10, + lineHeight: 30, + spacing: 50, font: "Helvetica", value: "Foo bar baz", color: "#FF00AE" }); expect( text ).toEqual({ size: 10, + lineHeight: 30, + spacing: 50, font: "Helvetica", value: "Foo bar baz", color: "#FF00AE" @@ -33,6 +39,8 @@ describe( "Text factory", () => { it( "should do so without data loss", async () => { const text = TextFactory.create({ size: 10, + lineHeight: 40, + spacing: 10, font: "Helvetica", value: "Foo bar baz", color: "#FFF"