Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[background image] Allow a PNG or JPEG file to be dropped onto a glyph in edit mode #1808

Merged
merged 8 commits into from
Nov 18, 2024
9 changes: 6 additions & 3 deletions src/fontra/client/core/glyph-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1134,9 +1134,12 @@ function ensureGlyphCompatibility(layerGlyphs, glyphDependencies) {
}

function stripNonInterpolatables(glyph) {
if (!glyph.components.length && !glyph.guidelines.length && !glyph.backgroundImage) {
return glyph;
}
// Hm, the following optimization oddly causes a false positive when undoing a bg img
// placement. TODO: figure out what's going on.
// if (!glyph.components.length && !glyph.guidelines.length && !glyph.backgroundImage) {
// console.log("have bg img?", !!glyph.backgroundImage);
// return glyph;
// }
return StaticGlyph.fromObject(
{
...glyph,
Expand Down
4 changes: 4 additions & 0 deletions src/fontra/views/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ body {
outline: none;
}

#edit-canvas.dropping-files {
background-color: #99556655;
}

.cleanable-overlay.overlay-layer-hidden {
display: none;
}
Expand Down
117 changes: 97 additions & 20 deletions src/fontra/views/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export class EditorController {
const canvas = document.querySelector("#edit-canvas");
canvas.focus();

canvas.ondragenter = (event) => this._onDragEnter(event);
canvas.ondragover = (event) => this._onDragOver(event);
canvas.ondragleave = (event) => this._onDragLeave(event);
canvas.ondrop = (event) => this._onDrop(event);

const canvasController = new CanvasController(canvas, (magnification) =>
this.canvasMagnificationChanged(magnification)
);
Expand Down Expand Up @@ -404,21 +409,21 @@ export class EditorController {
"action.add-component",
{ topic },
() => this.doAddComponent(),
() => this.canAddComponent()
() => this.canEditGlyph()
);

registerAction(
"action.add-anchor",
{ topic },
() => this.doAddAnchor(),
() => this.canAddAnchor()
() => this.canEditGlyph()
);

registerAction(
"action.add-guideline",
{ topic },
() => this.doAddGuideline(),
() => this.canAddGuideline()
() => this.canEditGlyph()
);

registerAction(
Expand Down Expand Up @@ -2105,10 +2110,12 @@ export class EditorController {
await this._pasteLayerGlyphs(pasteLayerGlyphs);
}

await this._writeBackgroundImageData(
backgroundImageData,
backgroundImageIdentifierMapping
);
if (this.fontController.backendInfo.features["background-image"]) {
await this._writeBackgroundImageData(
backgroundImageData,
backgroundImageIdentifierMapping
);
}
}

_makeBackgroundImageIdentifierMapping(backgroundImageData) {
Expand Down Expand Up @@ -2141,6 +2148,8 @@ export class EditorController {
const mappedIdentifier = identifierMapping[imageIdentifier] || imageIdentifier;
await this.fontController.putBackgroundImageData(mappedIdentifier, imageData);
}
// Writing the background image data does not cause a refresh
this.canvasController.requestUpdate();
}

async _unpackClipboard() {
Expand Down Expand Up @@ -2197,7 +2206,7 @@ export class EditorController {
}

async _pasteClipboardImage() {
if (!this.sceneSettings.selectedGlyph?.isEditing) {
if (!this.canPlaceBackgroundImage()) {
return;
}

Expand All @@ -2222,6 +2231,10 @@ export class EditorController {
return;
}

await this._placeBackgroundImage(dataURL);
}

async _placeBackgroundImage(dataURL) {
// Ensure background images are visible and not locked
this.visualizationLayersSettings.model["fontra.background-image"] = true;
this.sceneSettings.backgroundImagesAreLocked = false;
Expand All @@ -2239,12 +2252,14 @@ export class EditorController {
imageIdentifiers.push(imageIdentifier);
}
this.sceneController.selection = new Set(["backgroundImage/0"]);
return "paste background image"; // TODO: translate
return "place background image"; // TODO: translate
});

for (const imageIdentifier of imageIdentifiers) {
await this.fontController.putBackgroundImageData(imageIdentifier, dataURL);
}
// Writing the background image data does not cause a refresh
this.canvasController.requestUpdate();
}

async _pasteReplaceGlyph(varGlyph) {
Expand Down Expand Up @@ -2448,10 +2463,6 @@ export class EditorController {
});
}

canAddComponent() {
return this.sceneModel.getSelectedPositionedGlyph()?.glyph.canEdit;
}

async doAddComponent() {
const glyphName = await this.runGlyphSearchDialog(
translate("action.add-component"),
Expand Down Expand Up @@ -2485,10 +2496,6 @@ export class EditorController {
});
}

canAddAnchor() {
return this.sceneModel.getSelectedPositionedGlyph()?.glyph.canEdit;
}

async doAddAnchor() {
const point = this.sceneController.selectedGlyphPoint(this.contextMenuPosition);
const { anchor: tempAnchor } = await this.doAddEditAnchorDialog(undefined, point);
Expand Down Expand Up @@ -2733,9 +2740,6 @@ export class EditorController {
// TODO: We may want to make a more general code for adding and editing
// so we can handle both anchors and guidelines with the same code
// Guidelines
canAddGuideline() {
return this.sceneModel.getSelectedPositionedGlyph()?.glyph.canEdit;
}

async doAddGuideline(global = false) {
this.visualizationLayersSettings.model["fontra.guidelines"] = true;
Expand Down Expand Up @@ -3657,6 +3661,79 @@ export class EditorController {
);
location.reload();
}

canPlaceBackgroundImage() {
return (
this.fontController.backendInfo.features["background-image"] &&
this.canEditGlyph()
);
}

canEditGlyph() {
const positionedGlyph = this.sceneModel.getSelectedPositionedGlyph();
return !!(
positionedGlyph &&
!this.fontController.readOnly &&
!this.sceneModel.isSelectedGlyphLocked() &&
positionedGlyph.glyph.canEdit
);
}

// Drop files onto canvas

_onDragEnter(event) {
event.preventDefault();
if (!this.canPlaceBackgroundImage()) {
return;
}
this.canvasController.canvas.classList.add("dropping-files");
}

_onDragOver(event) {
event.preventDefault();
if (!this.canPlaceBackgroundImage()) {
return;
}
this.canvasController.canvas.classList.add("dropping-files");
}

_onDragLeave(event) {
event.preventDefault();
if (!this.canPlaceBackgroundImage()) {
return;
}
this.canvasController.canvas.classList.remove("dropping-files");
}

_onDrop(event) {
event.preventDefault();
if (!this.canPlaceBackgroundImage()) {
return;
}
this.canvasController.canvas.classList.remove("dropping-files");

const items = [];

for (const item of event.dataTransfer?.files || []) {
const suffix = item.name.split(".").at(-1);
if (suffix === "png" || suffix === "jpg" || suffix === "jpeg") {
items.push(item);
}
}

if (items.length != 1) {
/* no real need for await */ dialog(
"Can't drop files",
"Please drop a single .png, .jpg or .jpeg file",
[{ title: translate("dialog.okay"), resultValue: "ok", isDefaultButton: true }]
);
return;
}
const item = items[0];
const reader = new FileReader();
reader.onload = (event) => this._placeBackgroundImage(reader.result);
reader.readAsDataURL(item);
}
}

function clearSearchParams(searchParams) {
Expand Down