Compare commits

..

5 Commits

Author SHA1 Message Date
zsviczian
7233d1e037 2.6.3-beta-1 2024-10-30 23:02:05 +01:00
zsviczian
5972f83369 Merge pull request #2083 from dmscode/master
Update zh-cn.ts to 8f14f97
2024-10-30 22:08:28 +01:00
dmscode
0edfd7622c Update zh-cn.ts to 8f14f97 2024-10-29 07:36:30 +08:00
zsviczian
8f14f97007 2.6.2 2024-10-28 22:12:25 +01:00
zsviczian
758585a4c2 2.6.1 2024-10-28 20:26:57 +01:00
13 changed files with 212 additions and 50 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.6.0",
"version": "2.6.3-beta-1",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.6.0",
"version": "2.6.2",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.6-7",
"@zsviczian/excalidraw": "0.17.6-9",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",

View File

@@ -1106,8 +1106,49 @@ export class EmbeddedFilesLoader {
}
const getSVGData = async (app: App, file: TFile, colorMap: ColorMap | null): Promise<DataURL> => {
const svg = replaceSVGColors(await app.vault.read(file), colorMap) as string;
return svgToBase64(svg) as DataURL;
const svgString = replaceSVGColors(await app.vault.read(file), colorMap) as string;
return svgToBase64(svgString) as DataURL;
/*
try {
const container = document.createElement('div');
container.innerHTML = svgString;
const svgElement = container.querySelector('svg');
if (!svgElement) {
throw new Error('Invalid SVG content'); // Ensure there's an SVG element
}
// Check for width and height attributes
const hasWidth = svgElement.hasAttribute('width');
const hasHeight = svgElement.hasAttribute('height');
// If width or height is missing, calculate based on viewBox
if (!hasWidth || !hasHeight) {
const viewBox = svgElement.getAttribute('viewBox');
if (viewBox) {
const [ , , viewBoxWidth, viewBoxHeight] = viewBox.split(/\s+/).map(Number);
// Set width and height based on viewBox if they are missing
if (!hasWidth) {
svgElement.setAttribute('width', `${viewBoxWidth}px`);
}
if (!hasHeight) {
svgElement.setAttribute('height', `${viewBoxHeight}px`);
}
}
}
// Get the updated SVG string from outerHTML
const updatedSVGString = svgElement.outerHTML;
// Convert the updated SVG string to a base64 Data URL
return svgToBase64(updatedSVGString) as DataURL;
} catch (error) {
errorlog({ where: "EmbeddedFileLoader.getSVGData", error });
return svgToBase64(svgString) as DataURL;
}*/
};
/*export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {

View File

@@ -2621,26 +2621,31 @@ export class ExcalidrawAutomate {
return null;
}
const size = await this.getOriginalImageSize(imgEl, true);
if (size) {
const originalArea = imgEl.width * imgEl.height;
const originalAspectRatio = size.width / size.height;
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
const centerX = imgEl.x + imgEl.width / 2;
const centerY = imgEl.y + imgEl.height / 2;
let originalArea, originalAspectRatio;
if(imgEl.crop) {
originalArea = imgEl.width * imgEl.height;
originalAspectRatio = imgEl.crop.width / imgEl.crop.height;
} else {
const size = await this.getOriginalImageSize(imgEl, true);
if (!size) { return false; }
originalArea = imgEl.width * imgEl.height;
originalAspectRatio = size.width / size.height;
}
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
const centerX = imgEl.x + imgEl.width / 2;
const centerY = imgEl.y + imgEl.height / 2;
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
if(!this.getElement(imgEl.id)) {
this.copyViewElementsToEAforEditing([imgEl]);
}
const eaEl = this.getElement(imgEl.id);
eaEl.width = newWidth;
eaEl.height = newHeight;
eaEl.x = centerX - newWidth / 2;
eaEl.y = centerY - newHeight / 2;
return true;
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
if(!this.getElement(imgEl.id)) {
this.copyViewElementsToEAforEditing([imgEl]);
}
const eaEl = this.getElement(imgEl.id);
eaEl.width = newWidth;
eaEl.height = newHeight;
eaEl.x = centerX - newWidth / 2;
eaEl.y = centerY - newHeight / 2;
return true;
}
return false;
}

View File

@@ -3985,6 +3985,11 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onPaste, "ExcalidrawView.onPaste", data, event);
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
const ea = this.getHookServer();
if(data?.elements) {
data.elements
.filter(el=>el.type==="text" && !el.hasOwnProperty("rawText"))
.forEach(el=>(el as Mutable<ExcalidrawTextElement>).rawText = (el as ExcalidrawTextElement).originalText);
};
if(data && ea.onPasteHook) {
const res = ea.onPasteHook({
ea,

View File

@@ -17,6 +17,19 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
`,
"2.6.2":`
## Fixed
- Image scaling issue with SVGs that miss the width and height property. [#8729](https://github.com/excalidraw/excalidraw/issues/8729)
`,
"2.6.1":`
## New
- Pen-mode single-finger panning enabled also for the "Selection" tool.
- You can disable pen-mode single-finger panning in Plugin Settings under Excalidraw Appearance and Behavior [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
## Fixed
- Text tool did not work in pen-mode using finger [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
- Pasting images to Excalidraw from the web resulted in filenames of "image_1.png", "image_2.png" instead of "Pasted Image TIMESTAMP" [#2081](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2081)
`,
"2.6.0":`
## Performance
- Much faster plugin initialization. Down from 1000-3000ms to 100-300ms. According to my testing speed varies on a wide spectrum depending on device, size of Vault and other plugins being loaded. I measured values ranging from 84ms up to 782ms [#2068](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2068)

View File

@@ -355,6 +355,7 @@ FILENAME_HEAD: "Filename",
DEFAULT_PEN_MODE_DESC:
"Should pen mode be automatically enabled when opening Excalidraw?",
DISABLE_DOUBLE_TAP_ERASER_NAME: "Enable double-tap eraser in pen mode",
DISABLE_SINGLE_FINGER_PANNING_NAME: "Enable single-finger panning in pen mode",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "Show (+) crosshair in pen mode",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+

View File

@@ -355,6 +355,7 @@ FILENAME_HEAD: "文件名",
DEFAULT_PEN_MODE_DESC:
"打开绘图时,是否自动开启触控笔模式?",
DISABLE_DOUBLE_TAP_ERASER_NAME: "启用手写模式下的双击橡皮擦功能",
DISABLE_SINGLE_FINGER_PANNING_NAME: "启用手写模式下的单指平移功能",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "在触控笔模式下显示十字准星(+",
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
"在触控笔模式下使用涂鸦功能会显示十字准星 <b><u>打开:</u></b> 显示 <b><u>关闭:</u></b> 隐藏<br>"+

View File

@@ -1871,9 +1871,13 @@ export default class ExcalidrawPlugin extends Plugin {
const size = await ea.getOriginalImageSize(el);
if(size) {
ea.copyViewElementsToEAforEditing(els);
const eaEl = ea.getElement(el.id);
//@ts-ignore
eaEl.width = size.width; eaEl.height = size.height;
const eaEl = ea.getElement(el.id) as Mutable<ExcalidrawImageElement>;
if(eaEl.crop) {
eaEl.width = eaEl.crop.width;
eaEl.height = eaEl.crop.height;
} else {
eaEl.width = size.width; eaEl.height = size.height;
}
await ea.addElementsToView(false,false,false);
}
ea.destroy();

View File

@@ -85,6 +85,7 @@ export interface ExcalidrawSettings {
defaultMode: string;
defaultPenMode: "never" | "mobile" | "always";
penModeDoubleTapEraser: boolean;
penModeSingleFingerPanning: boolean;
penModeCrosshairVisible: boolean;
renderImageInMarkdownReadingMode: boolean,
renderImageInHoverPreviewForMDNotes: boolean,
@@ -261,6 +262,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
defaultMode: "normal",
defaultPenMode: "never",
penModeDoubleTapEraser: true,
penModeSingleFingerPanning: true,
penModeCrosshairVisible: true,
renderImageInMarkdownReadingMode: false,
renderImageInHoverPreviewForMDNotes: false,
@@ -1066,6 +1068,17 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("DISABLE_SINGLE_FINGER_PANNING_NAME"))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.penModeSingleFingerPanning)
.onChange(async (value) => {
this.plugin.settings.penModeSingleFingerPanning = value;
this.applySettingsUpdate();
}),
);
new Setting(detailsEl)
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))

27
src/utils/PDFUtils.ts Normal file
View File

@@ -0,0 +1,27 @@
//for future use, not used currently
import { ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { LinkParts } from "./Utils";
export function getPDFCropRect (props: {
scale: number,
linkParts: LinkParts,
naturalHeight: number,
naturalWidth: number,
}) : ImageCrop | null {
const cropRect = props.linkParts.ref.split("rect=")[1]?.split(",").map(x=>parseInt(x));
const validRect = cropRect && cropRect.length === 4 && cropRect.every(x=>!isNaN(x));
if(!validRect) {
return null;
}
return {
x: cropRect[0] * props.scale,
y: (props.naturalHeight/props.scale - cropRect[3]) * props.scale,
width: (cropRect[2] - cropRect[0]) * props.scale,
height: (cropRect[3] - cropRect[1]) * props.scale,
naturalWidth: props.naturalWidth,
naturalHeight: props.naturalHeight,
}
}

View File

@@ -18,7 +18,7 @@ import {
getContainerElement,
} from "../constants/constants";
import ExcalidrawPlugin from "../main";
import { ExcalidrawElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExcalidrawElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExportSettings } from "../ExcalidrawView";
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
@@ -30,6 +30,7 @@ import { CropImage } from "./CropImage";
import opentype from 'opentype.js';
import { runCompressionWorker } from "src/workers/compression-worker";
import Pool from "es6-promise-pool";
import { FileData } from "src/EmbeddedFileLoader";
declare const PLUGIN_VERSION:string;
declare var LZString: any;
@@ -428,14 +429,14 @@ export function addAppendUpdateCustomData (el: Mutable<ExcalidrawElement>, newDa
export function scaleLoadedImage (
scene: any,
files: any
files: FileData[],
): { dirty: boolean; scene: any } {
let dirty = false;
if (!files || !scene) {
return { dirty, scene };
}
for (const f of files.filter((f:any)=>{
for (const img of files.filter((f:any)=>{
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
if(!ef) return true; //mermaid SVG or equation
@@ -443,34 +444,85 @@ export function scaleLoadedImage (
if(!file || (file instanceof TFolder)) return false;
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
})) {
const [w_image, h_image] = [f.size.width, f.size.height];
const imageAspectRatio = f.size.width / f.size.height;
const [imgWidth, imgHeight] = [img.size.width, img.size.height];
const imgAspectRatio = imgWidth / imgHeight;
scene.elements
.filter((e: any) => e.type === "image" && e.fileId === f.id)
.filter((e: any) => e.type === "image" && e.fileId === img.id)
.forEach((el: any) => {
const [w_old, h_old] = [el.width, el.height];
if(el.customData?.isAnchored && f.shouldScale || !el.customData?.isAnchored && !f.shouldScale) {
addAppendUpdateCustomData(el, f.shouldScale ? {isAnchored: false} : {isAnchored: true});
const [elWidth, elHeight] = [el.width, el.height];
const maintainArea = img.shouldScale; //true if image should maintain its area, false if image should display at 100% its size
const elCrop: ImageCrop = el.crop;
const isCropped = Boolean(elCrop);
if(el.customData?.isAnchored && img.shouldScale || !el.customData?.isAnchored && !img.shouldScale) {
//customData.isAnchored is used by the Excalidraw component to disable resizing of anchored images
//customData.isAnchored has no direct role in the calculation in the scaleLoadedImage function
addAppendUpdateCustomData(el, img.shouldScale ? {isAnchored: false} : {isAnchored: true});
dirty = true;
}
if(f.shouldScale) {
const elementAspectRatio = w_old / h_old;
if (imageAspectRatio !== elementAspectRatio) {
if(isCropped) {
if(elCrop.naturalWidth !== imgWidth || elCrop.naturalHeight !== imgHeight) {
dirty = true;
const h_new = Math.sqrt((w_old * h_old * h_image) / w_image);
const w_new = Math.sqrt((w_old * h_old * w_image) / h_image);
el.height = h_new;
el.width = w_new;
el.y += (h_old - h_new) / 2;
el.x += (w_old - w_new) / 2;
//the current crop area may be maintained, need to calculate the new crop.x, crop.y offsets
el.crop.y += (imgHeight - elCrop.naturalHeight)/2;
if(imgWidth < elCrop.width) {
const scaleX = el.width / elCrop.width;
el.crop.x = 0;
el.crop.width = imgWidth;
el.width = imgWidth * scaleX;
} else {
const ratioX = elCrop.x / (elCrop.naturalWidth - elCrop.x - elCrop.width);
const gapX = imgWidth - elCrop.width;
el.crop.x = ratioX * gapX / (1 + ratioX);
// const ratioA = elCrop.x / (elCrop.naturalWidth - elCrop.x);
// el.crop.x = ratioA * imgWidth / (1 + ratioA);
if(el.crop.x + elCrop.width > imgWidth) {
el.crop.x = (imgWidth - elCrop.width) / 2;
}
}
if(imgHeight < elCrop.height) {
const scaleY = el.height / elCrop.height;
el.crop.y = 0;
el.crop.height = imgHeight;
el.height = imgHeight * scaleY;
} else {
const ratioY = elCrop.y / (elCrop.naturalHeight - elCrop.y - elCrop.height);
const gapY = imgHeight - elCrop.height;
el.crop.y = ratioY * gapY / (1 + ratioY);
// const ratioB = elCrop.y / (elCrop.naturalHeight - elCrop.y);
// el.crop.y = ratioB * imgHeight / (1 + ratioB);
if(el.crop.y + elCrop.height > imgHeight) {
el.crop.y = (imgHeight - elCrop.height)/2;
}
}
el.crop.naturalWidth = imgWidth;
el.crop.naturalHeight = imgHeight;
const noCrop = el.crop.width === imgWidth && el.crop.height === imgHeight;
if(noCrop) {
el.crop = null;
}
}
} else {
if(w_old !== w_image || h_old !== h_image) {
} else if(maintainArea) {
const elAspectRatio = elWidth / elHeight;
if (imgAspectRatio !== elAspectRatio) {
dirty = true;
el.height = h_image;
el.width = w_image;
el.y += (h_old - h_image) / 2;
el.x += (w_old - w_image) / 2;
const elNewHeight = Math.sqrt((elWidth * elHeight * imgHeight) / imgWidth);
const elNewWidth = Math.sqrt((elWidth * elHeight * imgWidth) / imgHeight);
el.height = elNewHeight;
el.width = elNewWidth;
el.y += (elHeight - elNewHeight) / 2;
el.x += (elWidth - elNewWidth) / 2;
}
} else { //100% size
if(elWidth !== imgWidth || elHeight !== imgHeight) {
dirty = true;
el.height = imgHeight;
el.width = imgWidth;
el.y += (elHeight - imgHeight) / 2;
el.x += (elWidth - imgWidth) / 2;
}
}
});