From e2d7ef23de7afd215e94a2ccaaf9ca7afe4a3257 Mon Sep 17 00:00:00 2001 From: Stefan Cameron Date: Wed, 13 Mar 2024 14:52:24 -0500 Subject: [PATCH] Add overflow option to drawText() API Originally requested here: https://github.com/geongeorge/Canvas-Txt/issues/83 And fix proposed here: https://github.com/geongeorge/Canvas-Txt/pull/94 By default, text overflows. Set to `false` to clip the text to the specified box. --- CHANGELOG.md | 6 ++++++ CONTRIBUTING.md | 13 ++++++------- src/docs/AppCanvas.vue | 16 +++++++++++----- src/lib/index.ts | 36 ++++++++++++++++++++++++------------ src/lib/model.ts | 14 +++++++++++--- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 365dfbc8..a1c47fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## UNRELEASED + +### Added + +- New `overflow?: boolean` config parameter for `drawText()` API: True (default) to allow overflow; false to clip text to box. + ## v1.0.0 - First official release 🎉 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7bc941d..39e40487 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,17 +41,16 @@ Run `npm test`, the Docs app, and the Node demo and manually verify your changes ## Pull requests -Always include a new bullet point in the [CHANGELOG](./CHANGELOG.md) under the __UNRELEASED__ heading at the very top. +Always include a new bullet point (CD, Change Description) in the [CHANGELOG](./CHANGELOG.md) under the __UNRELEASED__ heading at the very top, linking to the issue being addressed when applicable, typically ending your CD with `([#123](https://github.com/stefcameron/text-to-canvas/issues/123))` -Include your change description in one of the following subsections under "UNRELEASED": +Include your CD in one of the following subsections under "UNRELEASED": -> If an "UNRELEASED" heading doesn't exist, please add one! +> If an `## UNRELEASED` heading doesn't exist, please add one! -- "Breaking": If your change alters an existing API/type in a way that is not backward-compatible, or makes use of a Web API that doesn't yet have wide browser support. +- `### Breaking`: If your change alters an existing API/type in a way that is not backward-compatible, or makes use of a Web API that doesn't yet have wide browser support. - __AVOID__ this type of change as best as possible. -- "Added": If your change adds a new feature without breaking anything pre-existing. -- "Changed": If your change alters existing behavior without breaking anything pre-existing, including bug fixes. +- `### Added`: If your change adds a new feature without breaking anything pre-existing. +- `### Changed`: If your change alters existing behavior without breaking anything pre-existing, including bug fixes. - If you're fixing a bug, try to start your change description with, "Fixed ..." -- Always link to the issue being addressed when applicable, typically ending your change description with `([#123](https://github.com/stefcameron/text-to-canvas/issues/123))` And please fill-out the pull request template when prompted! diff --git a/src/docs/AppCanvas.vue b/src/docs/AppCanvas.vue index fcea6874..ee4cccc9 100644 --- a/src/docs/AppCanvas.vue +++ b/src/docs/AppCanvas.vue @@ -19,6 +19,7 @@ const initialConfig = { vAlign: 'middle', justify: false, debug: false, + overflow: true, }; const config = reactive(cloneDeep(initialConfig)); @@ -52,6 +53,7 @@ function renderText() { vAlign: config.vAlign, justify: config.justify, debug: config.debug, + overflow: config.overflow, // currently not configurable in demo UI fontFamily: 'Times New Roman, serif', fontSize: 24, @@ -163,9 +165,10 @@ onMounted(() => { size="small" /> +
- - + + @@ -174,7 +177,7 @@ onMounted(() => { - + @@ -183,12 +186,15 @@ onMounted(() => { + + +
- + - + diff --git a/src/lib/index.ts b/src/lib/index.ts index 18dee2ea..34601100 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -22,6 +22,13 @@ const drawText = ( fontWeight: config.fontWeight, }); + const { + width: boxWidth, + height: boxHeight, + x: boxX = 0, + y: boxY = 0, + } = config; + const { lines: richLines, height: totalHeight, @@ -33,8 +40,8 @@ const drawText = ( inferWhitespace: Array.isArray(text) ? config.inferWhitespace === undefined || config.inferWhitespace : undefined, // ignore since `text` is a string; we assume it already has all the whitespace it needs - x: config.x || 0, - y: config.y || 0, + x: boxX, + y: boxY, width: config.width, height: config.height, align: config.align, @@ -49,6 +56,12 @@ const drawText = ( ctx.font = getTextStyle(baseFormat); ctx.fillStyle = baseFormat.fontColor || DEFAULT_FONT_COLOR; + if (config.overflow === false) { + ctx.beginPath(); + ctx.rect(boxX, boxY, boxWidth, boxHeight); + ctx.clip(); // part of saved context state + } + richLines.forEach((line) => { line.forEach((pw) => { if (!pw.isWhitespace) { @@ -71,24 +84,23 @@ const drawText = ( }); if (config.debug) { - const { width, height, x = 0, y = 0 } = config; - const xEnd = x + width; - const yEnd = y + height; + const xEnd = boxX + boxWidth; + const yEnd = boxY + boxHeight; let textAnchor: number; if (config.align === 'right') { textAnchor = xEnd; } else if (config.align === 'left') { - textAnchor = x; + textAnchor = boxX; } else { - textAnchor = x + width / 2; + textAnchor = boxX + boxWidth / 2; } - let debugY = y; + let debugY = boxY; if (config.vAlign === 'bottom') { debugY = yEnd; } else if (config.vAlign === 'middle') { - debugY = y + height / 2; + debugY = boxY + boxHeight / 2; } const debugColor = '#0C8CE9'; @@ -96,7 +108,7 @@ const drawText = ( // Text box ctx.lineWidth = 1; ctx.strokeStyle = debugColor; - ctx.strokeRect(x, y, width, height); + ctx.strokeRect(boxX, boxY, boxWidth, boxHeight); ctx.lineWidth = 1; @@ -104,7 +116,7 @@ const drawText = ( // Horizontal Center ctx.strokeStyle = debugColor; ctx.beginPath(); - ctx.moveTo(textAnchor, y); + ctx.moveTo(textAnchor, boxY); ctx.lineTo(textAnchor, yEnd); ctx.stroke(); } @@ -113,7 +125,7 @@ const drawText = ( // Vertical Center ctx.strokeStyle = debugColor; ctx.beginPath(); - ctx.moveTo(x, debugY); + ctx.moveTo(boxX, debugY); ctx.lineTo(xEnd, debugY); ctx.stroke(); } diff --git a/src/lib/model.ts b/src/lib/model.ts index 451f5b07..4246c06b 100644 --- a/src/lib/model.ts +++ b/src/lib/model.ts @@ -105,9 +105,6 @@ export interface DrawTextConfig extends TextFormat { justify?: boolean; /** - * __NOTE:__ Applies only if `text`, given to `drawText()`, is a `Word[]`. Ignored if it's - * a `string`. - * * True indicates `text` is a `Word` array that contains _mostly_ visible words and * whitespace should be inferred _unless a word is whitespace (e.g. a new line or tab)_, based * on the context's general text formatting style (i.e. every space will use the font style set @@ -116,11 +113,22 @@ export interface DrawTextConfig extends TextFormat { * as Words with `text="\n"`). * * False indicates that `words` contains its own whitespace and it shouldn't be inferred. + * + * ❗️ Applies only if `text`, given to `drawText()`, is a `Word[]`. Ignored if it's + * a `string`. */ inferWhitespace?: boolean; /** True if debug lines should be rendered behind the text. */ debug?: boolean; + + /** + * True (default) if the text should overflow the box's boundaries when it's either too + * tall or too wide to fit. + * + * False if the text should be clipped to the box's boundaries. + */ + overflow?: boolean; } export interface BaseSplitProps {