Skip to content

Commit c1cf118

Browse files
authored
Merge pull request #22 from dommilosz/main
add text rotation and flipping
2 parents 018d29d + a0eb8db commit c1cf118

File tree

2 files changed

+98
-12
lines changed

2 files changed

+98
-12
lines changed

components/StuffPreview.tsx

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,42 +22,92 @@ export default function StuffPreview(props: StuffPainterProps) {
2222
const { canvas, width, ctx, img } = mkcanvas(props.width);
2323
const stuff = props.stuff;
2424
let font;
25-
// let imagedata: ImageData;
2625
let strings: string[];
2726
let measured: TextMetrics[];
2827
let line_height;
2928
let msg: ImageWorkerMessage;
3029
let imagedata: ImageData;
31-
let anchor_x: number, anchor_y: number;
3230
(async function() {
3331
switch (stuff.type) {
3432
case 'text':
3533
ctx.fillStyle = 'black';
3634
ctx.strokeStyle = 'black';
37-
ctx.textAlign = stuff.textAlign === 'justify' ? 'start' : stuff.textAlign!;
3835
font = `${stuff.textFontWeight} ${stuff.textFontSize}px "${stuff.textFontFamily}"`;
3936
// ctx.font is set multiple times intensionally
4037
ctx.font = font;
38+
const is_rotated_sideways = stuff.rotate === 90 || stuff.rotate === 270;
4139
strings = splitText({
4240
ctx: ctx,
4341
text: stuff.textContent!,
4442
justify: stuff.textAlign === 'justify',
45-
width: width
43+
width: is_rotated_sideways ? 10000 : width
4644
});
4745
ctx.font = font;
4846
measured = strings.map(s => ctx.measureText(s));
4947
line_height = stuff.textLineSpacing! + Math.max(...measured.map(m => m.actualBoundingBoxAscent), stuff.textFontSize!);
50-
canvas.height = line_height * strings.length + stuff.textLineSpacing!;
48+
49+
const text_intrinsic_height = line_height * strings.length + stuff.textLineSpacing!;
50+
51+
const needs_flip = is_rotated_sideways
52+
? (stuff.rotate === 90) !== !!stuff.flipV
53+
: (stuff.rotate === 180) !== !!stuff.flipH;
54+
55+
let effectiveTextAlign = stuff.textAlign;
56+
if (needs_flip) {
57+
if (stuff.textAlign === 'start') effectiveTextAlign = 'end';
58+
else if (stuff.textAlign === 'end') effectiveTextAlign = 'start';
59+
}
60+
const shiftMultiplier = needs_flip ? -1 : 1;
61+
62+
let y_offset = 0;
63+
64+
if (is_rotated_sideways) {
65+
const text_intrinsic_width = Math.max(...measured.map(m => m.width));
66+
canvas.width = text_intrinsic_width > 0 ? text_intrinsic_width : 1;
67+
canvas.height = width; // Use preview width for the height, which will be rotated to become the new width.
68+
69+
let h_align_offset = 0;
70+
switch (effectiveTextAlign) {
71+
case 'center':
72+
h_align_offset = (canvas.height - text_intrinsic_height) / 2;
73+
break;
74+
case 'end':
75+
h_align_offset = canvas.height - text_intrinsic_height;
76+
break;
77+
}
78+
const h_shift_offset = shiftMultiplier * stuff.textShift! * canvas.height;
79+
y_offset = h_align_offset + h_shift_offset;
80+
} else {
81+
canvas.height = text_intrinsic_height > 0 ? text_intrinsic_height : 1;
82+
}
83+
5184
ctx.font = font;
85+
ctx.fillStyle = 'black';
86+
ctx.strokeStyle = 'black';
87+
ctx.textAlign = 'start';
5288
for (let i = 0; i < strings.length; ++i) {
5389
const s = strings[i];
54-
anchor_x = ({
55-
'start': 0,
56-
'center': (width / 2 - measured[i].width / 2) | 0,
57-
'end': (width - measured[i].width) | 0,
58-
'justify': 0
59-
})[stuff.textAlign!] + (stuff.textShift! * width);
60-
anchor_y = line_height * (i + 1);
90+
let anchor_x;
91+
92+
if (is_rotated_sideways) {
93+
const text_block_width = canvas.width;
94+
anchor_x = ({
95+
'start': 0,
96+
'center': (text_block_width - measured[i].width) / 2,
97+
'end': text_block_width - measured[i].width,
98+
'justify': 0
99+
})[stuff.textAlign!];
100+
} else {
101+
anchor_x = ({
102+
'start': 0,
103+
'center': (width - measured[i].width) / 2,
104+
'end': width - measured[i].width,
105+
'justify': 0
106+
})[effectiveTextAlign] + (shiftMultiplier * stuff.textShift! * width);
107+
}
108+
109+
const anchor_y = y_offset + line_height * (i + 1);
110+
61111
if (stuff.textStroked) {
62112
ctx.strokeText(s, anchor_x, anchor_y);
63113
} else {

components/StuffWidget.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,42 @@ export default function StuffWidget(props: StuffProps) {
213213
<span class="option__title">{_('stroked')}</span>
214214
<input type="checkbox" onChange={mkmodifyboolean('textStroked')} checked={stuff.textStroked} />
215215
</div>
216+
<div className="stuff__option">
217+
<span className="option__title">{_('rotate')}</span>
218+
<button className="option__item" value="0"
219+
onClick={mkmodifynumber('rotate')} aria-label={_('rotate^none')}
220+
data-selected={stuff.rotate === 0}>
221+
<span className="stuff__label">{_('rotate^none')}</span>
222+
</button>
223+
<button className="option__item" value="90"
224+
onClick={mkmodifynumber('rotate')} aria-label={_('90-degree')}
225+
data-selected={stuff.rotate === 90}>
226+
<Icons.IconRotateClockwise2/>
227+
</button>
228+
<button className="option__item" value="180"
229+
onClick={mkmodifynumber('rotate')} aria-label={_('180-degree')}
230+
data-selected={stuff.rotate === 180}>
231+
<Icons.IconRotateClockwise/>
232+
</button>
233+
<button className="option__item" value="270"
234+
onClick={mkmodifynumber('rotate')} aria-label={_('270-degree')}
235+
data-selected={stuff.rotate === 270}>
236+
<Icons.IconRotate2/>
237+
</button>
238+
</div>
239+
<div className="stuff__option">
240+
<span className="option__title">{_('flip')}</span>
241+
<label className="option__item">
242+
<input type="checkbox" onChange={mkmodifyboolean('flipH')}
243+
checked={stuff.flipH} aria-label={_('flip^horizontal')}/>
244+
<Icons.IconFlipVertical/>
245+
</label>
246+
<label className="option__item">
247+
<input type="checkbox" onChange={mkmodifyboolean('flipV')}
248+
checked={stuff.flipV} aria-label={_('flip^vertical')}/>
249+
<Icons.IconFlipHorizontal/>
250+
</label>
251+
</div>
216252
</>;
217253
break;
218254
case 'pic':

0 commit comments

Comments
 (0)