Skip to content

Commit

Permalink
Add overflow option to drawText() API
Browse files Browse the repository at this point in the history
Originally requested here: geongeorge/Canvas-Txt#83

And fix proposed here: geongeorge/Canvas-Txt#94

By default, text overflows. Set to `false` to clip the text to the
specified box.
  • Loading branch information
stefcameron committed Mar 13, 2024
1 parent a56c1c6 commit e2d7ef2
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 🎉
13 changes: 6 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
16 changes: 11 additions & 5 deletions src/docs/AppCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const initialConfig = {
vAlign: 'middle',
justify: false,
debug: false,
overflow: true,
};
const config = reactive(cloneDeep(initialConfig));
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -163,9 +165,10 @@ onMounted(() => {
size="small"
/>
</div>

<br />
<el-row>
<el-col :span="12">
<el-row :gutter="12">
<el-col :span="8">
<el-form-item label="Horizontal Align">
<el-select v-model="config.align" placeholder="Align">
<el-option label="Center" value="center" />
Expand All @@ -174,7 +177,7 @@ onMounted(() => {
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="Vertical Align">
<el-select v-model="config.vAlign" placeholder="vAlign">
<el-option label="Middle" value="middle" />
Expand All @@ -183,12 +186,15 @@ onMounted(() => {
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-checkbox v-model="config.justify" label="Justify" />
</el-col>
</el-row>
<br />

<el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-checkbox v-model="config.justify" label="Justify Text" />
<el-checkbox v-model="config.overflow" label="Overflow" />
</el-col>
<el-col :span="12">
<el-checkbox v-model="config.debug" label="Debug mode" />
Expand Down
36 changes: 24 additions & 12 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -71,40 +84,39 @@ 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';

// Text box
ctx.lineWidth = 1;
ctx.strokeStyle = debugColor;
ctx.strokeRect(x, y, width, height);
ctx.strokeRect(boxX, boxY, boxWidth, boxHeight);

ctx.lineWidth = 1;

if (!config.align || config.align === 'center') {
// Horizontal Center
ctx.strokeStyle = debugColor;
ctx.beginPath();
ctx.moveTo(textAnchor, y);
ctx.moveTo(textAnchor, boxY);
ctx.lineTo(textAnchor, yEnd);
ctx.stroke();
}
Expand All @@ -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();
}
Expand Down
14 changes: 11 additions & 3 deletions src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down

0 comments on commit e2d7ef2

Please sign in to comment.