Skip to content

Commit

Permalink
Allow specifying labels to turn into roofs
Browse files Browse the repository at this point in the history
  • Loading branch information
lynn committed May 15, 2024
1 parent 55f2b94 commit 24958cb
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 90 deletions.
2 changes: 1 addition & 1 deletion src/bot/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class KunaBot {
const canvases = await Promise.all(
trees.map(tree =>
drawTreeToCanvas({

Check failure on line 73 in src/bot/bot.ts

View workflow job for this annotation

GitHub Actions / Check types

Argument of type '{ themeName: "dark"; tall: false; tree: Tree; renderer: (denotation: CompactExpr, theme: Theme) => RenderedDenotation<CanvasRenderingContext2D>; showMovement: false; }' is not assignable to parameter of type '{ themeName: ThemeName; tall: boolean; tree: Tree | DTree; renderer: (denotation: CompactExpr, theme: Theme) => RenderedDenotation<CanvasRenderingContext2D>; showMovement: boolean; truncateLabels: string[]; }'.
theme: 'dark',
themeName: 'dark',
tall: false,
tree,
renderer: denotationRenderText,
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ yargs
}
const theme = argv.light ? 'light' : 'dark';
const canvas = await drawTreeToCanvas({

Check failure on line 146 in src/cli.ts

View workflow job for this annotation

GitHub Actions / Check types

Argument of type '{ themeName: "light" | "dark"; tall: boolean; tree: Tree; renderer: (denotation: CompactExpr, theme: Theme) => RenderedDenotation<CanvasRenderingContext2D>; showMovement: boolean; }' is not assignable to parameter of type '{ themeName: ThemeName; tall: boolean; tree: Tree | DTree; renderer: (denotation: CompactExpr, theme: Theme) => RenderedDenotation<CanvasRenderingContext2D>; showMovement: boolean; truncateLabels: string[]; }'.
theme,
themeName: theme,
tall: argv.semantics,
tree: trees[0],
renderer: denotationRenderText,
Expand Down
64 changes: 43 additions & 21 deletions src/tree/draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ interface Location {
width: number;
}

interface TreeDrawerOptions {
theme: Theme;
layerHeight: number;
showMovement: boolean;
truncateLabels: string[];
}

class TreeDrawer {
private margin = 40;
private font = '27px Noto Sans Math, Noto Sans';
Expand All @@ -30,17 +37,16 @@ class TreeDrawer {
private locations: Map<MovementID, Location> = new Map();
private arrows: Array<[MovementID, MovementID]> = [];
private promises: Array<Promise<void>> = [];
get theme(): Theme {
return this.options.theme;
}

constructor(
private theme: Theme,
private layerHeight: number,
private showMovement: boolean,
) {
constructor(private options: TreeDrawerOptions) {
const width = 8400;
const height = 4400;
this.canvas = createCanvas(width, height);
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = theme.backgroundColor;
this.ctx.fillStyle = options.theme.backgroundColor;
this.ctx.fillRect(0, 0, width, height);
this.ctx.font = this.font;
this.ctx.textAlign = 'center';
Expand All @@ -55,7 +61,7 @@ class TreeDrawer {
}

private drawLine(x1: number, y1: number, x2: number, y2: number): void {
this.ctx.strokeStyle = this.theme.textColor;
this.ctx.strokeStyle = this.options.theme.textColor;
this.ctx.lineWidth = 1;
this.ctx.beginPath();
this.ctx.moveTo(x1, y1);
Expand Down Expand Up @@ -114,21 +120,29 @@ class TreeDrawer {
}

const [wordColor, word] =
this.showMovement && tree.movement && tree.movement.text
this.options.showMovement && tree.movement && tree.movement.text
? [this.theme.movedWordColor, tree.movement.text]
: [
this.showMovement && tree.movement?.movedTo
this.options.showMovement && tree.movement?.movedTo
? this.theme.traceColor
: this.theme.wordColor,
tree.word,
];

if (word !== undefined) {
const dy = 35 + (tree.denotation?.height(this.ctx) ?? 0);
this.drawLine(x, y + dy, x, y + this.layerHeight - 15);
this.drawText(word, x, y + this.layerHeight, wordColor);
const y1 = y + this.options.layerHeight - 15;
if (tree.roof) {
this.drawLine(x, y + dy, x - tree.width / 2, y1);
this.drawLine(x, y + dy, x + tree.width / 2, y1);
this.drawLine(x - tree.width / 2, y1, x + tree.width / 2, y1);
} else {
this.drawLine(x, y + dy, x, y1);
}

this.drawText(word, x, y + this.options.layerHeight, wordColor);
if (tree.word && tree.gloss) {
const yg = y + this.layerHeight + 30;
const yg = y + this.options.layerHeight + 30;
this.drawText(tree.gloss, x, yg, this.theme.textColor);
}
}
Expand All @@ -155,9 +169,9 @@ class TreeDrawer {
const n = tree.children.length;
for (let i = 0; i < n; i++) {
const dx = (i - (n - 1) / 2) * tree.distanceBetweenChildren;
this.drawTree(x + dx, y + this.layerHeight, tree.children[i]);
this.drawTree(x + dx, y + this.options.layerHeight, tree.children[i]);
const dy = 35 + (tree.denotation?.height(this.ctx) ?? 0);
this.drawLine(x, y + dy, x + dx, y + this.layerHeight - 15);
this.drawLine(x, y + dy, x + dx, y + this.options.layerHeight - 15);
}
}

Expand Down Expand Up @@ -220,28 +234,36 @@ class TreeDrawer {
theme: Theme,
) => RenderedDenotation<CanvasRenderingContext2D>,
): Promise<Canvas> {
const placer = new TreePlacer(this.ctx, this.theme, renderer);
const placer = new TreePlacer(this.ctx, renderer, {
theme: this.theme,
truncateLabels: this.options.truncateLabels,
});
const placed = placer.placeTree(tree);
this.drawTree(this.rootX, this.rootY, placed);
await Promise.all(this.promises);
if (this.showMovement) this.drawArrows();
if (this.options.showMovement) this.drawArrows();
this.fitCanvasToContents();
return this.canvas;
}
}

export function drawTreeToCanvas(options: {
theme: ThemeName;
themeName: ThemeName;
tall: boolean;
tree: Tree | DTree;
renderer: (
denotation: CompactExpr,
theme: Theme,
) => RenderedDenotation<CanvasRenderingContext2D>;
showMovement: boolean;
truncateLabels: string[];
}): Promise<Canvas> {
const { theme, tall, tree, renderer, showMovement } = options;
const layerHeight = tall ? 150 : 100;
const drawer = new TreeDrawer(themes[theme], layerHeight, showMovement);
return drawer.drawToCanvas(tree, renderer);
const layerHeight = options.tall ? 150 : 100;
const drawer = new TreeDrawer({
theme: themes[options.themeName],
layerHeight,
showMovement: options.showMovement,
truncateLabels: options.truncateLabels,
});
return drawer.drawToCanvas(options.tree, options.renderer);
}
6 changes: 6 additions & 0 deletions src/tree/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export function leafText(tree: Tree): string {
if (!('word' in tree)) {
throw new Impossible('Unexpected non-leaf ' + tree.label);
}
if (tree.movement && tree.movement.text) {
return tree.movement.text;
}
if (tree.movement && tree.movement.movedTo) {
return '';
}
if (tree.word.covert) return '';
return tree.word.text;
}
Expand Down
2 changes: 1 addition & 1 deletion src/tree/movement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export function makeMovement(): Movement {
export function moveUp(source: Leaf, target: Leaf) {
source.movement ??= makeMovement();
target.movement ??= makeMovement();
source.movement.movedTo = target.movement.id;
const sourceText = source.movement.text ?? leafText(source);
source.movement.movedTo = target.movement.id;
const text = (leafText(target) + ' ' + sourceText).trim();
target.movement.text = repairTones(text);
source.movement.text = undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/tree/place.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function summarize<C extends DrawContext>(tree: PlacedTree<C>): any {

test('it places trees', () => {
const measureText = (text: string) => ({ width: text.length * 20 });
const placer = new TreePlacer({ measureText }, themes.light, undefined!);
const placer = new TreePlacer({ measureText }, undefined!);
const tree = parse('gaı jí máq rú hao jí')[0];
expect(summarize(placer.placeTree(tree))).toMatchInlineSnapshot(`
{
Expand Down
67 changes: 54 additions & 13 deletions src/tree/place.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CanvasRenderingContext2D } from 'canvas';
import { DTree, Expr } from '../semantics/model';
import { toPlainText, toLatex, typeToPlainText } from '../semantics/render';
import { Branch, Leaf, Rose, Tree } from '../tree';
import { Branch, Leaf, Rose, Tree, treeText } from '../tree';
import { CompactExpr, compact } from '../semantics/compact';

import { mathjax } from 'mathjax-full/js/mathjax';
Expand All @@ -10,7 +10,7 @@ import { SVG } from 'mathjax-full/js/output/svg';
import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages';
import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor';
import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html';
import { Theme } from './theme';
import { Theme, themes } from './theme';
import { Movement } from './movement';

const adaptor = liteAdaptor();
Expand Down Expand Up @@ -57,6 +57,7 @@ interface PlacedLeafBase<C extends DrawContext> {
denotation?: RenderedDenotation<C>;
movement?: Movement;
coindex?: string;
roof?: boolean;
}

interface HasWord {
Expand Down Expand Up @@ -184,16 +185,31 @@ function layerExtents<C extends DrawContext>(
return extents;
}

interface TreePlacerOptions {
theme: Theme;
horizontalMargin: number;
truncateLabels: string[];
}

const defaultOptions: TreePlacerOptions = {
theme: themes.light,
horizontalMargin: 30,
truncateLabels: [],
};

export class TreePlacer<C extends DrawContext> {
private options: TreePlacerOptions;

constructor(
private ctx: C,
private theme: Theme,
private denotationRenderer: (
denotation: Expr,
theme: Theme,
) => RenderedDenotation<C>,
private horizontalMargin: number = 30,
) {}
options: Partial<TreePlacerOptions> = {},
) {
this.options = { ...defaultOptions, ...options };
}

private placeLeaf(
leaf: Leaf | (Leaf & { denotation: Expr | null }),
Expand All @@ -203,7 +219,7 @@ export class TreePlacer<C extends DrawContext> {
const word = leaf.word.covert ? leaf.word.value : leaf.word.text;
const denotation =
'denotation' in leaf && leaf.denotation !== null
? this.denotationRenderer(leaf.denotation, this.theme)
? this.denotationRenderer(leaf.denotation, this.options.theme)
: undefined;
const width = Math.max(
this.ctx.measureText(label).width,
Expand All @@ -222,6 +238,29 @@ export class TreePlacer<C extends DrawContext> {
};
}

private placeRoof(tree: Tree | DTree): PlacedLeaf<C> {
const label = getLabel(tree);
const denotation =
'denotation' in tree && tree.denotation !== null
? this.denotationRenderer(tree.denotation, this.options.theme)
: undefined;
const text = treeText(tree);

const width = Math.max(
this.ctx.measureText(label).width,
this.ctx.measureText(text ?? '').width,
denotation ? denotation.width(this.ctx) : 0,
);
return {
width,
label,
word: text,
gloss: undefined,
denotation,
roof: true,
};
}

private makePlacedBranch(
label: string,
denotation: RenderedDenotation<C> | undefined,
Expand All @@ -239,7 +278,7 @@ export class TreePlacer<C extends DrawContext> {
for (let j = 0; j < Math.min(r.length, l.length); j++) {
distanceBetweenChildren = Math.max(
distanceBetweenChildren,
l[j].right - r[j].left + this.horizontalMargin,
l[j].right - r[j].left + this.options.horizontalMargin,
);
}
}
Expand All @@ -257,7 +296,7 @@ export class TreePlacer<C extends DrawContext> {
): PlacedBranch<C> {
const denotation =
'denotation' in branch && branch.denotation !== null
? this.denotationRenderer(branch.denotation, this.theme)
? this.denotationRenderer(branch.denotation, this.options.theme)
: undefined;
const children = [
this.placeTree(branch.left),
Expand All @@ -272,10 +311,12 @@ export class TreePlacer<C extends DrawContext> {
}

public placeTree(tree: Tree | DTree): PlacedTree<C> {
return 'word' in tree
? this.placeLeaf(tree)
: 'children' in tree
? this.placeRose(tree)
: this.placeBranch(tree);
if ('word' in tree) {
return this.placeLeaf(tree);
} else if (this.options.truncateLabels.includes(tree.label)) {
return this.placeRoof(tree);
} else {
return 'children' in tree ? this.placeRose(tree) : this.placeBranch(tree);
}
}
}
4 changes: 2 additions & 2 deletions src/tree/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export const themes: Record<ThemeName, Theme> = {
denotationColor: '#FF4466',
wordColor: '#99EEFF',
movedWordColor: '#FF99EE',
traceColor: '#9999FF',
traceColor: '#DCDDDE',
},
light: {
backgroundColor: '#FFFFFF',
textColor: '#000000',
denotationColor: '#FF0000',
wordColor: '#3399FF',
movedWordColor: '#FF3399',
traceColor: '#AAAAFF',
traceColor: '#000000',
},
};
6 changes: 4 additions & 2 deletions src/web/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,10 @@ h1 {
margin-top: 8px;
}

.toggles label select {
margin-inline-start: 8px;
.toggles label {
display: inline-block;
margin-top: 4px;
margin-inline-end: 8px;
}

.parses {
Expand Down
Loading

0 comments on commit 24958cb

Please sign in to comment.